Skip to content

feat(fusion): a contract between the parts of a fan-out, truthful delegate outcomes (F7, F2) - #431

Open
plombeer31 wants to merge 2 commits into
harness/05-grammarfrom
harness/06-contract
Open

plombeer31 wants to merge 2 commits into
harness/05-grammarfrom
harness/06-contract

Conversation

@plombeer31

Copy link
Copy Markdown
Collaborator

What

Before: DelegateTask was { id, title, instructions, deliverable?, files? } and nothing described or checked the interface between parts — one worker wrote a class another called as an object, button ids differed between markup and script; a task that declared files and wrote nothing was ok; a fan-out where every worker failed still returned ok for the call.

After: fusion.delegate takes an optional contract (owners, provides of kind symbol/file/id/endpoint/env/flag/other, requires, checks as verify.run specs; ≤ 64 provides, ≤ 16 checks, ≤ 8,000 rendered chars), prepended to every brief with a per-task "You own / You provide / You may rely on", and after the fan-out each provide is checked for presence by language-agnostic means and the checks run through an injected runChecks seam (wired to the verify family in the next PR); results land in the status table. A task with declared files and no successful write/edit/patch is no_changes; the call's details.outcome is all_ok / partial / all_failed and the head line counts every status.

Why

Design call D3: a shared contract, no approval step — workers stay at the orchestrator's disposal. A wrong contract makes every part consistently wrong, which is cheaper to fix than inconsistent parts. Truthful statuses make the orchestrator re-delegate where it used to accept a false ok.

How it was verified

  • npm run lint clean
  • npx vitest run --minWorkers=1 --maxWorkers=3 src/llm/provider/openai src/prompt src/tools/fusion — 44 files / 728 tests green (baseline failures: none)
  • verified live: a fix task whose worker wrote nothing came back no_changes although its reply claimed the files were corrected; a contract with provides and requires was accepted on a live fan-out

Stacked on #430; merge in order.

…f a fan-out

Workers never see each other, and in the benchmark that cost two turns:
one worker wrote HD.Ship as a class while its sibling called it as an
object, and main.js looked for launch-btn against a btn-launch in the
markup. Each brief was consistent on its own; the disagreement lived
between them, where nothing was written down.

`fusion.delegate` gains an optional call-level `contract`:
  { owners?: {path: taskId},
    provides?: [{task, kind: symbol|file|id|endpoint|env|flag|other, name, in?}],
    requires?: [{task, name}],
    checks?: [{task?, ...verify.run args}] }

- delegate-args.ts validates it against the task ids; every error names
  the field. A `requires` name must match a `provides` name (the
  launch-btn / btn-launch mismatch is refused before any worker runs),
  and a non-file provide must have somewhere to be looked for (`in`, an
  owned path or a declared file). Limits: 64 provides, 16 checks, 8,000
  rendered chars. A contract sent as JSON text is parsed like `tasks`.
- worker-prompt.ts renders the shared CONTRACT block (owners, provides,
  requires, checks) above every TASK, followed by "You own / You provide
  / You may rely on" for that task, and a RULE to end the reply with a
  PROVIDED: list. Briefs without a contract are byte-identical.
- contract-checks.ts checks presence after the fan-out by
  language-agnostic means: a file exists; a symbol appears as a whole
  word in `in` or the owned/declared files; an id appears as
  id="…"/id='…'; anything else as a literal. Missing provides land as a
  note on the owner's row and on a `contract:` line under the head line
  of the status table. `checks` run through an injected
  `deps.runChecks` (verify's runner; undefined-safe — declared checks
  with no runner are reported as not run, never as passed); a task
  whose declared check fails becomes `failed` with `checks: …` as its
  error. The report also rides on `details.contract`.
- The descriptor's argsSchema and the native-tools JSON schema carry the
  new field (the descriptor is mounted only in fusion mode).
…r fusion.delegate

Two lies the fan-out result could still tell after patch 1:

- A worker that declared `files`, read them, and replied "I'm done!"
  without writing anything came back `ok` (run 14, repair 1). New task
  status `no_changes`: an `ok` task that declared `files`, made no
  successful write / edit / patch call (the collector now counts them
  as `tools.writes`) and changed none of its declared files on disk
  (`DeclaredFileReport.modified`). Both halves are needed — the call
  count alone misreads a worker that wrote through the shell, the disk
  alone misreads declared globs. Tasks without `files` never get it;
  statuses that already say why the work is incomplete are left alone.
  The feed line reads "no changes — …" and the row carries the reason.

- A fan-out where every worker failed returned `status: "ok"`. The
  call's status now summarises its tasks (`details.outcome`: `all_ok` /
  `partial` / `all_failed`) and is `error` only when every task failed
  or was cancelled — partial results stay `ok` because they are the
  value of a fan-out. The head line counts every status in a fixed
  order: `7 tasks: 5 ok, 1 no_changes, 1 failed`.

AGENTS.md's fusion section is updated to match.
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