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.
716 lines • 37.9 kB
TypeScript
/**
* kestrel.markets/protocol — the OVERSIGHT contract (protocol v0.3).
*
* The cross-repo seam between the CLI (OSS `kestrel` — `ascii`/Ink Rendering) and
* the Web dashboard (platform `kestrel.markets` — `html` Rendering). ADR-0035: the
* human never places a ticket — the human mode is PM/Pod OVERSIGHT (positions,
* plans, P&L, risk envelope) plus chat. "The agent's ASCII screen and the human's
* HTML chart are two Renderings of one Frame" (CONTEXT.md, *Rendering*) — so if the
* Web dashboard can show a number the CLI cannot, that number was fetched OUTSIDE
* this contract, and the protocol is broken. This module owns the shared model;
* both Renderings consume it, neither invents a value.
*
* Normative source: docs/design/oversight-protocol-contract.md §3–§5 (owner-approved
* 2026-07-14). The model has exactly EIGHT parts and nothing else belongs in it.
*
* HARD CONSTRAINTS (identical to src/protocol/index.ts — see its header):
* - ZERO RUNTIME dependencies. The ONLY imports are intra-protocol type-imports
* (`./session.ts`, `./index.ts` — both dependency-free); this module pulls in NO
* engine / session / blotter / fill / lang / grade / frame / render / ledger /
* cli code and must typecheck with `chdb` uninstalled. The import-graph boundary
* is an automated CI invariant (tests/protocol.boundary.test.ts). Hence the
* acting Kernel is a STRUCTURAL MIRROR of `src/frame/types.ts` `Kernel` — never
* an import of it (§3.2). CONFORMANCE, HONESTLY: today this leaf is a
* HAND-MAINTAINED parallel of `src/frame/types.ts` with NO compiler link — its
* field-for-field correspondence is verified by audit, not yet by a witness. The
* total projector `src/oversight/project.ts` (bead kestrel-telx.2, NOT YET
* LANDED) is the PLANNED conformance point: it will carry a `Record<keyof Kernel,
* true>` key-witness on the `src/frame` side so that a section added to `Kernel`
* breaks the build until this mirror handles it. Until it lands, adding a field
* to `src/frame/types.ts` `Kernel*` and forgetting it here does NOT auto-red — so
* any `Kernel*` edit must be reconciled against this leaf by hand.
* - SHAPES ONLY. No signing keys, no Stripe/commerce logic. Every signed,
* replayable artifact carries an opaque `*Ref` HANDLE, never a key or a signing
* routine (`OversightAct` 'fund' carries `authorizationRef`, never a credential).
* - GENERIC INSTRUMENTS ONLY. No founding-app tickers or strategy names in types,
* comments, or examples. Illustrative fixtures use SPX/SPY/QQQ (ARCHITECTURE §7);
* product symbols ride INSIDE opaque `symbol` fields at runtime.
*
* Version: `major.minor`. This leaf first landed at PROTOCOL_VERSION 0.3 (bumped 0.2 → 0.3, purely
* additive). It now bumps 0.3 → 0.4 for the `ActReceipt` `state` field (kestrel-ysqj): `submitAct`'s
* receipt gains an explicit queued-vs-bound stamp so 'admitted' and 'in force' are distinguishable to a
* remote caller — a shape change to an existing type, hence a minor bump (index.ts owns the constant;
* this note records why it moved). The schema tag `kestrel.session/<PROTOCOL_VERSION>` and the
* content-addressed view/rendering identities track it; the graded-bus conformance root does NOT (it is
* decoupled, ENGINE_VERSION-gated), so only the catalog-records bake re-pins, never a conformance root.
*
* NAMING: index.ts re-exports leaves with `export *` and already exports
* `Position`/`Instrument`/`Order`/`Fill`/`Side`, so every oversight type here is
* PREFIXED (`KernelPosition`, `KernelRestingOrder`, `KernelFill`, …). A collision is
* a compile error, not a style question.
*
* CLOSED-VOCABULARY IDIOM (mandatory): every union ships its runtime tuple + the
* both-ways exhaustiveness guard (`SESSION_DIAGNOSTICS` / `_SessionDiagnosticsExhaustive`
* in session.ts), so a member added to (or removed from) a union without editing its
* tuple BREAKS THE BUILD. That is what makes "an event outside the enum is a protocol
* violation" enforceable rather than aspirational.
*/
import type { OperationId, SessionDiagnostic, SessionId, TurnEntry } from "./session.ts";
import type { Scope } from "./index.ts";
export type CallerKind = "agent" | "human";
export declare const CALLER_KINDS: readonly ["agent", "human"];
/** How the Caller was decided. Auditable — never a guess we hide. */
export type CallerDetection = "flag" | "env" | "ci" | "tty";
export declare const CALLER_DETECTIONS: readonly ["flag", "env", "ci", "tty"];
/**
* Resolved ONCE, env-first / TTY-second (an agent can hold a PTY). The SINGLE source read by both
* the render-mode resolver and platform-bound telemetry, so rendering and analytics can never
* disagree about who is calling (ADR-0035 §a).
*
* FAIL-CLOSED: under ANY ambiguity `interactive` is `false`. A wrongly-launched TUI hangs an agent
* forever — the worst failure; guessing "agent" wrong merely gives a human plain text.
*/
export interface Caller {
readonly kind: CallerKind;
/** The detected harness, when known (a coding-agent CLI, CI). `null` ⇒ unknown, never invented. */
readonly harness: string | null;
readonly detectedBy: CallerDetection;
/** Interactive session eligible: a CONFIDENT human only — both TTYs, no agent env, no CI. */
readonly interactive: boolean;
}
/** What an order MEANS when it fires (CONTEXT: Mode). Orthogonal to Caller and to authentication. */
export type Mode = "sim" | "paper" | "live";
export declare const MODES: readonly ["sim", "paper", "live"];
/**
* Who is watching what, under which authority. The deterministic `sessionId` (a genesis hash — the
* same on a self-hosted Kestrel and on the managed backend) and the control-plane `operation`
* (platform-minted, NOT content-derived) are BRANDED distinctly in `./session.ts` and are never
* substituted for one another. With no account, a session opens on a local paper Pod: `mode:"paper"`,
* `operation` absent (ADR-0035 §b) — no broker, no signup.
*/
export interface OversightIdentity {
readonly caller: Caller;
readonly sessionId: SessionId;
readonly operation?: OperationId;
readonly mode: Mode;
readonly scopes: readonly Scope[];
}
export type Attribution = "OBS" | "CALC" | "MODEL";
export declare const ATTRIBUTIONS: readonly ["OBS", "CALC", "MODEL"];
/** Every rendered number is a Field, carried with its provenance so the Rendering invents nothing.
* `asOfSeq` is an ORDINAL, never a wall clock. */
export interface KernelField<T = number> {
readonly value: T;
readonly attribution: Attribution;
/** Required for MODEL, absent for OBS/CALC. */
readonly source?: string;
readonly modelVer?: string;
readonly confidence?: number;
readonly asOfSeq?: number;
}
/** A DATE-BLIND leg. `strike`/`right` are BOTH present for an option and BOTH absent for a
* spot/equity leg (ADR-0017 — a fictional strike is never written). `symbol` is opaque: no
* application ticker ever appears in this contract. */
export interface KernelLeg {
readonly symbol: string;
readonly strike?: number;
readonly right?: "C" | "P";
}
export interface KernelPosition {
readonly leg: KernelLeg;
readonly qty: number;
readonly basis: number;
readonly fair: number | null;
/** Running UNREALIZED P&L in DOLLARS (mirror of `src/frame/types.ts` `Position.unrealUsd`,
* kestrel-c11): `qty × (mark − basis) × multiplier`, marked to spot (equity/spot) or intrinsic
* (option) with the SAME dollar scaling the fill engine's `pnl` applies. It rides the contract so
* the human READS its P&L (`-$25.97`) instead of re-deriving it and mis-scaling cents-for-dollars
* (the 100× abandon bug) — a Rendering invents no value (CONTEXT.md *Rendering*). `null` ⇒ the
* mark is UNKNOWN (no spot) ⇒ rendered `—`, never a fabricated `0`. Required here (absent-not-
* hidden): the projector emits explicit `null`, never a dropped key. */
readonly unrealUsd: number | null;
/** The plan that OPENED the leg (provenance). */
readonly plan: string | null;
/** The plan that OWNS this inventory claim — exactly one plan may claim a line; owner and engine
* see the same claim (mutual visibility). Distinct from `plan`. */
readonly claimOwner: string | null;
readonly structure: string | null;
}
/** `live` and `clamped` are INDEPENDENT flags: a working order whose price was clamped is BOTH.
* They are never collapsed into one label — a `live` badge may never mask a clamped price
* (RUNTIME §4: a silent price is forbidden). */
export interface KernelRestingOrder {
readonly ref: string;
readonly leg: KernelLeg;
readonly side: "buy" | "sell";
readonly qty: number;
readonly px: number;
readonly live: boolean;
readonly clamped: boolean;
/** Price-resolution annotation (e.g. `fair=fallback(mid)`, `cap fair,0.73`). */
readonly note: string | null;
readonly plan: string | null;
}
/** A fill since the last vantage. DELIBERATE MIRROR EXCLUSION vs `src/frame/types.ts` `FillRecord`:
* the frame's `clock` (the HH:MM ET the fill printed at) is DROPPED. The oversight contract permits
* a wall clock in EXACTLY ONE place — `SpectatorFrame.asof` (§4, invariant 4) — and fill ORDER is
* carried by the Bus `seq`, never by a clock token on the deterministic path. Excluded on purpose,
* recorded here so the drop is not silent (not an oversight of the mirror). */
export interface KernelFill {
readonly leg: KernelLeg;
readonly side: "buy" | "sell";
readonly qty: number;
readonly px: number;
readonly plan: string | null;
}
export type PlanLifecycle = "authored" | "armed" | "fired" | "managing" | "done";
export declare const PLAN_LIFECYCLES: readonly ["authored", "armed", "fired", "managing", "done"];
export type PlanOutcome = "filled" | "expired" | "invalidated";
export declare const PLAN_OUTCOMES: readonly ["filled", "expired", "invalidated"];
export interface KernelPlanState {
readonly name: string;
readonly state: PlanLifecycle;
readonly outcome: PlanOutcome | null;
/** A logged reason (a de-arm/invalidation reason, a wake note). Never blank-on-unknown. */
readonly note: string | null;
/** The ARM-TIME gate-block reason when this plan is stuck `authored` on an unsatisfiable regime
* gate (mirror of `src/frame/types.ts` `PlanStateEntry.blockedReason`, kestrel-50w). Present ⇒
* render `authored (blocked: <reason>)`, so a bare `authored` (a live plan awaiting its WHEN) is
* DISTINGUISHABLE from one that can NEVER arm — the phantom-position trap (an overseer misreads
* `authored` as armed-and-live and believes in a position that never existed). `null` ⇒ not
* blocked. Fail-closed distinguishability, required here (absent-not-hidden). */
readonly blockedReason: string | null;
}
/** The cockpit risk envelope: remaining-R plus the three nested envelopes the fire-time router
* lives inside — **plan ⊆ book ⊆ owner**. The OWNER envelope IS the funded balance (ADR-0035 §g):
* you cannot lose more than you funded.
*
* DELIBERATE MIRROR EXCLUSION vs `src/frame/types.ts` `BudgetEnvelope`: the frame's `sizing`
* (`SizingHeadroom` — the max fillable size the remaining-R budget admits, kestrel-m9i.32) is
* DROPPED. It is an AGENT-authoring aid (so a model sizes WITHIN the envelope rather than hitting a
* silent fire-time clamp); a PM never authors tickets (ADR-0035, CONTEXT.md *PM*), so it is not PM
* oversight state. The bounded-risk fact the cockpit renders is the funded `ownerEnvelope`, carried
* below. Excluded on purpose, recorded here so the drop is not silent. */
export interface RiskEnvelope {
readonly remainingR: number;
readonly planEnvelope: number;
readonly bookEnvelope: number;
readonly ownerEnvelope: number;
}
/** The plan-lifecycle usage view (distinct from the cockpit `RiskEnvelope`). */
export interface KernelBudget {
readonly used: number | null;
readonly remaining: number | null;
readonly total: number | null;
readonly maxConcurrentR: number | null;
}
export type WakeSeverity = "routine" | "elevated" | "urgent";
export declare const WAKE_SEVERITIES: readonly ["routine", "elevated", "urgent"];
/** Why the author is looking now + the RELATIVE deadline. `deadlineMin` is minutes-to-close —
* never an absolute time, never a date (determinism). `null` ⇒ `T-— to close`. */
export interface KernelWake {
readonly reason: string;
readonly severity: WakeSeverity;
readonly deadlineMin: number | null;
}
/** Per-vehicle book health (the routing gate). A `dark` vehicle is NAMED, never hidden, and taints
* its dependents (they render UNKNOWN). */
export interface KernelVehicleHealth {
readonly symbol: string;
readonly bidPresentRate: number;
readonly twoSided: boolean;
readonly staleS: number;
readonly dark: boolean;
}
export type EngineActionKind = "fired" | "cancelled" | "rejected" | "clamped";
export declare const ENGINE_ACTION_KINDS: readonly ["fired", "cancelled", "rejected", "clamped"];
/** One L0/L1 engine action since the last vantage. `asofSeq` is an ordinal, never a wall clock.
* `reason` carries WHY a `rejected` action was refused (e.g. "uncovered sell refused: never
* naked") — the fail-closed refusal is DATA the overseer can read, not a swallowed error. */
export interface KernelEngineAction {
readonly id: string;
readonly kind: EngineActionKind;
readonly asofSeq: number;
readonly reason: string | null;
}
/** An owner act as the kernel already records it (id + kind + ordinal). The TYPED act vocabulary
* that PRODUCES these is `OversightAct` (§3.6); this is the recorded projection. */
export interface KernelOwnerAct {
readonly id: string;
readonly kind: string;
readonly asofSeq: number;
}
export type ClaimType = "predictor" | "regime";
export declare const CLAIM_TYPES: readonly ["predictor", "regime"];
/** A predictor/regime claim MUST be an honest MODEL Field (source + modelVer + confidence). An
* OBS/CALC "claim" is REFUSED at construction — a dishonest claim never renders as if honest. */
export interface KernelClaim {
readonly field: KernelField;
readonly claimType: ClaimType;
}
/** HARD, machine-checkable, narrowing-only — the ONLY channel that feeds admission (ADR-0026). */
export interface KernelMandate {
readonly objective: string;
readonly rUsd: number;
readonly successCriterion: string;
readonly riskRule: string;
}
/** SOFT, directional English — content-hashed, bound into grade provenance (`brief_hash`).
* HARD GUARD: the Brief can NEVER enter admission/narrowing. It directs perception and authoring;
* it authorizes nothing. */
export interface KernelBrief {
readonly text: string;
readonly hash: string;
readonly version: string | null;
}
/**
* The acting kernel, as the seam carries it. EVERY section is present (absent-not-hidden): an
* absent source section projects to an explicit `null`/`[]`, never to a dropped key.
*/
export interface OversightKernel {
readonly mandate: KernelMandate | null;
readonly brief: KernelBrief | null;
readonly wake: KernelWake | null;
readonly dataHealth: readonly KernelVehicleHealth[];
readonly unavailable: readonly string[];
readonly budgetEnvelope: RiskEnvelope | null;
readonly ownerActs: readonly KernelOwnerAct[];
readonly engineLog: readonly KernelEngineAction[];
readonly claims: readonly KernelClaim[];
readonly positions: readonly KernelPosition[];
readonly resting: readonly KernelRestingOrder[];
readonly fillsSinceLast: readonly KernelFill[];
readonly budget: KernelBudget | null;
readonly plans: readonly KernelPlanState[];
}
/** The two model tiers (ADR-0032). TIER (clock/price) is ORTHOGONAL to ORG (allocation). */
export type AgentTier = "strategist" | "watcher";
export declare const AGENT_TIERS: readonly ["strategist", "watcher"];
/** The four escalation triggers (ADR-0032 §4) under the owner-approved HYBRID policy:
* - `mandate-edge` AUTOMATIC, fail-closed — the cheap tier never pushes the risk boundary on its
* own judgment. It escalates rather than attempt-and-get-refused.
* - `brief-flag` ALWAYS-escalate event classes the Brief pre-declares (e.g. a regime break).
* - `regime` a structural regime break / SHOCK keyframe — definitionally the strategist's job.
* - `uncertainty` the watcher's OWN forced-comment certainty below threshold ("I am out of my
* depth; call the PM"). This is the only trigger that is the watcher's judgment. */
export type EscalationReason = "mandate-edge" | "brief-flag" | "regime" | "uncertainty";
export declare const ESCALATION_REASONS: readonly ["mandate-edge", "brief-flag", "regime", "uncertainty"];
/**
* WHO decided. The four rungs of the attribution ladder (ADR-0032 Resolved 3, extended):
* deterministic runtime → watcher → strategist → human. A superset of `AgentTier`, because the
* two rungs that are not models still author actions the cockpit must attribute.
*/
export type AuthorTier = "runtime" | "watcher" | "strategist" | "human";
export declare const AUTHOR_TIERS: readonly ["runtime", "watcher", "strategist", "human"];
/**
* THE TIER STAMP — carried by EVERY action, from day one (owner decision, 2026-07-14).
*
* TIERS ARE EXPOSED, NOT HIDDEN, and the reason is principled: **Kestrel already refuses
* unattributed judgment.** A `MODEL` Field is refused at construction without its receipt
* (source + modelVer + confidence — CONTEXT.md *Attribution*: "nothing above CALC goes
* unattributed"). An action AUTHORED BY a model deserves the same rule; hiding which tier decided
* would be honest about where a NUMBER came from and cagey about where a DECISION came from.
*
* A Rendering may FOLD the stamp ("just show me the book") — it may never HIDE it. Same discipline
* as absent-not-hidden. Because the stamp is in the contract from day one, NO RENDERING NEEDS
* REWORK when the second tier lights up.
*/
export interface Authorship {
readonly tier: AuthorTier;
/** The deciding model. `null` for `runtime` and `human` — ABSENT, never invented. */
readonly model: string | null;
/** The deciding actor's version: the model version for a model tier, the runtime version for
* `runtime`, `null` for `human`. Same receipt discipline as a MODEL Field's `modelVer`. */
readonly version: string | null;
}
/** The cascade, rendered per Book. Positions in time are ORDINALS (`seq`), never clocks. */
export interface AgentTierStatus {
/** Rare, frontier: the standing thesis + its last re-frame. The strategist authors the watcher's
* View and Brief (owner-approved); a re-brief is a NORMAL supersede. */
readonly strategist: {
readonly model: string | null;
readonly thesis: string | null;
readonly briefHash: string | null;
readonly lastReframeSeq: number | null;
};
/** Fast, cheap, in-loop: the wake-cadence actor. Its tactical authority is: manage armed Plans,
* reload/exit/adjust, size-within-budget, reschedule its own Wake, request a View, stand down —
* AND (owner-approved) arm a NEW BOUNDED PLAN within the existing Coverage + Mandate. New
* Coverage / thesis / allocation stays STRATEGIST-ONLY. The Gate admits every action regardless. */
readonly watcher: {
readonly model: string | null;
readonly lastActionSeq: number | null;
};
/** A pending escalation: the watcher woke the PM and is waiting for a re-brief. The per-event
* record is the first-class `oversight.escalation` event (§3.5.1); this is the standing status. */
readonly escalation: {
readonly pending: boolean;
readonly reason: EscalationReason | null;
readonly atSeq: number | null;
};
/** The content-hashed System Profile (ADR-0013) this cascade runs under — the ConfigId whose
* cadence axis (strategist calls/day vs watcher calls) is SWEPT on `ev_per_ktoken`, not fixed
* (owner-approved). The oversight stream NAMES it; it never computes a grade. */
readonly configId: string | null;
}
/** A Book = the org LEAF: the only place positions and orders live. */
export interface BookView {
readonly bookId: string;
/** Coverage = instruments + THE THESIS FOR WHY. Instruments alone are not coverage. */
readonly coverage: {
readonly symbols: readonly string[];
readonly thesis: string;
};
readonly kernel: OversightKernel;
readonly tiers: AgentTierStatus;
/** The derived status pill — projection only, no new values: severity + relative deadline +
* attention. `coalesced` = wakes folded into this one (attention spent, not risk). */
readonly status: {
readonly severity: WakeSeverity;
readonly deadlineMin: number | null;
readonly wakesRemaining: number | null;
readonly coalesced: number;
};
}
/**
* A Pod = the recursive node: allocating role + envelope + children. It holds NO POSITIONS and has
* NO KERNEL. Its `aggregate` is exactly the child-published org-facts fold the engine already
* models (`children(any).<fact>`): a total map of child id → the facts THAT CHILD published. A fact
* no child published is ABSENT — and absent is UNKNOWN, which de-arms a PM wake with a logged
* reason; it is never a silent `0`.
*/
export interface PodView {
readonly podId: string;
readonly envelope: RiskEnvelope;
readonly aggregate: Readonly<Record<string, Readonly<Record<string, number>>>>;
readonly children: readonly OrgNode[];
}
/** Discriminated so a Rendering can walk the tree without guessing. */
export type OrgNode = {
readonly node: "book";
readonly book: BookView;
} | {
readonly node: "pod";
readonly pod: PodView;
};
/** A rectangular pane of scalar cells — so BOTH Renderings can draw it without inventing a value.
* `null` is UNKNOWN and renders as such. NOT pre-rendered ascii. */
export interface SpectatorPane {
readonly paneId: string;
readonly columns: readonly string[];
readonly rows: readonly (readonly (string | number | null)[])[];
}
/** Spectator context: NOTHING AT STAKE (the bare-invocation orientation, ADR-0035 §c). Off the
* deterministic path — so live data and a WALL CLOCK are fine HERE, and nowhere else. */
export interface SpectatorFrame {
readonly watchlist: readonly string[];
readonly asof: string;
readonly panes: readonly SpectatorPane[];
}
/**
* THE contract object. CLI renders it `ascii`/Ink; Web renders it `html`. Same values on both.
*/
export interface OversightFrame {
readonly identity: OversightIdentity;
/** Ordinal position on the Bus — NOT a wall clock (determinism). Every `seq` below is ≤ this. */
readonly asofSeq: number;
/** The acting view: the PM's Pod. Phase 0 = a degenerate one-Book Pod. `null` on a bare
* spectator orientation (nothing at stake, nothing attached). */
readonly pod: PodView | null;
/** The spectator view: present on the bare orientation; `null` inside an acting session. */
readonly spectator: SpectatorFrame | null;
/** The slot awaiting an answer, if a Book is mid-turn (§3.5). `null` when nothing is pending. */
readonly pending: OversightDelivery | null;
/** The recorded conversation up to `asofSeq` — a projection of Bus message events (§3.7), so a
* cold Web client re-baselines its chat history from the snapshot, not from a side-channel. */
readonly messages: readonly OversightMessage[];
}
/**
* A Frame delivery awaiting an answer — the FOUR pentad legs (`sessionId`, `ordinal`, `parentHash`,
* `frameRoot`) plus the kernel PROJECTION of what was delivered. The fifth leg — the exact authored
* bytes — rides the turn.
*
* HARD RULE: `frameRoot` addresses the RUNTIME's canonical Frame. The `kernel` here is a VIEW. A
* Rendering must never re-hash the projection into `frameRoot`, and the projection must never be
* substituted for the delivered Frame. (Mirror of `session/controller-types.ts` `Delivery`, with the
* frame body replaced by its Rendering-neutral projection.)
*/
export interface OversightDelivery {
readonly sessionId: SessionId;
readonly ordinal: number;
readonly parentHash: string;
readonly frameRoot: string;
readonly bookId: string;
/** WHICH TIER owns this slot — the cascade is one Session, one Bus, two adapters interleaved at
* their wake ordinals by a SINGLE driver (owner-approved). `recordedAgent` replays the whole
* cascade byte-identically because both tiers are already above-the-line `Agent`s. */
readonly tier: AgentTier;
readonly kernel: OversightKernel;
}
/** What a committed turn DID to the standing document (mirror of `TurnDisposition`). */
export type TurnDisposition = "armed" | "revised" | "pass" | "stood-down" | "failure";
export declare const TURN_DISPOSITIONS: readonly ["armed", "revised", "pass", "stood-down", "failure"];
/** A committed turn as the cockpit sees it: the content-addressed `TurnEntry` (the full pentad,
* from `./session.ts` — imported, NOT re-declared) + its disposition + WHO AUTHORED IT. */
export interface OversightTurn {
readonly entry: TurnEntry;
readonly disposition: TurnDisposition;
/** The tier stamp — `{ tier, model, version }`, present from day one on every action. Replaces a
* bare `tier` field: the cockpit must be able to say not just WHICH TIER decided but WHICH MODEL,
* at WHICH VERSION (§3.3 `Authorship`). */
readonly authoredBy: Authorship;
/** The escalation this turn RAISED, when the watcher woke the PM. `null` otherwise. The CANONICAL
* record is the first-class `oversight.escalation` event (§3.5.1) — this is the back-reference. */
readonly escalated: EscalationReason | null;
}
/**
* The watcher woke the PM. Spends ATTENTION, never risk (ADR-0001's Wake invariant), so it is a Bus
* fact with a `seq` and never an authored action.
*/
export interface OversightEscalation {
readonly seq: number;
readonly bookId: string;
/** WHO called — the full stamp, so "which watcher, which version escalated" is answerable. */
readonly from: Authorship;
/** WHICH TIER was woken. `"strategist"` today; the ladder allows `"human"`. */
readonly to: AuthorTier;
/** WHY (ADR-0032 §4, hybrid policy). `mandate-edge` and `brief-flag` are AUTOMATIC; `uncertainty`
* is the watcher's own call, read off its forced-comment certainty. */
readonly reason: EscalationReason;
/** The watcher's forced-comment rationale for the call — the legible trace of a fast judgment
* (ADR-0032 §8.5). `null` ⇒ none authored; never invented. */
readonly note: string | null;
/** The re-brief that ANSWERED it, once it lands: the strategist's superseding turn. `null` while
* the escalation is still pending — absent, not hidden. */
readonly answeredBySeq: number | null;
}
/**
* Every structured human effect is a PM/owner act that lands on the Bus as a seq-ordered,
* replayable event — exactly like a wake. There is NO fourth mutation path.
*
* Approval gates CAPITAL, not trades: `fund` widens the owner envelope (human-signed); inside the
* funded envelope agents trade freely at machine speed with NO per-trade approval. Every de-risking
* act is ALWAYS FREE — authority only narrows.
*/
export type OversightAct = {
readonly act: "allocate";
readonly target: string;
readonly envelopeR: number;
} | {
readonly act: "arm";
readonly target: string;
} | {
readonly act: "de-arm";
readonly target: string;
readonly reason: string;
} | {
readonly act: "coverage";
readonly bookId: string;
readonly symbols: readonly string[];
readonly thesis: string;
} | {
readonly act: "fund";
readonly ownerEnvelope: number;
/** Human-signed capital authorization. Broker credentials (CLI-direct BYO broker) or a
* platform OAuth approval (managed) — the OSS side holds an OPAQUE HANDLE, never a key. */
readonly authorizationRef: string;
} | {
readonly act: "de-fund";
readonly ownerEnvelope: number;
} | {
readonly act: "pause";
readonly target: string;
} | {
readonly act: "veto";
readonly target: string;
readonly reason: string;
};
export type OversightActKind = OversightAct["act"];
export declare const OVERSIGHT_ACT_KINDS: readonly ["allocate", "arm", "de-arm", "coverage", "fund", "de-fund", "pause", "veto"];
/** The acts that only NARROW authority. These are ALWAYS admitted — de-risking is never gated,
* never queued behind an approval, never refused for want of capital. */
export declare const NARROWING_ACTS: readonly ["de-arm", "de-fund", "pause", "veto"];
/**
* The lifecycle stamp on an ADMITTED act's receipt — is it merely QUEUED (admitted, awaiting a slot to
* bind to) or BOUND (in force NOW)? Without it, `ok:true` collapses 'admitted' and 'in force' into one
* signal, and for a de-risking veto that gap is a SAFETY LIE: an owner reading `ok:true` reasonably
* believes the risk is removed NOW, when the act is in fact pending against a FUTURE slot (kestrel-ysqj).
*/
export type ActState = "queued" | "bound";
export declare const ACT_STATES: readonly ["queued", "bound"];
/**
* A submitted act's outcome. On `ok`, `seq` carries the Bus `seq` it landed at (the act IS an event) and
* `state` separates 'admitted' and 'in force' so the two are never collapsed into a bare `ok:true`:
* - `"queued"` — ADMITTED but NOT YET IN FORCE: a narrowing act ({@link NARROWING_ACTS}) that arrived with
* no open slot, waiting to bind the NEXT answered slot. There is no slot yet, so `slot` is absent
* (absent-not-hidden — the queued member simply has no `slot` key to invent a value for).
* - `"bound"` — IN FORCE NOW: a narrowing act that stood the pending slot down (`slot` = the ordinal of
* the delivered slot it bound to), or an envelope act (`fund`/`de-fund`) that took effect immediately
* (`slot` is `null` — it binds NO delivered slot, and `null` states that honestly rather than dropping
* the key).
*/
export type ActReceipt = {
readonly ok: true;
readonly seq: number;
readonly actId: string;
readonly state: "queued";
} | {
readonly ok: true;
readonly seq: number;
readonly actId: string;
readonly state: "bound";
readonly slot: number | null;
} | {
readonly ok: false;
readonly reason: string;
};
/**
* An act AS IT LANDS ON THE BUS — the act plus WHO authored it. A human owner act stamps
* `authoredBy.tier = "human"` (model and version `null`): the fourth rung of the attribution ladder,
* and precisely the event whose ABLATION REPLAY yields `human_alpha` (ADR-0032 Resolved 3, extended).
* An act a *strategist* authors (an allocation) stamps its tier and model the same way. Every action
* in this contract carries the stamp — no action is unattributed.
*/
export interface OversightActRecord {
readonly seq: number;
readonly actId: string;
readonly act: OversightAct;
readonly authoredBy: Authorship;
}
/** Who spoke. `owner` = the human above the root pod (the `human` rung of the ladder — §3.3).
* `pm`/`trader` = an agent replying in the conversation (its reasoning already rides the Bus as
* JOURNAL); they carry the full `Authorship` stamp on the events they author. */
export type MessageAuthor = "owner" | "pm" | "trader";
export declare const MESSAGE_AUTHORS: readonly ["owner", "pm", "trader"];
/**
* A chat message as a Bus fact. Content-addressed (`messageId = sha256(canonical(author,text,to))`)
* so a re-send at the same slot is an idempotent duplicate and never a second event.
*
* CARRIES NO AUTHORITY. A message can never arm, size, fund, or place. Anything that commits risk is
* an `OversightAct` (§3.6) the human explicitly confirms. Chat is sugar over authoring + owner acts.
*/
export interface OversightMessage {
readonly seq: number;
readonly messageId: string;
readonly author: MessageAuthor;
/** The Book this message is addressed to; `null` = the Pod / the desk. */
readonly to: string | null;
readonly text: string;
}
/**
* What the one input box resolved to. PURE and TOTAL — it never throws, never performs I/O, and
* never has an authority side effect. Resolution order is fixed:
* 1. full Kestrel grammar → `grammar` (executes deterministically)
* 2. a bare instrument/expiry → `shortcut` (SHOWS a View — adds NO authority)
* 3. a typed act → `act` (the ONLY structured path to authority)
* 4. grammar-SHAPED but invalid → `parse-error` (SURFACES; never silently becomes chat)
* 5. clear prose → `chat`
*/
export type InputResolution = {
readonly kind: "grammar";
readonly document: string;
} | {
readonly kind: "shortcut";
readonly view: string;
readonly symbols: readonly string[];
} | {
readonly kind: "act";
readonly act: OversightAct;
} | {
readonly kind: "chat";
readonly text: string;
readonly to: string | null;
} | {
readonly kind: "parse-error";
readonly diagnostics: readonly string[];
};
export type InputResolutionKind = InputResolution["kind"];
export declare const INPUT_RESOLUTION_KINDS: readonly ["grammar", "shortcut", "act", "chat", "parse-error"];
export type OversightEventType = "oversight.snapshot" | "oversight.delivery" | "oversight.turn" | "oversight.act" | "oversight.escalation" | "oversight.message" | "oversight.journal" | "oversight.diagnostic" | "oversight.finalized" | "oversight.failed";
export declare const OVERSIGHT_EVENT_TYPES: readonly ["oversight.snapshot", "oversight.delivery", "oversight.turn", "oversight.act", "oversight.escalation", "oversight.message", "oversight.journal", "oversight.diagnostic", "oversight.finalized", "oversight.failed"];
/** The two TERMINAL event types that end the stream (mirror of the `operation.completed` /
* `operation.failed` pair). `oversight.finalized` ends it cleanly; `oversight.failed` throws. */
export declare const OVERSIGHT_TERMINAL_TYPES: readonly ["oversight.finalized", "oversight.failed"];
/** True iff `t` is a contract event type. Anything else is a protocol violation the client MUST
* fail closed on (mirror of `isSseEventType`). */
export declare function isOversightEventType(t: string): t is OversightEventType;
/** The decoded stream event — a discriminated union on `type`. `seq` is present exactly on the
* BUS-BACKED members: those events ARE Bus facts and are replayable by seq. */
export type OversightEvent = {
readonly type: "oversight.snapshot";
readonly frame: OversightFrame;
} | {
readonly type: "oversight.delivery";
readonly seq: number;
readonly delivery: OversightDelivery;
} | {
readonly type: "oversight.turn";
readonly seq: number;
readonly turn: OversightTurn;
} | {
readonly type: "oversight.act";
readonly record: OversightActRecord;
} | {
readonly type: "oversight.escalation";
readonly escalation: OversightEscalation;
} | {
readonly type: "oversight.message";
readonly message: OversightMessage;
} | {
readonly type: "oversight.journal";
readonly seq: number;
readonly bookId: string;
readonly authoredBy: Authorship;
readonly text: string;
} | {
readonly type: "oversight.diagnostic";
readonly diagnostic: SessionDiagnostic;
readonly ordinal: number;
} | {
readonly type: "oversight.finalized";
readonly seq: number;
readonly sessionId: SessionId;
readonly tipHash: string;
readonly artifacts: readonly string[];
} | {
readonly type: "oversight.failed";
readonly reason: string;
};
/** Implemented LOCALLY (OSS: in-process controller + local BYOK agent + BYO broker) and REMOTELY
* (platform: hosted OAuth, managed keys, funded accounts). The CLI (Ink) and the Web dashboard are
* two CLIENTS OF THIS — that is the whole cross-repo seam. */
export interface OversightBackend {
readonly kind: "local" | "remote";
identity(): Promise<OversightIdentity>;
/** The current view — a PURE PROJECTION of harness state. */
frame(): Promise<OversightFrame>;
/** The live stream. `after` is the OPAQUE cursor; absent ⇒ from the current snapshot. */
stream(after?: string): AsyncIterable<OversightEvent>;
/** Parse-first resolution of the one input box. PURE, TOTAL, fail-closed (§3.8). */
resolveInput(text: string): InputResolution;
/** A typed act → a Bus event. Chat can NEVER reach this; only an `act` resolution can. */
submitAct(act: OversightAct): Promise<ActReceipt>;
/** Post an owner message → a Bus event (§3.7). Carries NO authority. */
say(text: string, to: string | null): Promise<{
readonly seq: number;
readonly messageId: string;
}>;
/** Answer a delivered slot — the ONE thing that crosses into the graded Bus as an authored action.
* The response vocabulary is the EXISTING `AuthoredResponse`; no fourth kind is added. */
respond(r: {
readonly kind: "authored";
readonly document: string;
} | {
readonly kind: "pass";
} | {
readonly kind: "stand-down";
readonly reason: string;
}): Promise<{
readonly ok: true;
readonly turn: OversightTurn;
} | {
readonly ok: false;
readonly diagnostic: SessionDiagnostic;
}>;
}
//# sourceMappingURL=oversight.d.ts.map