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.
251 lines • 15.6 kB
TypeScript
/**
* # grade/receipt — the immutable agent PROPOSAL / DECLINE / STAND-DOWN receipt (kestrel-a57.5)
*
* A **receipt** is the immutable, content-addressed record of one agent DECISION taken at a point in a
* Session: a plan it **armed** (accept), a plan it **declined**, or a **stand-down** (chose not to act).
* It is the curation-honest counterpart to the {@link ../blotter Blotter}: the Blotter records what the
* engine DID; a receipt records what the agent DECIDED, including the decisions that produced no order.
*
* **Curation honesty (the a57.5 doctrine).** A survivor-only ledger — one that keeps only the plans the
* agent armed — cannot grade *curation*: you cannot tell a good selector from a lucky one without the
* declines and stand-downs it left on the table. So declines and stand-downs are **MANDATORY**, not
* optional, and every receipt names the plan under decision (`plan`) so a declined/stood-down plan is a
* first-class row of the record, never dropped. (The subject plan rides the receipt directly — rather than
* living only in a frozen armory default — so curation is structural.)
*
* **What a receipt pins.** Four content hashes make a decision replayable and attributable:
* • **percept-hash** — the perceptual state the agent acted on. Today this is the sha256 of the canonical
* bus PREFIX the agent could have perceived (everything with `seq < proposed_seq`); the FULL percept-v5
* Frame (a rendered {@link ../frame Frame}) is a documented FUTURE seam (see {@link PerceptProjection}).
* • **plan-hash** — sha256 of the CANONICAL plan text, `print(parse(text))` (ADR-0004): text is a
* projection of the typed core, so the hash is invariant to whitespace/formatting, only the plan.
* • **counterfactual-hash** — the null/baseline the decision was measured against (ADR-0006:
* counterfactuals are syntax — `ungated | null | bracket`), pinned so a grade cannot silently swap it.
* • the **thesis axes** — the structured claim (when/where/how + conviction) — and the
* **measurement-version** pinning (which fill-model / grader / envelope version graded it), because EVs
* across measurement versions refuse naive comparison (ADR-0006).
*
* **Discipline (mirrors the Blotter, ADR-0011).** A receipt is IMMUTABLE and byte-stable: it is
* content-addressed (`receipt_id = "receipt:" + identity_sha256`, the sha256 over its own canonical bytes),
* so two byte-identical decisions share an id and any mutation changes the id. {@link serializeReceipt}
* is its one canonical serialization (lexicographically ordered keys, compact, one trailing newline) — the
* substrate a determinism check byte-compares. No wall clock, no RNG (RUNTIME §0). The pure PROJECTOR that
* lifts receipts off a Session bus is {@link ./receipts.ts}.
*/
import { sha256 } from "../crypto/sha256.ts";
/** sha256 hex of a UTF-8 string (deterministic). Re-exported from the portable {@link ../crypto/sha256.ts}
* digest so a receipt hashes byte-identically under Bun and inside a Worker isolate (kestrel-alw.17). */
export { sha256 };
/** Is `s` a lowercase-hex sha256? (fail-closed guard on any hash-shaped field). */
export declare function isSha256(s: unknown): s is string;
/** Raised when a receipt is malformed, contradictory, or violates the curation-honesty doctrine.
* The projector catches this and fails CLOSED (a malformed decision degrades to a logged stand-down;
* it never crashes and never silently drops the row). */
export declare class ReceiptValidationError extends Error {
constructor(message: string);
}
/** Raised specifically when a plan's SOURCE text fails to parse — the "parse escape → STAND_DOWN" trigger
* (AGENTS.md). A distinct subclass so the projector's degrade-to-stand-down branch keys off the TYPE, not a
* human-readable message substring (a reworded message can never silently break the mandatory-row guarantee).
* It IS a {@link ReceiptValidationError}, so every existing fail-closed catch still catches it. */
export declare class PlanParseError extends ReceiptValidationError {
constructor(message: string);
}
/** The receipt schema version. Bumped on any breaking receipt-shape change. */
export declare const RECEIPT_SCHEMA: "1.0.0";
/**
* What the agent DID with a candidate at this decision point:
* • `accept` — armed a proposed plan as-is (the survivor).
* • `reparameterize` — armed a proposed plan with adjusted parameters (still a survivor, a new plan-hash).
* • `novel_plan` — armed a freshly authored plan (not from the armory).
* • `decline` — considered a plan and chose NOT to arm it (a curation decision — MANDATORY to record).
* • `stand_down` — chose not to act at all (fail-closed default; AGENTS.md "parse escape → STAND_DOWN").
*/
export type DecisionKind = "accept" | "reparameterize" | "novel_plan" | "decline" | "stand_down";
/** The legal decision kinds, for fail-closed validation. */
export declare const DECISION_KINDS: ReadonlySet<DecisionKind>;
/** The decision kinds that ARM a plan (produce a live, executable standing) — as opposed to the no-action
* curation decisions (`decline`, `stand_down`) that leave nothing armed but MUST still be recorded. */
export declare const ARMING_DECISIONS: ReadonlySet<DecisionKind>;
/** The counterfactual (null/baseline) a decision was measured against — kestrel's counterfactual syntax
* (ADR-0006), the `Counterfactual` AST minus its `cf-` prefix: `ungated | null | bracket`. */
export type CounterfactualKind = "ungated" | "null" | "bracket";
/** The legal counterfactual kinds, for fail-closed validation. */
export declare const COUNTERFACTUAL_KINDS: ReadonlySet<CounterfactualKind>;
/**
* How the percept the agent acted on was projected. **Today** the projector renders the perceptual state as
* the canonical bus PREFIX (`kind: "bus-prefix"`) — the exact bus bytes the agent could have perceived up to
* its decision — and hashes those. The FULL percept-v5 Frame (a rendered {@link ../frame Frame} at the
* decision moment) is a documented FUTURE seam (4gl): when it lands, `kind` becomes `"frame"` and the same
* `sha256` slot pins the Frame's canonical bytes. `renderer_version` pins WHICH projector produced the
* bytes, so a percept-hash never silently changes meaning across a projector revision.
*/
export interface PerceptProjection {
/** `bus-prefix` today (the available representation); `frame` when the percept-v5 Frame lands (seam). */
readonly kind: "bus-prefix" | "frame";
/** The projector/renderer version that produced the hashed bytes (pins meaning across revisions). */
readonly renderer_version: string;
}
/**
* The perceptual state the agent acted on — the **percept-hash** plus its provenance. `sha256` is the
* percept-hash; `byte_count` and `seq_watermark` pin exactly WHAT was hashed (the canonical bus prefix and
* the last seq it contained), so a grader can regenerate and byte-compare it. See {@link PerceptProjection}
* for the today→Frame seam.
*/
export interface Percept {
/** The percept-hash: sha256 of the projected perceptual bytes (today, the canonical bus prefix). */
readonly sha256: string;
/** The byte length of the hashed perceptual representation. */
readonly byte_count: number;
/** The greatest bus `seq` the perceptual representation contained (the information watermark). */
readonly seq_watermark: number;
/** How the percept was projected (bus-prefix today; frame is the documented seam). */
readonly projection: PerceptProjection;
}
/**
* The plan under decision — its CANONICAL text and its **plan-hash**. `text` is `print(parse(source))`
* (ADR-0004), so `sha256` is invariant to formatting and pins only the plan's meaning. Both are `null` only
* for a bare `stand_down` that names no candidate; every other decision (accept/decline/reparam/novel) MUST
* carry the plan so the record is curation-honest (a declined plan is never dropped).
*/
export interface ReceiptPlan {
/** The canonical plan text (`print(parse(source))`), or `null` for a candidate-less stand-down. */
readonly text: string | null;
/** The plan-hash: sha256 of `text`, or `null` when `text` is `null`. */
readonly sha256: string | null;
}
/**
* The counterfactual (null/baseline) the decision was measured against, plus its **counterfactual-hash**.
* `kind` is the counterfactual syntax (ADR-0006: `ungated | null | bracket`); `sha256` pins its canonical
* descriptor so a grade cannot silently substitute a weaker baseline. Every receipt carries one — a
* decision measured against nothing is not gradable.
*/
export interface ReceiptCounterfactual {
readonly kind: CounterfactualKind;
/** The counterfactual-hash: sha256 over the canonical counterfactual descriptor. */
readonly sha256: string;
}
/** A single thesis-axis value — a structured, legible claim token (never free markdown). */
export type ThesisValue = string | number | boolean | null;
/**
* The **thesis axes** — the agent's structured claim, on three orthogonal axes plus conviction. WHEN the
* setup holds, WHERE it expresses, HOW it is expressed; `conviction` in [0,1]; and
* `day_type_probabilities` a distribution over named day-types summing to 1. This is the gradable claim: a
* grade can cluster receipts BY any axis (names are data, ADR-0006) to discover which theses actually pay.
*/
export interface ThesisAxes {
readonly when: {
readonly session_phase: ThesisValue;
readonly regime: ThesisValue;
readonly event: ThesisValue;
readonly trend_state: ThesisValue;
readonly volatility_state: ThesisValue;
};
readonly where: {
readonly product: ThesisValue;
readonly direction: ThesisValue;
readonly strike: ThesisValue;
readonly moneyness: ThesisValue;
readonly book_state: ThesisValue;
};
readonly how: {
readonly entry_method: ThesisValue;
readonly size_contracts: ThesisValue;
readonly reload: ThesisValue;
readonly invalidation: ThesisValue;
readonly exit: ThesisValue;
};
/** Conviction in [0,1]. */
readonly conviction: number;
/** A non-empty distribution over named day-types, summing to 1 (± 1e-9). */
readonly day_type_probabilities: Readonly<Record<string, number>>;
}
/**
* The measurement-version pinning — WHICH versioned machinery graded (or will grade) the
* decision. EVs across measurement versions refuse naive comparison (ADR-0006), so every version that could
* move a number is pinned on the receipt: the `fill_model`, the `commission_model`, the `bus_fidelity`
* level, the `risk_envelope`, and the `grader`.
*/
export interface MeasurementVersions {
readonly fill_model: string;
readonly commission_model: string;
readonly bus_fidelity: string;
readonly risk_envelope: string;
readonly grader: string;
}
/**
* One immutable agent decision receipt (kestrel-a57.5). Content-addressed: `receipt_id` is
* `"receipt:" + identity_sha256`, and `identity_sha256` is the sha256 over the receipt's own canonical
* bytes with `identity_sha256`/`receipt_id` removed — so the id IS the content and any mutation changes it.
* Byte-stable under {@link serializeReceipt}. Carries the plan under decision (curation honesty), the four
* content hashes (percept / plan / counterfactual), the thesis axes, and the measurement-version pinning.
*/
export interface ProposalReceipt {
readonly schema_version: typeof RECEIPT_SCHEMA;
readonly record_type: "ProposalReceipt";
/** `"receipt:" + identity_sha256`. */
readonly receipt_id: string;
/** sha256 over the canonical body (this receipt minus `identity_sha256`/`receipt_id`). */
readonly identity_sha256: string;
/** The session this decision belongs to (opaque calendar token, YYYY-MM-DD). */
readonly session_date: string;
/** The bus `ts` at which the decision was proposed (in sim, the bus clock — RUNTIME §0). */
readonly proposed_at: number;
/** The bus `seq` at which the decision was recorded (strictly after `percept.seq_watermark`). */
readonly proposed_seq: number;
/** The `ts` of the last information the agent could have used (≤ `proposed_at`). */
readonly information_cutoff: number;
readonly decision_kind: DecisionKind;
readonly percept: Percept;
readonly plan: ReceiptPlan;
readonly counterfactual: ReceiptCounterfactual;
readonly measurement_versions: MeasurementVersions;
readonly thesis: ThesisAxes;
/** A free author note, or `null`. Never load-bearing for identity beyond its bytes. */
readonly rationale: string | null;
}
/** The content-addressing input for {@link createReceipt}: the raw decision, before hashing/identity. The
* builder canonicalizes the plan text (`print(parse)`), computes the four hashes, and seals the identity. */
export interface ReceiptInput {
readonly session_date: string;
readonly proposed_at: number;
readonly proposed_seq: number;
readonly information_cutoff: number;
readonly decision_kind: DecisionKind;
readonly percept: Percept;
/** The plan SOURCE text (canonicalized by the builder), or `null` for a candidate-less stand-down. */
readonly plan_text: string | null;
readonly counterfactual: CounterfactualKind;
readonly measurement_versions: MeasurementVersions;
readonly thesis: ThesisAxes;
readonly rationale?: string | null;
}
/**
* The **plan-hash**: sha256 of the CANONICAL plan text (ADR-0004). The source is round-tripped through
* `print(parse(source))` so the hash pins the plan's MEANING, not its formatting. Returns the canonical
* text alongside the hash. Fail-closed: a parse escape raises {@link ReceiptValidationError} (the projector
* degrades such a decision to a logged stand-down — AGENTS.md "parse escape → STAND_DOWN").
*/
export declare function planHash(source: string): {
text: string;
sha256: string;
};
/** The **counterfactual-hash**: sha256 over the canonical counterfactual descriptor. Pins the exact
* null/baseline the decision was measured against so a grade cannot substitute a weaker one. */
export declare function counterfactualHash(kind: CounterfactualKind): string;
/**
* Build one immutable, content-addressed {@link ProposalReceipt} from a raw decision. Pure and
* deterministic (no wall clock, no RNG): the SAME input yields a byte-identical receipt. Canonicalizes the
* plan text, computes the percept/plan/counterfactual hashes, assembles the body, seals the identity
* (`identity_sha256` = sha256 over the canonical body; `receipt_id` = `"receipt:" + identity`), and
* validates fail-closed. Throws {@link ReceiptValidationError} on any contradiction.
*/
export declare function createReceipt(input: ReceiptInput): ProposalReceipt;
/** Validate a receipt's integrity (content-addressing, causal ordering, thesis membership) and its
* curation-honesty doctrine. Pure and total; throws {@link ReceiptValidationError} on any violation. */
export declare function validateReceipt(r: ProposalReceipt): void;
/** Serialize one receipt to its canonical bytes: deterministic machine-JSON (lexicographically ordered
* keys, compact, one trailing newline). Pure and total — the same receipt always produces byte-identical
* text, the substrate a determinism check byte-compares. */
export declare function serializeReceipt(receipt: ProposalReceipt): string;
//# sourceMappingURL=receipt.d.ts.map