Skip to content

Commit 794687f

Browse files
authored
fix(ce-compound): write Phase 1 subagent output to run artifacts to survive summary-collapse (#984)
1 parent 26e667a commit 794687f

2 files changed

Lines changed: 89 additions & 7 deletions

File tree

skills/ce-compound/SKILL.md

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,13 @@ If the user says yes, run the internal session-history step in Phase 1 (see step
9898
<critical_requirement>
9999
**The primary deliverable is ONE file - the final documentation.**
100100

101-
Phase 1 subagents return TEXT DATA to the orchestrator. They must NOT use Write, Edit, or create any files. Only the orchestrator writes files. Beyond the Phase 2 solution doc, its other writes are maintenance side effects — not additional deliverables, and creating one when absent is expected, not a violation of this rule:
101+
Phase 1 subagents write their full structured output to a per-run scratch artifact under `/tmp/compound-engineering/ce-compound/<run-id>/` and return only a compact confirmation containing the artifact path. The orchestrator Reads those artifacts back in Phase 2 assembly. This is scratch space, identical in spirit to `ce-code-review`'s per-reviewer run artifacts; it does not make the scratch files additional deliverables. **Only the orchestrator writes product files** — the final solution doc and the maintenance side effects below. Subagents must not touch `docs/`, project instruction files, or any tracked path. Beyond the Phase 2 solution doc, the orchestrator's other writes are maintenance side effects — not additional deliverables, and creating one when absent is expected, not a violation of this rule:
102102
- **`CONCEPTS.md`** — create or update in Phase 2.4 (Vocabulary Capture) when a qualifying domain term surfaces.
103103
- **A project instruction file** (AGENTS.md or CLAUDE.md) — a small edit when the Discoverability Check finds a gap.
104104

105105
Both ensure future agents can discover and ground in the knowledge store; neither makes the documentation any less the single deliverable.
106+
107+
**Why the scratch artifact (issue #956):** a subagent asked to return a long prose body as its inline response intermittently returns an executive summary instead ("Doc body complete — six sections filled. Returning above."), and the original prose is then unrecoverable from the orchestrator side. Writing to disk first means the full output always survives; the inline confirmation is just a pointer, and the orchestrator falls back to whatever the subagent did return inline only when the artifact is missing.
106108
</critical_requirement>
107109

108110
### Phase 0.5: Auto Memory Scan
@@ -128,7 +130,23 @@ If no relevant entries are found, proceed to Phase 1 without passing memory cont
128130

129131
### Phase 1: Research
130132

131-
Launch research subagents. Each returns text data to the orchestrator.
133+
Launch research subagents. Each writes its full output to a per-run scratch artifact and returns only the artifact path to the orchestrator.
134+
135+
**Run ID and run dir (before dispatching any subagent):** generate a unique run identifier and create the run directory. This scopes every Phase 1 artifact file to the same directory so the orchestrator can Read them back in Phase 2.
136+
137+
```bash
138+
RUN_ID=$(date +%Y%m%d-%H%M%S)-$(head -c4 /dev/urandom | od -An -tx1 | tr -d ' ')
139+
mkdir -p "/tmp/compound-engineering/ce-compound/$RUN_ID"
140+
```
141+
142+
Pass `{run_id}` (the resolved `$RUN_ID` value) into every Phase 1 subagent prompt. Each subagent **writes its full structured output** to its own file under `/tmp/compound-engineering/ce-compound/{run_id}/`, **confirms the write succeeded** (the file exists and is non-empty), and then **returns only a one-line confirmation containing the artifact path** — not the prose body inline. Artifact filenames by subagent:
143+
144+
- **Context Analyzer**`/tmp/compound-engineering/ce-compound/{run_id}/context.json` (frontmatter skeleton, category path, filename, track)
145+
- **Solution Extractor**`/tmp/compound-engineering/ce-compound/{run_id}/solution.md` (the full doc-body prose sections)
146+
- **Related Docs Finder**`/tmp/compound-engineering/ce-compound/{run_id}/related.json` (links, refresh candidates, overlap assessment)
147+
- **Session History** synthesis subagent (when run) → `/tmp/compound-engineering/ce-compound/{run_id}/session-history.md` (prose findings)
148+
149+
**Return the full output inline whenever the artifact write did not succeed.** This covers both cases where the orchestrator's Phase 2 inline fallback would otherwise have nothing to read: (a) `{run_id}` is empty or did not resolve (non-Claude-Code platforms where the pre-resolution failed), so there is no path to write to; and (b) `{run_id}` resolved but the write itself failed — tool permission denied, absolute-path writes unavailable, disk error, or the post-write existence check came back empty. In either case the subagent must return its complete structured output inline instead of a path, because the path would point at a file that does not exist. Return only the bare path when — and only when — the write is confirmed on disk. The artifact pattern is a reliability improvement, not a hard requirement; the orchestrator handles a missing artifact in Phase 2 by using the inline return.
132150

133151
**Dispatch order:**
134152
- Launch `Context Analyzer`, `Solution Extractor`, and `Related Docs Finder` in parallel (background)
@@ -146,13 +164,14 @@ Launch research subagents. Each returns text data to the orchestrator.
146164
- Incorporates auto memory excerpts (if provided by the orchestrator) as supplementary evidence
147165
- Reads `references/yaml-schema.md` for category mapping into `docs/solutions/`
148166
- Suggests a filename using the pattern `[sanitized-problem-slug].md` — no date suffix, even if existing files in the target directory have one; the `date:` frontmatter field is the canonical creation date
149-
- Returns: YAML frontmatter skeleton (must include `category:` field mapped from problem_type), category directory path, suggested filename, and which track applies
167+
- Writes to `context.json`: YAML frontmatter skeleton (must include `category:` field mapped from problem_type), category directory path, suggested filename, and which track applies. Returns only the artifact path.
150168
- Does not invent enum values, categories, or frontmatter fields from memory; reads the schema and mapping files above
151169
- Does not force bug-track fields onto knowledge-track learnings or vice versa
152170

153171
#### 2. **Solution Extractor**
154172
- Reads `references/schema.yaml` for track classification (bug vs knowledge)
155173
- Adapts output structure based on the problem_type track
174+
- **Writes the full doc-body prose** (all track-appropriate sections below) to `solution.md` and returns only the artifact path. This is the subagent most prone to the issue #956 summary-collapse, so its prose must land on disk rather than only in the inline return.
156175
- Incorporates auto memory excerpts (if provided by the orchestrator) as supplementary evidence -- conversation history and the verified fix take priority; if memory notes contradict the conversation, note the contradiction as cautionary context
157176

158177
**Bug track output sections:**
@@ -181,7 +200,7 @@ Launch research subagents. Each returns text data to the orchestrator.
181200
- **High**: 4-5 dimensions match — essentially the same problem solved again
182201
- **Moderate**: 2-3 dimensions match — same area but different angle or solution
183202
- **Low**: 0-1 dimensions match — related but distinct
184-
- Returns: Links, relationships, refresh candidates, and overlap assessment (score + which dimensions matched)
203+
- Writes to `related.json`: Links, relationships, refresh candidates, and overlap assessment (score + which dimensions matched). Returns only the artifact path.
185204

186205
**Search strategy (grep-first filtering for efficiency):**
187206

@@ -265,7 +284,7 @@ Launch research subagents. Each returns text data to the orchestrator.
265284
- the output schema above
266285
- the filter rule above
267286

268-
The subagent reads only the scratch paths and returns prose findings. If synthesis fails, note the failure and continue without session context.
287+
The subagent reads only the scratch paths, **writes its prose findings to `/tmp/compound-engineering/ce-compound/{run_id}/session-history.md`, and returns only that artifact path once the write is confirmed** (same #956 reliability rationale — session-history findings are long-form prose prone to summary-collapse). If `{run_id}` did not resolve or the artifact write failed, it returns the prose inline instead (per the inline-fallback rule above). If synthesis fails, note the failure and continue without session context.
269288

270289
### Phase 2: Assembly & Write
271290

@@ -275,7 +294,7 @@ Launch research subagents. Each returns text data to the orchestrator.
275294

276295
The orchestrating agent (main conversation) performs these steps:
277296

278-
1. Collect all text results from Phase 1 subagents
297+
1. **Collect Phase 1 results from the run artifacts.** For each Phase 1 subagent, `Read` its artifact file under `/tmp/compound-engineering/ce-compound/{run_id}/` (`context.json`, `solution.md`, `related.json`, and `session-history.md` when session history ran). The artifact holds the subagent's full output. **Fall back to the subagent's inline return only when its artifact file is absent or empty** (e.g., `{run_id}` did not resolve, or the subagent failed to write). The artifact is authoritative when present — this is what makes the workflow resilient to the issue #956 summary-collapse, where the inline return is only an executive summary.
279298
2. **Check the overlap assessment** from the Related Docs Finder before deciding what to write:
280299

281300
| Overlap | Action |
@@ -553,7 +572,8 @@ Knowledge track:
553572
554573
| ❌ Wrong | ✅ Correct |
555574
|----------|-----------|
556-
| Subagents write files like `context-analysis.md`, `solution-draft.md` | Subagents return text data; orchestrator writes one final file |
575+
| Subagents write product files into `docs/` or edit tracked paths | Subagents write only scratch artifacts under `/tmp/compound-engineering/ce-compound/<run-id>/` and return the path; orchestrator writes the one final doc |
576+
| Subagent returns a long prose body only as its inline response | Subagent writes full output to its run artifact; orchestrator Reads it back (inline return is fallback only) |
557577
| Research and assembly run in parallel | Research completes → then assembly runs |
558578
| Multiple files created during workflow | One solution doc written or updated: `docs/solutions/[category]/[filename].md` (plus optional maintenance writes: a `CONCEPTS.md` create/update from Phase 2.4 and a small instruction-file edit for discoverability) |
559579
| Creating a new doc when an existing doc covers the same problem | Check overlap assessment; update the existing doc when overlap is high |

tests/pipeline-review-contract.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,68 @@ describe("ce-compound frontmatter schema expansion contract", () => {
688688
})
689689
})
690690

691+
describe("ce-compound Phase 1 artifact contract", () => {
692+
// Regression guard for issue #956: Phase 1 subagents that returned long-form
693+
// prose only as their inline Agent response failed silently when the harness
694+
// collapsed the return to an executive summary. The fix mirrors ce-code-review's
695+
// proven /tmp run-artifact pattern: subagents write full output to disk and the
696+
// orchestrator Reads it back with the inline return as a fallback.
697+
test("generates a run id and run dir before dispatching Phase 1 subagents", async () => {
698+
const content = await readRepoFile("skills/ce-compound/SKILL.md")
699+
700+
// A run identifier scopes the per-subagent artifact files
701+
expect(content).toContain("RUN_ID")
702+
// Run dir under the shared cross-invocation scratch namespace
703+
expect(content).toContain("/tmp/compound-engineering/ce-compound/")
704+
expect(content).toContain('mkdir -p "/tmp/compound-engineering/ce-compound/')
705+
})
706+
707+
test("Phase 1 subagents write full output to the run-artifact path", async () => {
708+
const content = await readRepoFile("skills/ce-compound/SKILL.md")
709+
710+
const phase1 = content.slice(
711+
content.indexOf("### Phase 1: Research"),
712+
content.indexOf("### Phase 2: Assembly & Write"),
713+
)
714+
715+
// Subagents are instructed to write their full structured output to the run dir
716+
expect(phase1).toContain("/tmp/compound-engineering/ce-compound/")
717+
// ...and return a compact confirmation containing the artifact path
718+
expect(phase1.toLowerCase()).toContain("artifact path")
719+
// Inline return is required whenever the write did not succeed (not only when
720+
// {run_id} is missing) so Phase 2's fallback always has content to read.
721+
expect(phase1.toLowerCase()).toContain("write did not succeed")
722+
expect(phase1.toLowerCase()).toContain("the write itself failed")
723+
})
724+
725+
test("Phase 2 assembly reads artifacts with inline-return fallback", async () => {
726+
const content = await readRepoFile("skills/ce-compound/SKILL.md")
727+
728+
const phase2 = content.slice(
729+
content.indexOf("### Phase 2: Assembly & Write"),
730+
content.indexOf("### Phase 2.4: Vocabulary Capture"),
731+
)
732+
733+
// Orchestrator reads the per-subagent artifact files
734+
expect(phase2).toContain("/tmp/compound-engineering/ce-compound/")
735+
// Inline return is the documented fallback when the artifact is absent
736+
expect(phase2.toLowerCase()).toContain("fall back")
737+
})
738+
739+
test("no longer imposes an absolute no-write rule on Phase 1 subagents", async () => {
740+
const content = await readRepoFile("skills/ce-compound/SKILL.md")
741+
742+
// The brittle absolute prohibition is gone — only product-file writes are reserved
743+
// to the orchestrator; scratch artifacts under /tmp are now expected.
744+
expect(content).not.toContain(
745+
"They must NOT use Write, Edit, or create any files.",
746+
)
747+
expect(content).not.toContain(
748+
"Subagents return text data; orchestrator writes one final file",
749+
)
750+
})
751+
})
752+
691753
describe("learnings-researcher local prompt domain-agnostic contract", () => {
692754
test("local prompt frames as domain-agnostic not bug-focused", async () => {
693755
const agent = await readRepoFile(

0 commit comments

Comments
 (0)