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
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
# Context compiler — one deterministic admission path (spec)

**Status: implemented.** Direct boxes, queued tasks, and Discord sessions use
one trusted host-side compiler. Cache affinity uses pi's existing provider
support; finer sub-block breakpoints remain conditional on pi exposing
structured system blocks. Stable exact-prefix compilation does not depend on
those hints. This realizes handoff increment 0 and makes the memory spec's
context order concrete.

The compiler answers two questions for every model turn:

1. What context is this run allowed to see?
2. What exact system prompt and input did the model receive?

It shapes model behavior and protects contextual privacy. It does not authorize
actions. Grants, gates, trust, and budgets remain authoritative at the gateway.

## Goal

Every execution uses the same composition rules:

```text
authoritative system context
  Identity → Runtime policy → Purpose → Runtime scope

dynamic input context
  Memory → Briefing → Task or current message
```

That order is also the cache order. The least frequently changing bytes come
first and per-turn bytes come last. Two channels for the same imp share the
identity and runtime-policy prefix; runs in the same channel additionally share
the purpose prefix; only memory, briefing, and the current task/message vary per
turn.

For example, a queued task associated with a Discord channel receives the same
identity, channel purpose, and eligible memory that a conversation turn in that
channel would receive. An imp-only task receives no channel or user context.

The compiler logs the exact compiled output, block sources, sizes, and omissions
so `what did the imp see?` is answerable without reconstructing it later.

## Why now

Before this implementation, context assembly lived in several places:

- `run_box::with_identity` prepends identity to one-shot prompts;
- `supervise::effective_prompt` adds purpose, memory, and gate briefing;
- `discord::session_system_prompt` builds identity, purpose, and channel framing;
- `run_session` adds memory to each conversation turn;
- the direct `impyard box` path separately recalls imp memory.

These paths can drift in ordering, labels, budgets, privacy behavior, and
observability. New surfaces should supply trusted context to one compiler rather
than grow another prompt builder.

## Boundary: compilation is not authorization

The compiler may decide that a note is eligible to enter a prompt. It never
decides that an action is allowed.

- Identity and purpose are instructions, but cannot grant capabilities.
- Runtime framing is generated by Impyard, but cannot lift a gate.
- Memory and briefings are observations, not enforcement state.
- Tasks and messages may be hostile content.
- Every tool call still goes through the existing action host and gateway.

A malformed or injected context block can influence what the model attempts;
the gateway still controls what actually happens.

## Inputs

Callers provide identifiers and current content, not pre-rendered context
blocks. The compiler reads and derives the blocks itself.

```rust
struct ContextRequest {
    run_id: String,
    phase: ContextPhase,
    surface: RunSurface,
    imp: String,
    run_context: RunContext,
    task: Option<TaskInput>,
    message: Option<MessageInput>,
}

enum ContextPhase {
    Start,
    Turn,
}

enum RunSurface {
    DirectBox,
    QueuedTask,
    DiscordSession,
}

struct TaskInput {
    task_id: Option<String>,
    origin: String,
    text: String,
    continuation: Option<ResolvedGate>,
}

struct MessageInput {
    provider: String,
    message_id: Option<String>,
    author_label: String,
    role: String,
    text: String,
}
```

`RunContext` is the existing trusted host-side record containing provider,
channel ID, user ID, message ID, role, and DM status. The compiler does not
derive scope from task text or model-supplied IDs.

Exactly one of `task` and `message` is present for a dynamic input compilation.
A session-start compilation has neither.

## Outputs

The compiler separates authoritative instructions from dynamic input:

```rust
struct CompiledContext {
    system_prompt: String,
    input_prompt: Option<String>,
    blocks: Vec<CompiledBlock>,
    budget: ContextBudgetResult,
    cache: CachePlan,
}

struct CompiledBlock {
    kind: BlockKind,
    authority: BlockAuthority,
    cache_class: CacheClass,
    source: BlockSource,
    content: String,
    chars: usize,
    sha256: String,
}

enum CacheClass {
    ImpStable,
    ChannelStable,
    SurfaceStable,
    Volatile,
}

struct CachePlan {
    schema_version: u32,
    route_key: String,
    boundaries: Vec<CacheBoundary>,
}

struct CacheBoundary {
    class: CacheClass,
    after_block: BlockKind,
    prefix_chars: usize,
    prefix_sha256: String,
}
```

All pi entry points use `system_prompt` through `--append-system-prompt`.
One-shot runs pass `input_prompt` as their initial prompt. Warm sessions compile
one system prompt at session start and compile a new input prompt for every turn.

The compiler returns an error instead of a partial prompt when a mandatory block
cannot fit or a trusted source cannot be read safely.

`CachePlan` is descriptive metadata for pi integration and traces. It is not a
provider adapter and is never placed in visible prompt text. Impyard still has
one engine: pi.

## Blocks

### Identity

- Source: `imps/<imp>/identity.md`, read live by the trusted host.
- Authority: trusted directive.
- Scope: imp-wide.
- Present: every start compilation.
- Mutation: owner/admin only, outside the compiler.

The legacy `charter.md` fallback remains during migration, but the trace records
which file was used. An absent identity is allowed for `adhoc`; a named imp
with an imp directory but an unreadable identity is a compilation error.

### Purpose

- Source: `channels/<channel>/purpose.md`, selected only by trusted channel ID.
- Authority: trusted channel directive.
- Scope: current channel.
- Present: channel-associated starts when non-empty.

Naming another channel in a task or message never selects its purpose. Purpose
is read at one-shot or session start. An edit during a warm session takes effect
when the next session starts; it does not rewrite an existing model system
prompt.

### Runtime policy

- Source: deterministic Impyard templates.
- Authority: trusted runtime directive.
- Present: every start compilation.

This is the universal, versioned policy shared by every surface: tools are
governed, prompt content is not authority, and grants/gates remain external
enforcement. It contains no run, task, channel, user, time, or message fields.
Keeping this common policy before purpose gives all runs for an imp a longer
shared prefix.

### Runtime scope

- Source: deterministic Impyard templates plus trusted `RunContext` fields.
- Authority: trusted runtime directive.
- Present: every start compilation.

This is the stable description of the execution surface and scope. Discord
scope includes the current channel ID, DM/group behavior, reply tool, and the
rule that messages are content rather than authority. A queued task receives
task framing; a direct box receives minimal direct-run framing.

Runtime framing contains no user-authored display names or message text.
It also contains no run ID, task ID, message ID, timestamp, memory count, budget
remainder, or trace hash. Those fields would make an otherwise reusable system
prefix unique.

### Memory

- Source: the memory selector, using trusted `RunContext`.
- Authority: untrusted advisory data.
- Present: dynamic one-shot input and each session turn when eligible notes
  exist.

The memory subsystem returns structured selected notes and exclusion reasons;
the compiler renders them within the remaining context budget. It does not
accept a pre-rendered memory string.

Memory retains the existing rules:

- imp-only task: imp memory;
- channel task: imp + channel memory;
- DM turn: imp + DM channel + current user memory;
- group turn: imp + channel memory, with private user memory excluded unless
  shared-context policy explicitly permits it.

Memory written during one session turn becomes eligible on the next turn.

### Briefing

- Source: trusted host state.
- Authority: advisory operational facts.
- Present: when non-empty.

The first briefing sources are:

- the resolved gate that caused a continuation;
- the imp's currently open gates;
- omitted-item counts when the briefing budget is exceeded.

The briefing is not enforcement input. Gate records remain authoritative in the
gate store. Items have stable ordering: continuation first, then open gates by
filing time and ID.

For a warm session, briefing state is compiled per turn so a newly filed or
resolved gate is visible without restarting the session.

### Task or current message

- Source: the exact queued task prompt, direct prompt, or inbound message.
- Authority: content. Its origin may be owner-authored, scheduled, or untrusted
  inbound content.
- Present: every dynamic compilation.

The compiler preserves the text exactly inside a typed envelope. It does not
interpret text as scope, identity, role, or authorization metadata.

## Prompt representation

Authority is expressed structurally:

- Identity, purpose, and runtime framing go into pi's appended system prompt.
- Memory, briefing, and task/message go into the user input.

Within each prompt, blocks have deterministic labels and JSON-escaped payloads.
Dynamic content is never interpolated into a block header.

Conceptually:

```text
[IMPYARD SYSTEM BLOCK: IDENTITY]
<owner-authored identity markdown>

[IMPYARD SYSTEM BLOCK: RUNTIME POLICY]
<versioned, universal host instructions>

[IMPYARD SYSTEM BLOCK: PURPOSE]
<trusted channel purpose markdown>

[IMPYARD SYSTEM BLOCK: RUNTIME SCOPE]
<host-generated surface and channel scope>
```

```json
{"block":"memory","authority":"advisory","items":[...]}
{"block":"briefing","authority":"advisory","items":[...]}
{"block":"task","origin":"discord","text":"..."}
```

JSON escaping prevents message text containing a fake closing delimiter from
changing the outer prompt structure. Labels help the model reason about trust;
they are not a substitute for gateway enforcement.

## Prompt-cache contract

Prompt caching is a byte-prefix optimization, not a semantic one. A harmless
change in whitespace, tool order, or JSON serialization can turn a cache hit
into a miss. The compiler therefore treats prefix stability as part of its
public contract.

The full provider prefix is larger than `system_prompt`. It normally contains,
in order, pi's built-in instructions and tool definitions, Impyard's appended
system context, then conversation messages. Impyard directly controls only its
own blocks, but integration must preserve the complete prefix:

- pi version, model, enabled tool set, tool schemas, and tool ordering are
  deterministic for runs expected to share a cache;
- system blocks are ordered from broadest/most stable to narrowest/least stable;
- all per-run and per-turn data is appended after the stable system prefix;
- previous warm-session messages are never regenerated, normalized, or
  reordered; each new turn only appends content;
- budget pressure may remove or shorten volatile suffix blocks, but may not
  rewrite an earlier stable block or its separator.

The reusable Impyard-owned boundaries are:

1. **imp boundary** — identity plus runtime policy;
2. **channel boundary** — imp boundary plus purpose, when present;
3. **surface boundary** — the complete system prompt, including runtime scope.

Each boundary hash covers the exact UTF-8 prefix, including labels and
separators. `route_key` is an opaque SHA-256-derived value from the engine/model
family, pi/tool-set fingerprint, compiler schema version, and imp boundary.
It excludes raw imp/channel/user names and all volatile IDs. A deployment may
add a stable traffic shard when one key becomes too hot; the mapping must remain
stable for requests intended to share a prefix.

Provider behavior is an integration concern, not a second compiler:

- OpenAI automatic caching can reuse exact prefixes of eligible requests. The
  pinned pi maps its session ID to `prompt_cache_key`, so Impyard supplies
  `route_key` through pi's `--session-id`. Each run still has a separate session
  directory, so this shares provider cache affinity without sharing local
  transcripts. Pi may place explicit breakpoints on models that support them.
- Anthropic sees a hierarchy of tools, system, then messages. The pinned pi
  applies cache controls to the system prompt, tool definitions, and growing
  conversation. Finer Impyard-owned boundaries require structured system blocks.
  Do not put a breakpoint after timestamps or other changing blocks.
- Pi currently receives Impyard's appended system context as one string. The
  compiler still gives automatic caching a stable prefix; provider-specific
  sub-block breakpoints wait for a verified pi mechanism or a narrowly scoped
  request-shaping change in the existing gateway. They do not justify a new
  provider abstraction.

The compiler promises cache-friendly input, not a cache hit. Provider routing,
minimum token thresholds, expiry, concurrent first writes, and eviction remain
outside Impyard's control. Cache usage must be measured from provider usage
fields rather than inferred from matching hashes.

## Context budgets

Version one measures Unicode characters, not model tokens. Character accounting
is deterministic, dependency-free, and testable. Token-aware budgeting can be
added later without changing the block contract.

Owner configuration is compiled from `org.toml` with per-imp overrides:

```toml
[context]
max_injected_chars = 48000
identity_max_chars = 12000
purpose_max_chars = 8000
briefing_max_chars = 4000
task_max_chars = 24000
```

Memory keeps its existing admin/channel maximum and also receives the remaining
compiler budget. The effective memory limit is the smaller value.

Allocation happens in this order, even though rendering uses the block order
defined above:

1. Validate and reserve identity, runtime policy, purpose, and runtime scope.
2. Validate and reserve the task or current message.
3. Build a bounded briefing, prioritizing continuation state over open gates.
4. Give the remaining elastic budget to ranked memory.
5. Render blocks in canonical order and verify the total.

Rules:

- Identity, purpose, runtime policy, runtime scope, and task/message are never silently
  truncated. An oversized mandatory block causes a compilation error.
- Briefing may omit lower-priority items but must include an omitted count.
- Memory may omit notes using its deterministic ranking and records why each
  candidate was excluded.
- The total applies only to context injected by Impyard. Pi's built-in system
  prompt and accumulated warm-session history are measured separately by pi.
- Empty blocks are omitted and consume no separators or labels.

## Determinism

Given the same source snapshot and request, compilation produces byte-identical
system and input prompts.

To make that true:

- blocks have a fixed order;
- memory and gates use stable tie-breakers;
- templates are versioned constants;
- text sources normalize CRLF to LF once, preserve all other content bytes, and
  use one documented separator between non-empty blocks;
- JSON is serialized from typed structures with fixed field order; maps are
  sorted before serialization and never rely on hash-map iteration order;
- timestamps and random IDs do not appear in compiled prompts unless they are
  part of an explicit source record;
- hashes cover exact UTF-8 block content;
- trace timestamps are metadata and do not affect prompt hashes.

Template or normalization changes require a compiler schema-version bump. They
are intentional cache invalidations, not silent formatting refactors.

Live files and host state may change between compilations. The trace captures the
snapshot used for that compilation.

## Sessions

A warm Discord session has two compilation phases.

### Start

Compile once before spawning pi:

```text
system: Identity + Runtime policy + Purpose + Runtime scope
input:  none
```

### Turn

Compile before writing each RPC prompt:

```text
system: unchanged
input:  Memory + current Briefing + current Message
```

The host saves the trusted turn context before compilation. Messages remain
serialized: the next turn is not compiled until the previous turn ends. This
keeps actor attribution and memory authorization aligned with the active turn.

If turn compilation fails, that message is not delivered to pi. The session may
remain alive for later messages, and the failure is recorded. A start failure
prevents the session container from launching.

## One-shot runs

Direct, scheduled, continuation, research, and code tasks compile immediately
before box provisioning:

```text
system: Identity + Runtime policy + optional Purpose + Runtime scope
input:  Memory + optional Briefing + Task
```

Compilation failure marks the run and queued task failed with a clear diagnostic.
There is no fallback to the old prompt builders or to an uncompiled raw prompt.

## Trace and owner interface

Each compilation appends one trusted-side trace event to
`runs/<run-id>/context.jsonl`:

```json
{
  "schema_version": 1,
  "ts": "2026-07-11T10:00:00Z",
  "run_id": "2026-07-11-10-00-00-abcd",
  "phase": "turn",
  "turn_id": "discord:message:123",
  "surface": "discord-session",
  "imp": "yuko",
  "scope": {
    "channel_id": "discord:456",
    "user_id": "discord:789",
    "is_dm": true
  },
  "budget": {
    "limit_chars": 48000,
    "used_chars": 7312,
    "remaining_chars": 40688
  },
  "blocks": [
    {
      "kind": "identity",
      "authority": "trusted-directive",
      "cache_class": "imp-stable",
      "source": "imps/yuko/identity.md",
      "chars": 820,
      "sha256": "...",
      "content": "..."
    }
  ],
  "cache": {
    "schema_version": 1,
    "route_key": "impyard-pc-...",
    "boundaries": [
      {
        "class": "imp-stable",
        "after_block": "runtime-policy",
        "prefix_chars": 1512,
        "prefix_sha256": "..."
      }
    ]
  },
  "system_prompt_sha256": "...",
  "input_prompt_sha256": "...",
  "status": "compiled"
}
```

Failed compilations are also recorded with `status: "failed"`, the source that
failed, known block sizes, and a safe error message.

The trace file is written with owner-only permissions. It is hidden from boxes by
the runtime-state shadow mounts described below. Commands:

```text
impyard runs show <run-id>             # block summary and hashes
impyard runs context <run-id>          # exact latest compiled system + input
impyard runs context <run-id> --all    # every session compilation
```

`runs show` does not print full private context by default.

## State and mount isolation

The compiler is not a real admission boundary if the box can bypass it by
reading raw runtime files with shell tools. The current read-only repository
mount includes gitignored runtime directories, so this implementation must also
isolate trusted state.

Required changes:

- Context traces remain at `runs/<run-id>/context.jsonl` for simple run
  inspection, but the box sees an empty shadow mount at the repository's
  `runs/` path. Only the current run's required child mounts are overlaid.
- Raw `memory/`, legacy `notes/`, `journal/`, `queue/`, `gates/`, and other runs are not readable
  inside a box.
- The complete `channels/` tree is not exposed. A run may receive an explicit
  read-only mount for its current channel history/files when its trusted context
  permits it.
- The current run's writable workspace, pi session directory, and optional code
  worktree remain mounted as today.
- Read-only context access needed by the imp goes through the compiled prompt
  or a governed, scope-checking read tool.

Version one keeps the existing host paths and adds empty tmpfs or equivalent
shadow mounts over `runs/`, `memory/`, legacy `notes/`, `journal/`, `queue/`, `gates/`, and
`channels/` after mounting the repository read-only. Docker then overlays only:

- the current run's writable workspace, pi session directory, and pihome;
- the current code worktree when present;
- the current channel directory read-only when trusted context grants it.

This requires no runtime-state migration. Moving state to a configurable external
root can happen later without changing the compiler contract.

Acceptance includes a real locked box attempting to read another user's notes,
another channel's history, another imp's journal, and a context trace. All
must fail at the filesystem boundary.

## Integration changes

The implementation removes these behavior paths after their callers migrate:

- `run_box::with_identity`;
- `supervise::effective_prompt`;
- `discord::session_system_prompt`;
- direct calls to `memory::render_recall` from runners.

The memory subsystem exposes structured recall results rather than a complete
prompt string. The context compiler becomes the only module that renders memory
for a model.

Box provisioning accepts a compiled system prompt and scoped mounts; it does not
read identity, purpose, memory, or briefing itself.

Pi invocation must also keep cache-relevant request structure deterministic.
Extension paths and tool registrations are sorted, tool schemas do not contain
run-specific text, and equivalent runs use the same model identifier. If pi
offers provider cache controls, the runner passes `CachePlan` through that
existing engine path. Impyard does not add an in-process provider seam.

## Failure behavior

- Missing optional purpose or empty memory is not an error.
- Unreadable required identity, invalid trusted context, oversized mandatory
  content, or trace-write failure fails closed before model input.
- A trace must be durable before its compiled input is sent to pi.
- Failed session turns remain visible in the trace and run history.
- Error messages may identify a source path and size but never echo secrets or
  private memory into a shared channel.

## Security invariants

- Trusted scope comes only from host-owned run metadata.
- User or task text cannot select an imp, channel, user, purpose, or memory
  namespace.
- Identity and purpose are structurally separated from untrusted input.
- Memory is always labeled advisory and never enters authorization logic.
- Raw private state cannot be read from inside the box to bypass recall policy.
- Mandatory context is never silently truncated.
- Every model input has a durable, exact trace written first.
- Volatile values never appear before a declared stable cache boundary.
- Compilation failure never falls back to an older or less restrictive path.
- The gateway remains the final authority for every external action.

## Non-goals

- Changing grants, gates, budgets, or trust evaluation.
- Research-corpus retrieval beyond the existing memory selector.
- Embeddings or a vector database.
- Summarizing or compacting pi's warm conversation history.
- Implementing or storing a model-provider prompt cache inside Impyard.
- Reloading identity or purpose in the middle of a live pi session.
- Multi-organization isolation; this spec prepares the boundary but does not add
  organizations.

## Build order

### 1. Pure compiler and trace schema

- Add typed request, block, budget, output, and trace structures.
- Implement deterministic templates, escaping, hashing, cache boundaries, and
  budget errors.
- Add snapshot and shared-prefix tests for all block combinations.
- Do not change runtime behavior yet.

### 2. One-shot integration

- Start the run record before compilation.
- Migrate direct boxes and supervised tasks.
- Pass compiled system context through `--append-system-prompt`.
- Remove `with_identity` and `effective_prompt` after parity tests pass.

### 3. Session integration

- Compile static system context before session spawn.
- Compile memory, briefing, and message for every serialized turn.
- Remove `session_system_prompt` and runner-owned memory rendering.
- Verify memory written in turn N appears in turn N+1.
- Verify pi keeps tool definitions and prior message bytes stable between turns;
  pass cache hints only through a verified pi mechanism.

### 4. Trace inspection

- Add context summaries to `runs show`.
- Add `runs context` for exact owner inspection.
- Preserve trace/run linkage across failures and warm-session turns.

### 5. State isolation and live adversarial test

- Move or shadow raw trusted runtime state outside box visibility.
- Mount only the current workspace and explicitly authorized channel material.
- Run filesystem escape, forged-scope, oversized-context, and prompt-injection
  tests in a real locked container.

Steps 2–5 are one release boundary. The old prompt builders must not remain as
fallbacks after the release is declared complete.

## Acceptance tests

1. Direct, queued, and session runs with the same scope compile identical
   identity and purpose blocks.
2. Imp-only tasks receive no channel purpose or channel/user memory.
3. A DM receives imp + DM channel + current-user memory.
4. A group turn excludes private user memory by default.
5. A task saying "use channel 999" cannot select channel 999 context.
6. Memory and briefing shrink deterministically under pressure; identity,
   purpose, and task never truncate.
7. Identical source snapshots produce identical prompt bytes and hashes.
8. Two tasks with different memory and text but the same imp/channel/surface
   have byte-identical system prompts and boundary hashes.
9. Two channels for one imp share the exact imp boundary; changing one
   channel's purpose leaves that boundary unchanged and changes only that
   channel's later boundaries.
10. No run ID, task ID, message ID, user ID, timestamp, budget remainder, or
    trace hash appears before a stable boundary.
11. Stable tool definitions and their order are identical for equivalent runs;
    a tool-set change intentionally changes the cache route key.
12. The stored trace hashes match the exact strings passed to pi.
13. A turn is not delivered when durable trace writing fails.
14. A warm session sees newly written memory on its next turn without changing
    its original system prompt or any prior message bytes.
15. Identity or purpose edits affect the next run/session, not an already-live
    session.
16. `runs show` explains block inclusion and omission; `runs context` reproduces
    exact model input.
17. On a cache-capable live provider, a repeated eligible prefix reports a
    non-zero provider cache read after the initial write. The test may skip when
    the selected model is below its provider's minimum or pi exposes no cache
    control/usage field; unit prefix tests may not skip.
18. A locked box cannot read raw notes, another channel's files, another
    imp's journal, queue/gate state, or context traces.
19. Existing gateway decisions and action-gate behavior are unchanged.

## Recommended defaults

- System/input separation: always on.
- Stable-prefix ordering and cache-boundary tracing: always on.
- Character budgeting in v1.
- Full exact traces: on, owner-only, hidden from boxes by runtime-state shadow
  mounts.
- Identity/purpose truncation: never; fail with a size diagnostic.
- Task/message truncation: never; fail with a size diagnostic.
- Briefing: continuation first, then open gates, with omitted count.
- Memory: ranked within the smaller of its own limit and remaining context.
- Identity and purpose refresh: next run or next warm session.
- Trace before delivery: required; fail closed.