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.

502 lines (408 loc) 26.1 kB
# Founder seat-Views — seeds, candidates, and the LATENT roadmap, per pod seat A **View** decides *what* an agent sees when the runtime wakes it; the agent decides *what to do* with it. A Kestrel pod is not one agent but a small organization of **seats** roles distinguished by the deliberation budget they can afford and each seat reads a different screen because each seat does a different job at a different speed. This page is the founder library of those screens: for every seat, the View we seed it with, the screen that View renders, and the ladder of candidate panes queued behind it. > **These are hypotheses, not defaults.** Under [ADR-0041](../adr/0041-percept-inflection-and-template-as-hypothesis.md), > every founder View is a **graded seed** that enters the [ADR-0029](../adr/0029-agent-requested-emergent-view-authoring-loop.md) > tournament against the required baseline pair exactly as any agent-authored lens does. The null > hypothesis is that a pane **hurts** until a both-poles matched set says otherwise. Nothing on this > page is "the default for" its seat. A seed is frozen into a default only by owner-gated realized > evidence never by taste, never by appearing here. Read every "seed" below as "the current > hypothesis we are grading," and read the ladders as "what we are queued to falsify next." Every rendered screen on this page is **byte-real** generated from the live renderer by `scripts/gen-founder-views-doc.ts`, never typed. A renderer change re-pins these screens or CI goes red (the same single-source discipline the CLI docs carry, kestrel-wa0j.66). Every View block is written in real Kestrel syntax and parses + round-trips byte-stable the accept corpus `tests/golden/accept/founder-seat-views.kestrel` is its contract. --- ## The four-layer model A seat's screen is not a monolith. It is assembled through four layers, each answering a different question, each independently authored and independently graded: 1. **Seat** *who is this?* A role in the pod org, fixed by its affordable deliberation budget: a PM and a Strategist are slow, deliberate, frontier-class judgment (dollars, a few times a day); a Watcher is the fast reflex (pennies, seconds); a Trigger is a non-LLM reflex below even the Watcher. Seats' latency envelopes differ by **orders of magnitude**, which is why no single model fills them all well. 2. **Tasks** *what must it decide?* The seat's job, in doctrine: allocate an envelope, author a leaf, manage held inventory, summon a bigger brain. The tasks fix what information is decision-relevant. 3. **View config** *what does it see?* The role-keyed founder View the panes, in order, at the band the wake zooms to. This is the layer this page is about. Perception is tuned here. 4. **Persona / Brief** *how does it lean?* The soft, directional English the seat reasons *from* (a "disciplined risk manager," a "momentum chaser"). Personas tune **policy**; Views tune **perception** different channels, both role-attachable, and the Brief [never enters admission](../adr/0032-two-tier-strategist-watcher-agent-architecture.md) (it directs, it never authorizes). The seat and its persona are POLICY; the View is PERCEPTION. This page fixes layer 3 and leaves layers 1, 2, and 4 to their own documents. ### The pod tier stack ``` ┌─────────────────────────────────────────────┐ humans sit humans above the root, always above the root └───────────────────────┬─────────────────────┘ ╔══════════════════════════════════════════╪══════════════════════════════════════════╗ L0 · RISK (a LAYER, not a seat) clamps / vetoes · may never open risk outranks every node including the PM rendered surface: the kernel safety block ╚══════════════════════════════════════════╪══════════════════════════════════════════╝ ┌──────────────────────┴──────────────────────┐ PM node (runs a Pod: allocates + discovers)│ · Allocator envelope allocation slow · $$ · aggregate · Scanner scan-fire read-why minutes · single-name └──────────────────────┬──────────────────────┘ arms / assigns Coverage / authors leaves ┌──────────────────────┴──────────────────────┐ Trader node (runs a Book: manages a leaf) · Strategist frames + authors plans a few/day · $$ · frontier · Watcher manages · escalates seconds · ¢ · fast reflex └─ Trigger summons the Watcher sub-second · non-LLM └──────────────────────┬──────────────────────┘ off-tape ······························┴······ Historian reads Blotters after close, (no live seat's latency budget) curates lineage + evolves the Brief ``` Reading the stack: **Risk is the L0 layer**, not a seat strictly subtractive, above everyone. The **PM** runs a Pod (allocates children's envelopes, arms/de-arms, assigns Coverage, watches aggregates) and splits into **Allocator** + **Scanner**. The **Trader** runs a Book (a leaf, the only place positions live) and splits into **Strategist** + **Watcher**. The **Trigger** is the pod's third live tier below the Watcher a non-LLM reflex that *summons*, never decides. The **Historian** works off-tape, after the close, with a latency budget no live seat has. Each split is real, not cosmetic: it earns a seat only where the sub-jobs diverge simultaneously on (a) latency by 1 order of magnitude, (b) a typed boundary a document crosses, and (c) independent gradability each with its own cells and matched sets. That filter is why Risk stays a mechanism, the Grader stays an engine, and the adversarial-checking panel stays a protocol none of them a seat. --- ## Strategist — the frontier framer **Role.** Runs a Book. Frames the session at the open and on a regime break, authors the plans, and prices the defined-risk exits. Frontier-class judgment, a few times a day, at dollars a call. **Tasks.** Orient at the open; commit a thesis; author armed plans with their own exits so the book is never naked; re-frame on SHOCK or a Watcher escalation. **The founder View.** ```kestrel VIEW strategist-open instruments levels tape chain acting ``` This is today's **measured** open View it is exactly the shipped OPEN default (`DEFAULT_OPEN_PANES`), which is why its screen below is the OPEN briefing byte-for-byte. It is a seed, not a settled answer; it is simply the one the tournament has not yet beaten. The screen it renders: <!-- GEN:BEGIN strategist-open --> <!-- GENERATED by scripts/gen-founder-views-doc.ts do not hand-edit · source: docs/percept-lab/live/open.txt · view strategist-open · kestrel-renderer/8 --> ``` ==== SAFETY / CONTROL KERNEL (non-configurable; leads every frame) ==== frame=OPEN -- WAKE -- reason=phase boundary: open orientation severity=routine deadline=T-385m to close -- DATA-HEALTH -- SPX: bid_present_rate=1.000 two_sided=true stale_s=0.3 dark=false SPXW: bid_present_rate=0.960 two_sided=true stale_s=0.4 dark=false unavailable capabilities: macro calendar -- POSITIONS / INVENTORY-CLAIMS -- flat no positions / inventory claims -- RESTING ORDERS -- none resting -- BUDGET / REMAINING-R -- remaining_R=5.00 plan_envelope=1.00 book_envelope=5.00 owner_envelope=10.00 sizing: UNKNOWN (no sizing headroom) -- OWNER ENVELOPE + ACTS -- owner_envelope=10.00 owner acts: none this session -- L0/L1 ENGINE LOG -- engine actions: none -- PREDICTOR / REGIME CLAIMS -- regime 1.00 (source=SPX:regime.intraday, modelVer=regime-v1, conf=0.72) KESTREL · OPEN briefing · T-385m to close · regular · 09:40 ET instruments: SPX index signal mult 100 tick 0.05 SPXW option-underlier exec mult 100 tick 0.05 levels · SPX spot 5123.60 · prior_close 5108 · hod 5127.40 · lod 5111.20 · vwap 5120.90 · or 5112.50–5125 tape 5m · axis 5111.20→5127.40 · anchor @ 09:35 ET 09:35 ───███████─── 09:40 ──███████████─ 09:45 ───█████████─ 09:50 ──██████████─── 09:55 ────█████████─ 10:00 ──███─── chain (near-money) · SPX strike R bid ask fair flags 5120 C 14.20 14.80 14.50 b76 nLiq=7 5120 P 10.60 11.10 10.80 b76 nLiq=7 5125 C 12.30 12 fallback(mid) bid dark 5125 P 13.10 13.70 KERNEL (acting) positions: (none) resting: (none) fills since last: (none) premium budget: used +0.00 / remaining +500.00 (total 500, maxR 3) plans: or-break: armed fade-close: authored ``` <!-- GEN:END strategist-open --> ### Candidate ladder | rung | panes | status | | --- | --- | --- | | **seed (today's measured default)** | `instruments levels tape chain acting` | rendered above | | graded-candidate | `vol` (straddle / expected-move decomposition) | awaiting both-poles economics | | graded-candidate | `prior-context` (gap vs prior close) | awaiting both-poles economics | | graded-candidate | `series-summary` (numeric trend stats) | merged (PR #243), awaiting both-poles economics | **The honest line on this ladder.** Every restraint pane added to the strategist so far has **LOST to baseline** on matched sets. The mechanism is specific: forcing a `range-velocity` read made the strategist stand down on the ORB fakeout which *looks* like discipline but graded on the matched set, baseline's fakeout plan **floors at a profitable exit** because the model's authored defined-risk exit already prices the fakeout, so the stand-down **forfeited money it should have made**. A pane that cuts the loss on the restraint pole while killing the gain on the action pole nets to zero-or-worse. Fable's structural rubric is saturated (26/26); only economics on matched setups can separate a good pane from a passivity trap. This is not a mark against the program it is the program working. We publish the losses at the same fidelity as any win because a founder library that only showed its winners would be lying about the trust substrate. --- ## Watcher — the fast reflex **Role.** Manages the leaf between the Strategist's re-frames. Low-latency perception armed-plan control + escalation. Seconds, at pennies a call. **Tasks.** Watch the inventory it holds; act within the plans it was handed; when it reaches the edge of its own certainty it does **not** guess it escalates via a Wake ("call the Strategist"), because escalating costs a frontier call, never an unbounded action. **The founder View.** ```kestrel VIEW watcher-wake delta tape levels chain acting ``` The `delta` pane leads: the cheapest possible wake states *what moved* since the last look, which is the watcher's core question. (A WAKE default never shows `delta` a founder pane is a graded seed, not a blessing, so a View must name it.) The screen it renders: <!-- GEN:BEGIN watcher-wake --> <!-- GENERATED by scripts/gen-founder-views-doc.ts do not hand-edit · rendered: renderWakeDelta(watcherWakeInput, { view: watcher-wake }) · view watcher-wake · kestrel-renderer/8 --> ``` ==== SAFETY / CONTROL KERNEL (non-configurable; leads every frame) ==== frame=WAKE -- WAKE -- reason=spot crosses above hod severity=elevated deadline=T-96m to close -- DATA-HEALTH -- SPX: bid_present_rate=1.000 two_sided=true stale_s=0.2 dark=false unavailable capabilities: none -- POSITIONS / INVENTORY-CLAIMS -- +2 C@5125 basis=10.40 UNKNOWN claim=UNKNOWN -- RESTING ORDERS -- ref=o2 sell C5125@21.50 LIVE qty=2 -- BUDGET / REMAINING-R -- remaining_R=2.60 plan_envelope=1.00 book_envelope=5.00 owner_envelope=10.00 sizing: UNKNOWN (no sizing headroom) -- OWNER ENVELOPE + ACTS -- owner_envelope=10.00 owner acts: none this session -- L0/L1 ENGINE LOG -- engine actions: none -- PREDICTOR / REGIME CLAIMS -- no predictor / regime claim wired KESTREL · wake 3 · 37m since last · T-96m to close · regular · 14:24 ET · reason: spot crosses above hod delta · SPX delta since 13:47 (37m ago) spot 5141.80 change=+6.60 (prior 5135.20) hod 5141.80 change=+3.40 (prior 5138.40) vwap 5128.30 change=+2.30 (prior 5126) tape 5m · axis 5132.80→5141.80 · anchor @ 14:14 ET 14:14 ───████████──── 14:19 ───███████████████─── 14:24 ─███████████████─ levels · SPX spot 5141.80 · prior_close 5108 · hod 5141.80 · lod 5111.20 · vwap 5128.30 · or 5112.50–5125 chain (near-money) · SPX strike R bid ask fair flags 5140 C 7.90 8.40 8.10 b76 nLiq=5 5140 P 6.20 6.70 6.40 b76 nLiq=5 KERNEL (acting) positions: +2 SPXW 5125C basis 10.40 fair 17.20 (or-break) resting: SELL 2 SPXW 5125C @ 21.50 [fair=fallback(max(mid,intrinsic))] (or-break) fills since last: BUY 2 SPXW 5125C @ 10.40 @13:52 (or-break) premium budget: used +240.00 / remaining +260.00 (total 500, maxR 3) plans: or-break: managing ``` <!-- GEN:END watcher-wake --> ### Candidate ladder | rung | pane | status | | --- | --- | --- | | **seed** | `delta tape levels chain acting` | rendered above | | candidate | `armed-plan` (the watcher reads the plan it manages) | **decision pending** kestrel-wa0j.29 (armed-plan pane vs affirm position-not-plan doctrine) | | candidate | `position-greeks` / `theta-bleed` | in review (PR #73 — the watcher half of the theta cell) | | candidate | `news-alert` | **LATENT** (Train 3) roadmap only, not in the catalog | A watcher's sign is not the strategist's. `range-velocity` was passivity for the strategist (whose exit already handles the fakeout) but may be **correct** for a watcher managing a stop-less trap. The cell key carries a `role` axis for exactly this reason: **a pane's sign is role-dependent**, and pooling grades across seats is ill-typed. --- ## Scanner — the single-name deep read **Role.** The PM's discovery half. A **Scan** is a Wake whose scope is a *universe* rather than a Coverage (all of NYSE/NASDAQ at `move(1d) > p99`) wide and slow. When one fires, the Scanner does the single-name deep read that decides whether the PM authors a new leaf (Book + Coverage + thesis + budget) into the pod. **Tasks.** Read one name deeply on a minutes-latency budget; separate the real move from the head-fake; hand the PM a leaf worth authoring or ignore it. **The founder View.** ```kestrel VIEW scan-fire levels series-summary prior-context tape 5m ``` The designed seed is `levels series-summary prior-context tape 5m`. The `series-summary` pane (numeric trend stats closing the embedder-illegible gap where a bar-art trend reads ~0.45 to every embedder) **merged to the catalog via PR #243** (kestrel-wa0j.63), so the screen below now renders the designed seed in full, `series-summary` line included. The screen it renders: <!-- GEN:BEGIN scan-fire --> <!-- GENERATED by scripts/gen-founder-views-doc.ts do not hand-edit · rendered: renderWakeDelta(scanFireInput, { view: scan-fire }) · view scan-fire · kestrel-renderer/8 --> ``` ==== SAFETY / CONTROL KERNEL (non-configurable; leads every frame) ==== frame=WAKE -- WAKE -- reason=scan fire: SPX move(1d) > p99 gap-and-go candidate severity=routine deadline=T-210m to close -- DATA-HEALTH -- SPX: bid_present_rate=1.000 two_sided=true stale_s=0.2 dark=false unavailable capabilities: none -- POSITIONS / INVENTORY-CLAIMS -- flat no positions / inventory claims -- RESTING ORDERS -- none resting -- BUDGET / REMAINING-R -- remaining_R=5.00 plan_envelope=1.00 book_envelope=5.00 owner_envelope=10.00 sizing: UNKNOWN (no sizing headroom) -- OWNER ENVELOPE + ACTS -- owner_envelope=10.00 owner acts: none this session -- L0/L1 ENGINE LOG -- engine actions: none -- PREDICTOR / REGIME CLAIMS -- no predictor / regime claim wired KESTREL · wake 1 · 6m since last · T-210m to close · regular · 12:30 ET · reason: scan fire: SPX move(1d) > p99 gap-and-go candidate levels · SPX spot 5142.70 · prior_close 5108 · hod 5144 · lod 5119 · vwap 5131.20 · or 5121–5138 series-summary · SPX window 11 buckets · 1m each drift +4.90 (+0.10%) close 5137.80 5142.70 close-vs-vwap +11.50 (+0.22%) close 5142.70 vs vwap 5131.20 slope +0.49 pts/bucket (least-squares over 11 closes) velocity 1-bucket |move| p50=0.50 p90=0.90 max=1.00 n=10 prior-context · SPX vs prior close: UP +34.70 (+0.68%) spot 5142.70 vs prior close 5108 tape 5m · axis 5136.90→5144 · anchor @ 12:24 ET 12:24 ──████████████████── 12:29 ─███████████████─── 12:30 ─██─────── ``` <!-- GEN:END scan-fire --> ### Candidate ladder | rung | panes | status | | --- | --- | --- | | **designed seed** | `levels series-summary prior-context tape 5m` | rendered above (`series-summary` merged, PR #243 / kestrel-wa0j.63) | | candidate | `read-why` (verbatim-quote-or-refuse content pane) | **LATENT** (Train 3 / kestrel-wa0j.42) | | candidate | `shock` (stat-block) | **LATENT** (kestrel-wa0j.13, the SHOCK-phase template) | The scanner is where the PM's passivity trap lives, and it has **two natural poles**: the leaf it should have authored (the real breakout it ignored) and the head-fake leaf it should have left alone. Both poles exist by construction, so the scan-fire cell is gradable exactly the way the program demands. --- ## Allocator — the aggregates cockpit **Role.** The PM's allocation half. Runs a Pod node: allocates children's envelopes, arms and de-arms, assigns Coverage, watches aggregates. A PM never authors tickets PM actions are allocations and envelope changes. **Tasks.** Read the pod's aggregate exposure and envelope utilization across children; move budget; narrow authority downward (never upward); de-arm on a wake whose fact went absent. **The View.** There is **no OSS View for the Allocator yet** we say so plainly. The aggregates cockpit is the `PodView`, and it is a **platform-lane** deliverable (kestrel-1xno, Phase 2 pod fan-out), not a screen you can render from this repo today. The org tree is charter-only at present; `PodView.children` / `PodView.aggregate` are degenerate until fan-out feeds them real child facts. Two invariants are already fixed by contract, and they shape the View that will exist: - **A `PodView` has no kernel, by construction.** It allocates and aggregates; it never holds positions. - **Positions appear only at Books.** The leaf is the only place inventory lives; every node above a Book allocates and aggregates. A fact no child published is **absent** and absent is UNKNOWN, which de-arms a PM wake with a logged reason, never a silent zero. When the Allocator View lands, it will be a graded seed on this page like the others. Until then, this section is a placeholder honest about its own absence. --- ## Trigger — the non-LLM reflex (not a View) **Role.** The pod's third live tier, below the Watcher. A learned reflex that **summons, never decides**: it recognizes a moment worth a wake and hands it up, and that is all it may do. **Why it has no View.** A View is a rendering *for a reader that reasons in language*. The Trigger does not read a screen it reads the **frozen Frame's features directly**. Its input is the numeric embedding of the frame produced by the **fp32 CPU embedder**, carrying its **regime id + bank sha** as receipts (the embedding is taken under one pinned render regime so it is deterministically recomputable). Its output is not a plan and not a screen it is the **`DETECTOR` wake kernel line**: a summons that costs the Watcher a look. **Summon, never suppress.** The Trigger may raise a wake; it may **never** cancel one, de-arm a plan, or veto an action. Its only failure mode is a wake that did not need to happen (graded on the recall / calls-saved frontier), never a missed safety event because it can only add attention, never remove it. It is a TIER in the architecture, not a value on the role axis: it has no judgment cells to grade, so it is not a seat. See ADR-0048 percept embedding geometry + the runtime embedding-trigger cascade (pending, PR #124) for the embedder contract and the detector-wake boundary. --- ## Historian — the off-tape curator (not a View) **Role.** The sleeper seat. After the close, with a latency budget no live seat has, the Historian reads the session's record and curates the pod's memory. **Why it has no frame and no View.** Its input class is **off-tape**: it does not consume a live Frame at a wake, it consumes **artifacts** Blotters, Journal reasoning, and Grade results, the complete replayable record of what happened and how it was scored. Its output is not control: it is **Brief updates + lineage curation** evolving the soft directional guidance the live seats reason from, and clustering recurring authored names into the strategy families that graduate into the Armory. **The PM-7 connection.** The Historian's grading question is exactly PM-7's: *is the updated Brief better than the frozen one?* an updated-Brief-vs-frozen matched comparison. That makes the Historian the natural owner of the persona-channel's evolution: the same evidence loop that grades a pane's EV grades a Brief's edit. It is a seat because its job is independently gradable on a budget nothing live can match but it touches the **policy** channel (the Brief), not the **perception** channel (the View), so it appears in this library only to mark its own boundary. --- ## Measurement caveat Everything above is a set of **hypotheses entering a tournament**, and this section is the fine print that the rest of the page depends on. - **These are entries, not verdicts.** Each founder View enters the [ADR-0029](../adr/0029-agent-requested-emergent-view-authoring-loop.md) loop against the required baseline pair. A seed's presence here confers no status; the null is that it hurts. - **Defaults are frozen only by owner-gated realized evidence.** No seed becomes a default by taste, by argument, or by shipping in this doc. Promotion is owner-gated on realized-blotter evidence under two orthogonal, never-conflated rules: **(a)** both-poles matched-set grading on economics (`frozenPlanEv`), because a disciplined-looking pane can be pure passivity; and **(b)** Pareto non-inferiority on **every** grade axis, never a scalar composite. - **The ledger grades per cell, and the cell has five axes.** The TemplateRecord ledger ([kestrel-wa0j.26](../adr/0041-percept-inflection-and-template-as-hypothesis.md)) keys evidence on **instrumentClass × band × archetype-family × phase × role** (a 5-tuple). Grades are stored as **pole-vectors per cell, never sums** the passivity trap is a sign structure, and the record shape must be unable to hide it. There is no coercion between cells: pooling a pane's grades across **seats** (the `role` axis) or across **phases** is ill-typed, because a pane's sign is role-dependent and phase-dependent. The seat-relative economics are load-bearing: the EV-per-token admission threshold is **seat-relative**, because deliberation budgets differ by orders of magnitude a pane too expensive for a frontier Strategist can be trivially affordable for a small, fast Watcher. - **Salience-class panes carry a difficulty-impact stamp before benchmark use.** A pane changes how deep the decisive field sits in the rendering, and burial depth is what creates honest item difficulty (kestrel-bwmz). A rendering that makes everything maximally legible cannot express the `b [1.65, 4.0]` difficulty band where frontier seats live so a salience-class pane must be stamped with its difficulty impact (does it erase load-bearing burial, or preserve it?) before it is admitted to a benchmark season. Legibility is not free; some of it is the measurement. The founder library is the seed set for the first tournament, and the ledger is where it earns or loses its place. Nothing here is settled until the blotter says so.