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.
337 lines (315 loc) • 19.7 kB
text/typescript
/**
* kestrel.markets/protocol — the CATALOG contract (protocol v0.2).
*
* A {@link CatalogEntry} is the content-addressed, reproducibility-complete
* descriptor of ONE gradeable subject the managed backend (or a self-hosted
* Kestrel) publishes: it pins EVERYTHING a client needs to reproduce the exact
* numbers a Grade will certify — the input roots, the schema, the corpus tier,
* the engine/Judge versions, the single permitted fill model, the View and
* Rendering identities the subject was authored under, and the provenance of the
* record. Two runs that pin the same entry are replayable to identical results
* (ADR-0010 "legible text is truth; binary stores are projections"; ADR-0011 the
* Blotter projector is a pure function of the bus).
*
* HARD CONSTRAINTS (identical to src/protocol/index.ts — see its header):
* - ZERO runtime dependencies. This file imports NOTHING. It is pure types +
* own constants and must typecheck with the engine/`chdb` uninstalled.
* - SHAPES ONLY. Every content-addressed artifact is an opaque HANDLE
* ({@link ContentRoot}); never a key, a signing routine, or an inlined blob.
* - GENERIC ONLY. No founding-app tickers, strategy names, or private corpus
* identities appear in types, doc comments, or examples. Product identities
* ride INSIDE these fields at runtime; they are not part of the contract.
*/
/* ─────────────────────────── content-addressed handles ─────────────────────── */
/**
* Opaque content-addressed handle (a `sha256:…` digest, resolved server-side).
* Same opaque-`*Ref` discipline as {@link import("./index.ts").EnvelopeRef} — the
* contract ships the ADDRESS of a replayable artifact, never its bytes. Aliased
* to `string` (not branded) so a catalog author writes the digest directly.
*/
export type ContentRoot = string;
/**
* The corpus tier a catalog entry draws on — an OPAQUE string, deliberately not a
* closed enum. It reuses the bus `corpus_tier` vocabulary already landed on the
* runtime line (e.g. `"public-baseline"`), so the catalog names the SAME concept
* the recorded bus stamps rather than coining a parallel tier axis.
*/
export type CorpusTier = string;
/* ─────────────────────────────── requirements ──────────────────────────────── */
/**
* The engine and Judge versions a subject was produced under. A client whose
* engine/Judge do not satisfy these MUST fail closed rather than grade against a
* drifted implementation — reproducibility is pinned, never approximated. Values
* are opaque version tags (the client compares, the protocol does not interpret).
*/
export interface CatalogRequirements {
/** Engine (runtime) version the entry was produced under. */
readonly engineVersion: string;
/** Judge version whose certified numbers the entry pins. */
readonly judgeVersion: string;
}
/* ─────────────────────────────── provenance ────────────────────────────────── */
/**
* Where a catalog entry came from and when it was recorded. The `source` is an
* opaque content-addressed handle to the originating record (never a private
* upstream path); `recordedAt` is an ISO-8601 timestamp. Provenance is a stated
* FACT on the entry, not something the protocol computes.
*/
export interface CatalogProvenance {
/** Opaque handle to the originating record. */
readonly source: ContentRoot;
/** ISO-8601 time the entry was recorded. */
readonly recordedAt: string;
}
/* ───────────────────────────── input pin (kestrel-m9i.27) ───────────────────── */
/**
* The reproducible AUTHORING INPUT a benchmark cell pins (kestrel-m9i.27) — content-addressed, so the
* cell's exact input is recoverable and a plan-doc difference is VISIBLE (unlike a name-only id, which
* collides two different docs authored under one plan name):
*
* - `fixed-plan` a cell with a FIXED authored plan doc pins the DOC by its OWN content hash
* (`planDoc = "sha256:" + sha256(doc)`); the doc itself is stored content-addressed
* alongside, so the exact authored plan is recoverable BYTE-FOR-BYTE from the manifest.
* - `agent-authored` a cell whose agent authors the plan LIVE has no fixed doc, so it pins the CONFIG /
* prompt-profile (`config`, a content root) — the reproducible input there is the
* config, not a doc.
*
* Each handle is an opaque {@link ContentRoot} (`sha256:<hex>`), the same address discipline as the rest of
* the entry. A discriminated union so the two cases never conflate. A cell whose pinned fixed-plan doc is
* missing or does not hash to its pin is REFUSED by the loader (fail-closed) — never silently reproducible.
*/
export type InputPin =
| { readonly kind: "fixed-plan"; readonly planDoc: ContentRoot }
| { readonly kind: "agent-authored"; readonly config: ContentRoot };
/* ─────────────────────────────── catalog entry ─────────────────────────────── */
/**
* One content-addressed, reproducibility-complete catalog entry (djm.2 AC2).
* Pins the full surface required to replay a subject to identical certified
* numbers. Every field is a stated, content-addressed fact:
*
* - `contentRoots` the input roots (e.g. recorded bus + input tape) the entry
* is projected from — ADR-0010/0011 projections pin here.
* - `schema` the wire schema this entry conforms to (e.g.
* `"kestrel.session/0.2"`); its minor tracks PROTOCOL_VERSION.
* - `corpusTier` the opaque {@link CorpusTier}, aligned with bus `corpus_tier`.
* - `requirements` the engine/Judge versions a replay must satisfy.
* - `permittedFillModel` the SINGLE fill model this entry is graded under
* (the two-bus rule: one graded bus, one named model).
* - `viewIdentity` the View identity the subject perceived under (handle).
* - `renderingIdentity` the Rendering identity — how the Frame was rendered to
* tokens — the subject was authored under (handle).
* - `provenance` where the entry came from and when it was recorded.
* - `inputPin` the reproducible AUTHORING INPUT (kestrel-m9i.27): a fixed-plan cell pins its
* authored plan doc by content hash (recoverable byte-for-byte); an agent-authored
* cell pins its config. OPTIONAL — additive to this frozen contract.
*/
export interface CatalogEntry {
/**
* The stable, human-readable HANDLE a client passes to `openSession({ kind: "catalog", id })` — the ONE
* field that is a NAME, not a `sha256:` digest (a generic regime label like `"choppy-1101-strict"`; never a
* founding-app ticker or strategy). It rides here, on the wire contract, so the discover→open loop CLOSES
* off the public catalog projection ALONE: a client `catalog()`s, reads `entry.id`, and opens that exact
* subject — with no reach past the SDK barrel into any host-internal catalog to recover the addressing key.
*/
readonly id: string;
/** Content-addressed input roots the entry projects from (ADR-0010/0011). */
readonly contentRoots: readonly ContentRoot[];
/** Wire schema tag this entry conforms to (e.g. `"kestrel.session/0.2"`). */
readonly schema: string;
/** Opaque corpus tier, aligned with the bus `corpus_tier` vocabulary. */
readonly corpusTier: CorpusTier;
/** Engine/Judge versions a faithful replay must satisfy. */
readonly requirements: CatalogRequirements;
/** The single permitted fill model this entry is graded under. */
readonly permittedFillModel: string;
/** The View identity the subject perceived under (content-addressed handle). */
readonly viewIdentity: ContentRoot;
/** The Rendering identity the subject was authored under (content-addressed handle). */
readonly renderingIdentity: ContentRoot;
/** Where the entry came from and when it was recorded. */
readonly provenance: CatalogProvenance;
/**
* The reproducible AUTHORING INPUT this cell pins (kestrel-m9i.27): a fixed-plan cell pins its authored
* plan DOC by content hash (the doc recoverable byte-for-byte); an agent-authored cell pins its CONFIG.
* OPTIONAL — additive to this frozen contract, so a pre-m9i.27 entry still conforms; the catalog LOADER
* fails closed on a benchmark cell whose fixed-plan doc is missing or does not hash to its pin (never
* silently reproducible). {@link InputPin}.
*/
readonly inputPin?: InputPin;
}
/* ─────────────────────────────── catalog LISTING (discovery) ────────────────── */
/**
* A calendar period a listed subject covers — `start`/`end` are ISO-8601 date strings
* (`YYYY-MM-DD`). The sub-key order (`start` then `end`) is part of the canonical
* {@link CatalogListing} byte shape.
*/
export interface CatalogPeriod {
/** ISO-8601 date the subject's tape begins (`YYYY-MM-DD`). */
readonly start: string;
/** ISO-8601 date the subject's tape ends (`YYYY-MM-DD`). */
readonly end: string;
}
/**
* The DISCOVERY projection the `catalog` op returns — the ONE shape BOTH transports serve, byte-identical
* (kestrel-dnxq). It is distinct from {@link CatalogEntry}, the reproducibility-COMPLETE surface (content
* roots, engine/Judge versions, View/Rendering identities, the permitted fill model) resolved at
* `openSession`/grade time: a browsing client choosing WHAT to open needs the human-legible menu
* (title / description / instrument / period / frame count / free), never the replay pins — and the managed
* backend deliberately does NOT ship the pins in its `GET /catalog` listing (they resolve server-side at
* open). `CatalogListing` is therefore the honest INTERSECTION both a self-hosted mirror and the managed
* backend can emit for the same subject; it is the camelCase projection of the deployed platform's listing
* row (`artifact_id → id`, `frame_count → frameCount`; the optional `content_hash` reproducibility pin is
* NOT part of discovery and is dropped).
*
* INVARIANT (kestrel-dnxq): the catalog op's protocol OBJECTS are byte-identical across transports — the
* SHAPE and key order below are the same on LOCAL and HTTP. The catalog CONTENTS are transport-specific and
* legitimately differ: the LOCAL transport lists its offline in-process sample; the managed transport lists
* the live catalog. Shape parity is the guarantee; content parity is NOT claimed (and a cross-transport id
* addresses a subject only on the transport that listed it — the two catalogs are different catalogs).
*
* Canonical key order: `id`, `title`, `description`, `instrument`, `period`, `frameCount`, `free`.
*/
export interface CatalogListing {
/** The stable, addressable catalog HANDLE. The self-hosted LOCAL transport opens it as an incremental
* `openSession({ kind: "catalog", id })`; the managed transport serves it as a BATCH simulation (`sim`) —
* managed incremental `openSession` over a catalog subject is not yet served (it refuses fail-closed with a
* typed hint naming `sim`). (The managed backend's `artifact_id`, surfaced under the protocol `id` name.) */
readonly id: string;
/** The human name of the subject (the discovery menu label). */
readonly title: string;
/** A one-line human description of the scenario. */
readonly description: string;
/** The generic instrument the subject is authored over (e.g. `"QQQ"`). */
readonly instrument: string;
/** The calendar period the subject's tape covers. */
readonly period: CatalogPeriod;
/** The number of decision Wake frames the subject presents (a discovery hint; the replay pins resolve at
* `openSession`). */
readonly frameCount: number;
/** `true` ⇒ the subject opens with no payment (a free/trial subject). */
readonly free: boolean;
}
/* ─────────────────────────── ex-ante PRICING (kestrel-adge) ─────────────────── */
/**
* A priced COMMERCIAL TERM on a catalog line — the JSON projection of the offer-terms
* vocabulary (a `kind`-discriminated object; cf. {@link import("./offer.ts").OfferTerms}).
* Carried OPAQUELY on the catalog contract: `kind` is stated, but the remaining fields are
* PRODUCER-DEFINED and this contract does NOT close them (a period may be an ISO-8601
* duration or a word; an amount may be a decimal string or a structured Money) — a
* budget-holding brain reads the term, the wire does not interpret it. Same pass-through
* discipline as {@link CatalogSettlementMethod.params}, so a producer's exact term shape is
* surfaced without a second, divergence-prone `OfferTerms` fork living on the catalog.
*/
export interface CatalogTerm {
/** The term discriminant (e.g. `"one_shot"`, `"recurring"`). */
readonly kind: string;
/** Producer-defined term fields, carried verbatim (not interpreted by the contract). */
readonly [field: string]: unknown;
}
/**
* An accepted machine SETTLEMENT RAIL advertised alongside the price sheet — the wire
* projection a minted 402 Offer settles on (mirrors the platform's served
* `settlement_methods`). Distinct from {@link import("./index.ts").SettlementMethod} (the
* Offer's `kind`-discriminated rail): this is the price-sheet's discovery view, keyed by
* `method`, so the catalog surfaces EXACTLY what the platform publishes. `method` is an
* OPAQUE string (e.g. `"account-credit"`, `"stripe-mpp"`, `"x402"`), never closed here.
*/
export interface CatalogSettlementMethod {
/** The settlement rail identifier (opaque; e.g. `"account-credit"` | `"stripe-mpp"` | `"x402"`). */
readonly method: string;
/** Optional settlement network (e.g. `"base"`, `"tempo"`). */
readonly network?: string;
/** Optional accepted settlement asset codes, in preference order. */
readonly accepts?: readonly string[];
/** Optional rail parameters, carried verbatim (not interpreted by the contract). */
readonly params?: Readonly<Record<string, unknown>>;
}
/**
* One PURCHASABLE line on the ex-ante price sheet — the exact line item a 402 Offer is
* priced against, projected into the catalog so a budget-holding agent can PRICE a paid
* boundary BEFORE it provokes a short-lived Offer. Mirrors the platform's served price
* line (snake_case, verbatim): `amount`/`asset` is the price, `label` is what-you-get,
* `scope`/`ceiling`/`requires_human` bound the authority, and an optional `artifact_id` is
* a concrete requestable paid subject. `scope` is an OPAQUE string (the same deliberate
* non-closure as {@link CorpusTier}), so the catalog never forks the authority vocabulary.
*/
export interface CatalogPriceLineItem {
/** Stable machine SKU an Offer's intent is priced against. */
readonly sku: string;
/** The authority scope this line admits into a child Envelope (opaque string). */
readonly scope: string;
/** Human-facing label — what the buyer gets. */
readonly label: string;
/** The metered unit the amount is charged per. */
readonly unit: string;
/** Price per unit, a decimal string (to avoid float error). */
readonly amount: string;
/** Settlement asset code the amount is quoted in. */
readonly asset: string;
/** Worst-case dollar ceiling of the admitted Envelope (the bounded-risk bound). */
readonly ceiling: number;
/** A concrete, requestable paid `artifact_id` for THIS line (optional; most lines price a surface). */
readonly artifact_id?: string;
/** True when the scope carries agreements only a human signer can establish. */
readonly requires_human: boolean;
/** The base pricing TERM (optional; absent ⇒ the implicit one-shot exact settlement). */
readonly terms?: CatalogTerm;
/** Additional priced TERMS for the same capability (optional). */
readonly alternatives?: readonly CatalogPriceAlternative[];
}
/** An alternative priced TERM for a line item — the same capability under a second pricing
* structure (e.g. a recurring plan alongside the one-shot line). Mirrors the platform's
* served alternative shape verbatim. */
export interface CatalogPriceAlternative {
/** The pricing term this alternative is priced under. */
readonly terms: CatalogTerm;
/** Human-facing label (e.g. `"Monthly data plan"`). */
readonly label: string;
/** Price for this alternative, a decimal string. */
readonly amount: string;
/** Settlement asset code. */
readonly asset: string;
/** Worst-case dollar ceiling of the Envelope this alternative admits. */
readonly ceiling: number;
}
/**
* The EX-ANTE PRICING block (kestrel-adge / platform bead z3is, "The Perch") — the
* machine-readable answer to "what can I BUY, and for how much" a budget-holding agent
* reads off the FREE catalog BEFORE it provokes a 402. It is the CANONICAL projection of
* the SAME price line items a 402 Offer is minted against (estimate ≡ meter), so it can
* never drift from what an Offer actually quotes. Mirrors the platform's served `pricing`
* shape VERBATIM (snake_case), so the platform is a CONSUMER of this contract, not a
* diverger. OPTIONAL on {@link CatalogPage} — absent ⇒ today's bare listing, fully
* backward-compatible.
*/
export interface CatalogPricing {
/** True iff at least one line item is machine-purchasable today (a positive amount, no human signer). */
readonly paid_available: boolean;
/** The canonical HTTP path the full, authoritative price sheet is served at. */
readonly price_sheet_url: string;
/** The sheet's content version (a pricing experiment bumps this). */
readonly price_sheet_version: string;
/** Settlement currency the amounts are quoted in. */
readonly currency: string;
/** Every purchasable SKU, verbatim from the price sheet the Offer mint reads. */
readonly skus: readonly CatalogPriceLineItem[];
/** The accepted settlement rails a minted Offer settles on, in preference order. */
readonly settlement_methods: readonly CatalogSettlementMethod[];
/** A human-readable note describing the sheet (machine-legible guidance). */
readonly note: string;
}
/**
* The catalog DISCOVERY BODY (kestrel-adge) — the wrapped shape `GET /catalog` serves:
* the {@link CatalogListing}[] menu PLUS the optional ex-ante {@link CatalogPricing} block.
* This is the CANONICAL cross-face catalog wire shape (HTTP/SDK/MCP): the SDK's
* `catalog()` returns this exact page, so the return TYPE matches the served runtime shape
* (resolving the class where a bare-array type dropped `pricing`). `pricing` is OPTIONAL —
* a body without it decodes to a page whose `entries` are today's bare listing, so existing
* consumers are unaffected and a self-hosted mirror that serves no price sheet is honest.
*/
export interface CatalogPage {
/** The canonical discovery menu — the byte-identical {@link CatalogListing}[] across transports. */
readonly entries: readonly CatalogListing[];
/** The ex-ante pricing block, when the producer serves one (absent ⇒ a bare listing). */
readonly pricing?: CatalogPricing;
}