impyard 0.1.1

Rent the intelligence, own the governance — a control plane for imps: software colleagues whose every action passes through a gateway you control (default-deny egress, injected credentials, budgets, approval gates, audit).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
# Impyard — implementation handoff

**Audience: a coding agent** starting implementation of Impyard with no prior
context. This document is self-contained. Where it references the working
reference implementation ("Yuko", the repo this doc was authored in), the path
is `/home/manas/projects/yuko/1` and its KB is `~/research-kb`; if you have
read access, port from it — if not, this document plus the design doc are
sufficient to rebuild.

**Companion documents** (same `docs/` dir, read in this order):
1. `digital-imp-platform.md` — the design, plain-English, owner-approved
   structure (the 13 pieces). **The design is settled; do not re-litigate it.**
2. `vendor-nanoclaw.md`, `spike-nanoclaw-2026-07-06.md`, `vendor-letta.md` —
   evaluated references with borrow/reject verdicts.
3. `how-it-works.md`, `build-plan.md` — how the reference implementation works
   and how it was built (increment-by-increment).

---

## 1. What you are building

**Impyard**: a platform where the owner describes a "imp" in a
folder of config files, deploys it in minutes, and the imp does proactive,
ongoing agentic work (research, monitoring, curation, correspondence) — while
**every action passes through governance machinery the imp cannot touch**:
a policy judge with default-deny, budget ledgers, per-action-class trust that
is *earned*, human approval gates on everything irreversible, and a permanent
audit record.

One line: **rented intelligence, owned governance.** The LLM harness is a
swappable-in-principle, pinned-in-practice engine; the product is the
organization around it.

What differentiates Impyard from every existing framework (verified by four
evaluations — pi, NanoClaw, OpenClaw, Letta): runtimes, channels, and memory
tricks exist off the shelf; **governed identity, earned trust,
budgets-as-ledgers, and decisions-as-records exist nowhere.** That is the
build.

---

## 2. Decision log (settled — do not reopen without the owner)

| # | Decision | Date | Rationale (short) |
|---|---|---|---|
| D1 | Design doc first; code only when owner green-lights | 2026-07-08 | Owner call |
| D2 | **Productizable / multi-tenant trust model from day one** | 2026-07-08 | Owner call; org = hard boundary; but build order still starts single-org |
| D3 | **pi is the only engine. No provider seam, no adapter layer** | 2026-07-08 | Owner call. The container contract (§7.3) is the de-facto coarse boundary; do not design an in-process abstraction |
| D4 | **TypeScript everywhere** is the destination | 2026-07-08 | pi extensions force TS imp-side; shared contract types; Node 24 type-stripping = no build step |
| D5 | Existing Rust judge (PDP) stays **until first major surgery** (org-boundary increment), then ports to TS | 2026-07-08 | It works; it parses no hostile input (memory-safety argument void); live policy corpus uses only `true`, `==`, `!=`, `\|\|`, `in` → ~80-line TS evaluator replaces CEL; `node:sqlite` replaces the Rust SQLite ledgers |
| D6 | **Budget gates proactive work only** — owner-filed/chat work ALWAYS runs | 2026-07-07 | Owner correction, now invariant. Classification happens at dispatch (supervisor), not in-run |
| D7 | Budget/spend caps live in **owner-only config**; agent may propose pace (cadence) but never money | 2026-07-07 | The agent must never be able to raise its own budget |
| D8 | Enforcement never lives in pi extensions | 2026-07-08 | Extensions run inside the container (untrusted zone). Soft budget stop = supervisor at dispatch; hard stop = gateway stops serving credentials (incl. the model key) |
| D9 | Briefs (finished deliverables) live in the **disk cache**, not the KB git repo | 2026-07-08 | Owner call. Notes are the durable substrate; briefs are derived artifacts |
| D10 | **Promotion rule** (from Letta, repurposed for security): imps append notes; only the curator session promotes into the always-loaded core; charter edits always gate to owner | 2026-07-08 | Closes the injection→self-programming hole |
| D11 | Trust **never ports** on imp export/import; imported imps restart at T0 | 2026-07-08 | Track record only means something where earned |
| D12 | Channels **relay, never act**; inbound email = content, never commands (spoofable); outbound email gated at T0 | 2026-07-08 | Security invariant |
| D13 | Adopt-vs-build verdicts: NanoClaw **declined**, Letta **declined**, OpenClaw never a candidate. Borrow ideas only | 2026-07-06/08 | See vendor docs; revisit triggers recorded there |
| D14 | Owner's 13-piece model is the build structure; six-block view is the work-division view | 2026-07-08 | Owner call |
| D15 | **No invented action-class taxonomy.** Governed requests are matched on the standard HTTP vocabulary (protocol/method/host/port/path/headers/payload) + MCP's own terms (`tools/call`, tool name); deployment-specific meaning attaches to owner-named rule `name`s. Replaces the reference's fixed classes (`acquire-source`, …). Budgets/trust/gates will bind to rule names | 2026-07-08 | Owner call. Standard vocabulary ports across deployments; invented taxonomies don't. See `docs/judge-spec.md` |
| D16 | **The gateway terminates TLS with a host-minted CA** (key at `~/.impyard/ca/`, never on the box) to see full requests; `tunnel` verdict is the escape hatch for cert-pinning clients / interception-breaks-pi fallback | 2026-07-08 | Follows from D15 — matching all params requires seeing inside TLS. Verified: pi honors `NODE_EXTRA_CA_CERTS`, so `tunnel` is unused for pi |
| D17 | **The gateway is Rust; orchestration stays TypeScript.** The trusted core (TLS termination, CA/cert minting, judge, vault + OAuth refresh, injection, metering, call log) is a Rust binary; the box runner, docker lockdown, CLI, and future supervisor stay TS. Seam = the container contract (§7.3) + the JSON policy/config files. **Reverses D5's deferral and expands it** (D5 kept the Rust judge only "until first major surgery"; the surgery is now — the gateway parses hostile request/response bodies and evaluates expressions over them, so the memory-safety argument D5 called void is back), and **refines D3/D4** (TS is the destination for orchestration, not the trusted request path) | 2026-07-08 | Owner call. The gateway now terminates TLS and parses attacker-controlled bodies on the hot path — exactly the hostile-input parser Rust is for; CEL (D18) has a mature Rust impl. Port-first: port the working TS gateway to parity, then build metering on the Rust base |
| D18 | **CEL is the one expression language** — judge conditions, derived-field extraction from bodies, and currency/spend mapping all evaluate CEL against a shared context (`request.*`, `response.*`, `decision.*`, `subject`, `trust.*`, `environment.*`, `vars`). **Reverses D5's plan to shrink CEL** to a ~80-line matcher | 2026-07-08 | Owner call. Metering needs arbitrary currency = f(request, response) expressions; one language across judge/extract/meter beats three ad-hoc matchers. cel-interpreter (Rust) is mature. The structured judge matcher ports first (parity); CEL lands with the metering increment and can retrofit the judge |
| D19 | **ImpSpec is TOML, compiled at `deploy`.** Owner authors `org.toml` (shared grants + fleet-aggregate caps + metering) and `imps/<name>/imp.toml` (per-imp overlay); `deploy` validates and compiles them into the runtime config the gateway reads (`runs/compiled/{policy,budget}.json`), tagging each rule and limit with its **scope** (`org` or `org/<name>`). The gateway applies rules/limits to a call's subject by the same ancestor match the ledger uses. Lifecycle verbs use infrastructure vocabulary, not HR: `create`, `deploy`, `suspend`/`resume`, `archive` (was hire/retire) | 2026-07-09 | Owner call: TOML over YAML; no anthropomorphic verbs; compile-at-deploy (a validation/provision gate, keeps the gateway lean, source-of-truth is the TOML). Scope-tagged rules reuse the ledger's ancestor logic |
| D20 | **The language boundary = the trust boundary.** The entire *trusted host-side* control plane is Rust — one `impyard` binary with subcommands (`serve`, `create`, `deploy`, `box`, `connect`, `vault-sync`, and the future supervisor). TypeScript is confined to the *untrusted box*: pi (vendored) and the pi extensions, which talk to the Rust side over the serialized container contract (§7.3). **Reverses D4** ("TS everywhere is the destination" — written before the gateway went Rust) and **supersedes D17's split** (which drew the line at "hostile input on the hot path"; the orchestration is also trusted and shares the gateway's schemas, so the real line is host vs box) | 2026-07-09 | Owner call. The gateway↔orchestration split was an *artificial* boundary inside the trusted zone forcing every governance schema (policy/budget/credential/provider/identity) to be declared twice; unifying kills the duplication and yields one binary + one toolchain. The one remaining cross-language boundary now coincides with the trust edge, where serialization is mandatory anyway. Decided before building the supervisor/queue/channels to avoid a second port |

**Note (2026-07-08):** build is proceeding **from scratch** in the Impyard
repo in small increments (owner call), not porting Yuko as §6 suggests. D5's
~80-line TS judge is now the live `src/judge.ts` (built fresh, not ported);
its match language is D15's, not CEL. Shipped so far: CLI scaffold, the box
(§3.3), the judge + inspecting gateway (§3.7/§3.9 seed), and **credential
injection** — the model key now lives in a host-side vault
(`~/.impyard/vault/`) and is injected in transit; the box carries only a
sentinel; and **gateway-owned OAuth refresh** (the gateway refreshes expired
tokens itself via a provider table — no pi dependency in the credential path;
single-flight, atomic vault write, fail-closed, audit to
`runs/credentials.jsonl`). This is build-plan increment 3's injection +
refresh. The hard-budget half (gateway declines to inject on an empty ledger)
attaches at the same pre-inject checkpoint and is still pending — see
`docs/injection-spec.md`.

**Note (2026-07-08, later):** the gateway is now **Rust** (`gateway/`), per
D17/D18 — TLS termination, CA/leaf minting (rcgen), judge, vault, injection,
and OAuth refresh all ported to parity and verified live end to end (the box
runs through it); the TS gateway modules are retired. Orchestration
(`src/`: box runner, lockdown, CLI, `vault-sync`) stays TS. See
`docs/rust-port.md`. Next: the metering/currency/budget model (call log →
namespaced identity → CEL currency mapping → drawdown limits) built on the
Rust base with CEL.

---

## 3. Architecture: the 13 pieces (technical version)

The design doc has the plain-English version; this is the implementer's cut.

### 3.1 Engine (pi)
- Package: `@earendil-works/pi-coding-agent` — **mind the fork**: this is the
  maintained fork lineage used by the reference; do not confuse with
  `@mariozechner/*` originals. Pin exact version.
- Invocation pattern (from reference): `pi --mode json -e <ext1> -e <ext2> ...
  --session-dir <dir>` with prompt on stdin/arg. Sessions record to
  `<session-dir>/*.jsonl`; each model call logs `usage`
  `{input, output, cacheRead, cacheWrite, totalTokens, cost:{total}}` — this
  is the token/cost accounting source (§7.6).

### 3.2 Behavior kit (pi extensions, TypeScript)
Extensions load in-process (`defineTool` + typebox `Type.Object` schemas).
Reference set (port from `packages/pi-extensions/src/`):
- `journal-emitter` / `journal` — append events to `journal/events.jsonl`
- `task-protocol` — tools: `declare_decision_point`, `escalate`, `close_task`,
  `message_user`, `queue_research_task`, `propose_charter_edit`,
  `propose_cadence`
- `mailbox-poll` — deliver owner steer messages at turn boundaries
- `gateway-client` — route governed actions to gateway; obey verdicts
- `web-search`, `tools` (`fetch_page`, `recall`) — thin wrappers over gateway
  endpoints
- `memory`, `notes` — `memory_write`, `note` (atomic `[[wiki-link]]` markdown)
- `store` — `persistSource`, `recall` search, Discord log append
Rule: extensions shape behavior; they never enforce safety (D8).

### 3.3 The box (container)
Per-session Docker container:
- repo/config mounted **read-only**; only task workspace, journal, mailbox
  writable
- **no secrets inside**; the repo's `.env` is **shadowed** inside the mount so
  keys can't be read off the read-only mount either
- **egress lockdown**: container joins a NAT-disabled Docker bridge; only
  route out is the host gateway (HTTP(S)_PROXY honored by pi — verify with a
  live task once per setup)
- hard ceiling timeout on session lifetime
- current caveat carried from reference: the model credential is copied into a
  throwaway per-run config the container reads. This changes at increment 3
  (model key behind gateway).

### 3.4 Filing system (four stores)
- **Config**: read-only mounted (the ImpSpec).
- **Cache** (`~/loop-store` pattern in reference; per-imp namespace in
  Impyard): raw fetched pages/searches (`sources/<kind>-<hash>.md`), channel
  logs (`discord/YYYY-MM-DD.jsonl`), finished briefs. Off-git. TTL + size
  caps (new requirement, not in reference).
- **Store** (git): linked atomic notes — one idea per note, `[[links]]`,
  frontmatter — used for facts, procedures, lessons. Every write is a git
  commit (audit + one-command revert).
- **Core**: the curated always-loaded set, assembled by a **deterministic
  context compiler** (new component — increment 0): labeled, size-budgeted
  blocks (charter / working set / procedures / note index / org context /
  task). Same assembly every session; log the compiled result so "what did
  the imp see" is answerable.
- **Promotion rule** (D10): imps append; a separate curator session (which
  never fetches raw web content) promotes into core; charter → owner gate.

### 3.5 Wake-ups
Heartbeat (periodic bounded triage session) + event triggers (message,
schedule fire). **A wake-up never does work inline — it files a task** (§3.6).
Cadence in the agent-proposable `cadence.yaml` (owner approves).

### 3.6 Task queue
One durable queue per imp; states `waiting → running → needs-review →
done`. All work becomes a task: owner-filed (reactive), heartbeat/planner
/schedule-filed (proactive, labeled as such — reference uses a
`loop:proactive` label). Queue is readable by the imp (dedup: "don't
re-propose what's queued"). Reference uses GitHub Issues
(`q/research`, `loop:in-progress`, `loop:needs-review` labels) — see open
question Q3 for built-in queue vs GitHub mirror.

### 3.7 Gateway (keys + judge)
One trusted host process (reference splits it: `broker/` TS on :7213 +
`pdp/` Rust on :7212; Impyard may keep the split until D5's port merges them
functionally):
- holds all credentials (vault); injects **in transit**; imps never see keys
- default-deny; verdicts: `allow | deny | gate | budget-and-deduct`
- consults per-(imp, action-class) trust + ledgers before answering
- **writes a decision record for every answer** with full context
  (reference: `pdp/data/decisions.jsonl`)
- endpoints pattern (reference): `/v1/search`, `/v1/fetch`, `/v1/recall`
  (recall is ungoverned — reads already-acquired data); adding a capability =
  one endpoint + one thin tool; policy/credentials/lockdown come free
- persists all retrievals to the cache (raw-data custodian)
- increment 3: model calls also proxied here (hard budget + failover)

### 3.8 Budgets
- Ledgers: tokens/hour, currency/day, searches/day per imp; org aggregate
  cap above (fleet cannot run away collectively).
- **Soft stop (supervisor, dispatch-time)**: over-cap ⇒ don't start proactive
  work; reactive always runs (D6). Throttled owner notice when paused.
- **Hard stop (gateway)**: empty ledger ⇒ stop serving credentials.
- Accounting source: pi session `usage` entries, tallied per run into an
  append-only usage ledger (reference: `runs/.daemon/usage.jsonl`,
  `{ts, ref, kind, tokens, cost}` rows; tally logic in
  `packages/driver/src/usage.ts` — recursively sum objects bearing numeric
  `totalTokens` + `cost.total`, skip `stdout*` files).

### 3.9 Approval desk (gates + trust)
- Trust ladder per (imp, action-class): T0 all-irreversibles-gated (birth
  state) → T1 longer deadlines → T2 free + sampled review (pass rate decays
  sampling toward a 10% floor) → T3 audit-only.
- Failed review ⇒ sampling back to 100%. `--incident` ⇒ automatic tier
  demotion. Promotion **human-only**.
- Gates are durable records (reference: `gates/pending/`), fail closed
  (deadline ⇒ escalate-once or auto-deny per policy). Reference mechanism for
  KB publishes: PR against KB repo, merge=approve close=deny — with briefs
  moving to cache (D9), gates attach instead to note/procedure **promotion**
  and to `send-communication`.
- Action classes (reference set + designed additions): `spend-external-api`,
  `acquire-source`, `access-private-source` (owner-gated,
  **deny-on-timeout** — private data fails closed), `commit-to-kb` (becomes
  promote-to-core), `share-knowledge`, `send-communication`.

### 3.10 Channels (Discord first, email second)
- Edge relays only: ack (👀), file task or deliver steer; buttons for
  gates/reviews/proposals (Approve/Deny, Pass/Fail). **No model at the edge.**
- Discord: obey only `DISCORD_OWNER_ID`; hand-rolled gateway client in
  reference (`packages/daemon/src/discord.ts`) — resumes sessions, reconnects
  on blips; do NOT adopt discord.js (owner preference: thin,
  dependency-free).
- Email: inbound = content only (D12); outbound = `send-communication`, T0.

### 3.11 ImpSpec
```
imps/<name>/
  imp.yaml    # OWNER-ONLY: template, engine image pin, tool grants
                 # (= gateway endpoints + action classes), budgets, channels,
                 # kb/store namespaces, escalation routing
  charter.md     # AGENT-PROPOSABLE via propose_charter_edit → owner button
  cadence.yaml   # AGENT-PROPOSABLE via propose_cadence → owner button
  procedures/    # seed procedures; grow via store under promotion rule
  policies/      # OWNER-ONLY imp overlay on org policy corpus
```
No credentials in specs, ever. No trust in specs, ever (trust = derived
ledger state). Export/import = spec + knowledge snapshot, secrets nulled,
trust stripped (D11). Lifecycle verbs: `hire` (scaffold from template),
`deploy` (validate → provision surfaces + ledger rows + T0 trust + first
heartbeat), `steer`, `suspend`, `audit`, `retire` (archive; journals +
decision records immutable forever).

### 3.12 Supervisor
One long-running process, **no model**. Watches queues, dispatches sessions,
enforces concurrency, fires heartbeats/schedules, runs digest clock, owns
imp lifecycles. Two laws from shipped bugs (§8):
1. **Every timer/cursor is durable** (disk-persisted; restart-at-any-moment
   is the normal case).
2. **One writer lane per surface** (journal relay, queue dispatch, ledger
   append, store commit — serialized per imp).

### 3.13 Observability
- Append-only journal per session (every search/fetch/decision/narration/file
  write) → reports rendered **from the journal**, digests computed **from
  ledgers** — never model-written.
- Digest cadence (reference: 12h + `/digest` on demand + spend line in
  `/status`): ran-by-kind, tokens+$ vs cap, queue/gates/reviews pending.
- Fleet view (later): per imp — state, queue depth, spend, per-class trust,
  gates waiting.

---

## 4. Work-division view (six blocks, six contracts)

For subdividing work: **A Definition** (spec/templates/lifecycle),
**B Governance** (judge/taxonomy/ledgers/trust/records), **C Execution**
(supervisor/queue/box/journal/engine), **D Custody** (vault/injection/egress/
endpoints), **E Knowledge** (store/core/recall/curation/retention),
**F Front Office** (channels/approvals/digests/fleet/roles). Contracts:
ImpSpec (A→all), Task (→queue), DecisionRequest→Decision (C,D→B), Journal
events (C→E,F), Human verdicts (F→B), Container contract (inside C). Tenancy
is a property of all six, not a block. **Subdivide B then A first** — they own
the vocabulary (action classes) and the input (spec) everything else consumes.

---

## 5. Invariants (testable; violating any is a bug)

1. Nothing the imp ingests is trusted — including notes it wrote after
   ingesting (attacker-reachable via prompt injection).
2. No secrets in any imp, spec, or container. Injection in transit only.
3. No egress except through the gateway (NAT-disabled bridge).
4. Code/config mounts read-only; an imp cannot edit its rules, extensions,
   or spec.
5. No matching rule ⇒ deny. Unanswered gate ⇒ deny (fail closed).
   `access-private-source` ⇒ owner gate with deny-on-timeout.
6. The agent proposes direction and pace; never capability, money, or trust.
7. Channel edges relay; they never act. Only authenticated owner principals
   are obeyed.
8. Budget gates proactive dispatch only; owner-filed work always runs (D6).
9. Journals and decision records are append-only and permanent.
10. Digests/reports are computed, never model-generated.
11. Every timer durable; one writer per surface.
12. `frozen` mode gates all state-changing actions instantly (org-scoped).

---

## 6. Reference implementation map (Yuko repo)

Live and verified as of 2026-07-08. Port, don't rewrite, wherever it fits.

| Path | What it is | Impyard disposition |
|---|---|---|
| `packages/driver/src/run.ts` | Per-task runner: housekeeping, gate sweep, prompt builders (attend/plan/curate/research), PDP autostart, container spawn | Generalize into session-runner; prompt builders → context compiler (increment 0 replaces ad-hoc assembly) |
| `packages/driver/src/usage.ts` | Token/cost tally from pi session JSONL | Port as-is |
| `packages/driver/src/{gates,trust,review,reviews}.ts` | Gate sweep/resolve, trust CLI, review verdicts | Port; extend to promotion + send gates |
| `packages/driver/src/{container,lockdown}.ts` | Docker run + egress lockdown (~40 lines, NAT-disabled bridge) | Port as-is |
| `packages/driver/src/{tracker,kb,report,steer,journal-read}.ts` | GitHub Issues client, KB publish, report renderer, steering | Port; tracker behind queue abstraction (Q3) |
| `packages/daemon/src/daemon.ts` | Supervisor: tick loop (20s poll), dispatch (issue/attend/plan/curate), journal relay w/ `processed` cursor + `supersedeJournal`, budget gate (`proactiveAllowed`), digest, cadence, proposals (charter/cadence buttons), usage recording | The Impyard supervisor grows from this. Keep: durable stamps (`runs/.daemon/last-plan`, `last-curate`, `last-digest`), `loop:proactive` labeling, throttled pause notice |
| `packages/daemon/src/discord.ts` | Hand-rolled Discord gateway (resume, reconnect, buttons) | Port as channel adapter #1 |
| `packages/daemon/src/schedule.ts` | `schedules.json` timed research | Fold into wake-ups |
| `packages/pi-extensions/src/*` | The behavior kit (§3.2) | Port; add tool-rules layer (protocol shape only) |
| `broker/src/broker.ts` | Gateway TS half: fetch/search/recall + key injection + raw-store persist | Grows into §3.7 |
| `pdp/` (Rust, :7212) | Judge: YAML policies + 5-op conditions, SQLite ledgers/trust, decision records, `environment.yaml` mode dial | Keep running; port to TS at org-boundary increment (D5) |
| `policies/*/*.yaml` | org/team/learned precedence, conditions | Port format as-is |
| `schemas/` | DecisionRequest/Context/Decision + journal event schemas, action classes | **The narrow waist — port first, change least** |
| `loop.json` | `tracker_repo, kb_dir, principal_id, pdp_url, gateway_mode (shadow/enforce/off), run_mode (host/container), egress_lockdown, token_budget_hourly` | Becomes org+imp config split |
| `cadence.json` | `plan_every_hours, curate_every_hours, max_concurrent_research` | Becomes per-imp `cadence.yaml` |
| State dir `runs/.daemon/` | `usage.jsonl`, `last-digest`, `last-plan`, `last-curate` | Pattern generalizes to the durable scheduler table |

Env conventions: secrets in repo-root `.env` (gitignored), loaded by driver;
`LOOP_*` env vars override config. **Never** commit `.env`, `runs/`,
`mailbox/`, `gates/`, `reviews/`. Refer to keys by name only; never print
values.

---

## 7. Technical specifics

### 7.1 Toolchain
Node 24 (native TypeScript type-stripping — **no tsc, no build step**;
`node --check <file>` for syntax). `node:sqlite` for ledgers when the judge
ports. Dependencies: near-zero by policy — search the pi extension ecosystem
before building, hand-roll thin over adopting subsystems.

### 7.2 Condition evaluator (replaces CEL at D5 port)
Live corpus uses only: `"true"`, `==`, `!=`, `||`, `in [list]`, over bindings
`trust.tier`, `environment.mode`, `environment.change_freeze` (extend
bindings as classes grow). ~80 lines, zero deps. Property-test against the
Rust engine's outputs on the recorded decision corpus before cutover.

### 7.3 Container contract (the engine boundary — D3)
In: task file + mailbox dir. Out: `journal/events.jsonl` (append-only) +
workspace artifacts. Egress: gateway only. Lifecycle: run-until-`close_task`
or ceiling timeout. All scheduler state host-side. Any image honoring this is
a valid runtime; **do not** build an in-process abstraction on top of it.

### 7.4 Journal relay discipline (bug-scar critical)
The supervisor relays `events.jsonl` to channels tracking a per-run
`processed` count. **Before every re-dispatch into a reused task dir, call
`supersedeJournal` (rename `events.jsonl` → `events.prev.jsonl`)** — else a
new run with `processed=0` re-relays the previous session's events (this
shipped as duplicate Discord messages + a duplicate GitHub issue).

### 7.5 Boot-test trick
To boot the supervisor without double-connecting the real Discord bot:
`DISCORD_BOT_TOKEN=bogus node <supervisor>` (the exact var name matters — a
test once used `DISCORD_TOKEN=bogus` and connected as the real bot via the
`.env` value).

### 7.6 Accounting
Parse session JSONL lines containing `"usage"`; recursively tally nodes with
numeric `totalTokens` (+ `cost.total`), don't descend into counted nodes;
skip `stdout*` files. Real-world scale for sanity checks: a research run ≈
0.4–0.9M tokens / $0.8–1.1; curate ≈ 0.1M/$0.3; chat-attend ≈ 0.2M/$0.5.

### 7.7 Proposal flow (charter/cadence)
Extension emits `charter_proposed` / `cadence_proposed` journal events →
supervisor renders Approve/Decline buttons → on approve: write file
(charter → git commit scoped to that path; cadence → merge + live reload +
reset cadence stamps). Nothing applies without the button.

---

## 8. Ops scars — bugs actually shipped; do not re-ship

1. **In-memory cadence timers** reset on every restart ⇒ the 8h planner
   *never fired* (daemon restarted more often than the window). Fix pattern:
   persist stamps, load on boot, fire catch-up if overdue, small boot grace
   (~90s) to let channel reconnects settle.
2. **Journal replay race** (§7.4): reused task dir + `processed=0` ⇒ old
   events relayed as new. First fix attempt was a *prompt* guard — wrong
   layer; the real fix was mechanical (supersede). Lesson: fix state races in
   state, not in prompts.
3. **Stale gateway process**: after code changes, a detached broker keeps
   serving old code (supervisor restart doesn't restart it). Symptom: calls
   logged but side-effects missing. Kill by port and restart explicitly.
4. **JSDoc `*/` landmine**: a glob like `runs/*/journal` inside a block
   comment closes it. Write `runs/<run>/journal` in comments.
5. **Bash pipelines mask exit codes** (`cmd | head` hides `cmd`'s failure) —
   use `if cmd; then` when the exit code matters.
6. Machine TZ is CEST (UTC+2); logs are UTC ISO — reconcile before reasoning
   about "when did X fire".

---

## 9. Build plan (increments; each live + tested before the next)

**Increment 0 — prove two pieces inside Yuko (no Impyard scaffolding):**
- Context compiler: replace ad-hoc `build*Prompt` with deterministic budgeted
  blocks; log compiled context per session. Accept: existing runs behave the
  same; "what did it see" is answerable from the log. The concrete contract,
  security boundary, and migration are in `docs/context-compiler-spec.md`.
- Promotion rule: split store writes (imp append-only) from core promotion
  (curator only); charter already gates. Accept: a planted "malicious" note
  cannot enter the compiled core without a curator run; curator diff is
  git-visible.

**Increment 1 — ImpSpec + hire/deploy (single org):**
CLI scaffolds `imps/<name>/` from a template; deploy validates spec,
provisions surfaces (queue, journal dir, store namespace, ledger rows, T0
trust), registers with supervisor. Re-express Yuko as `imps/yuko/`.
Accept: **zero behavior change** for Yuko running from its spec.

**Increment 2 — second imp (monitor template):**
Shares gateway/supervisor/judge; own identity, budgets, trust, namespaces.
Accept: two imps run concurrently; ledgers and trust records never bleed;
per-imp digest correct.

**Increment 3 — model key behind gateway (hard budget):**
Proxy model calls; serve credential only while ledger positive; failover
here. Accept: an over-hard-cap imp's model call fails at the gateway
mid-run; reactive soft-cap behavior unchanged (D6).

**Increment 4 — queue as first-class (Q3):**
Built-in durable queue; optional GitHub mirror. Accept: kill/restart
supervisor mid-flight, no lost/duplicated tasks (one-writer lanes hold).

**Increment 5 — email channel:** outbound first (`send-communication`, T0
gated); inbound as content-only into the cache. Accept: spoofed inbound mail
cannot cause any action; outbound requires button until promoted.

**Increment 6 — fleet view + org digest.** Computed only. Accept: matches
ledgers exactly.

**Increment 7 — roles + sign-in:** approver/auditor via OIDC; judge consumes
verified principal ids. Accept: an approver can resolve only granted classes;
auditor is read-only.

**Increment 8 — org boundary + judge port (D5) + adversarial pass:**
Namespace policies/ledgers/records/stores per org; port judge to TS
(property-tested against recorded decisions); then attack tenancy on purpose
(ledger bleed, store path traversal, gateway routing). Accept: documented
attacks fail.

---

## 10. Working with the owner

- **Thin pieces over subsystems** — prefers small dependency-free code over
  framework adoption (declined discord.js, NanoClaw, Letta on this basis).
  Search existing ecosystems before building, then build thin.
- **Always update docs** in the same change: README, how-it-works (plain
  English register for owner-facing docs), build-plan, package READMEs.
- **Test as you build; verify live.** Every increment proven on the running
  system before moving on. Report failures honestly with output.
- **Propose, don't assume, on forks**: pin genuinely owner-level decisions
  with short option sets (this is how D1–D3 were made). Don't re-ask settled
  ones (§2).
- **Commit only when asked.** Never commit `.env`, `runs/`, `mailbox/`,
  `gates/`, `reviews/`.
- Security habits: refer to API keys by name only; never echo values; treat
  charter/notes/memory as attacker-reachable text.

---

## 11. Non-goals (v1) and risks

**Non-goals:** engine swap layer; embeddings/vector store (keyword recall
until it measurably fails); cross-org sharing; agent-editable policy
(improvement loop proposes diffs, humans merge); model-written observability;
automatic trust promotion.

**Top risks:** pi is a load-bearing pinned rental · inbound email authn
(content-only until solved) · gate fatigue at T0 (gate the irreversible, not
the mundane; surface approval load in digests) · budget hard-stop absent
until increment 3 (dispatch-gate only; in-flight runs finish) · tenancy is a
claim until increment 8 attacks it · journals grow forever (cold-storage
answer needed) · OpenClaw-derived ideas were never code-verified.

## 12. Open questions for the owner

Q1 name ("Impyard" is placeholder) · Q2 third/fourth templates (correspondent
vs ops-runner) · Q3 queue: built-in day one vs GitHub-backed until
increment 4 · Q4 inbound email authn: any scheme trustworthy, or content-only
permanent · Q5 journal cold storage: local archive vs object storage.