Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions docs/GATE5-MEMORY-CONTRACT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<!-- Edge-owned. Deliberately NOT under kernel/. -->

> **Ownership.** This contract describes a `relayhistory` / `ai-hist` integration:
> provider CLI syntax, JSON output, exit-code behaviour and provider-specific
> traps. That is SDK/control-plane knowledge, not kernel design authority.
> RFC-0001 §4 and settled decision #13 keep the Rust kernel provider-neutral and
> closed-vocabulary, so this file lives under `docs/` — it previously sat under
> `kernel/`, where its location signalled kernel ownership regardless of what the
> text said, and a future implementer could reasonably have grown the kernel
> around `ai-hist` on that signal alone.

# Gate 5 — what a relayhistory-backed MemoryProvider has to do

Read of `AgentWorkforce/relayhistory` at `3e7df69`, 2026-09-08. RFC-0001 gate 5
says relayhistory is "consumed over its serialization contract, not rewritten",
so this records that contract rather than proposing a design that ignores it.

## What already exists on the flows side

PR `#221` (issue `#220`) landed the seam, and it is the part that is hard to retrofit (`kernel/MEMORY.md` is titled "Step memory, slice 1 (#220)" because it names the issue):
`MemoryProvider`, `MemoryPack`, and a `memory.injected` journal entry carrying
the consuming `step_id` and initial `attempt`. That is RFC decision 10 — memory
tokens charged to the consuming step, itemized, no shared pools.

What is wired is `FixedMemoryProvider`, which returns
`{"text":"fixed memory pack","citations":[]}` with synthetic usage of 7 input
tokens and `"0.002"`. `kernel/MEMORY.md` says so plainly: "a substrate stub:
there is no retrieval, relayhistory call, or claim about memory quality."

## The contract to consume

Retrieval is `ai-hist pack`:

ai-hist pack <query...> [--project P] [--tag T] [--source S]
[--limit N] [--tokens N] [--fts] [--json]

- `--json` emits `{ "query": ..., "entries": [...] }`.
- `--tokens N` is the budget, applied as `chars_budget = tokens * 4`
(`crates/ai-hist/src/lib.rs:2369`) — a four-characters-per-token
approximation, not a tokenizer. A provider must not report that figure back
as exact usage; decision 10's accounting is only checkable if the number
means something.

**The trap worth writing down before anyone implements this.** `pack_entries`
calls `std::process::exit(1)` when there are no results, after printing
`{"query": ..., "entries": []}`. Exit 1 here means *no memory matched*, not
*the call failed*. A provider that treats nonzero as an error will report every
cold-start step as a memory failure, and a provider that treats it as fatal
will fail closed on exactly the runs that have nothing to remember yet.

The trajectory side is `ai-hist push`, which takes `--install-service` and an
interval. Gate 5's bar is "every relayflow run pushes trajectories to
relayhistory **without opt-in code**", so that is a service the cell runs, not
a call each flow makes.

## What gate 5 still needs, in order

1. A `RelayhistoryMemoryProvider` over `ai-hist pack --json`, mapping a step's
declared `memory: { scope, query, budget }` onto the pack arguments and
reporting honest usage rather than the char-budget approximation.

**It does not live in the kernel.** The adapter belongs at the
SDK/control-plane edge and reaches the kernel across the journal protocol
boundary. RFC-0001 §4 and settled decision #13 keep the Rust kernel small,
pure and closed-vocabulary, so `relayflowd` must not gain an `ai-hist`
dependency, a subprocess call, or any relayhistory-shaped vocabulary.
`MemoryProvider` in `kernel/relayflowd/src/memory.rs` stays an **injected
protocol seam** — the kernel declares the shape it will accept and never
learns who satisfies it. Reading item 1 as "implement this trait inside
relayflowd" is the failure mode this paragraph exists to prevent.
2. Exit-1-means-empty handled explicitly, with a test that a step whose query
matches nothing still runs and journals an empty pack.
3. The push side wired as a service.

None of this is the acceptance test. RFC gate 5 asks for "an agent avoiding a
mistake recorded in a previous run's trajectory, with the citation in its
output" — a behavioural bar, not a passing suite. The three items above are
what makes attempting it possible.
2 changes: 1 addition & 1 deletion ops/SCOREBOARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ Every row starts RED and moves only on evidence. AMBER blocks nothing here
| 4 — chief / harness | RED | not started |
| 5 — memory | RED | scoped by harness directive 3 (relayfile + relayhistory per customer) |
| 6 — integrations via relayfile | RED | **BLOCKED on gates 2-4** (Khaliq, 2026-08-28, option B — this repo has no example flows to convert; helper surface may be built now but does not make the gate green) — harness (design partner) needs slack/notion helpers; also unblocks its `REPLACE-WHEN: gate-2` shims |
| 7 — sandbox routing | RED | regression suite needs darwin-arm64 placement |
| 7 — sandbox routing | **AMBER** | in progress — #227 merged 2026-09-08 (`be3c95ed`). Routing is journaled as a `RoutingDecision` fact at `StepRouted` and dispatch consumes it without re-deciding. **Not GREEN**: gate 7 requires the same flow YAML to run locally and in cloud, and the cloud half is unproven. Design, test inventory and the mutation transcript live in the PR #227 review artifacts, not in this row. |
| 8 — identity + credentials | RED | regression suite needs multi-principal runs |
| 9 — self-improving agents | RED | depends on 5 + 8 |
Loading