Skip to content

fix(subagents): route deliverables through the artifact channel - #176

Merged
jkyberneees merged 4 commits into
mainfrom
fix/subagent-artifact-delivery
Sep 2, 2026
Merged

fix(subagents): route deliverables through the artifact channel#176
jkyberneees merged 4 commits into
mainfrom
fix/subagent-artifact-delivery

Conversation

@jkyberneees

Copy link
Copy Markdown
Contributor

Closes the sub-agent response truncation workstream (SUBAGENT_ARTIFACT_DELIVERY_PLAN.md v2, adversarially reviewed by 3 sequential sub-agents).

Root cause

The artifact protocol (M1-M3) worked, but nothing steered the LLMs into it: the parent tool description never mentioned the 2048-rune headline cap, the artifact channel, or artifact_read; the child-side note was a weak buried sentence; cut headlines were invisible (bare ellipsis). So children answered in headline text, parents synthesized from the cut believing it was everything.

Changes (plan proposals A+A2, B+B2, C, D1, E, F)

  • A/A2 — parent contract: delegate_tasks Description() now documents the two-channel result-delivery model (headline ~2000 chars + artifacts, artifact_read(id) loop) with a copy-pastable guidance template; guidance schema description carries the same nudge.
  • B/B2 — child contract: childArtifactNote rewritten (cap stated as a loss fact, small-task exemption, flat-files-only with the nested-dir consequence, headline shape = status + artifact names + key decisions); subagentIdentity closing line aligned.
  • C — visible truncation: summary_truncated / summary_runes envelope fields (omitempty — old-parent/new-child and new-parent/old-child both degrade clean) + render marker with the artifact_read hint emitted only when the tool exists in-process (mid-tree parents don't have it).
  • D1 — id aliasing + provenance: first occurrence keeps the plain id; duplicates register as .t probe-increment aliases that never evict live entries (real stems like report.t2 can collide — artifactIDRe allows dots); artifact lines and artifact_read output carry the owning task index and the effective id.
  • F — bounds: 128 KiB per-call inline budget (largest-first, metadata-only degradation) + per-run registry floor so a collating call's artifacts cannot be evicted mid-run.
  • E: .odek-artifacts/ staging gitignored.

Verification

  • RED-first: cmd/odek/subagent_delivery_test.go (14 tests: description/schema/note/identity pins, truncateWithLen, marker + conditional hint, omitempty wire compat, aliasing/no-eviction, provenance, inline budget, registry floor); dup-id pin flipped to first-wins.
  • go vet clean; scoped -race on subagent+artifact suites green; full cmd/odek package green (73s).
  • docs/SUBAGENTS.md synced (envelope fields, two-channel contract, aliasing/provenance/budget).

Sub-agent responses were commonly truncated: the parent tool description
did not document the two-channel result model, the child-side artifact
note was too weak to act on, and cut headlines were invisible. The
artifact protocol existed (M1-M3) but nothing steered the LLMs into it.

- Description(): two-channel result-delivery contract (headline ~2000
  chars + artifacts, artifact_read loop) + guidance schema nudge (A/A2)
- childArtifactNote(): loss-fact framing, small-task exemption, flat
  files only with stated consequence, headline shape (B); identity
  closing line aligned (B2)
- visible truncation marker: summary_truncated/summary_runes (omitempty,
  wire-compatible) + conditional artifact_read hint gated on
  artifactReadEnabled (C)
- artifact ids: first-wins with probe-increment <id>.t<N> aliases that
  never evict live entries; task provenance on artifact lines and
  artifact_read output (D1)
- 128 KiB per-call inline budget (largest-first) + per-run registry
  floor so a collating call's artifacts stay resolvable (F)
- gitignore .odek-artifacts/ staging (E)

RED-first: subagent_delivery_test.go (14 tests) + flipped dup-id pin.
go vet clean; scoped -race and full cmd/odek package green. Docs:
docs/SUBAGENTS.md envelope + artifact sections synced.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 2, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
odek 35de194 Commit Preview URL

Branch Preview URL
Sep 02 2026, 08:27 PM

The ui-js suite failed 2/3 CI runs today (and once on main): four tests
slept fixed real-time windows against a 1s setInterval sweep and a 1.5s
friction-gate setTimeout. Under runner load the timers starve past the
window and the assertions run too early.

Replace all four sleeps with a waitFor(cond) poll (20ms cadence, 15s
deadline). The wrong-word friction test now first proves the gate passed
via the correct word, so its negative assertion is never vacuous.
Test-only; no product code touched.
…rig hygiene, stem collisions

Judge pass 1 on the branch diff validated 4 findings:
- P1: the collate loop rendered before registering, so an aliased
  duplicate advertised the plain id and artifact_read(plain) resolved to
  the WRONG task's bytes. Register first, render second; notes still
  append after the artifacts block.
- P2: byOrig entries now die with their registry entry on eviction
  (stale alias ids previously rendered as dead ends; long-lived serve
  leaked one map entry per registration).
- P3: same-task stem collisions (report.md + report.txt both report id
  'report') now get occurrence-indexed byOrig slots; the render consumes
  them in envelope order, matching registration order.
- P4: the provenance test now uses production order and asserts the
  effective id positively (- dup.t2 () and the plain id negatively
  (- dup () — previously vacuous. Adds
  TestRegisterArtifact_SameTaskStemCollision and
  TestEvictionCleansEffectiveIDMap.
…lision

Judge pass 2 caught a hazard in the occurrence fix: byOrig/occ were keyed
by (origID, taskIdx), but every delegate_tasks call has a task 0 — a
later call's duplicate id would register at occ>=1 while its render
looked up occ=0 and resolved to an EARLIER call's plain id (silent wrong
content, same class as the P1 finding).

The registry key is now (origID, taskID, occ) with taskID the per-call
unique id minted at spawn; the collate loop threads it into registration
and rendering. Adds TestEffectiveIDs_NoCrossCallCollision pinning both
calls resolving to their own ids.
@jkyberneees
jkyberneees merged commit 7322db0 into main Sep 2, 2026
10 checks passed
@jkyberneees
jkyberneees deleted the fix/subagent-artifact-delivery branch September 2, 2026 20:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant