# Kestrel — founding architecture

> A typed, token-efficient **language + runtime for agentic trading**. The slow
> intelligence (an LLM agent, and the human) *authors*; the fast runtime *perceives*
> and *executes* at millisecond latency. Named for the falcon that wind-hovers —
> holding perfectly still with the sharpest eyes in the animal kingdom — and then
> **stoops**, dropping onto prey faster than you can react. That is the whole product:
> **see everything, then strike at speed.**

**Status:** founding document (2026-07-11), reconciled with the design grill of the
same day. Where wording differs, `CONTEXT.md` (the glossary) and `docs/adr/0001`–`0007`
are canonical.

Domain: **kestrel.markets**. Published to npm as **`kestrel.markets`** (MIT).

---

## 1. Why Kestrel exists — two hard requirements

Kestrel did not come from a trading strategy. It came from two lived frustrations
running LLM agents against live markets, and those two frustrations *are* the hard
requirements. Everything else is downstream of them.

1. **Perception parity — the agent must SEE what the human sees.** Today a human
   stares at rich charts and candlesticks and then tries to *narrate* them to the
   agent in prose. That is absurd and lossy. Kestrel gives the agent the same rich,
   structured market picture the human has — as **one canonical truth rendered many
   ways**: a token-efficient screen (ASCII/unicode/markdown) for the agent's context,
   and a real chart (HTML) for the human. Same numbers, same moment, different
   Renderings of one Frame.

2. **Latency — the agent can NEVER be in the hot path.** By the time an agent reads
   a price, computes fair value, and submits a limit order, the market has moved and
   the order is stale. So the agent does not trade in the loop. It **pre-authors
   deterministic strategy** — a standing program the runtime fires at machine speed
   without waking anyone. Slow judgment, compiled into a fast reflex
   (*fire-then-inform*).

Every design decision below is judged against these two. If a change makes the agent
see less, or puts it back in the hot path, it is wrong regardless of any other merit.

---

## 2. The primitive — one language, four surfaces

Kestrel is **one language with one lexical core** (ADR-0001): four statement kinds
sharing the trigger algebra, series references, and price expressions. All four are
readable by humans and agents, and token-efficient — they are what an LLM emits and
consumes, so every token counts. View, Wake, and Plan are **standing** (authored →
armed → versioned → superseded); Grade is **imperative** (a job that runs and
returns).

| surface | question it answers | lifecycle |
|---|---|---|
| **View** | *what should I see?* | standing |
| **Wake** | *when should I look?* | standing |
| **Plan** | *what may execute?* | standing |
| **Grade** | *did it actually work?* | imperative |

The canonical representation is a **typed TS object model**; text is a projection of
it — `parse(text) → objects`, `print(objects) → canonical text`, byte-stable
round-trip (ADR-0004). Every document is a **module** (ESM-like): its named statements
are importable; a file can be a full pod, a single book, or a library of plan
templates (ADR-0003).

### View — what should I see?

A View is the standing spec: which panes, at what token budget. Each moment it
materializes into a typed **Frame** — the instance carrying every value with its
source — which renders to ASCII, unicode, markdown, JSON, or HTML. Same Frame, many
Renderings; the agent's screen and the human's chart are two renderings of one Frame.
A View selects panes; it never computes.

```kestrel
VIEW open budget 1800
  macro   week quarter rv20
  vol     straddle expected-move
  tape    skyline 5m vwap detector-strip
  levels  registry
  chain   fair realness
```

Every Frame carries a **kernel** bound to the *session*, not the View: an acting
session (a book attached) always shows positions, resting orders, budget, wake reason
— non-configurable; a spectator Frame (publication, research, historical charts)
carries provenance only. You cannot configure away the acting kernel — only genuinely
have nothing at stake.

### Wake — when should I look?

A Wake is a standing subscription over the same trigger algebra Plans use, but it
spends **attention** (tokens, wakes), never risk. Wakes are event-driven — never an
arbitrary clock. A Wake names the View it delivers: the wake decides *when*, the View
decides *what*.

```kestrel
WAKE session-open
  WHEN phase open                          # from the instrument's session calendar
  DELIVER open MANDATORY

WAKE fade-setup
  WHEN second failed-break of hod within 30m
  DELIVER execution
  PRIORITY 2  COALESCE hod-action
  BUDGET 12 wakes/day

WAKE fast-move
  WHEN velocity(1m) > p99                  # scale-relative: window × baseline
  DELIVER zoom-1m
```

**There is no absolute shock**: magnitude is judged at a window (`velocity(1m)`,
`move(1w)`) against that window's own trailing baseline (`p99`, `3sigma`) — and the
delivered View zooms to the wake's window. A **Scan** is a Wake over a *universe*
rather than a coverage (all listed equities at `move(1d) > p99`) — wide and slow where
coverage wakes are narrow and fast. One platform-default Wake exists in every acting
session: the **staleness backstop** (if nothing woke you in N, wake anyway; not
removable while holding positions).

### Plan — what may execute?

A Plan is a standing, bounded-risk program: `trigger → actions → bracket →
invalidation → TTL`. The runtime evaluates it every tick and **fires the
pre-authorized action at the tick, then informs the author in parallel**
(fire-then-inform). The plan — not the order — is the agent's unit of output.

```kestrel
PLAN chase-urgent budget 0.2R ttl +30m regime {intraday: trend}
  USING signal SPX exec SPY 0dte
  WHEN spot crosses above hod AND velocity(1m) >= p95
  DO buy 2 +1 C @ min(fair-3c, mid) peg esc fair 3m cap fair cancel-if under vwap
  EXIT under vwap held 60s @ fair esc mid 30s
```

The author's **belief is expressed as the shape of the plan** — which strikes, how far
under `fair` it rests, its sizing, and whether adverse movement means RELOAD (buy
more), INVALIDATE (thesis dead, ride the tail), or EXIT (get out). Belief is never a
number the engine computes. Scope of Plan v1 — including inventory binding (`basis`
anchor, held-leg quantifiers, on-arm trigger) and the reserved-but-refused `atomic`
keyword — is ADR-0005.

### Grade — did it actually work?

A Grade replays anything authored — a Plan, a Wake (attention efficiency), a View (did
agents decide better through it), a regime tag (gated-beats-pooled), a Pod node (the
PM's allocations) — across recorded sessions under a **named, versioned fill model**,
and reports honest EV with counterfactuals built in (ADR-0006).

```kestrel
GRADE plan chase-urgent OVER 2025-01..2026-06 FILL maker-v1
  VS ungated
  BY regime.intraday, vehicle, lineage
```

**Names are data**: a recurring plan name is a lineage (one strategy, many authored
instances); `BY name` / `BY lineage` are first-class grade dimensions, and clustering
names with outcomes is how strategy families and sub-regimes *emerge* rather than
being hand-defined.

---

## 3. The org model — pods all the way down

The org is in the language (ADR-0002). The **Pod** is the recursive node: an
allocating role (**PM**) + a risk envelope + children, where each child is a **Book**
(the leaf — the only place positions live, run by a **Trader** under an assigned
**Coverage**) or another Pod. Depth is unbounded; the org is **data** — a standing
Kestrel document the PM revises intraday (spinning up five traders for the day is
authoring five leaves). Humans always sit above the root: a pod is the largest thing
that runs on its own.

```kestrel
POD example-pod
  RISK  day-loss 2R -> halt
  BOOK  alpha  budget 1.0R  coverage SPY   thesis "..."
  BOOK  beta   budget 0.5R  coverage QQQ   thesis "..."
```

Depth-independent invariants at every edge: **budgets nest** (child ⊆ parent),
**authority only narrows downward**, **positions live at leaves only**, and **Risk
(L0) outranks every node including the root**. Org facts are **path-scoped series**
resolved lexically like ESM (`pnl` = my scope; `alpha.pnl` = my child;
`children(any).drawdown` = a PM's wake operand). The typical shape: standing books at
fast windows on core coverage, pod-level Scans wide-and-slow for discovery — a Scan
fires, the PM evaluates, and may author a new leaf.

### The authority tiers — who acts, at what speed

- **L2 — judgment (seconds–minutes).** The LLM agent and the human. Reads Frames, does
  research, forms a thesis, authors View/Wake/Plan, allocates (PM). Never touches the
  wire.
- **L1 — the runtime (ms–1s).** Evaluates armed triggers every tick; fires
  pre-authorized actions at machine speed; wakes L2 *in parallel* with the fire.
- **L0 — Risk (µs–ms).** Bounded-risk invariants. Clamps or vetoes any order; **may
  never open risk.** The backstop under every node of every pod.

Intent flows down; veto flows up. L2 is where intelligence lives and it is too slow to
trade; L1 is fast and deterministic and has no judgment; L0 keeps everyone bounded.

---

## 4. The module spine and its seams

Each module is deep (a small interface over a lot of behavior) and meets its neighbors
at a named seam. Adapters vary behind a seam; the interface does not.

```
                         L2  agents + humans  (author: View / Wake / Plan / Pod)
                          |  intent ↓            ↑ inform (fire-then-inform)
   ┌──────────────────────┼───────────────────────────────────────────────┐
   |                  Wake router               Frame ── renderer ── (ascii|unicode|md|json|html)
   |                      |                       ↑                              |
   |     series registry ──────────────────┐     | Fields (attributed,          |
   |     (register once → every surface)    |     |  watermarked)                |
   |          ↓            ↓                ↓     |                              |
   |      triggers      Grade panes    canonical market state ── ExecutionFair   |
   |          |                          (one causal coord)      (@fair, receipts)
   |    ┌─────┴──────────────────────────────────────────────────┐             |
   |    |     DSL core (parse/print/eval · typed TS object model)  |             |
   |    └─────┬──────────────────────────────────────────────────┘             |
   |          ↓                                                                  |
   |   rule engine (tick loop, fire-then-inform)  ── Gate seam ──┐               |
   |          |                                                  |               |
   |     L0 Risk envelope (bounded-risk clamps)                  |               |
   └──────────┼──────────────────────────────────────────────────┼─────────────┘
              ↓                                                    ↓
        Session {driver, clock, gate, bus, mode}           adapters: broker (BYO/MCP)
        sim | paper | live  = ONE path, different gate              market-data feed
              ↑                                                      lake (chDB / R2)
         event bus (typed, append-only, replay-byte-stable)
```

### Canonical market state
One causal market coordinate per underlying — spot, HOD/LOD, opening range, velocity,
structural levels — sourced from the **deepest instrument**, health-gated. Never
reconciles two estimates at runtime: the source hierarchy is fixed a priori; on feed
failure it enters an explicit **degraded** mode that taints dependents and de-arms
affected plans. Every derived number references one canonical series; nothing is
recomputed per caller. (This kills the classic bug class where the screen's
"authoritative" value disagrees with the level the engine fires on.)

### ExecutionFair — the `@fair` anchor
The honest **price at which a market maker will fill you**, not the midpoint of a
possibly-fictional book. Underlying-anchored intrinsic + a vol read backed out of the
*liquid* quotes, floored at intrinsic. Receipt-gated (model version, fit quality,
freshness) — it carries whether it can be trusted, not just a number. **The observed
mid is a health signal (thin/dark-book fingerprint), never a value.** `@fair` resolves
here. Belief is the author's; fair is the engine's.

### The DSL core
Parses, prints, and evaluates all four surfaces into one typed object-model family
(ADR-0004). Fails **closed**: any parse escape resolves to STAND_DOWN, never an
unbounded order. One evaluator for price expressions across every surface — exactly
one place knows what `fair`, `mid`, `lean(a,b,x)`, `peg`, `cap`, and `esc` mean.

### The rule engine
The tick loop. Evaluates armed Wake/Plan triggers each tick over detectors and series
computed causally from the stream it already owns (never a second market-data
connection). On a confirmed trigger it fires through the **Gate** and wakes L2 in
parallel. Deterministic and replayable: same input sequence → same event sequence.

### The series registry
Anything with a name whose value changes over time registers **once** and becomes
simultaneously: a trigger operand, a View pane, and a Grade column — by construction,
one definition, no trigger/screen/grade skew. Two kinds of series, and only two:
**market facts** (ambient per signal instrument, spectator-visible) and **org facts**
(path-scoped in the pod tree, acting-sessions only). Registration is taste-free;
replay decides what earns usage; an unresolvable series reads UNKNOWN and de-arms its
statement with a logged reason.

### Frame + renderer
The engine serializes a typed **Frame** of **Fields**. Every rendered number is a
`Field { series_id, value, attribution, source_watermark }` — attributed
(OBS / CALC / DETECTOR / MODEL / POLICY / UNKNOWN) and watermarked (instrument, engine
sequence, model version). **The renderer is a pure function of the Frame; it invents
no value** — it chooses only glyphs, layout, and token spend, parameterized by format
and by the target model's tokenizer (which glyphs are cheap is an empirical, per-model
fact). **Streaming renderings are append-only (ADR-0008):** the live tape is vertical
— one row per candle, newest last — so a long-running agent's KV cache stays valid
and each update costs O(new bars); 2D price×time grids are keyframe-only or
human-facing. This is what makes the screen a trainable curriculum and satisfies
requirement №1.

### Wake router
Turns the trigger algebra into attention. Subscriptions carry attention budget,
priority, and a delivered View — never risk. Past budget, wakes **coalesce and
downgrade, never silently drop** (the squelch is visible in the kernel). **A Wake is
not a Plan** — shared WHEN algebra, disjoint authority.

### Session
The one object that owns a run: `{ driver, clock, gate, bus, mode }`, modes
**sim | paper | live** as one code path with different gate adapters — honest grading
by construction, and the single thing the API/CLI/MCP sit on. A Pod definition is a
**class**; a run is an **instance** (ADR-0007): sim and paper instances are unbounded
(thousands of parallel runs across versions is the normal shape of Grade); **the live
instance is a singleton per pod lineage, platform-enforced**. Promotion is a document
revision of the live pod, never a flag flip.

### Agent harness
The L2 loop: Frame → LLM (+ research) → authored Kestrel → validated by the DSL core →
armed. Parse failure routes to STAND_DOWN. This is where intelligence enters, and it
is deliberately **outside** the hot path.

### Adapters (the edges)
- **Broker execution** — BYO-broker: the client's own plans through the client's own
  account, via a broker adapter or broker MCP. Kestrel never holds custody.
- **Market-data feed** — the live tick/quote source; colocated with the runtime.
- **Lake** — chDB over S3-compatible object storage, native in TS/Bun. **Object
  storage is the only source of truth**; local disk is either an engine-managed
  filesystem cache (named, size-capped, LRU — repeated reads go local-fast) or an
  explicit dev/fixture root for creds-free tests — never a silent alternate data
  source. Read-only history for Grade and sim.

### Grade / fill model
One **FillModel** seam returning a calibrated `p_fill`, an expected fill price, and a
support flag — not a binary fill. Every plan *and* the structural null cross the same
fill model, so strategies are compared on fills, not on fill assumptions. The fill
model is itself calibrated against realized live fills and versioned; EVs across
versions are not comparable and are stamped as such.

---

## 5. The stack — TS/Bun, top to bottom

- **TypeScript on Bun**, everything: DSL core, rule engine, renderer, harness,
  API/CLI/MCP. chDB runs natively in TS — no second language anywhere.
- **The ms rule engine is fine in TS.** This is millisecond-budget work (faster than
  an LLM round-trip), not microsecond HFT. The latency floor is network-to-exchange;
  language overhead is noise. Colocate runtime + feed + broker gateway.
- **Surfaces:** HTTP/SSE (programs) · CLI (humans) · MCP (agents) — three faces of one
  runtime: same Frames, same grammar, same provenance ceiling.
- **Validation:** the grammar and engine are proven against a privately-maintained
  golden corpus (grammar parses + recorded session replays) and property tests
  (round-trip, replay-byte-stability, fail-closed). Correctness is earned by tests,
  never assumed from provenance.

---

## 6. Non-negotiable invariants — enforce TYPES, not control flow

A thing belongs in the platform iff violating it is *always* wrong. If two reasonable
strategies could legitimately differ on it, it is a free dial, not a platform
constraint.

1. **Bounded risk (bounded by default, fail-closed).** Every action has a *computable*
   `max_loss` (never unknown, never unbounded) **and** `size × max_loss ≤ budget`;
   enforced at the output boundary, fail-closed — an unknown or unbounded max_loss is
   refused (`no-bounded-envelope`). This alone forbids the always-wrong cases (a naked
   call and short equity are unbounded) while permitting a budgeted cash-secured put, so
   `no-uncovered-short` is an author-overridable **application default** in the Risk
   envelope, *not* a platform invariant — two reasonable strategies differ on it (this
   invariant's own membership test). Unbounded risk is reachable only by explicit
   willing-human enablement, never a silent default. See ADR-0005.
2. **Causality + fence.** No look-ahead. Same bus + same sequence ⇒ byte-identical
   Frame and byte-identical event stream. Replay-stability is a test, not a hope.
3. **Fair carries receipts; mid is never a price** — never a silent price anchor, never
   worth. (As a *book-state observation* in triggers — spread regime, dark-book detection —
   mid/marks are legitimate signal; the structural ban on mark operands applies to EXIT,
   where acting on a lying mark is the failure mode: ADR-0005.)
4. **Fail-closed parse → STAND_DOWN.** Unknown series read UNKNOWN and de-arm their
   statement with a logged reason — never crash, never silently false.
5. **Honest Grade under one named fill model**, counterfactuals built in.
6. **Provenance ceiling.** Authority is set by the authenticated channel; a document's
   declared provenance can only *narrow* it. Unvetted plans are capped.
7. **Org invariants at every edge:** budgets nest; authority narrows downward;
   positions at leaves only; **live is a singleton per pod**.
8. **The acting kernel is non-configurable.** No agent trades blind to its own
   inventory.

---

## 7. Platform vs application

Kestrel is the **platform**: the language, the runtime, the honest judge —
instrument-, strategy-, and edge-agnostic. **Applications** — actual books, coverages,
armories of vetted plans, and the theses behind them — are built *on* Kestrel and are
not part of it. The platform ships nothing that reveals any application: examples in
docs and tests are illustrative, chosen to teach language features. Kestrel is **not**
an algorithm, a predictive model, a signal service, or a broker-dealer; the
intelligence is the author's (agent or human), and the platform's job is to let it
see, act, and be judged honestly.

---

## 8. Build order (first milestones)

Each milestone is graded against the acceptance suite before the next begins.

1. **Typed object model + DSL core** — all four surfaces + Pod/Book/USING/imports;
   parse/print round-trip; fail-closed. The language exists first.
2. **Canonical state + ExecutionFair + Frame** — the correctness floor everything
   reads.
3. **Rule engine + Session over a sim gate** — grade a recorded day end to end;
   byte-identical replay.
4. **Agent harness + Wake router** — close the Frame → LLM → plan loop; event-driven
   wakes.
5. **Paper gate** — live feed, simulated fills, same Session path.
6. **Surfaces** — HTTP/SSE, CLI, MCP over Session.
7. **Live broker adapter** — BYO-broker, colocated, human-gated promotion, singleton
   enforced.

---

## 9. Open questions (honestly unresolved)

- **Near-expiry fair.** ExecutionFair degrades toward intrinsic when the near-money
  book goes dark close to expiry (all gamma, no time value, no liquid quotes to
  reconstruct from) — exactly where belief-vs-fair matters most. How much does the
  engine assert vs hand to the author?
- **Multi-instrument graph.** Base case: one underlying + its tradeable projections;
  general case: an N-node instrument graph with signal/execution as per-plan roles.
  Deferred; types must not preclude it.
- **Atomic multi-leg.** Named defined-risk structures need whole-structure preflight +
  an atomic execution adapter. v1 reserves the `atomic` keyword and refuses it loudly
  (ADR-0005) — never parse-and-leg silently.
- **The advisory line.** BYO-broker dodges custody, not the investment-adviser line;
  auto-arming someone else's book is discretionary management. Live stays BYO-broker
  AND BYO-plan; anything more is a registered-entity question.
