Repository navigation
feat(config): let subagent sessions opt into compression, memory tools and the dreamer - #833
Neko-Yukari wants to merge 7 commits into
Conversation
…d the dreamer A child session gets one stored fact, session_meta.is_subagent, and that single flag decides three unrelated things: whether the session takes the reduced path, whether ctx_memory and ctx_note are visible, and whether the dreamer retrospective may read it. A long-running child (an explorer walking a repository, a reviewer reading fifteen files) wants the first and not the other two. Keep the stored flag as the parent-link record and derive the three answers from a new subagent config block: - compress: getOrCreateSessionMeta presents a child as primary, so every reduced-path gate (compartments, historian, full guidance, the flag handed to the Rust module) follows without touching any of the 25 call sites. - memory_write: decided from the stored column through storedSubagentMode, because the tools must stay hidden when compression is on. - dream: the retrospective session query drops its subagent filter. Defaults reproduce today's behaviour exactly.
|
Harness coverage advisory: these conventional twins were not touched:
Cover every shipped harness or explain why the surface does not exist there. See CONTRIBUTING.md. This is advisory only; no draft state is changed. |
|
Waiting for This PR needs a linked issue with the |
There was a problem hiding this comment.
All reported issues were addressed across 9 files
Reply to a comment to ask cubic a question or push back. It learns from your replies.
View guided diff | Re-trigger cubic
…/report gates on the stored fact - OC2 boot installs the policy: setSubagentPolicy's only caller was the OC1 lane, so compress/memory_write were dead on OpenCode 2. - drop subagent.dream. No harness ever acted on it (OC1 hardcodes the filter, OC2 loses children in rootSessionActivity, Pi has no filter at all); the input semantics belong to cortexkit#832. - strip subagent.* from project config: it is user-tier, and a project value would also leak process-wide through the policy. - the memory-tool gates, channel-2 delivery, /ctx-wrapup, note-nudge accumulation and /ctx-status now read the stored is_subagent, so subagent.compress cannot hand a child a memory write, a follow-up turn after the run ended, or a wrapup. resolveStoredSubagentMode() creates the row and re-reads the raw column, which also closes the first-pass window in hideSubagentTools. - Pi: drop the no-op setSubagentPolicy call; child processes never register the extension, so the value could not reach them.
Covers the lane that had no caller: setup installs the project's switches, a disabled project leaves another project's policy alone, and the documented process-wide limitation (last enabled boot wins) is asserted rather than implied.
There was a problem hiding this comment.
All reported issues were addressed across 17 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
Converting it to 'not a subagent' would let /ctx-wrapup write into a child session when the row cannot be read; the pre-existing gate let the getOrCreateSessionMeta error escape, and that is the behaviour to keep.
The replay removed the primary-only tools whenever the stored flag said child, so a storage-busy turn served a reduced tool map even with subagent.memory_write on. Read the same policy the managed pass reads (in memory, still no write).
OC1's task tool turns experimental.primary_tools into child-session permission denies, and the list carried ctx_memory/ctx_note unconditionally, so the host refused the call before this package's own gate was consulted. With subagent.memory_write on, the two entries are no longer added; user-supplied entries stay.
Refs #832 (draft: waiting for
design-approved)What this does
Adds a
subagentconfig block with two independent switches:{ "subagent": { "compress": false, // children take the primary path: compartments, historian, full guidance, mural, caveman, Rust pass inputs "memory_write": false // children may call ctx_memory / ctx_note } }Both default to today's behaviour, so an unchanged config changes nothing.
The reason it is two switches and not one: a child session carries one stored fact (
session_meta.is_subagent), and that fact answers two unrelated questions. One is what the session is shown: compartments, the historian, guidance, mural, caveman, the Rust pass inputs, the system-prompt identity, the todo snapshot. The other is what it may do: write the shared memory store. Wanting the first without the second is the case that motivated the issue.How it is implemented
session_meta.is_subagentstays exactly what it is today, the record that this session has a parent, so the dashboard,storedSubagentMode, the RPC handlers and the OC2 parent fallback keep working on the truth.plugin/subagent-policy.tsholds the resolved policy (set once per plugin instance from the resolved config, next tosetKeepSubagents) and the two derived answers.getOrCreateSessionMetaandreadSessionMetapass their result througheffectiveSubagent. That is where the shown gates are decided: 25 non-test call sites read.isSubagentoff those two functions, covering compartment injection, guidance, historian eligibility,fullFeatureMode, mural, caveman, the todo snapshot and the flag handed to the Rust module. None of them needed a change.storedSubagentMode/resolveStoredSubagentMode:subagent-tool-policy, socompressnever hands a childctx_memory;/ctx-wrapup, so a compressed child stays ineligible;/ctx-status, so the status surfaces agree with the RPC handlers.resolveStoredSubagentModecreates the session row and re-reads the raw column. That also closes the first-pass window inhideSubagentTools: on a fresh child the row is written during the same pass, and the projectiongetOrCreateSessionMetareturns still says primary.subagent.*is user-tier only:stripUnsafeProjectConfigFieldsdrops it from project config with a warning, the waydebug_rpcandpi.subagent_extensionsare dropped. A project value would also leak process-wide, because the policy lives in the module.registerContext, which now installs the policy right after itsenabledgate. Before this, the only caller ofsetSubagentPolicywas the OpenCode 1server, so both switches were dead on OpenCode 2, the harness where this config matters most.pi-plugin/src/index.ts:1170-1175for a spawned child,:1182-1187for an in-process child) return beforestartPiMagicContextRuntime, so a Pi child never registers the extension and the value cannot reach it. A Pi child has no Magic Context tools today.Harness coverage (required)
getOrCreateSessionMeta, and the write gate isguardSubagentToolswith the same policy check.registerContextinstalls the policy, pinned byv2/hooks/subagent-policy-wiring.test.ts(it fails 3/3 if that call is removed), and the tool-map hiding plus/ctx-statusread the stored fact.rust-mode-transform.tspassesis_subagent: sessionMeta.isSubagentinto the module. Two production sites read it inside Rust,crates/mc-module/src/lib.rs:10736(the historian does not fire for a child) and:10886(no emergency refold for a child), and both match whatcompresspromises: the historian gate follows the switch.Known limitation
The policy is process-global, like
keep_subagents. OpenCode Desktop runs one plugin instance per open project in the same process, so the last enabled project to boot owns it, and changing the block needs a restart.v2/hooks/subagent-policy-wiring.test.tsasserts that ordering, so it stays visible instead of surprising someone later. Making it per-instance means threading the policy through every gate, which is a repository-wide change and not part of this PR.Verification
bun run typecheckinpackages/pluginand inpackages/pi-plugin: both exit 0.dream-timer.test.ts's "newest matching root wins past the first page", which takes about 20s against a 5s timeout on this Windows machine and fails identically on the previous headf9ced64c6.channel2-subagent.test.tsrun on its own: 1 pass, 1 fail. Its source-text guard slices with"\n}\n"while this working tree is CRLF, so it captures the whole file and trips on an unrelatedisSubagent. It also fails identically onf9ced64c6. It is not part of the 14-suite run above.rpc-handlers.ts:7) predates this branch.setSubagentPolicycall is removed), stored-fact gates ignoringcompress, first-pass tool visibility on a fresh child withcompress: true, and project-tier stripping ofsubagent.*.Note on CI:
ci.ymlruns on pushes tomaster/main/train/**, on the nightly schedule and on manual dispatch, not on pull requests. The PR-time workflows aredesign-gate,harness-coverage,review-findings, the Linux smoke run and Socket. So the full unit and e2e matrix cannot run on this branch until it is on a train branch; the numbers above are from running the suites locally.Not in this PR
dream, which the first commits carried and this one removes. No harness acted on it: OpenCode 1 hardcodes the filter (dreamer/retrospective-raw-provider.ts:142), OpenCode 2 lost children torootSessionActivity'sWHERE parent_id IS NULL, and Pi has no filter at all. The comment above the OpenCode 1 filter also records why children were excluded in the first place: a child has no user messages, agent-written task prompts trip the frustration regex, and delegation-heavy periods drown the scan budget at 30:1. What counts as a child's retrospective input is a semantics question for Subagent sessions should be able to use compartments, memory tools and the dreamer, with three separate switches #832, and a boolean cannot answer it.subagent_agents), mentioned in the issue. It needs the agent identity atgetOrCreateSessionMetatime, which would mean a host-DB read on a hot path; better as a follow-up with its own design.

Confidence Score: 5/5
The reviewed changes appear safe to merge; the previous memory-tool blocker is fixed.Summary
Adds independent, user-only switches for child-session compression and memory writes.
Reviews (4) · Last reviewed commit: "fix(subagent): let memory_write reach th..." · Reviewed by Greptile