UNPKG

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.

60 lines (59 loc) 4.25 kB
/** * # frame/seat — the pod SEAT roster: the closed `role` axis of the TemplateRecord cell key * (ADR-0041 §2, Amendment A1 / kestrel-wa0j.52) * * A **Seat** is a role in the pod org, fixed by the deliberation budget it can afford (CONTEXT.md * "Seat"): a PM allocates + discovers, a Strategist frames + authors, a Watcher manages + escalates. * ADR-0041 §2 makes `role` a first-class axis of the evidence cell key **because a pane's sign is * role-dependent** — a restraint pane can be passivity for the strategist (whose authored exit * already prices the fakeout) yet correct for the watcher managing a stop-less trap. Pooling grades * across seats is therefore ill-typed, and the roster is the schema of that phantom index. * * ## Why the roster is pinned NOW, closed, while the ledger is empty (kestrel-wa0j.52) * ADR-0041 §2 pins `instrumentClass` NOW on the argument that a cell-key axis VALUE added after the * first TemplateRecord is an evidence-splitting migration. Amendment A1 applies the SAME argument to * the `role` axis's VALUE ROSTER: the ledger currently writes `role ∈ {strategist, watcher}`, but the * PM-seat percepts (scan-fire read-why, the aggregates cockpit) are beaded, and if `pm` is added to * the roster after pm-cell grading starts under a workaround key, the evidence splits exactly the way * the ADR forbids. So the roster is pinned to the full seat set **`{pm, strategist, watcher}`** now, * CLOSED — opened only by ceremony — while it costs one enum value and zero migration. * * ## What is NOT a seat (the seat-vs-mechanism distinction, kestrel-wa0j.52 reconciliation) * `risk` is the **L0 layer**, not a seat — strictly subtractive (clamps/vetoes, never opens risk), * its rendered surface the kernel safety block; a `riskmgr` persona is a Brief style on a seat, not a * seat. `Trigger` is a non-LLM reflex TIER with no judgment cells to grade. `Historian` works * off-tape on the policy (Brief) channel, not perception. `risk` stays a ceremony-addable VALUE if an * auditing/reading agent seat is ever earned — adding a value later does not split evidence (old * records stay keyed; a new value opens new cells by ceremony), which is exactly why only the AXIS * (not each value) had to be pinned pre-ledger, and the seat roster is pinned to what shipped doctrine * names today. * * ## Distinct from the cost-tier `Tier` ({@link ../session/harness/roster.ts}) * `harness/roster.ts` has its own `Tier = "strategist" | "watcher"` — the **serving-lane cost tier** * (few-expensive-turns vs the large zero-cash fan-out). That is a DIFFERENT concept from this pod * SEAT: the values overlap but the axes do not. This module owns the pod-org role axis; nothing here * imports the cost tier and nothing there should import this. */ /** * The closed pod-seat roster — the `role` axis of the TemplateRecord cell key (ADR-0041 §2 / A1). * CLOSED at any instant; opened only by ceremony (a later `risk` reading-seat would be added HERE, * with its own cells + matched sets). This array is the ONE source; {@link SeatId} is its element type * and {@link isSeatId} its runtime guard, so the type and the runtime roster can never drift. */ export const SEAT_IDS = ["pm", "strategist", "watcher"]; /** Runtime membership in the closed {@link SEAT_IDS} roster. Fail-closed callers use this to REFUSE an * unknown role rather than coerce it (the ledger keys on the role — an unknown value must never be * silently admitted into a cell, ADR-0041 §2 no-coercion). Pure. */ export function isSeatId(value) { return typeof value === "string" && SEAT_IDS.includes(value); } /** Assert a value is a member of the closed seat roster, or THROW naming the roster (never a silent * default — the same fail-closed discipline the parser and the pane catalog carry). Returns the * value narrowed to {@link SeatId}. */ export function assertSeatId(value) { if (!isSeatId(value)) { throw new Error(`unknown seat "${String(value)}" — the pod-seat roster is closed (ADR-0041 §2 / A1); known seats: ${SEAT_IDS.join(", ")}. ` + `A new seat is admitted only by ceremony (its own cells + matched sets), never coerced.`); } return value; }