feat(memory): journal step-declared packs with exact resume accounting - #221
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
maintainability lens — PASSMaintainability review — PR #221 (step memory, slice 1)Concerns1. 2. Two code paths validate the 3. Collapsed error detail hides the actual failure. 4. 5. Implicit format contract in 6. Notes
No maintainability blockers. Structure is clean enough that the concerns above are addressable in a follow-up. REVIEW_PASSED |
history lens — PASSBlockers: none. PR #221 passes the HISTORY lens at Notes: I found no reintroduction of a deliberately removed pattern recorded in DRIVE-LOG. The new provider failures retain both a typed completion reason and explanatory detail ( The implementation aligns with settled decisions #2 and #10: an existing journaled pack bypasses the provider ( The commit subjects accurately describe their changes: Concerns, non-blocking: Memory validation still scans historical entries ( REVIEW_PASSED |
structure lens — PASS→ Read docs/RFC-0001-everything-is-a-relayflow.md $ wc -l /Users/khaliqgant/AgentWorkforce/flows-ops/kernel/relayflowd-core/src/memory.rs /Users/khaliqgant/AgentWorkforce/flows-ops/kernel/relayflowd-core/src/state/memory.rs /Users/khaliqgant/AgentWorkforce/flows-ops/kernel/relayflowd-journal/src/memory.rs /Users/khaliqgant/AgentWorkforce/flows-ops/kernel/relayflowd/src/engine/memory.rs /Users/khaliqgant/AgentWorkforce/flows-ops/kernel/relayflowd/src/memory.rs /Users/khaliqgant/AgentWorkforce/flows-ops/kernel/relayflowd-core/src/state/budget.rs /Users/khaliqgant/AgentWorkforce/flows-ops/kernel/relayflowd-core/src/spec.rs /Users/khaliqgant/AgentWorkforce/flows-ops/kernel/relayflowd-core/src/state.rs $ ls /Users/khaliqgant/AgentWorkforce/flows-ops/kernel/ 2>/dev/null; echo "---"; ls /Users/khaliqgant/AgentWorkforce/flows-ops/
|
|
🎯 review-swarm: PASSED (M:pass H:pass S:pass) Lens transcripts posted as sibling comments above. |
There was a problem hiding this comment.
4 issues found across 44 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/sdk/tests/worker-cli.test.ts">
<violation number="1" location="packages/sdk/tests/worker-cli.test.ts:501">
P3: The assertion `expect(result.budget).toBeUndefined()` does not actually verify the behavior the test name claims ("excludes its charge from completion usage"). The SDK worker (`packages/sdk/src/worker.ts` `execute()`) calls `stepComplete(..., { output, started_pins, end_pins })` and never includes a `usage`/`budget` field, so `result.budget` is `undefined` unconditionally and this line can never fail. Either exercise a real charge-exclusion path or remove/rename the claim so the test doesn't give false coverage of the billing behavior actually implemented in the kernel/provider modules.</violation>
</file>
<file name="kernel/relayflowd/src/engine/drive.rs">
<violation number="1" location="kernel/relayflowd/src/engine/drive.rs:159">
P2: When a memory provider or budget failure is retryable with nonzero backoff, this call blocks in the retry timer before the reservation is released at the following branch. The reserved worker is therefore counted as busy and cannot accept independent work during the backoff; release the reservation before waiting or make the memory-failure path return without blocking.</violation>
</file>
<file name="kernel/relayflowd/src/engine/memory.rs">
<violation number="1" location="kernel/relayflowd/src/engine/memory.rs:34">
P2: When two resumes drive the same memory-bearing deterministic step concurrently, both calls can reach `provide` before either injection is committed. The later append is rejected after the provider call, causing an avoidable second provider query and a failed resume; serialize memory injection or elect the committing attempt before calling the provider.</violation>
</file>
<file name="kernel/relayflowd-journal/src/memory.rs">
<violation number="1" location="kernel/relayflowd-journal/src/memory.rs:62">
P1: When no `memory.injected` row exists, this early return preserves caller-supplied `summary.memory`. An epoch can therefore restore an unjournaled pack and make execution skip the provider; reject non-empty summary memory unless it is derived from recorded facts.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| [], | ||
| |row| row.get(0), | ||
| )?; | ||
| if !has_memory { |
There was a problem hiding this comment.
P1: When no memory.injected row exists, this early return preserves caller-supplied summary.memory. An epoch can therefore restore an unjournaled pack and make execution skip the provider; reject non-empty summary memory unless it is derived from recorded facts.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kernel/relayflowd-journal/src/memory.rs, line 62:
<comment>When no `memory.injected` row exists, this early return preserves caller-supplied `summary.memory`. An epoch can therefore restore an unjournaled pack and make execution skip the provider; reject non-empty summary memory unless it is derived from recorded facts.</comment>
<file context>
@@ -0,0 +1,79 @@
+ [],
+ |row| row.get(0),
+ )?;
+ if !has_memory {
+ return Ok(());
+ }
</file context>
| if skipped_dispatches.remove(&(step.id.clone(), attempt)) { | ||
| continue; | ||
| } | ||
| let injection = self.ensure_step_memory(&mut journal, &step, attempt); |
There was a problem hiding this comment.
P2: When a memory provider or budget failure is retryable with nonzero backoff, this call blocks in the retry timer before the reservation is released at the following branch. The reserved worker is therefore counted as busy and cannot accept independent work during the backoff; release the reservation before waiting or make the memory-failure path return without blocking.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kernel/relayflowd/src/engine/drive.rs, line 159:
<comment>When a memory provider or budget failure is retryable with nonzero backoff, this call blocks in the retry timer before the reservation is released at the following branch. The reserved worker is therefore counted as busy and cannot accept independent work during the backoff; release the reservation before waiting or make the memory-failure path return without blocking.</comment>
<file context>
@@ -150,6 +156,18 @@ impl<C: Clock> Engine<C> {
if skipped_dispatches.remove(&(step.id.clone(), attempt)) {
continue;
}
+ let injection = self.ensure_step_memory(&mut journal, &step, attempt);
+ if !matches!(injection, Ok(true)) {
+ if let Some(dispatcher) = &self.dispatcher {
</file context>
| if state.steps[&step.id].memory.is_some() { | ||
| return Ok(true); | ||
| } | ||
| let candidate = self |
There was a problem hiding this comment.
P2: When two resumes drive the same memory-bearing deterministic step concurrently, both calls can reach provide before either injection is committed. The later append is rejected after the provider call, causing an avoidable second provider query and a failed resume; serialize memory injection or elect the committing attempt before calling the provider.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kernel/relayflowd/src/engine/memory.rs, line 34:
<comment>When two resumes drive the same memory-bearing deterministic step concurrently, both calls can reach `provide` before either injection is committed. The later append is rejected after the provider call, causing an avoidable second provider query and a failed resume; serialize memory injection or elect the committing attempt before calling the provider.</comment>
<file context>
@@ -0,0 +1,81 @@
+ if state.steps[&step.id].memory.is_some() {
+ return Ok(true);
+ }
+ let candidate = self
+ .memory_provider
+ .provide(journal.run_id(), &step.id, request);
</file context>
| const result = completions[0]?.[5] as { output: { instruction: string }; budget?: unknown }; | ||
| expect(result.output.instruction).toContain('Use context'); | ||
| expect(result.output.instruction).toContain(JSON.stringify(pack)); | ||
| expect(result.budget).toBeUndefined(); |
There was a problem hiding this comment.
P3: The assertion expect(result.budget).toBeUndefined() does not actually verify the behavior the test name claims ("excludes its charge from completion usage"). The SDK worker (packages/sdk/src/worker.ts execute()) calls stepComplete(..., { output, started_pins, end_pins }) and never includes a usage/budget field, so result.budget is undefined unconditionally and this line can never fail. Either exercise a real charge-exclusion path or remove/rename the claim so the test doesn't give false coverage of the billing behavior actually implemented in the kernel/provider modules.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/sdk/tests/worker-cli.test.ts, line 501:
<comment>The assertion `expect(result.budget).toBeUndefined()` does not actually verify the behavior the test name claims ("excludes its charge from completion usage"). The SDK worker (`packages/sdk/src/worker.ts` `execute()`) calls `stepComplete(..., { output, started_pins, end_pins })` and never includes a `usage`/`budget` field, so `result.budget` is `undefined` unconditionally and this line can never fail. Either exercise a real charge-exclusion path or remove/rename the claim so the test doesn't give false coverage of the billing behavior actually implemented in the kernel/provider modules.</comment>
<file context>
@@ -461,3 +461,42 @@ setTimeout(() => {}, 5000);
+ const result = completions[0]?.[5] as { output: { instruction: string }; budget?: unknown };
+ expect(result.output.instruction).toContain('Use context');
+ expect(result.output.instruction).toContain(JSON.stringify(pack));
+ expect(result.budget).toBeUndefined();
+}, 20_000);
</file context>
…ed again Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR
…gain (#223) main is red. #221 added `memory` to STEP_COMMON_FIELDS (packages/sdk/src/step-fields.ts:25) without updating the pin that guards it: FAIL tests/verb-field-lint.test.ts > closed per-verb step fields > pins the per-verb descriptor and generates every foreign-field pair from it AssertionError: expected [ 'id', 'type', 'dependsOn', …(3) ] to deeply equal [ 'id', 'type', 'dependsOn', …(2) ] (run 34098150100, main @ 6394a2e.) The pin exists so a change to the closed vocabulary "cannot be silently undone" — it is an acknowledgement gate, not a duplicate of the source. Adding `memory` to it is the acknowledgement, and the comment records why the field is common rather than verb-specific: any step kind may declare a pack, so it generates no foreign-field pairs. This restores the gate rather than weakening it: the test still fails if the descriptor changes again without a matching edit here. Verified locally: vitest tests/verb-field-lint.test.ts 78 passed full SDK suite 684 passed, 3 skipped, 0 failed The branch CI for #221 was already failing this before it merged (run 34097610746 on feat/step-memory-220); the merge carried the red onto main. Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR Co-authored-by: kjgbot <kjgbot@agentrelay.dev> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
… on CI Explains #221 merging red and #215 merging over a failed lens: the loop checks a review-swarm marker, mergeability and a commenter allowlist, with zero CI references. kjgbot is allowlisted, so the lead's own objection cannot block. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR
…inst #221 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR
`linux-x64-artifact` fails on this branch:
FAIL tests/verb-field-lint.test.ts > closed per-verb step fields
AssertionError: expected [ 'id','type','dependsOn', …(4) ]
to deeply equal [ 'id','type','dependsOn', …(3) ]
`requirements` was added to STEP_COMMON_FIELDS (step-fields.ts:26) without
updating the pin that guards that list. The pin is an acknowledgement gate
rather than a duplicate of the source, so adding the field to it IS the
acknowledgement.
The comment records why it is common rather than verb-specific, matching the
`memory` entry directly above: any step kind may declare placement
requirements, so it generates no foreign-field pairs.
Second time this trap has fired — #221 hit it with `memory` and merged red,
breaking main for ~90 minutes. Filing a follow-up so the failure message says
what to do rather than a third lane rediscovering it.
Verified:
vitest tests/verb-field-lint.test.ts 78 passed
full SDK suite 741 passed, 3 skipped, 0 failed
Pushed to this PR's own branch rather than a new PR, so the fix lands where the
work is.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR
…227) * test: capture placement and shared workspace regression for #225 * feat: journal fixed step placement and pin declared workspaces * fix(sdk): pin `requirements` in STEP_COMMON_FIELDS `linux-x64-artifact` fails on this branch: FAIL tests/verb-field-lint.test.ts > closed per-verb step fields AssertionError: expected [ 'id','type','dependsOn', …(4) ] to deeply equal [ 'id','type','dependsOn', …(3) ] `requirements` was added to STEP_COMMON_FIELDS (step-fields.ts:26) without updating the pin that guards that list. The pin is an acknowledgement gate rather than a duplicate of the source, so adding the field to it IS the acknowledgement. The comment records why it is common rather than verb-specific, matching the `memory` entry directly above: any step kind may declare placement requirements, so it generates no foreign-field pairs. Second time this trap has fired — #221 hit it with `memory` and merged red, breaking main for ~90 minutes. Filing a follow-up so the failure message says what to do rather than a third lane rediscovering it. Verified: vitest tests/verb-field-lint.test.ts 78 passed full SDK suite 741 passed, 3 skipped, 0 failed Pushed to this PR's own branch rather than a new PR, so the fix lands where the work is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR * fix: document local pin defaults and explain routing failures * fix(placement): reject invalid replay facts and pin commit objects * test(placement): a resumed attempt keeps its pin when the worktree HEAD moves Closes the open finding on #227. The concern was that a declared local run resuming after its worktree HEAD changed would record a different source pin for the same durable route. Nothing tested it: `placement_pins` only called `starting_pins` directly (which does re-read HEAD, correctly — that is the worker's job), and `crash_resume/workspace_identity` covers aliases and canonical subtrees, not a moving HEAD. The test starts an agent step over a git worktree, lets the dispatcher take the lease so the run parks, commits again to move HEAD, then resumes under a fresh Engine — a new boot id, so the leased attempt reads as dead and the step is retried. It asserts both attempts carry the elected revision, and that a recording dispatcher was asked for pins exactly once. Mutation-verified rather than trusted green: forcing the `covered` branch off in `resolve_agent_pins` makes it fail (pin_requests 2, expected 1). Worth recording what that mutation also showed. Under it the *revision* assertion still held, because the projection reads `carried.workspace.iter().chain(worker.workspace.iter())` and takes the first match. So carried-first is what actually protects the pin; the `covered` short-circuit only avoids the needless question. The test now pins both. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR --------- Co-authored-by: kjgbot <kjgbot@agentrelay.dev> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rovider in code #221 landed the seam and the itemized per-step accounting (decision 10), not retrieval. Gate 5's remaining work is a provider over relayhistory's serialization contract plus the trajectory push. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR
… against it
RFC-0001 gate 5 says relayhistory is "consumed over its serialization contract,
not rewritten", so this reads the contract from the repo (3e7df69) rather than
proposing a design that ignores it.
Retrieval is `ai-hist pack --json`, emitting { query, entries }. The `--tokens`
budget is applied as chars = tokens * 4, an approximation and not a tokenizer,
so a provider must not report it back as exact usage — decision 10's per-step
accounting is only checkable if the number means something.
The trap worth having in writing: pack_entries calls std::process::exit(1) when
nothing matches, AFTER printing an empty entries array. Exit 1 means "no memory
matched", not "the call failed". A provider treating nonzero as an error would
report every cold-start step as a memory failure.
Also records what #221 already landed — the MemoryProvider seam and itemized
memory.injected accounting — versus what is still a stub, so nobody re-derives
that gate 5's hard part is done and its retrieval is not.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR
Two cubic findings on #240, both mine. The gate 7 row said the kernel "carries no provider names" two sentences after naming `RoutingDecision (profile, provider, fallbacks_attempted, workspace)`, and `placement.rs` declares `pub provider: String`. I meant the kernel hardcodes no provider identities and contains no ranking; what I wrote reads as false against the struct on the same row. Rewritten to say the chosen provider is the journaled fact while the kernel holds no provider identities and no ranking. The gate 5 contract note credited the memory seam to #221. `kernel/MEMORY.md` is titled "Step memory, slice 1 (#220)"; #221 is a separate PR. Corrected. Neither changes a verdict — gate 7 stays AMBER and the gate 5 contract is unchanged — but a scoreboard that contradicts itself is worse than one that is merely out of date, because the contradiction is what a reader trusts least. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR
Both blockers the history lens raised are mine, and H1 is the worse kind: a commit that announced it was correcting an attribution and reversed it instead. H1 - GATE5-MEMORY-CONTRACT.md:9 said "#220 landed the seam ... #221 is a separate PR". #220 is the ISSUE; PR #221 implemented it and closed it. The line now reads "PR #221 (issue #220) landed the seam", and explains that kernel/MEMORY.md is titled with #220 because it names the issue. The historical commit stays; the current document is corrected here, as the lens asked. H2 - SCOREBOARD.md:14 asserted "full kernel suite 205 passed / 0 failed" and described a case as "mutation-verified" while supplying neither commands nor a transcript. That is the failure class AGENTS.md rules 1-2 prohibit -- evidence is captured, not narrated -- and a pass count drifts while a transcript does not. The row now cites the run rather than restating a number, and says how the mutation check was performed without claiming the sentence is the proof. Documentation only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR
…ing on (#240) * docs(scoreboard): gate 7 is AMBER — #227 landed the darwin-arm64 suite it waited on The row said RED because "regression suite needs darwin-arm64 placement". That suite merged last night as be3c95e and is green: full kernel run is 205 passed / 0 failed on main at c9bf155, on darwin arm64. I merged the work and left the row that tracks it stale, which is the same staleness this lane spent four ticks correcting in other files. AMBER rather than GREEN, deliberately. RFC-0001 gate 7 requires the same flow YAML to run locally AND in cloud with no placement config, and only the local half is provable from this repo. Promoting it to GREEN on the strength of a passing kernel suite would repeat exactly what the gate 2 row already warns about: its bar is the real workload in production, not a test run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR * docs(gate5): record relayhistory's contract before writing a provider against it RFC-0001 gate 5 says relayhistory is "consumed over its serialization contract, not rewritten", so this reads the contract from the repo (3e7df69) rather than proposing a design that ignores it. Retrieval is `ai-hist pack --json`, emitting { query, entries }. The `--tokens` budget is applied as chars = tokens * 4, an approximation and not a tokenizer, so a provider must not report it back as exact usage — decision 10's per-step accounting is only checkable if the number means something. The trap worth having in writing: pack_entries calls std::process::exit(1) when nothing matches, AFTER printing an empty entries array. Exit 1 means "no memory matched", not "the call failed". A provider treating nonzero as an error would report every cold-start step as a memory failure. Also records what #221 already landed — the MemoryProvider seam and itemized memory.injected accounting — versus what is still a stub, so nobody re-derives that gate 5's hard part is done and its retrieval is not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR * docs: correct a self-contradicting claim and a PR attribution Two cubic findings on #240, both mine. The gate 7 row said the kernel "carries no provider names" two sentences after naming `RoutingDecision (profile, provider, fallbacks_attempted, workspace)`, and `placement.rs` declares `pub provider: String`. I meant the kernel hardcodes no provider identities and contains no ranking; what I wrote reads as false against the struct on the same row. Rewritten to say the chosen provider is the journaled fact while the kernel holds no provider identities and no ranking. The gate 5 contract note credited the memory seam to #221. `kernel/MEMORY.md` is titled "Step memory, slice 1 (#220)"; #221 is a separate PR. Corrected. Neither changes a verdict — gate 7 stays AMBER and the gate 5 contract is unchanged — but a scoreboard that contradicts itself is worse than one that is merely out of date, because the contradiction is what a reader trusts least. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR * docs(gate5): place the relayhistory provider outside the kernel boundary The structure lens raised a P1 and it is right. Item 1 read "a RelayhistoryMemoryProvider implementing the existing MemoryProvider trait", and that trait lives in kernel/relayflowd/src/memory.rs -- so the wording naturally directs the implementation into relayflowd, where a subprocess/provider integration would violate RFC-0001 section 4 and settled decision #13. That is a structural defect in the contract, not a naming quibble: a contract that reads as an instruction to put ai-hist inside the Rust kernel will eventually be followed. The item now states where the adapter lives (SDK/control-plane edge, crossing the journal protocol boundary), keeps the kernel-side MemoryProvider an injected protocol seam only, and prohibits an ai-hist dependency, a subprocess call, or relayhistory-shaped vocabulary in relayflowd. Documentation only; no product code or tests are touched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR * docs: fix a reversed PR attribution and two unsupported claims Both blockers the history lens raised are mine, and H1 is the worse kind: a commit that announced it was correcting an attribution and reversed it instead. H1 - GATE5-MEMORY-CONTRACT.md:9 said "#220 landed the seam ... #221 is a separate PR". #220 is the ISSUE; PR #221 implemented it and closed it. The line now reads "PR #221 (issue #220) landed the seam", and explains that kernel/MEMORY.md is titled with #220 because it names the issue. The historical commit stays; the current document is corrected here, as the lens asked. H2 - SCOREBOARD.md:14 asserted "full kernel suite 205 passed / 0 failed" and described a case as "mutation-verified" while supplying neither commands nor a transcript. That is the failure class AGENTS.md rules 1-2 prohibit -- evidence is captured, not narrated -- and a pass count drifts while a transcript does not. The row now cites the run rather than restating a number, and says how the mutation check was performed without claiming the sentence is the proof. Documentation only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR * docs: move the gate-5 contract out of kernel/ and shrink the gate-7 row Both remaining structure blockers, and the first is sharper than the wording fix I made an hour ago. P1 - location, not phrasing. I had added a paragraph saying the relayhistory adapter must not live in relayflowd. Directionally right, but the FILE still sat at kernel/GATE5-MEMORY-CONTRACT.md, and a document under kernel/ reads as kernel design authority no matter what its text says. It specifies ai-hist CLI syntax, JSON output, exit-code behaviour and provider traps -- SDK/control-plane knowledge that RFC-0001 section 4 and settled decision 13 keep out of the provider-neutral Rust kernel. Moved to docs/ and added an explicit ownership header saying why, so location and text now agree. P2 - the gate-7 scoreboard cell had become a second design report: Rust symbols, test names, crash behaviour, a mutation claim, commit hashes and suite counts in one table cell. Reduced 1420 chars to 382: gate state, what is journaled, and the reason it is not GREEN. The implementation narrative and mutation transcript belong in the PR #227 review artifacts, which AGENTS.md already requires to carry the literal transcript -- a row asserting "mutation-verified" was never evidence. Documentation only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtQSAcGDta5VH9xiZFT4sR --------- Co-authored-by: kjgbot <kjgbot@agentrelay.dev> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
A step can now declare
memory: {scope, query, budget}in both SDK and kernel dialects. The daemon journals the requested declaration, injected pack, provider identity, and consuming step's cost before execution. Resume and semantic retry reuse that fact without calling the provider or charging it again.The first commit,
72e03d5, adds the failing real-daemon SIGKILL test before implementation. The test kills after injection and before completion, resumes through the CLI, and asserts identical context, one injection, and exactly20input tokens /5output tokens /"0.005"total spend (one memory charge plus one worker completion).RELAYFLOW_MEMORY; dispatch includes the recorded payload; the SDK worker supplies its pack to the real CLI/wrapper. Completion usage excludes the already charged memory cost.Slice 1 only: the default provider returns a fixed synthetic pack and usage (
7input tokens /0output tokens /"0.002"). There is no retrieval, relayhistory call, or behavioural quality claim. A crash before a successful append can invoke the provider again because no injection was committed. Historical removal remains future epoch-compaction work. Details: kernel/MEMORY.md.The existing large spec/compiler files receive declaration and lowering hooks; provider logic, journal validation, and replay logic live in separate small modules. Existing tests and gate configuration remain intact.
Closes #220.
Captured verification follows; all output is literal. Final checks exited 0. The first test execution deliberately exited 101 at the unsupported declaration.
Test-first red at 72e03d5
Final kernel workspace gate
SDK parity and real wrapper execution
SDK type checks and build