kestrel.markets
Version:
A typed, token-efficient language + runtime for agentic trading: agents author bounded plans, the runtime fires them at the tick. CLI + typed library + MCP server.
197 lines • 13.6 kB
TypeScript
/**
* # grade/receipts — the pure PROJECTOR that lifts agent receipts off a Session bus (kestrel-a57.5)
*
* `projectReceipts(bus)` is the deterministic fold from one canonical Session {@link ../bus Bus} into the
* {@link SessionReceipts} curation record — the immutable set of every agent PROPOSAL, DECLINE, and
* STAND-DOWN taken in the session. It mirrors the {@link ../blotter Blotter} projector's discipline
* (ADR-0011): **a pure function of the bus** — same bus in ⇒ byte-identical receipts out, no engine-state
* scrape, no wall clock, no RNG.
*
* **Curation honesty (the a57.5 doctrine).** The projector records EVERY decision, not only the armed ones.
* A survivor-only record cannot grade curation, so declines and stand-downs are MANDATORY rows — the record
* carries the plans the agent declined and stood down, alongside the plans it armed.
*
* **How a decision rides the bus (today, and the seam).** An agent decision is carried on the existing
* **CONTROL** stream — the L2→L1 author-intent channel, whose `type` vocabulary is open (bus/types) — with
* `type ∈ {propose, decline, stand_down}`, `target` naming the plan, and the structured
* {@link DecisionDescriptor} (thesis axes, counterfactual, measurement versions, plan source) carried as
* canonical JSON in the CONTROL `note`. This keeps the bus schema UNCHANGED. The note-as-JSON transport is a
* documented SEAM: when a typed proposal envelope lands (a57.14) the descriptor moves onto a typed payload,
* but the projector's contract — receipts are a pure projection of the bus — is unchanged. The **percept**
* the agent acted on is projected today as the canonical bus PREFIX (`seq < proposed_seq`); the percept-v5
* {@link ../frame Frame} is the future enrichment (see {@link ./receipt.ts} `PerceptProjection`).
*
* **Fail closed (AGENTS.md).** A CONTROL decision whose `note` is missing/malformed is not silently dropped:
* it is recorded in {@link SessionReceipts.skipped} with a logged reason (a corruption cannot masquerade as a
* well-formed decision, and it cannot fabricate a thesis). A well-formed decision whose PLAN text fails to
* parse degrades to a STAND-DOWN receipt (plan = null) whose rationale records the escape — the curation row
* is kept ("parse escape → STAND_DOWN").
*/
import type { BusEvent, Mode } from "../bus/index.ts";
import { type CounterfactualKind, type DecisionKind, type MeasurementVersions, type ProposalReceipt, type ThesisAxes } from "./receipt.ts";
/** The CONTROL `type`s the projector reads as agent decisions. Other CONTROL actions (arm/disarm/author/
* supersede/allocate) are ignored — they are not decisions with a thesis. */
export declare const DECISION_CONTROL_TYPES: ReadonlySet<string>;
/**
* The structured agent decision carried in a CONTROL event's `note` (canonical JSON). This is the SEAM
* payload the projector decodes; when a typed proposal envelope lands (a57.14) these fields move onto a
* typed bus payload without changing the projector's contract.
*/
export interface DecisionDescriptor {
/** The fine-grained decision (accept/reparameterize/novel_plan/decline/stand_down). Must agree with the
* CONTROL `type`: an arming kind under `propose`, `decline` under `decline`, `stand_down` under `stand_down`. */
readonly decision_kind: DecisionKind;
/** The plan SOURCE text under decision (canonicalized to the plan-hash by the builder), or `null` for a
* candidate-less stand-down. Curation honesty: a declined plan carries its text here, never dropped. */
readonly plan_text: string | null;
readonly counterfactual: CounterfactualKind;
readonly measurement_versions: MeasurementVersions;
readonly thesis: ThesisAxes;
/** The `ts` of the last information the agent used (≤ the decision's `ts`). Defaults to the last
* perceived event's `ts` when absent. */
readonly information_cutoff?: number;
readonly rationale?: string | null;
}
/** Encode a decision descriptor as the canonical-JSON string a CONTROL `note` carries. The inverse of the
* projector's decode; exported so a harness (and tests) write the exact bytes the projector reads. */
export declare function encodeDecision(d: DecisionDescriptor): string;
/**
* The thesis a DRIVER-authored decision carries today: **UNSTATED**.
*
* The receipt schema demands a thesis on every row, but the Session action surface
* ({@link import("../session/agent.ts").Action}) carries no structured thesis — an agent states its
* intent as free-text `journal`/`note` prose, never as the gradable axes. The producer therefore states
* NOTHING on the agent's behalf: every axis is `null` (no claim), `conviction` is `0` (the floor — the
* receipt asserts no confidence the agent did not express), and the day-type distribution is the single
* explicit `unstated` mass. This is the honest encoding of "no thesis was stated", never a plausible
* thesis invented by the runtime (a fabricated claim would be exactly the "silently false" the fail-closed
* doctrine forbids, and it would poison the very clustering the axes exist to enable).
*
* **Seam (a57.14).** When the typed proposal envelope lands, the agent states its OWN axes and they ride
* here instead. A grader can tell the two apart mechanically: `day_type_probabilities.unstated === 1`.
*/
export declare const UNSTATED_THESIS: ThesisAxes;
/** The commission model this engine applies to a simulated fill: **none**. A truthful, checkable pin —
* the sim charges no commission, so a grader must not compare its EV against a commissioned run. */
export declare const DRIVER_COMMISSION_MODEL = "none-v0";
/** The bounded-risk envelope version the engine enforces (`budget NR` + the never-naked/intrinsic-floor
* admission Gate). Bump when the envelope's arithmetic changes — EVs across versions refuse comparison. */
export declare const DRIVER_RISK_ENVELOPE = "budget-r-v1";
/** The grader version a receipt minted by this engine is measured by (the headline grader). */
export declare const DRIVER_GRADER = "grade-v1";
/**
* Pin the measurement versions for a decision minted by the Session driver. Pure: a function of the run's
* judge (`fill_model`) and the bus's fidelity only — no clock, no RNG. Everything a version could move a
* number through is pinned, so a grade can refuse to compare EVs across versions (ADR-0006).
*/
export declare function driverMeasurementVersions(judge: {
readonly fillModel: string;
readonly fidelity: string;
}): MeasurementVersions;
/** One agent decision the driver is about to write onto the bus (the producer's input). `control` is the
* coarse CONTROL marker; `decision_kind` is the fine kind — the projector re-checks the pairing
* fail-closed, so a mismatch here is SKIPPED with a reason, never silently banked. */
export interface DecisionEmit {
readonly control: "propose" | "decline" | "stand_down";
/** The plan NAME the decision is about — the key the curation verdict reconciles against the bus's
* arm record. Omitted only for a candidate-less stand-down. */
readonly target?: string;
readonly decision_kind: DecisionKind;
/** The plan SOURCE under decision, or `null` for a candidate-less stand-down. Never dropped: a declined
* or stood-down plan carries its text so the row is curation-honest. */
readonly plan_text: string | null;
readonly rationale?: string | null;
readonly measurement_versions: MeasurementVersions;
/** The agent's stated thesis. Defaults to {@link UNSTATED_THESIS} — the producer invents no claim. */
readonly thesis?: ThesisAxes;
/** The baseline the decision is measured against. Defaults to `null` (the flat/no-position baseline). */
readonly counterfactual?: CounterfactualKind;
}
/**
* Build the CONTROL event FIELDS that carry one agent decision receipt onto the Session bus — the
* producer half of the a57.5 seam (kestrel-a57.18). The driver spreads the result into its own `emit`
* (which stamps `ts`/`seq`), so the transport stays the unchanged CONTROL stream and the projector's
* contract — receipts are a pure projection of the bus — is untouched.
*
* Pure and deterministic: same input ⇒ byte-identical `note`. It performs NO validation of its own — the
* projector is the single validator (one definition, never a second one), and it fails closed on anything
* this writes that does not cohere.
*/
export declare function decisionControlFields(d: DecisionEmit): {
readonly type: string;
readonly target?: string;
readonly note: string;
};
/** One CONTROL decision the projector could NOT lift into a receipt — recorded, never silently dropped
* (fail-closed). `seq` anchors it to the bus; `reason` is the logged cause. */
export interface SkippedDecision {
readonly seq: number;
readonly reason: string;
}
/** The session header a receipt record projects from — the opaque calendar token and the mode, read purely
* from the opening META. */
export interface ReceiptSession {
readonly date: string;
readonly mode: Mode;
}
/**
* The **curation-honesty verdict** — the structural answer to "can this record grade CURATION?" (the a57.5
* doctrine, made mechanical rather than an honor system). A survivor-only record — arming decisions with no
* declines/stand-downs — CANNOT distinguish a good selector from a lucky one, and a receipt set that does not
* reconcile with the actual arm record on the bus is a self-reported narrative, not the curation-honest
* counterpart to the {@link ../blotter Blotter}. When `gradable` is `false` a grader MUST REFUSE to score
* curation; `reasons` names every fail-closed cause (empty iff gradable). Pure/deterministic — computed from
* the bus and the projected receipts, never a wall clock or engine scrape.
*/
export interface CurationVerdict {
/** `true` iff the record is safe to grade for curation: it is NOT survivor-only AND its arming receipts
* reconcile with the bus's arm record. A grader refuses curation grading when this is `false`. */
readonly gradable: boolean;
/** Every fail-closed reason the record is not curation-gradable, in a stable order (empty iff gradable). */
readonly reasons: readonly string[];
/** Count of arming receipts (accept/reparameterize/novel_plan) — the survivors. */
readonly armed: number;
/** Count of the declines and stand-downs a survivor-only ledger would drop (MANDATORY rows). */
readonly declined_or_stood_down: number;
/** `true` iff the bus carries an arm/execution record (any `PLAN` armed transition or `CONTROL arm`);
* when `false` the receipt channel is projected in isolation and reconciliation is not applicable. */
readonly arm_record_present: boolean;
/** Plan names armed on the bus (PLAN armed / CONTROL arm) with NO corresponding arming receipt — a
* curation-hiding divergence (the engine armed something the receipt record does not own). Sorted. */
readonly unreceipted_arms: readonly string[];
/** Plan names an arming receipt claims it armed that the bus's arm record never armed — a self-reported
* arm the execution log does not back. Sorted; only computed when {@link arm_record_present}. */
readonly phantom_arms: readonly string[];
}
/**
* The immutable curation record of one Session: every agent decision receipt, in bus order, plus the
* decisions that failed to lift (recorded, never dropped). A pure projection of the bus — byte-stable under
* {@link serializeReceipts}.
*/
export interface SessionReceipts {
readonly session: ReceiptSession;
/** Every agent decision receipt, ordered by `proposed_seq` (bus order). Carries armed, declined, AND
* stood-down decisions — curation honesty. */
readonly receipts: readonly ProposalReceipt[];
/** Malformed CONTROL decisions, recorded with a logged reason (fail-closed; never silent). */
readonly skipped: readonly SkippedDecision[];
/** The structural verdict on whether this record can grade CURATION — survivor-only detection plus
* reconciliation against the bus's arm record. A grader REFUSES curation grading when not `gradable`. */
readonly curation: CurationVerdict;
}
/**
* Project one Session bus into its immutable {@link SessionReceipts} curation record. Pure and deterministic
* (ADR-0011): no wall clock, no RNG, no engine-state scrape — the SAME bus yields byte-identical receipts
* (proven by re-projecting and byte-comparing {@link serializeReceipts}). Records every armed/declined/
* stood-down decision (curation honesty); records malformed decisions in `skipped` (fail-closed).
*/
export declare function projectReceipts(bus: readonly BusEvent[]): SessionReceipts;
/** Serialize a {@link SessionReceipts} record to its canonical bytes: deterministic machine-JSON
* (lexicographically ordered keys, compact, one trailing newline). Pure and total — the same record always
* produces byte-identical text, the substrate a determinism check byte-compares. The key order is the one
* {@link canonicalizeJson canonical byte order} (canonical/json); the single trailing newline is canon. */
export declare function serializeReceipts(record: SessionReceipts): string;
/** The declined and stood-down receipts (the curation the agent left on the table) — the non-arming
* decisions a survivor-only record would drop. A convenience over {@link SessionReceipts.receipts}. */
export declare function declinedAndStoodDown(record: SessionReceipts): readonly ProposalReceipt[];
//# sourceMappingURL=receipts.d.ts.map