Skip to content

fix(runtime)!: write every agent task's prompt input index from the live task plan (#1234) - #1285

Merged
aviggiano merged 5 commits into
unstablefrom
refactor/1234-live-prompt-input-index
Oct 6, 2026
Merged

aviggiano merged 5 commits into
unstablefrom
refactor/1234-live-prompt-input-index

Conversation

@aviggiano

@aviggiano aviggiano commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #1234. @mrthankyou reproduced and diagnosed this bug in #1253 (closed), and the commits credit him as co-author.

Problem

A task that waits on a dynamic group has its prompt rendered at runtime. On the default topology these are dedupe-findings, aggregate-test-files and final-report. They were compiled without promptArtifactAuthoritySelectors, so materializePromptArtifactAuthority returned early. The .ultrafuzz/authorities/<attempt-id>.json file their prompts name was never written. The selectors came only from plan-time renders, and the index was rebuilt from the launch-time task manifest (controls/tasks.json), which does not contain the children a dynamic group generates.

Change

This implements the scope in the maintainer's latest comment on #1234.

  1. materializePromptArtifactAuthority builds the index from the in-memory task plan after dynamic expansion (semanticArtifactTaskDeclarations, through the new declaredAncestorOutputs), keeping only the admitted dependency directories. It no longer reads task.taskManifestPath.

  2. The index is written before every attempt of every agent task. renderAncestorArtifactPathAuthority lists its paths in the prompt. The contract and path placeholders render a sentence that tells the agent which producers[].outputs entries to use.

  3. Deleted:

    • assertPromptArtifactAuthorityUnchanged, its call sites, and promptArtifactAuthoritySnapshotsByTask
    • the sealed-selector equality check and the sealed-closure throw in prompt-artifact-authority.ts
    • the promptArtifactAuthoritySelectors manifest field and promptArtifactAuthoritySelectorsFor
    • the addedAuthoritySelectors refresh refusal in prompt-refresh.ts

    The agent-output gates (the semantic gates and the aggregation/report omission checks) are unchanged.

  4. The stock prompts no longer call the index a "sealed JSON authority" or a "sealed selector". final-report.md no longer says the index leaves out unrelated patches, plans and bundles. It now says the index lists every admitted ancestor output and the agent should use only the named handoff paths.

Where this departs from the #1234 scope

  • The index drops its selectors field. The issue says "the file path and JSON shape stay the same". The file path, the producers[].outputs structure and the validator are unchanged, but the required selectors field is gone. After item 3, a runtime-rendered prompt has no selector data to put in it, and filling it in anyway would mean making up state. schema_version stays ultrafuzz.prompt-artifact-authority.v1. Nothing in the runtime reads the file back (parsePromptArtifactAuthorityBytes is deleted), and the file is rewritten before every attempt, so I kept v1 rather than bump to v2. Maintainer: please confirm v1, or ask for a v2 bump.
  • Cleanup that follows from the spec:
    • selectorId (and its SHA-256 self-check and promptArtifactAuthorityPathSelectorId) is removed from the run plan's ancestor_artifact_path_authority reference. Its only readers were the deleted manifest selectors and the refresh refusal. Run-plan v4 has not been released.
    • The execution snapshot no longer contains a copy of the compiled task manifest as controls/tasks.json. Its only reader was the deleted index read-back.

Breaking changes

  • The index no longer has a selectors field. It lists every declared output of the task's admitted ancestors.
  • plan.json path-authority references drop selectorId.
  • smithers/tasks.json (still ultrafuzz.smithers.workflow.v4) drops promptArtifactAuthoritySelectors.
  • resume no longer refuses an edited prompt that names a new authority.
  • Some runs launched before this release carry promptArtifactAuthoritySelectors and selectorId, which this release's readers reject: those where a static prompt named an authority, which includes every default topology run. Finish such a run with the release that launched it. The CHANGELOG has the full entry.

Tests

  • New test in dynamic-lifecycle.test.ts: "a deferred join run from the execution snapshot gets a prompt input index listing its ancestors' outputs". It runs the execution snapshot's workflow in process, prepares a deferred join, runs its agent, and checks that the written index lists the generated child's findings.json and the planner's plan.json. It fails on unstable, where the file is never written.
  • Rewritten tests:
    • generated-workflow-verifier.test.ts: the index is rewritten for every attempt.
    • prompt-artifact-authority.test.ts: derivation from the live plan, ordering, rejecting a directory outside the ancestor closure, and validation.
    • runtime.test.ts: a refreshed prompt that names a new authority is applied.
  • Prompt tests pin the reworded aggregation sentence.
  • format:check, lint, lint:strict:ci, docs:check, typecheck and knip pass.

🤖 Generated with Claude Code

RetriggerConfidence Score: 3/5

The PR should not merge until an agent’s edits to the task-local index are detected before another generation or an attempt is accepted.

Fix All in Claude CodeFindings

  1. P1 Security Index edits go undetected ▶
  2. P2 Unused index can block tasks ▶
  3. P2 Path lists inflate prompts ▶
  4. P2 Version hides incompatible index ▶
Fix with agent prompt
### Issue 1
packages/runtime/src/templates/smithers/workflows/workflow.tsx:2560
During a model attempt, the agent can replace its task-local input index, but the wrapper accepts the result without checking the index again. A schema-correction generation in the same attempt does not rewrite it, so that generation can read forged ancestor entries. Restore an integrity check between generations and before accepting the attempt.

**How this was verified:** The index is written in the agent’s workspace, and the post-generation path checks dependency artifacts but not the index.

### Issue 2
packages/runtime/src/templates/smithers/workflows/workflow.tsx:965-967
The runtime now serializes an index before every agent attempt, even when the prompt does not use one. If a valid fan-in has enough declared ancestor outputs to exceed the index’s 32 MiB limit, serialization fails before model work starts. That limit can stop an otherwise valid task whose prompt never needs the index.

### Issue 3
packages/prompts/src/render.ts:1049-1056
Exact-path authorities now put every requested path directly into the prompt. For a large path group, this uses substantial model context where the previous rendering stayed constant-size. Bound the list for prompt use or convey it compactly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

### Issue 4
packages/runtime/src/prompt-artifact-authority.ts:30
The document still identifies itself as `ultrafuzz.prompt-artifact-authority.v1`, but it no longer has the formerly required `selectors` field and now rejects documents containing it. A consumer using that version cannot distinguish the incompatible shapes. Give the new shape a new schema version, even though current runtime code does not read the file back.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The PR builds each agent’s prompt input index from the live, dynamically expanded task plan, writes it on attempts, and changes prompts and persisted references to use an unfiltered output index.

  • Deferred joins can now discover generated ancestors’ declared outputs.
  • The changed index lifecycle leaves in-attempt edits unchecked, while unconditional indexing and inline path lists introduce avoidable limits and prompt cost.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Plan[Live expanded task plan] --> Closure[Declared ancestor outputs]
  Admission[Verified dependency admission] --> Filter[Admitted producers]
  Closure --> Filter
  Filter --> Index[Task-local JSON index]
  Index --> Agent[Agent and correction generations]
  Agent --> Result[Accepted attempt]
Loading

Reviews (1) · Last reviewed commit: "Merge branch 'unstable' into refactor/12..."

aviggiano and others added 5 commits October 6, 2026 00:10
…ive task plan (#1234)

- materializePromptArtifactAuthority builds .ultrafuzz/authorities/<attempt>.json from
  the in-memory, post-expansion task plan (semanticArtifactTaskDeclarations), filtered
  to the admitted dependency directories, for every agent task; it no longer reads the
  execution snapshot's controls/tasks.json, so a deferred join (dedupe-findings,
  aggregate-test-files, final-report) gets the file its prompt names
- the index lists every declared output of each admitted ancestor; the document drops
  `selectors`, and ancestor_artifact_path_authority lists its paths in the prompt
- delete assertPromptArtifactAuthorityUnchanged, its call sites, the snapshot map, the
  sealed-selector equality check and the sealed-closure throw
- add declaredAncestorOutputs beside declaredAncestorOutputsByContract
- dynamic-lifecycle test: the execution snapshot's workflow, rendered in process,
  prepares a deferred join and runs its agent, and the index lists the generated
  child's findings.json and the planner's plan.json; the in-process render's inert
  agent gets a no-op generate

Co-authored-by: mrthankyou <52643283+mrthankyou@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tors (#1234)

- drop promptArtifactAuthoritySelectors from the task manifest schema (in place,
  ultrafuzz.smithers.workflow.v4) and the workflow task specs, with
  promptArtifactAuthoritySelectorsFor and the selector limits
- drop the task specs' sourceTaskManifestPath, which only the index read
- resume no longer refuses an edited prompt that names an authority its task was not
  compiled with
- CHANGELOG: breaking entry for #1234

Co-authored-by: mrthankyou <52643283+mrthankyou@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…hot task manifest (#1234)

- remove `selectorId` from the run plan's `ancestor_artifact_path_authority`
  reference (ultrafuzz.run-plan.v4, unreleased) and from the renderer, with the
  plan's self-hash check and promptArtifactAuthorityPathSelectorId; its readers
  (the manifest selectors and the refresh refusal) are gone
- stop copying the compiled task manifest into the execution snapshot as
  controls/tasks.json; its only reader was the deleted authority read-back
- delete parsePromptArtifactAuthorityBytes, which only tests called; tests check
  the written index with assertValidPromptArtifactAuthority
- reword the comment, the unknown-contract diagnostic and the index errors that
  still described a sealed selector or selected outputs
- the deferred-join test reads the launch task manifest from
  controls/runtime-base-tasks.json; reflow the in-process workflow doc comment
- CHANGELOG: the #1234 entry notes the dropped `selectorId`

Co-authored-by: mrthankyou <52643283+mrthankyou@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- stock prompts no longer call the index a sealed JSON authority or a sealed
  selector; final-report says the index lists every admitted ancestor output
  and that only the named report handoff paths are to be used
- CHANGELOG: trim the #1234 entry and name the runs it affects (static prompts
  naming an authority, so every default-topology run) and their plan.json
  selectorId
- tests: drop the dead taskManifestPath alternative from the base agent check,
  rename the prompt-structure authority test, and pin the reworded aggregation
  sentence

Co-authored-by: mrthankyou <52643283+mrthankyou@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Resolve conflicts: keep this branch's removal of the sealed-selector test, and both CHANGELOG entries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@@ -2645,7 +2558,6 @@ function artifactAwareAgent(
Reflect.deleteProperty(unstructuredArgs, "ultrafuzzTaskRuntime");
const result = await executionAgent.generate(unstructuredArgs);
assertDependencyArtifactAdmissionCurrent(task);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Index edits go undetected During a model attempt, the agent can replace its task-local input index, but the wrapper accepts the result without checking the index again. A schema-correction generation in the same attempt does not rewrite it, so that generation can read forged ancestor entries. Restore an integrity check between generations and before accepting the attempt.

How this was verified: The index is written in the agent’s workspace, and the post-generation path checks dependency artifacts but not the index.

Knowledge Base Used: Runtime orchestration

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/runtime/src/templates/smithers/workflows/workflow.tsx
Line: 2560

Comment:
**Index edits go undetected** During a model attempt, the agent can replace its task-local input index, but the wrapper accepts the result without checking the index again. A schema-correction generation in the same attempt does not rewrite it, so that generation can read forged ancestor entries. Restore an integrity check between generations and before accepting the attempt.

**How this was verified:** The index is written in the agent’s workspace, and the post-generation path checks dependency artifacts but not the index.

**Knowledge Base Used:** [Runtime orchestration](https://app.greptile.com/monad-foudnation/-/custom-context/knowledge-base/monad-developers/ultrafuzz/-/docs/runtime-orchestration.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code

Comment on lines 965 to +967
const workspaceRoot = realpathSync(task.workspacePath);
const authorityPath = prepareTaskLocalAuthorityPath(workspaceRoot, promptArtifactAuthorityRelativePath(task));
writeFileDurable(authorityPath, expected);
const captured = readBoundedRegularArtifactSnapshot(
workspaceRoot,
authorityPath,
`artifact-contract failure: prompt artifact authority is unavailable ${task.attemptId}`,
MAX_PROMPT_ARTIFACT_AUTHORITY_BYTES,
true
);
parsePromptArtifactAuthorityBytes(captured.bytes);
if (!captured.bytes.equals(expected)) {
throw new Error(
`artifact-contract failure: prompt artifact authority changed while materialized ${task.attemptId}`
);
}
promptArtifactAuthoritySnapshotsByTask.set(
task.attemptId,
Object.freeze({
path: captured.path,
bytes: Buffer.from(captured.bytes),
identity: captured.identity
})
);
}

function assertPromptArtifactAuthorityUnchanged(task: (typeof taskSpecs)[number]): void {
if (promptArtifactAuthoritySelectors(task).length === 0) return;
const expected = promptArtifactAuthoritySnapshotsByTask.get(task.attemptId);
if (expected === undefined) {
throw new Error(`artifact-contract failure: prompt artifact authority was not prepared ${task.attemptId}`);
}
const workspaceRoot = realpathSync(task.workspacePath);
const authorityPath = promptArtifactAuthorityPath(task, workspaceRoot);
const captured = readBoundedRegularArtifactSnapshot(
workspaceRoot,
authorityPath,
`artifact-contract failure: prompt artifact authority is unavailable ${task.attemptId}`,
MAX_PROMPT_ARTIFACT_AUTHORITY_BYTES,
true
);
parsePromptArtifactAuthorityBytes(captured.bytes);
if (
captured.path !== expected.path ||
!sameImmutableFileIdentity(captured.identity, expected.identity) ||
!captured.bytes.equals(expected.bytes)
) {
throw new Error(`artifact-contract failure: prompt artifact authority was modified ${task.attemptId}`);
}
writeFileDurable(authorityPath, serializePromptArtifactAuthority(authority));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unused index can block tasks The runtime now serializes an index before every agent attempt, even when the prompt does not use one. If a valid fan-in has enough declared ancestor outputs to exceed the index’s 32 MiB limit, serialization fails before model work starts. That limit can stop an otherwise valid task whose prompt never needs the index.

Knowledge Base Used: Runtime orchestration

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/runtime/src/templates/smithers/workflows/workflow.tsx
Line: 965-967

Comment:
**Unused index can block tasks** The runtime now serializes an index before every agent attempt, even when the prompt does not use one. If a valid fan-in has enough declared ancestor outputs to exceed the index’s 32 MiB limit, serialization fails before model work starts. That limit can stop an otherwise valid task whose prompt never needs the index.

**Knowledge Base Used:** [Runtime orchestration](https://app.greptile.com/monad-foudnation/-/custom-context/knowledge-base/monad-developers/ultrafuzz/-/docs/runtime-orchestration.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code

Comment on lines +1049 to 1056
function renderAncestorArtifactPathAuthority(input: PromptRenderInput, paths: readonly string[]): string {
const listed = paths.map((artifactPath) => markdownCodeSpan(artifactPath)).join(", ");
return renderAncestorArtifactAuthority(
input,
paths.length === 1 ? `whose declared \`path\` is ${listed}` : `whose declared \`path\` is one of ${listed}`
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Path lists inflate prompts Exact-path authorities now put every requested path directly into the prompt. For a large path group, this uses substantial model context where the previous rendering stayed constant-size. Bound the list for prompt use or convey it compactly.

Knowledge Base Used: Prompt library

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/prompts/src/render.ts
Line: 1049-1056

Comment:
**Path lists inflate prompts** Exact-path authorities now put every requested path directly into the prompt. For a large path group, this uses substantial model context where the previous rendering stayed constant-size. Bound the list for prompt use or convey it compactly.

**Knowledge Base Used:** [Prompt library](https://app.greptile.com/monad-foudnation/-/custom-context/knowledge-base/monad-developers/ultrafuzz/-/docs/prompt-library.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

type: "object",
additionalProperties: false,
required: ["schema_version", "run_id", "attempt_id", "artifact_path_base", "selectors", "producers"],
required: ["schema_version", "run_id", "attempt_id", "artifact_path_base", "producers"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Version hides incompatible index The document still identifies itself as ultrafuzz.prompt-artifact-authority.v1, but it no longer has the formerly required selectors field and now rejects documents containing it. A consumer using that version cannot distinguish the incompatible shapes. Give the new shape a new schema version, even though current runtime code does not read the file back.

Knowledge Base Used: Artifact management

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/runtime/src/prompt-artifact-authority.ts
Line: 30

Comment:
**Version hides incompatible index** The document still identifies itself as `ultrafuzz.prompt-artifact-authority.v1`, but it no longer has the formerly required `selectors` field and now rejects documents containing it. A consumer using that version cannot distinguish the incompatible shapes. Give the new shape a new schema version, even though current runtime code does not read the file back.

**Knowledge Base Used:** [Artifact management](https://app.greptile.com/monad-foudnation/-/custom-context/knowledge-base/monad-developers/ultrafuzz/-/docs/artifact-management.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code

@aviggiano
aviggiano merged commit 0b29d6c into unstable Oct 6, 2026
17 checks passed
@aviggiano
aviggiano deleted the refactor/1234-live-prompt-input-index branch October 6, 2026 16:16
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.

Write the prompt input index from the live task plan; delete the sealed prompt-artifact authority binding

1 participant