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
Markdown
# 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.