refactor(agent): read the charter and the window's shrinking through the roles - #468
Conversation
The dispatch charter had four read points and none of them was on a harness module: the identity segment reached into a dispatch-layer ContextVar for the brief, and the tool registry held a module-level function for the checks. This moves the two reads that belong to a role onto that role and leaves the two that do not where they are. - prompt / stopWhen: DefaultMemory.assemble() fills task_brief and task_done_when on the TurnContext; the identity segment renders ctx.task_brief and no longer imports the dispatch layer at all. The curator's TurnContext rebuild carries the two new fields through. - checks / code: ActionModule gains judge(name, params, prior). DefaultAction answers with charter.judge(); ToolRegistry takes an injected verifier_provider and asks it once per call, ahead of the permission gate, the way it already asks the plugin gates. The in-process lane builds its own DefaultAction since it runs no harness. - tools stays on ToolRegistry.withheld_names(): it is a real gate, and CapabilitySelection is documented as a view, never a permission. - timeoutSeconds stays in the ACP client. The charter imports stay inside the calls on purpose: raven.agent.subagent pulls its manager and every backend on package import, so naming the charter at module scope makes importing the harness import 173 modules instead of 29. import-linter reads direction and cannot see this cycle. Verified with the feature off against the base commit (a full run_turn capture, byte-identical after normalising nonces and paths) and with it on (the in-process worker lane, byte-identical including both refusals; a real-model dispatch; an ACP fork carrying the charter across the process boundary). Each new link was mutation-checked. Contract pins bumped: CONTRACTS_VERSION 19, CONTRACTS_LINE_CEILING 2830. Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
The loop ran five recoveries inline -- proactive compaction, the standing image window, a context overflow, a picture refused inside a tool result, pictures refused for their size -- as ~200 lines of loop body plus five class methods of ~300 lines, all deciding the same question: how does the window get smaller so the next call fits. That is the Memory role's question, and this moves the policy half onto it while the mechanism half stays put. - Memory gains shrink(messages, pressure, state, model). WindowPressure names the five reasons a window is asked to get smaller; WindowState carries one turn's readings and retry budgets (held by the shell, since the role outlives the turn); ShrinkResult says whether the window got smaller, which on a reactive pressure is the shell's cue to retry the iteration. - The six `continue` statements, `iteration -= 1`, and the capability-table cache verdict do not move: re-entering the iteration at the top is what lets the hooks and the standing passes see a retry exactly as they saw the attempt, so the retry stays the loop's. - New package raven/agent/window (compaction, images, shrink): the pure pieces the loop and the Memory role both read, imported by both and importing neither. compaction.py moves there from the loop; the picture provenance and withdrawal notes move there from _shared.py; the five operations and their bounds move there from turn_path.py and main.py. Added to the "cargo does not import the loop shell" import-linter contract. - REASONING_EFFORT_LADDER moves to contracts/llm_provider.py: the loop's empty-response descent and the window's head summary both read it. - WindowState.image_window has no default: 0 is a real value meaning no picture stays, so a state built without saying so would withdraw them all. turn_path.py: +68 / -537. Contract pins: CONTRACTS_VERSION 28, CONTRACTS_LINE_CEILING 3200, ledger entries for the new names. Verified against the base commit with scripted providers, byte for byte after normalising nonces and paths: six recovery scenarios (each of the five moves, when broken, turns exactly its scenarios red), the whole turn with the playbook off, and the playbook-on worker lane with two check refusals. A real-model dispatch (two workers, three checks each) completes with every tool call succeeding. Full suite green. Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
Reviewed the current head after its merge from main. I covered the repository rules and canonical runtime vocabulary, the full diff, the affected charter/window callers, both feature commits and surrounding history, compatibility of the moved internal helpers and expanded harness contracts, test changes for weakening, and the cargo-versus-loop architecture boundary. The refactor preserves the prior retry/state transitions while moving policy behind Memory, and the charter prompt/check paths remain joined end to end.
Verification: the focused changed-area run passed 247 tests; the PPT ledger passed 13 tests under the all-extras environment; uv run lint-imports kept all 10 contracts. The full all-extras run reported 23,127 passed and 120 skipped, with one unrelated test_cli_theme failure caused by this runner's NO_COLOR=1; that exact test passed when rerun with NO_COLOR unset. GitHub's four Python test shards are also green on this head.
LivXue
left a comment
There was a problem hiding this comment.
Review: verified against f69e6cf7f vs merge-base 3b35e7d34
This is a high-quality refactor. turn_path.py loses 537 lines, the contract paper
is written with more care than most changes of this size get, and the verification
method in the description (per-commit byte-compare under a scripted provider, plus a
mutation check to prove the comparison is not vacuous) is the right one.
Two findings are, in my reading, blocking. Both are small to fix.
B1. The charter's checks/code enforcement went from unconditional to opt-in,
and one registry that dispatches inside a live charter scope did not get the opt-in.
This contradicts "no behaviour change intended", and it moves a security boundary in
the permissive direction. Detail inline at registry.py:407.
B2. Four of the five tests this PR adds live in a directory the default run excludes,
so the guard it wrote for its own new failure mode never executes. Detail inline at
test_subagent_charter_e2e.py:215.
Four more I would like resolved in the same change, each one or two lines:
contracts/context.py:80 (positional-slot shift on a contract-tier dataclass),
harness/__init__.py:52 (no bind_action beside bind_memory),
contracts/harness.py:13 (the paper contradicts itself), and CONTEXT.md:93
(AGENTS.md section 6: three new contract-tier terms and a new ruled package are
undefined, and the charter is renamed to "playbook", which CONTEXT.md already
defines as a different feature).
Nine further comments are non-blocking.
On the nine: five of them are pre-existing code this PR relocates verbatim
(images.py:77, images.py:90, memory.py:217, memory.py:262, shrink.py:237).
I verified them byte-identical to the base after normalising the _name -> name
de-privatisation. They are not defects introduced here; they show up as + lines
to anyone scanning added hunks, and creating the file is the one moment the fix is
free. Fixing them is your call, not a condition of merge.
One side note outside this diff: promoting REASONING_EFFORT_LADDER to contracts
did not reach rpc/methods/console.py:843, which still validates
agents.defaults.reasoningEffort against four of the ladder's six rungs, so the
console rejects xhigh/max while config files and the Anthropic transport accept
them. Worth a separate PR.
Nothing here argues against the shape of the refactor. The policy/mechanism split is
the right seam, and the shell keeping the six continue sites and the iteration
rollback is what makes it safe.
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: charter checks are bypassed by the Curator registry, and the new charter-path guards are outside default CI collection.
The new review evidence changes my prior stance. I verified that a bare ToolRegistry() under an active charter whose rule forbids the call still executed it and returned RAN:blocked; the Curator constructs that same bare registry and dispatches through it inside the turn's charter scope. I also verified that default pytest collection finds zero tests from test_subagent_charter_e2e.py, while the explicitly named file collects 30. tests/test_harness_modules.py remains green (25 passed), but does not cover the missing charter join.
The existing blocking threads already state the required fixes, so I am not duplicating inline findings or replying under another reviewer's name. My earlier clean stance is withdrawn until those open blockers are addressed.
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: the merge-only revision leaves the Curator charter-enforcement regression and excluded charter-path tests unchanged.
I reviewed the delta from f69e6cf7f0f1: this head only merges three unrelated main commits, so the PR's feature files and affected callers are unchanged. I also checked the full thread record; the blocking threads remain open with no replies, resolution, or maintainer deferral.
Reverified on this head: a charter-forbidden call through a bare ToolRegistry() still returned RAN:blocked; default pytest collection still found zero test_subagent_charter_e2e tests; and uv run --all-extras pytest tests/test_harness_modules.py -q passed 25 tests. I covered the revision delta and history, the relevant caller path, default test collection, the standing review record, and the repository's test/domain constraints. No new inline findings.
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: the unchanged charter-enforcement and default test-discovery blockers still have to be resolved.
This head only merges current main; compared with bb64cb073980, it changes the session manager and its tests but leaves the PR's own patch unchanged. I found no new defect in that merge delta, but the existing blockers still reproduce on the merged tree: a bare registry executes the charter-forbidden probe, and default collection still finds zero entries from test_subagent_charter_e2e.py.
Covered: the project rules (AGENTS.md/CLAUDE.md, CONTEXT-MAP.md, and CONTEXT.md), the full PR diff and merge delta, relevant callers and history, compatibility/API concerns, test placement and whether coverage was weakened, and the harness/role architecture. No new inline findings.
Verification: git diff --check github/main...HEAD passed; uv run pytest tests/test_harness_modules.py -q (25 passed); explicit charter integration suite (30 passed); merged session suite (37 passed); affected loop/contracts/window unit selection (192 passed). The default-collection diagnostic returned 0 charter integration entries.
Review of the charter move found two holes and four things to put right. A registry nobody handed a verifier_provider returned no opinion at all, where the module-level helper it replaced ran on every registry. Of the ten ToolRegistry construction sites, two pass a provider; the curator builds a bare one and dispatches through it inside the turn's charter_scope, so a check or code judge that refused a curator call before this series stopped refusing it. The seat now falls back to the default Action role, which is what that helper already was. The tests for the charter move lived under tests/integration, which norecursedirs keeps out of the default run and no workflow names, so four of five never executed. The two that need no external resource move into tests/test_segments.py, and the registry fallback gets its own regression in tests/test_subagent_charter.py. Also: - AssemblyContext's task_brief / task_done_when move to the end of the dataclass, where the same pair already sits on TurnContext. Inserted mid-list they shifted describe_tool two positional slots, silently rebinding the tenth argument of any out-of-tree SegmentBuilder fixture. - The Action role is admitted through a bind() that names a missing member, the way the Memory role already is. Without it a role lacking judge fails inside ToolRegistry.execute, whose except arm answers "no opinion" -- so the shape error would have read as a permissive playbook. - contracts/harness.py said the in-loop recoveries stay with the shell while MemoryModule.shrink said Memory decides them. The paper now draws the line where the code does: Memory answers what to give up, the shell owns the mechanism and the retry bounds. - CONTEXT.md defines WindowPressure, WindowState and ShrinkResult, and its cargo-contract roster names agent/window. - WindowPressure dispatch compares with == rather than is, since it is a str Enum and the documented string is equal but not identical; a zero image budget reads as a budget in the withdrawal note, matching the guard above it; a negative image window can no longer invert the standing pass. - Two comments that described something the tree does not do, and a test whose name promised the retry budgets while its body drove the one pressure that touches none. Verified: full suite 22898 passed, no failure the base branch does not also show; ruff, format and import contracts clean; the four scripted A/Bs against the branch these sit on are byte-identical. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: move the Memory-to-engine charter join test into default collection and finish the required Charter terminology and cargo-documentation fixes.
This revision fixes the bare-registry enforcement bypass, positional compatibility, Action-role admission, recovery ownership prose, and several non-blocking observations. I found no new defect in the fix commit.
Two established issues remain. Default collection still finds zero entries from test_subagent_charter_e2e.py; the new default-suite tests cover _briefed and identity rendering separately, but not the DefaultMemory.assemble -> _briefed -> engine join. Removing self._briefed(turn) therefore still leaves the default tests green. Also, CONTEXT.md now defines the window terms and adds window to the cargo roster, but the roster still omits harness, while changed runtime prose continues to use playbook for the separately defined Charter concept.
Covered: AGENTS.md/CLAUDE.md and the Runtime context documents, the full final-comparison diff and this revision's delta, affected callers and history, positional/API compatibility, test-strength changes, and harness/import architecture. Patch-risk result: revise (impact high, likelihood high, protection partial, recovery easy, confidence high).
Verification at this head: focused default-suite selection, 130 passed; explicit charter integration file, 30 passed; default collection diagnostic, 0 charter-integration entries; import-linter, 10 contracts kept; git diff --check, passed.
|
Thanks -- this found two real holes. Fourteen of the fifteen are addressed in ec24a4e; the three pre-existing ones I propose to send separately, reasoning at the end. Both blocking items, fixed. The registry early return was a genuine regression, and the curator path is what makes it bite. You are right that the The four "fix before merge", fixed.
Six of the nine non-blocking, fixed. Dispatch compares with On the curator: I kept the two lines and rewrote the comment, which now says the rebuild is partial on purpose and that the brief reaches the identity through The misnamed test is split: one asserts what the standing pass does, and a new one drives Three I propose to leave. The filed-note regex truncation, the base64 copy in Verification after the fixes: full suite 22898 passed, with no failure |
|
Blocking: the default suite still needs to exercise the Memory-to-engine charter join, and the remaining Section 6 documentation mismatches need correction. Thanks for the detailed accounting. I agree that the bare-registry bypass and the other implementation/API issues named here are fixed, and I accept the filed-note truncation, base64-copy cost, and double-tokenisation as explicitly deferred non-blocking follow-ups. Two parts do not hold yet:
Verification: |
Three follow-ups from the second review pass. The join the first round left uncovered: DefaultMemory.assemble -> _briefed -> engine. The two tests that landed in the default suite covered each half on its own, so deleting the _briefed call left them green. The new test drives a real assemble under a bound charter and asserts on what the engine was handed; removing the call turns it red and nothing else does. CONTEXT.md is the map, so the prose follows it: what a dispatch carries is a Charter, defined there, and Playbook is the separate library concept. The comments and docstrings this series added now say Charter. The cargo roster also names `harness`, which the import contract has listed since the roles landed. Verified: full suite 22899 passed, no failure main does not also show; ruff, format and import contracts clean; the new test fails on the mutant that drops the brief. Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; suggestions only, and they are marked inline.
This revision closes the remaining concerns from my side. The default suite now exercises the real DefaultMemory.assemble -> _briefed -> engine join, the cargo roster includes harness, and the affected prose consistently uses Charter rather than the distinct Playbook term. I found no new defect in the delta.
The three explicitly deferred, pre-existing follow-ups remain named here: filed-note caption truncation, the base64 length copy, and duplicate transcript tokenisation in _compact_ahead. They do not block this change. LivXue owns the existing review threads, so I have not resolved or regraded them.
Covered: repository rules and Runtime context documents, the full final-comparison diff and new delta, affected callers/history, compatibility, test strength, and harness/import architecture. Patch-risk result: merge / human_review_required (impact high, likelihood low, protection strong, recovery easy, confidence high).
Verification at this head: focused default-suite selection, 131 passed; the new join regression is collected exactly once by default; import-linter, 10 contracts kept; git diff --check, passed.
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
This head only merges the latest main; the feature patch is unchanged, there is no manual conflict-resolution delta, and the newly combined trajectory/replay paths remain compatible with the registry changes. All fifteen review threads are now resolved by their author. The already named deferred, pre-existing follow-ups remain outside this change and are not merge conditions.
Covered: repository rules and Runtime context documents, the final-comparison diff and merge delta, affected callers and history, compatibility, test strength, and harness/import architecture. No new findings. Patch-risk result: merge / human_review_required (impact high, likelihood low, protection strong, recovery easy, confidence high).
Verification at this head: focused harness/contract selection, 131 passed; trajectory/replay/regression selection, 150 passed; import-linter, 10 contracts kept; git diff --check, passed.
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
This revision only merges the latest main commit. Its incoming changes are confined to the two READMEs and tests/test_cli_onboard_commands.py, with no overlap in the feature-modified files, so the reviewed feature patch is unchanged from a0db8b641eb2.
I rechecked the repository rules and terminology documentation, the final diff and its caller/history context, compatibility and test integrity, and the architecture boundaries. Verification: git diff --check github/main...HEAD; uv run --all-extras pytest tests/test_cli_onboard_commands.py tests/test_harness_modules.py tests/test_subagent_charter.py tests/test_segments.py tests/test_contracts_two_tier_ledger.py -q (463 passed); uv run --frozen --python 3.12 lint-imports (10 contracts kept). No new findings.
…es (#469) ## Summary Follows #468 (merged as `869ad78a`), which moved the charter reads and the window's mid-turn shrinking onto the harness roles. This branch is rebased onto `main` and carries only its own three commits. A sub-agent's product logic was an `AgentHook` subclass: six phase methods over a fourteen-field context it could write, with the turn's own state kept in the context's free-form `metadata` dict. Surveyed before this change, the five bundled plugins had 71 phase methods, 42 of them doing real work, reading fourteen context fields and using six decision fields. Every one of them fits a small set of verbs. This PR makes those verbs the contract, in three commits. **1. A sub-agent writes a conduct instead of six hook phases.** New factory-loop-tier paper `contracts/agent_conduct.py`: `AgentConduct` with nine optional verbs (`intake`, `select_tools`, `advise`, `system_addendum`, `review`, `salvage`, `outbound`, `archive`, `observe`), answered against a read-only `StepView` of fourteen fields, with `Intake` and `Verdict` (Accept / Resample / End) as the answers and a `ConductFactory` because the host builds one conduct per turn. It sits beside `loop_hooks` rather than replacing it. The six phases remain the loop's *timing* contract: when the loop asks, in what order, what a refusal does. This is the *judgement* contract: given one step, what does this agent say about it. `ConductHook` seats one in the other, so a plugin that still ships a plain `AgentHook` keeps working and the composite's ordering, merging and rollback are untouched. All five bundled plugins are ported: design-engine, ppt-engine, code-flow, oncall-flow and research-flow. Research keeps its six-phase gate chain: the gates now subclass a plugin-private `Gate` and read a `GateCtx` the conduct builds from its `StepView` and its own turn-private facts, so six thousand lines of gate bodies did not move and its wrappers still see the cross-gate state they read. The turn's conduct and the system-message addendum it splices live in the turn's own `metadata` dict, not on the hook: one hook instance serves every turn a process runs, and a system turn can overlap a user one on the same chain. **2. The conducts seat on the roles.** `MemoryModule.intake`, `PlanningModule.advise`, `ActionModule.review` and `ActionModule.salvage` each take this turn's conducts, so a replaced role decides what a plugin's judgement does rather than the seat deciding it. The composition rules live in `raven/agent/harness/conducts.py`: the first non-Accept verdict wins, advice joins in order, the first salvage wins, an intake threads the text through each conduct. `run_turn` binds the harness for the turn the way it binds the model, and the seat falls back to the same rules when nothing is bound, which is how a plugin test drives a hook directly. Contract papers: the conduct paper is factory-loop tier, so the contract-tier digest and `CONTRACTS_VERSION` do not move with it. `CONTRACTS_LINE_CEILING` 3,200 to 3,420, each bump with its own review paragraph in `tests/test_kernel_budget.py`. **3. What the review found.** Eleven threads, all resolved in the third commit. The ones that changed behaviour rather than prose: `CodeFlowHook` kept its `rolls_back_iterations = False` (inheriting `True` from the new base had it hold every reply behind the draft gate instead of streaming); `StepView` now publishes deep-frozen nested values, since freezing only the outer tuple left a conduct able to write through to `ctx.messages`; a turn's conducts are collected at the role boundary, so a replaced role sees one batch of the turn's judgements rather than one call per seat; an addendum is located by its own text rather than a saved offset, which two conducts appending system text used to shift out from under each other; `Planning` and `Action` gained binders, so a replacement role missing a new member is named at assembly rather than swallowed by the composite's `except Exception`. `CONTEXT.md` carries the Agent Conduct term and the Agent Hook entry no longer describes what this PR replaces. ## Type - [ ] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [x] Refactor - [ ] Other ## Verification Gates. CI runs these at the branch tip (17 pass, 1 skipped -- the skipped row is the disabled Claude Mention workflow). Locally, at each of the three commits: ``` uv run ruff check --no-cache raven evolver agents plugins-dist tests scripts uv run ruff format --check --no-cache raven evolver agents plugins-dist tests scripts uv run lint-imports uv run pytest -q npx commitlint --from origin/main --to HEAD --config commitlint.config.cjs uv run python scripts/check_commit_messages.py origin/main..HEAD uv run python scripts/check_source_language.py origin/main..HEAD uv run python scripts/check_large_files.py origin/main..HEAD ``` Import contracts: 10 kept, 0 broken at each of the three commits. The suite is green at each, and no commit adds a failure that `main` does not already show on this machine (this checkout has the heavy extras uninstalled, so the local run carries failures `main` carries too -- CI is the authority, and it is green). The loop's own e2e files, which the default selection leaves out, were run too (charter, playbook, truncation, direct chat, fallback chain, ACP stdio): no new failure. Behaviour preservation. The oracle for this change is the hook seam itself: all five plugins driven through every phase of a multi-iteration turn under the same scripted provider, recording each phase's decision and the observers it files. Outputs normalised for nonces and temp paths, then byte-compared. Re-run after the review fixes at the branch tip `81e6f423` (all three commits), against `main` at `c65c2059`: | scenario | bytes | | --- | --- | | five-plugin hook seam | 23,840 == 23,840 | | a whole turn with no playbook (host turn, spawned sub-agent, tool calls and refusals) | 12,821 == 12,821 | | six window-shrink scenarios | 133,371 == 133,371 | These cover the tip, not each commit separately. The seam comparison is the one that matters most for the third commit: its first fix changed `rolls_back_iterations` for code-flow, which decides whether replies stream or are held, and the seam oracle records the decision at every phase, so an unintended change there shows as a diff rather than as silence. A unit test proves the seat is a real decision point rather than a pass-through: with a lenient Action role bound, a conduct's Resample is not applied, and Planning's advice and Memory's intake are what the loop receives. Real model, Sonnet 4.5 through OpenRouter, at the branch tip: - playbook on, in-process lane: two workers, each receives the charter (3 checks), writes its file, finishes; 26 recorded model calls - playbook on, ACP fork lane: the charter prompt crosses the process boundary (the fork answers ZEBRA where the task text says PONG, so the brief reached the forked model through Memory), and a charter check blocks `write_file` to `notes/hi.txt` against a `pathPrefix: out/` rule - Raven-Research, a fork running the full gate chain: `plain-first: escalated_tool`, then `sufficiency-gate: released on the pages at iteration 4 after 2 searches / 2 pages` - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk No user-visible behaviour change is intended, and the hook-seam comparison is byte-identical on every phase it covers. For plugin authors: the six-phase `AgentHook` is unchanged and a plugin shipping one keeps working. `ConductHook` is an adapter that seats a conduct in that same chain. For anyone replacing a harness role: the role protocols gain `intake`, `advise`, `review` and `salvage`, so a replacement written against the old protocol needs them. Rollback is a revert of the merge commit. Nothing persisted on disk changes shape. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: yao pengfei <yaopengfei@shanda.com> Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
Summary
The agent loop already names four strategy roles (Memory, Planning, Capability, Action), but two judgements a turn makes still lived in the shell around them. This moves both onto the roles, in two dependent commits, with no behaviour change intended.
1. The charter reads through Memory and Action. A dispatched sub-agent's playbook (
_meta["raven.playbook"]) was read from four places inside the loop and the tool registry.promptandstopWhenare now read byDefaultMemory.assemble(), which fillstask_briefandtask_done_whenon theTurnContextthe identity segment renders.checksandcodeare read byDefaultAction.judge(), whichToolRegistry.executeasks once per proposed call, the way it already asks the permission gate.toolsstays inwithheld_names()and the registry still decides what a refusal does, so a replaced role withholds nothing it could not already withhold.2. Mid-turn window shrinking seats on Memory. The five recoveries the loop ran inline go through one method,
MemoryModule.shrink(messages, pressure, state, model), withWindowPressure,WindowStateandShrinkResultas the carriers:The retry mechanism stays in the shell. The six
continuesites and the iteration rollback did not move, so a hook sees a retried call exactly as it saw the first.WindowStateis a per-turn object the shell owns and hands to each call, because the role outlives the turn;image_windowhas no default, since 0 is a real value meaning "no picture stays".The pure pieces both sides need move to a new package,
raven/agent/window/, which the import contracts treat as cargo that may not import the loop shell.REASONING_EFFORT_LADDERmoves tocontracts/llm_provider.pybecause the loop's empty-response descent and the window's head summary both read it and neither package may import the other.Contract papers:
CONTRACTS_VERSION26 to 28,CONTRACTS_LINE_CEILING3,040 to 3,200, each bump with its own review paragraph intests/test_kernel_budget.py.A follow-up PR carries the third step, which gives a sub-agent nine verbs in place of the six hook phases. It is kept separate because it touches every bundled plugin.
Type
Verification
Gates, run at each of the two commits:
Import contracts: 10 kept, 0 broken at both commits. The suite is green at both; the failures this machine shows are the same ones it shows on an unmodified
main(missing browser and optional packages), and neither commit adds one. The loop's own e2e files, which the default selection leaves out, were run too (charter, playbook, truncation, direct chat, fallback chain, ACP stdio): no failure thatmaindoes not also show.Behaviour preservation. Each commit was run against a worktree of its own parent under the same scripted provider, outputs normalised for nonces and temp paths, then byte-compared. Four scenarios, eight comparisons, all identical:
Mutation check on the second commit: breaking each of the five moves inside
DefaultMemory.shrinkturns exactly its own scenario red, so the comparison is not vacuous.Real model, Sonnet 4.5 through OpenRouter, at both commits:
playbook on, in-process lane: both workers receive the charter, write their files and finish
playbook on, ACP fork lane: the charter prompt crosses the process boundary, and a charter check blocks a
write_fileoutside the fenceRelevant tests pass locally
Relevant lint / type checks pass locally
User-facing docs or screenshots are updated when needed
Risk
No user-visible behaviour change is intended, and the comparisons above are byte-identical on every path they cover.
For anyone replacing a harness role:
MemoryModulegainsshrink, so a replacement written against the old protocol needs it.DefaultMemory.bind()names the missing member at assembly rather than failing inside a turn.Rollback is a revert of the merge commit. Nothing persisted on disk changes shape.
Related Issues
N/A