Skip to content

Document adversarial implementation workflow WIP - #181

Draft
taras wants to merge 29 commits into
mainfrom
agent/adversarial-implementation-workflow-wip
Draft

Document adversarial implementation workflow WIP#181
taras wants to merge 29 commits into
mainfrom
agent/adversarial-implementation-workflow-wip

Conversation

@taras

@taras taras commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Synchronizes the living end-goal target with the contracts that have settled on
main (under #292), carries
the document-logic proof #290
requires before this PR can merge, closes the workflow over the components its
root declares, and keeps every stage at the Agent ceiling
#302 settles.

  • Head: 568f19a528a4506230d5fd8c457191ced6abd261
  • Base: main at 5542c921bb8145458dc5f2446a61a1082a916f41
  • The branch was rebased onto that base, not merged — 28 commits replayed
    clean, no conflicts. Force-pushed with
    --force-with-lease=…:6305c429113583f0b8ecb29562801dc3deeeaf05; the lease
    held (+ 6305c429...568f19a5 … (forced update)).
  • 🧪 Recover the hot journal a killed run leaves before reading its store #512 required no reconciliation. The one merge in a0210e1..348f0956 is
    test-only — a helper that lets a test read a killed run's SQLite store without
    writing to it, plus the two tests that use it. It ships no capability these
    documents describe. artifacts.md's claim is about the next connection — the
    run's next owner, which can write and does recover last-committed state — while
    🧪 Recover the hot journal a killed run leaves before reading its store #512 concerns an observer that cannot, so the two are complementary rather than
    in conflict. The one test in this PR's reported evidence that 🧪 Recover the hot journal a killed run leaves before reading its store #512 changed,
    packages/cli/tests/workflow-crash.test.ts, was re-run: 1 passed (3 steps).
  • Earlier heads and bases: 6305c42 on 348f0956, 4961e03, 35df675 and bb367b2 on a0210e1, fca704c on 1948bf0, 51b4899 on 1948bf0,
    688395f on f3d777d, 59f743f on f3d777d, 9f10b9e on 7a8a9b7,
    40382a5 on 6a777dd, e552c43 on de6d835, 9010557 and 443a591
    before those.
  • Draft.

Why

The target is kept current as capabilities land: a settled contract is described
in the present tense, and a capability that does not exist yet says so with the
issue that would supply it. Seven merges landed under the previous base, and
four of them change what this document may say in the present tense.

The ten merges in 348f0956..5542c921

Merged Effect on the reference state
#528 <Let> replaces <Capture> No compatibility alias, so Capture left the reserved set and now resolves as an ordinary component. Nine structural invocations across five documents became <Let>, plus every prose reference to the removed directive. Implementation.md's seed becomes the shipped direct-value form <Let as="pullRequest" value={{}} />.
#516 / #296 <Issue>, <IssueTracker> The previous <Issue pullRequest={…} finding={…} /> spelling was invalid against a closed schema. Replaced by the public contract: <IssueTracker url={props.tracker}> wrapping a paired <Issue title={finding.title} as="deferredIssue"> whose body is the description, binding exactly { url }. New required tracker root prop, threaded to the stage. Both names move into the built workflow-host category.
#532 ambient authentication Recorded where the documents already discuss Repository, Git.Push, PullRequest and Issue: HTTP Git uses the invoking user's credential-helper chain for the exact locator; SSH uses the selected ambient agent and known-host policy; GitHub API reads GH_TOKEN, then GITHUB_TOKEN, then gh auth token --hostname github.com. Live host input per provider invocation — never a prop, secret schema, copied credential, retained value, Workspace material, argument, URL, journal field, output or diagnostic cause. Replay acquires nothing.
#521 crashed-run inspection Ordinary inspection reads the retained snapshot; only SQLite's exact SQLITE_READONLY_ROLLBACK falls through to recovery, which copies database and journal under recovery coordination and rolls back into the copy. The authoritative source stays byte-identical. Coordination grants no executor or lifecycle authority and opens no Workspace or provider effects. list stays complete-or-error.
#525 testing activation Inherited; no PR claim contradicts it.
#518 <Session.Launch> Inherited. It prepares a native UI session and performs no model turn, so it does not replace <Prompt> and does not implement #302. A sweep check forbids either claim.
#509 timeout diagnostics Inherited, test-support only.
#530 <Json> Shipped, and deliberately not introduced — this PR has no need for it, and a sweep check forbids a gratuitous mention.
#529, #531 Site/documentation-only for this PR.

The earlier merges, and what each did to the reference state

Merged Effect on the reference state
#495 <Git.Push> Moves from the unresolved inventory into the built workflow-host category. Publishes the selected checkout's exact current named branch and commit to the same branch at canonical origin; no props, no result, renders nothing, never force-pushes, changes no upstream tracking, performs no implicit stage or commit. #370's administrative state is not treated as the capability's state.
#500 + #504 <PullRequest> Built for the Deno workflow host and #295 settled. Required title; optional positive number; optional base; optional draft; rendered body; structured bound result. Requires matching successful Git.Push evidence, never pushes, never rewrites the head. The stale #295/#297 open question is gone.
#497 generated-XMD observation evaluateGeneratedXmd() and the pinned observation machinery are shipped trusted-host APIs. Prose that called all generated-XMD admission unbuilt is split from what is genuinely still owed.
#498 history forks Compatible forks, history --forkable, forkability reasons, lineage, changed-definition replay admission and retained Workspace-root copying are shipped under #368. Every "not built" claim about forks is gone.
#503 per-test timeout <Test timeout="<duration>"> ships; 20 s is the default, not a fixed limit. Two stale comments corrected.
#501 nested root execution <Execution host="run"> inside canonical <Test>. Inherited with no reference-document edit — no affected document discusses nested root execution.
#493 component bundle (previous base) Unchanged: the five-entry workflow.components declaration is preserved byte-for-byte.

What each merge did not make true

The revision loop, settled by #301's amendment

The Architect's amendment on
#301
settles the composition this PR previously reported as a gap: an expression prop
that evaluates to undefined is omitted before prop validation and before
the durable JSON boundary. Required props still fail as missing, null stays an
explicit value, an unbound name still fails, and it is the general expression-prop
contract rather than a PullRequest special case.

Implementation.md now composes it as the amendment specifies — the seed once,
before the loop, and one invocation inside it:

```ts eval
let pullRequest = {};
```

<PullRequest
  number={pullRequest.number}
  title={proposal.title}
  draft={true}
  as="pullRequest"
>
  {proposal.report}
</PullRequest>

First iteration: pullRequest.number is undefined, number is omitted, and the
unnumbered create-or-adopt contract applies. The result replaces the seed through
ordinary as binding, and later iterations pass the retained positive number and
select the numbered update contract for that exact pull request. This is what the
loop needs, because a revision commits again — so an unnumbered second request
would find a pull request holding a different headSha and refuse it as a
deliberate conflict.

The omission itself is not built. The engine refuses such a prop today
(Expression prop … evaluated to a non-serializable value (undefined),
packages/core/src/expand.ts:3043), so both inventories carry it as #301's,
unbuilt. No engine code changes here; Planning owes the focused proof of optional
omission, required-prop refusal, JSON-clean retention/replay and the
first-create/later-update loop before implementation is handed off.

Component inventory

Re-run after rebasing; every category size is asserted and every written name
must land in exactly one category.

  • 17 ordinary resolvedAgent, Break, Each, Elicit, Else,
    Fetch, File, Glob, If, Let, Loop, Output, Parse, Prompt,
    Return, SafeParse, Session
  • 5 bundled workflow stagesDiscovery, Implementation,
    InstructionFiles, Planning, UserCheckpoint
  • 10 built workflow-host-only<Repository>, <Worktree>, <Dir>,
    <Git.Switch>, <Git.Add>, <Git.Commit>, <Git.Push>, <PullRequest>,
    <Issue>, <IssueTracker>
  • 1 issue-owned unresolved<Expand>Evaluate Agent-generated XMD through a constrained allowlist #369

Confirmed at the registration site: packages/workflow/src/composition/installation.ts
registers exactly those ten names. Plain xmd run resolves none of them
because it composes no workflow — host-scoped resolution evidence, never an
unbuilt-component claim. <Agent.AddDir> is in no category: #302's architecture
excludes it.

Shipped is not the same as reachable. <Git.Push> and <PullRequest> are
built and registered, and the implementation stage still cannot reach them —
<Expand> (#369) has to produce the change before there is anything to stage,
commit, push or open a pull request for. What blocks the stage is now separated
by kind in the documents: two unresolved names (<Expand>, <Issue>), one
unbuilt engine capability (#301's undefined omission), and two shipped
components out of reach behind them.

Both explicit unresolved-name inventories (start.md, primitives.md) are
updated, and the pairing sweep is 2/2 — one <Expand>/#369 row in each.

#290 evidence — unchanged, with one comment corrected

All existing planning-loop, revision-routing, user-authority, exhaustion,
ordering and mutation evidence is preserved. Per the Planner's decision the
TypeScript root-Agent-provider proof for criterion 6 is retained: it proves
the complete 20-call trace and the authorization boundary directly and in under
a second, and a configurable Markdown timeout does not call for a duplicate long
ACP scenario. What changed is only the justification in two comments, which
had cited a fixed 20-second ceiling that #503 removed.

How to verify it

Frozen document evidence

document/schema/component probe   9 documents, 5 schemas
    ordinary (17) · bundled stages (5) · issue-owned (1) ·
    built workflow-host-only (10)                             PROBE OK
    category sizes asserted; an unclassified or misclassified
    written component fails the probe
bundle declaration probe (actual root, five entries)          PASS
composition probe (every final report branch inside <Dir>
    and the root <Output>; control = the nested-<Output>
    revision at 949cb31, executed unmodified — the control
    SHA is recomputed after every rebase)                     PASS
amendment probe (#301's undefined-prop composition)           PASS
merged-state sweep checks 8 and 9                             PASS
refresh checks (new this revision)                            PASS
    no structural Capture spelling; the exact Let forms incl.
    the direct-value seed; IssueTracker/Issue nesting with no
    forbidden Issue prop; tracker root->stage propagation;
    tracker grants no authority and IssueApi stays separate;
    ambient auth never retained/propped/Agent-visible; recovery
    never mutates the source; #301/#302/#369 gaps intact.
    Denial-aware by sentence over reflowed paragraphs, and
    36 findings at 6305c429.
    8. no Agent is given a registered path, filesystem view or
       stage-registered directory
    9. the executable inventory agrees with 17/5/2/8
    both FAIL at 35df675; the corrected denials and the
    shipped-but-unreachable distinction are exempt, self-tested
    so an exemption cannot swallow a stale sentence
    A. seed declared once before the loop; exactly one
       <PullRequest> inside it, carrying number={pullRequest.number}
    B. already works: a member read on the seed yields undefined
       in an expression prop without failing, and an `as=` binding
       shadows the eval-declared name
    C. not built: an undefined expression prop is refused today,
       so calling the omission unbuilt is accurate. This check
       fails if the amendment ships without the prose catching up.
root-contract sweep                                           PASS, 10,950 probes
#301/bundle, #293, lifecycle, Git/forge-read, suspension,
    stale start/resume, authority sweeps                      PASS
    self-test: 11 stale sentences match, 4 denials do not
unresolved-pairing sweep                                      PASS 4/4

The new merged-state sweep

Rejects: <Git.Push> or <PullRequest> described as unbuilt; #295 described as
an open contract; forks or forkability described as unbuilt; a fixed 20-second
<Test> timeout; all generated-XMD admission described as unbuilt; observation
admission described as mutation admission; <Expand> described as a shipped
public component.

merged-state sweep @ fca704c (control)   FAIL — 5 of 7 checks fire
merged-state sweep @ bb367b2             PASS — all 7 clean

Every check is self-tested in both directions: each stale sample must match, and
each settled sample must be exempt without the exemption swallowing a stale one.
The reconciled contract sweeps fail at fca704c too (8 findings), so their pass
here is discriminating rather than vacuous.

Focused shipped regressions

packages/workflow/tests/git-push.test.ts
packages/workflow/tests/pull-request.test.ts            8 passed (54 steps) | 0 failed
packages/core/tests/generated-xmd.test.ts
packages/workflow/tests/generated-observations.test.ts  10 passed (68 steps) | 0 failed
packages/cli/tests/workflow-fork.test.ts
packages/workflow/tests/workflow-fork.test.ts           2 passed (17 steps) | 0 failed
packages/testing/tests/test-component.test.ts           1 passed (18 steps) | 0 failed
packages/workflow/tests/workflow-definition.test.ts
packages/core/tests/workflow-component-bundle.test.ts
packages/workflow/tests/workflow-bundle.test.ts
packages/cli/tests/workflow-declaration.test.ts         12 passed (75 steps) | 0 failed
scripts/tests/adversarial-planning-workflow.test.ts     2 passed (5 steps) | 0 failed
deno task xmd test workflows/adversarial-implementation exit 0, 7 tests
git diff --check                                        exit 0
348f0956 is an ancestor of HEAD                         OK
packages/cli/tests/workflow-crash.test.ts (#512 changed it)
                                                        1 passed (3 steps) | 0 failed

Scope

This turn's commits change 11 files in total. bb367b2 (the merge
reconciliation) changes 9:

scripts/tests/adversarial-planning-workflow.test.ts
specs/adversarial-implementation-workflow.md
specs/markdown-agents-vision.md
workflows/adversarial-implementation/Implementation.md
workflows/adversarial-implementation/artifacts.md
workflows/adversarial-implementation/primitives.md
workflows/adversarial-implementation/runtime.md
workflows/adversarial-implementation/start.md
workflows/adversarial-implementation/tests/planning-logic.test.md

35df675 (the #301 amendment) changes 3, two of them again:

specs/adversarial-implementation-workflow.md
workflows/adversarial-implementation/Implementation.md
workflows/adversarial-implementation/primitives.md

Total branch scope against the base: 26 files — eleven reference documents
and fifteen test assets.

Intentionally unchanged

Risks and limitations

  • This is a living target. It describes target behavior in explicit target terms
    and shipped behavior in the present tense; a reader taking a fragment out of
    context can still confuse the two.
  • The document now describes <Git.Push> and <PullRequest> as shipped without
    this branch executing either under a real workflow run — that evidence is the
    merged PRs' own regressions, re-run above, not a run of this workflow.
  • The revision loop is composed as Compose the supervised adversarial implementation workflow #301's amendment specifies, but the omission
    it depends on is unbuilt — so the implementation stage still would not run
    today, now at the seeded number prop rather than at a conflicting upsert.
  • Much of the remaining evidence is documentation evidence — parsing, schema
    compilation, name resolution, text sweeps. Prove the shipped adversarial-planning document logic #290's proof, the boundary test and
    the bundle probe are the exceptions: they execute.
  • The effectionx ✨ Register the agent components as ordinary function components #244 pump-tail limitation is carried through every retention
    claim rather than resolved.

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown

PR #181: Document adversarial implementation workflow WIP

26 files, +5042 / -0

Scope

🔴 PR has 5042 lines changed. Split into focused PRs.

🟡 5042 lines changed. PRs under 400 receive more thorough review.

🟡 26 files changed. Are all changes related?

Structural

✅ No structural bloat detected.

Slop

✅ Slop indicators look low.

Static Analysis

✅ Oxlint found no issues.

Correctness

No extraneous code patterns detected.

@taras

taras commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

End-goal implementation audit — 2026-08-02

Reviewed PR head 28f764e94b9bcaa9f7db0dcc689c31dc06466dac against current main at de4f7757f216063b5b48e431561d141bcbf40097. This audit treats #181 as the target workflow we are building toward, not as a conventional merge candidate. It is invalid after either head changes.

The target remains coherent: the user owns material decisions; planner and implementor converge through evidence; workflow state and environmental effects are explicit; agents do not own Git, GitHub, or workspace effects; and every live resource is scope-owned.

Settled contracts to fold into the target documents

These are documentation updates, not missing product features.

  • Replace inputs with props in start.md, runtime.md, InstructionFiles.md, Discovery.md, Planning.md, UserCheckpoint.md, and Implementation.md. props is the settled declaration vocabulary; inputs is ordinary metadata and does not declare component or root props.
  • Replace the remaining future-facing Support schema-validated component returns #176 language in artifacts.md and primitives.md with the shipped return contract: Markdown value components declare returns and one direct <Return>; registered function returns bind by reference by default; returns opts into JSON validation and requires as; uncaptured strings render and uncaptured non-strings render nothing.
  • Reclassify <If>/<Else>, <Loop>/<Break>, <Answers>/<Answer>, and <Return> as shipped reserved structural syntax. Repository files and registrations cannot claim these names.
  • Reclassify <TempDir>, <File>, <Glob>, <Parse>, <SafeParse>, and <Elicit> as shipped core defaults. A repository component may override each ordinary default.
  • Update the parsing description to the shipped contracts: schemas compile before content, <Parse> binds the validated JSON value, and <SafeParse> binds { ok: true, value } or { ok: false, input, errors } without swallowing child or schema-compilation failures.
  • Update the filesystem description to the shipped contracts: contextual cwd is implemented; <File> reads or atomically replaces UTF-8 text and returns no write handle; <Glob> returns a sorted, deduplicated string[]; both are observationally confined but are not a security sandbox.
  • Update the elicitation description to the shipped architecture: <Elicit> is provider-neutral, validates and durably fingerprints the question and answer, xmd run supplies WebForm as the current host provider, and <Answers> supplies document-owned answers without choosing a transport. Cross-stage stop and resume remains separate work.
  • Update loop durability language: <Loop> records entered iterations and terminal break, exhausted, or error outcomes and refuses stale replay. Workflow-level cancellation and stage-stop records remain future behavior.
  • Record that dynamic agent and session selection through expression props is implemented, and that Agent, Session, Prompt, Elicit, and the other function components resolve through scope-local registration with repository override precedence.
  • Update the PR review guide to the actual target files: start.md, Discovery.md, Planning.md, UserCheckpoint.md, and Implementation.md.

Shipped foundation available to the end goal

The document-level logic in InstructionFiles, Discovery, Planning, and UserCheckpoint is therefore no longer speculative once its declarations are updated to the settled contracts. The remaining gap is workflow orchestration and ownership, not conditional logic, parsing, file discovery, or elicitation.

Missing feature checklist for the complete workflow

Workflow identity, artifacts, and resumption

  • Implement <Workflow> as the owner of one internal run identity without exposing a required runId prop.
  • Resolve base once to a pinned source revision and publish the run and revision through a contextual Run API.
  • Define immutable artifact versions for handoffs, plans, verdicts, implementation results, user decisions, and environmental effects, keyed by stable component and loop-iteration identity.
  • Persist run snapshots in Git objects reachable from refs/xmd/runs, including explicit push/fetch behavior for the custom ref.
  • Restore named stage inputs from the run record and render their exact content into later prompts without transfer files.
  • Record completed stages, completed loop iterations, success, failure, cancellation, and stop reasons without inventing a successful terminal record for interrupted work.
  • Reconcile durable effects with the run identity so resumption cannot duplicate or silently adopt an effect from another run.
  • Resolve replay across replaced ephemeral environments (Implement retained Workflow Workspace restoration #218).

Stage execution and user continuation

  • Implement <Stage> to select one manual stage, restore its declared inputs, publish its outputs, and stop cleanly at the stage boundary.
  • Define how a later invocation selects and resumes the same workflow run and stage.
  • Define in-band runtime input that can resume a stopped workflow. <Elicit> solves an in-run question but not cross-process stage continuation.
  • Implement the automated stop arbitration from the target: iteration completion, configured file change, or user input, with deterministic priority and cancellation behavior.
  • Preserve the current user-authority contract across every resumed transition; resumption must not turn an unanswered material choice into implicit approval.

Workspace ownership

  • Implement <Worktree> to create or reconcile one workspace from the pinned revision and reuse it across discovery, planning, implementation, and review.
  • Publish contextual cwd for all child file, process, agent, and Git operations.
  • Define deterministic worktree identity, branch naming, existing-state reconciliation, and behavior when the base branch moves.
  • Clean up a clean workspace on success; retain dirty, unpushed, failed, or cancelled work with its path, branch, state, and recovery reason.
  • Ensure projected content resources stop before the owning workspace cleans up (Bind projected content resources to the component invocation scope #203).
  • Keep worktree filesystem isolation distinct from the security boundary.

Enforceable sandbox and permissions

  • Implement <Sandbox> as an enforceable capability boundary for filesystem roots, environment variables, processes, network access, secrets, and durable effects.
  • Give planner stages repository read/search capability without source mutation.
  • Give implementor stages write access only to the workflow-owned worktree, never shared Git metadata.
  • Give deterministic components narrow Git and GitHub authority rather than exposing that authority through an agent prompt.
  • Own and halt every process, agent session, stream, and watcher before the enclosing stage or workflow scope closes.
  • Resolve containment that remains safe under concurrent filesystem mutation, or explicitly bind unattended execution to a platform sandbox that provides it (Contain filesystem access at the provider boundary #227).
  • Install default-on secret rejection before journal events and sidecar Git snapshots become durable (Reject secrets before journal persistence by default #199).

Deterministic Git and GitHub effects

  • Implement <Commit> to validate the exact allowed path set, refuse unrelated changes, write Git metadata outside agent authority, and return a structured commit identity.
  • Define commit reconciliation after interruption, including the case where the commit may have succeeded before the process lost its result.
  • Implement <PullRequest> to create or resolve the PR for the workflow branch idempotently and return its number, URL, head, base, state, and review/check state.
  • Implement <Issue> to create or resolve provenance-linked deferred findings idempotently.
  • Record the inputs, observed pre-state, applied effect, and returned state of every Git and GitHub operation in the workflow run.
  • Ensure replay never creates duplicate commits, pull requests, or issues and never trusts a moved PR head without a fresh review.

End-to-end workflow contract

  • Make start.md execute discovery → handoff checkpoint → plan convergence → authorization → implementation/review loop → final user acceptance in the same workflow-owned workspace.
  • Prove malformed agent JSON receives only the bounded correction turns shown in the document and cannot reach control flow or durable effects.
  • Prove a failed planner verdict returns its prescriptive revision prompt to the same implementor session and preserves all prior versions.
  • Prove material choices stop at <UserCheckpoint> and only the user response authorizes the next transition.
  • Prove deferred findings create provenance-linked issues without silently expanding the active PR chain.
  • Prove cancellation and failure halt live resources, preserve recoverable work, and leave enough state for an explicit resume.
  • Prove the complete run can resume after process exit without hidden transcript state or user copy/paste.
  • Prove agents cannot directly perform the workflow-owned Git, GitHub, sandbox, or retention effects.

Not blockers for this workflow

  • External JSON Schema references (Resolve external JSON Schema refs through XMD filesystem and fetch capabilities #192): the target already captures self-contained schemas.
  • Structured <File> handles: the current target passes relative paths and does not consume a write handle.
  • xmd play: it remains the layer built after this implementation loop works; it is not required to make the loop executable.
  • Unattended runtime inspector control: ordinary decisions use the in-band user protocol; inspector support remains operational meta-control.

This checklist should shrink as features land. A settled contract updates the target documents and moves to the shipped foundation; only capabilities absent from the runtime remain unchecked.

@taras

taras commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Current delivery map

The root document is the workflow. A durable workflow run may continue through multiple document executions.

Each issue remains an independently reviewable stop point.

Living target and shipped document logic

Workflow identity and environment foundations

#199 and #227 can proceed independently. #218 is designed and stacks on PR #341 until #289 merges.

Persistence and workspace composition

Workspace state is retained across completion, failure, cancellation, and interruption; scope teardown releases only ephemeral provider attachments. Point-in-time snapshots, rollback, and training export are not prerequisites for normal resumption.

Deterministic Git and GitHub effects

After #291 and #293:

Authority, composition, and certification

Dependency spine

#181/#292 → #305 → #290

#289 / PR #341 → #218 ─┐
                        ├→ #293 → #294
#199 + #289 / PR #341 → #291 ─┘

#291 → #297
#294 + #297 → #295 → #296
#227 + #293 → #302

#290 + #291 + #293 + #294 + #295 + #296 + #302
  → #301 → #300 → #299

#298 is superseded by #291, which owns durable lookup, xmd workflow run/continue, restoration of declared values, cross-process continuation, and durable outcomes.

Implementors validate authorized slices. Taras settles #227, #290 exhaustion, and #302's term and installation architecture before those stories are delegated. #218's product decisions are recorded in the issue; its implementor validates that design rather than choosing a replay or provider model.

@taras

taras commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Parallel delivery map

Current at PR head 28f764e94b9bcaa9f7db0dcc689c31dc06466dac. Boxes on the same horizontal tier may proceed in parallel. Arrows are merge dependencies, not merely preferred ordering.

                                      START
                                        |
      +---------------------------------+----------------------------------+
      |                                 |                                  |
      v                                 v                                  v
+----------------------+      +----------------------+          +----------------------+
| A. Synchronize #181  |      | B. #203 projected   |          | C. #199 persistence  |
| to settled contracts |      | resource ownership  |          | safety stack         |
+----------+-----------+      +----------+-----------+          +----------+-----------+
           |                             |                                 |
           v                             |                                 |
+----------------------+                 |                                 |
| Prove current        |                 |                                 |
| document logic       |                 |                                 |
+----------+-----------+                 |                                 |
           |                             |                                 |
           |                  +----------v-----------+                     |
           |                  | Run identity +       |                     |
           |                  | pinned base revision |                     |
           |                  +----+-------------+---+                     |
           |                       |             |                         |
           |       +---------------+             +--------------------+    |
           |       |                                                    |    |
           |       |        PRODUCT DECISION TRACKS                     |    |
           |       |                                                    |    |
           |       |   +----------------------+   +------------------+  |    |
           |       +<--| #218 environment     |   | #227 containment |  |    |
           |           | replay ruling + impl |   | / sandbox ruling |  |    |
           |           +----------+-----------+   +---------+--------+  |    |
           |                      |                         |           |    |
           |                      v                         |           |    |
           |           +----------------------+            |           |    |
           |           | Worktree lifecycle   |<-----------+-----------+    |
           |           | and contextual cwd   |            |                |
           |           +----------+-----------+            |                |
           |                      |                        |                |
           |                      |            +-----------v-----------+    |
           |                      |            | Artifact ledger +     |<---+
           |                      |            | refs/xmd/runs history |
           |                      |            +-----------+-----------+
           |                      |                        |
           |                      +------------+-----------+
           |                                   |
           |                                   v
           |                       +------------------------+
           |                       | Stage + manual resume  |
           |                       +-----------+------------+
           |                                   |
           |        +--------------------------+--------------------------+
           |        |                          |                          |
           |        v                          v                          v
           |  +-------------+       +--------------------+      +--------------------+
           |  | Commit      |       | GitHub effect      |      | Sandbox            |
           |  | effect      |       | reconciliation API |      | implementation     |
           |  +------+------+       +---------+----------+      +---------+----------+
           |         |                        |                           |
           |         |              +---------+---------+                 |
           |         |              |                   |                 |
           |         |              v                   v                 |
           |         |       +-------------+     +-------------+          |
           |         |       | PullRequest |     | Issue       |          |
           |         |       | effect      |     | effect      |          |
           |         |       +------+------+     +------+------+          |
           |         |              |                   |                 |
           +---------+--------------+---------+---------+-----------------+
                                            |
                                            v
                              +-----------------------------+
                              | Supervised complete workflow|
                              +--------------+--------------+
                                             |
                                             v
                              +-----------------------------+
                              | Automated continuation      |
                              | file/input/iteration race   |
                              +--------------+--------------+
                                             |
                                             v
                              +-----------------------------+
                              | End-to-end certification    |
                              +-----------------------------+

Parallel waves

Wave 1
  A  Target synchronization -> document-logic proof
  B  #203 projected-resource ownership
  C  #199 scanner and persistence-safety stack
  D  #218 and #227 investigation; Taras resolves the product choices

Wave 2
  Run identity/base pinning
  #218 implementation after the ruling
  remaining #199 slices
  #227 mechanism prototyping after the ruling

Wave 3
  Worktree             requires #203 + Run identity + #218
  Artifact ledger      requires Run identity + #199

Wave 4
  Stage/resume         requires Worktree + artifact ledger
  Commit               requires Worktree + artifact ledger
  GitHub effect core   requires artifact ledger
  Sandbox              requires Worktree interface + #227 ruling

Wave 5
  PullRequest and Issue may proceed in parallel after the shared
  GitHub reconciliation contract lands.

Wave 6
  Supervised workflow joins document logic, Stage/resume, Commit,
  PullRequest, Issue, and Sandbox.

Wave 7
  Automated continuation -> certification

Shared-code collision rules

Execution / durability:
  Run identity, #199, and #218 may be investigated concurrently.
  Their integration commits merge serially because they share execution setup,
  durable context, and replay boundaries.

Expansion / projection:
  #203 owns this lane until its lifecycle contract lands.

Workspace / Git:
  Worktree lands before Commit; both use the same repository ownership boundary.

GitHub:
  PullRequest and Issue split only after one shared idempotent reconciliation
  contract is merged. Neither invents its own replay or uncertain-result rule.

Product decisions:
  Implementors do not choose #218 durability semantics or #227 sandbox guarantees.

Maximum useful initial concurrency is three implementation lanes (A, B, C) plus planner/user investigation of #218 and #227. More implementation lanes would mostly create integration conflicts at the execution and durability boundary.

@taras

taras commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Milestone story map

Created the Adversarial implementation workflow milestone. Every task in the approved delivery sequence now has a canonical story:

PR #181 itself is also attached to the milestone as the living end-goal artifact. Each new story records scope, observable acceptance criteria, dependencies, and its sequence position. Existing stories #199, #203, #218, and #227 remain canonical rather than being duplicated.

@taras
taras force-pushed the agent/adversarial-implementation-workflow-wip branch from 1a01e58 to 014447b Compare August 4, 2026 19:49
@taras

taras commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

Fresh head on current main — #292

Replaced the stale head 1a01e58 (based on e0b37ea, seven commits behind) with 014447b, re-derived from 7d7bdf2 — the #325 merge. No commit from the old head was cherry-picked, merged, or reapplied; the 11 files were re-authored. git merge-base with origin/main is exactly 7d7bdf2.

The markup did not run

Every stage passed props through expression props as agent={props.planner}. On current main that fails with props is not defined: an expression prop reads the bare binding, while text and content interpolation read the namespace. Reproduced at the root and inside a component body; the bare form works in both.

This is #305, still open — "Expression-valued component props can read props.name" is one of its acceptance criteria, so the target was written against a language change that has not landed. 22 expression-prop sites are corrected to the spelling main supports; the 16 text-interpolation sites keep {props.X}. The asymmetry is documented with #305 named as what removes it.

The error model reaches the stages

A stage component is split by its <Output> boundary: the region inside runs under the output error mode, everything outside is documentation and runs under throw, which no <PrintErrors> region replaces. A stage therefore returns a complete validated result or it fails, keeping only what it had already rendered. That is what makes each repair loop's final <Parse> a gate.

throwOnError is load-bearing for the same reason, and not for the reason I first assumed: without it a failed prompt records its failure and returns its text, raising nothing for the mode to decide (agent/function-components.ts:210-221).

One claim I did not repeat: architecture.md says an outer <PrintErrors> can print a failure an <Output> region decided, but expand.ts:2292 consults decidedByOutput only on the projected-content path, and a probe showed an outer boundary not printing a nested component's <Output> failure. The target states only what was observed.

Stale claims removed

<CollectFailures><PrintErrors> in the structural set · "the unresolved diagnostic" → "the unresolved printed error" · "replays recorded results" → replay restores a recorded outcome without re-executing, and a failed run is still a complete record. Replay is described as reaching the state execution resumes from, never as the continuation itself.

Vocabulary

Collapsed onto the concepts #289, #291 and #298 authorize — artifact ledger, artifact version, run identity, pinned source revision, stop reason, terminal record, stage boundary, declared inputs, cross-process continuation — replacing the four names these files used for a ledger and the three for a run. Engine state is described as scoped to the operation that owns it, with no module-scoped registry. Missing capabilities cite the issue that supplies them (#289, #291, #293, #294, #295, #296, #297, #298, #300, #302) rather than saying only "not implemented".

Classification

Shipped: InstructionFiles, Discovery, UserCheckpoint, Planning. Missing: the <Workflow>/<Sandbox>/<Worktree> spine, <Stage>, the artifact ledger, and — inside Implementation<Commit>, <PullRequest>, <Issue>, which is why that stage cannot expand rather than merely lacking a wrapper. Blocked on a product decision: #218, #227, and planning-loop exhaustion under #290, which stays explicitly unresolved — the output reports verdict.passed and does not call an exhausted loop converged.

Evidence

Check Result
inspectDocument on all 9 frontmatter-bearing files parse and compile; every one returnMode=text
compileParseSchema on all 5 embedded draft-07 schemas compile
inspectComponent — 21 shipped names 12 structural, 9 registered
inspectComponent — 5 repository components markdown
inspectComponent — 9 missing names unresolved
InstructionFiles executed against this repo's AGENTS.md renders exact path and content

Gates: deno task fmt, lint, check, test (349 passed, 0 failed), check:jsr (Success Dry run complete), and pnpm exec tsc --project tsconfig.node.json --noEmit.

Outstanding before merge

#203 is still open and is an input to this target, not something this PR closes. Before merge this branch rebases onto the origin/main that carries it, and the projected-resource and component-lifetime language is realigned with the behavior actually merged.

@taras

taras commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

Rebased onto 511776e — fresh review head 68733d7

014447b68733d7, rebased onto origin/main at 511776e (the #326 merge). Lease was pinned to 014447b. git merge-base with origin/main is exactly 511776e; two commits on the branch, both signed.

#203 as merged

#326 is test-and-spec-only: it adds L8 (a projected persist resource stops ahead of one the component retained after projecting) and Q14 (a projected daemon is gone after the invocation, signalled while the <TempDir> still exists). Those were precisely the two acceptance criteria this target had the weakest evidence for — I had cited O23/O24 and CW3, which are adjacent but not the same claim. #203 is now closed.

The target states the merged behavior and why it matters here: projected content keeps its caller's bindings while its live effects belong to the component invocation and stop before it cleans up its own. That ordering is what <Worktree> (#293) is built on — a process a stage starts stops before the workspace it ran in is removed, so cleanup cannot pull the ground out from under a running effect. Recorded in primitives.md (execution foundation, and the <Worktree> entry) and in the specification's cleanup-and-recovery section.

<PrintErrors> — contract restored, discrepancy filed

architecture.md is authoritative, so the output row reads as the registry states it:

| output | fails the run; <PrintErrors> can print instead | every <Output> region |

My earlier head under-stated that row to match observed behavior. That was the wrong call — the target does not get to redefine a settled contract from an engine observation. The engine discrepancy is now classified as missing and filed as #327, reproduced on 511776e in both arrangements: the failure arising in the callee's own <Output> region, and the failure projected into one. A control case (the same failure directly inside <PrintErrors> at the root) prints and continues, so the mechanism is not broken in general.

The distinction the target still draws is the registry's own: throw is the one mode a boundary cannot replace. Every stage's parsing sits in documentation, so each stage's final <Parse> is a gate for that reason — not because printing is unavailable. No workflow document writes <PrintErrors>, so nothing here depends on #327.

Revalidation on the rebased head

All probes rerun: inspectDocument parses all 9 frontmatters; compileParseSchema compiles all 5 embedded draft-07 schemas; inspectComponent resolves 21 shipped and 5 repository names and confirms 9 missing ones unresolved; InstructionFiles still runs end to end against this repo's AGENTS.md. Vocabulary sweep clean.

Gates rerun: deno task fmt, lint (0 errors), check, test (349 passed, 0 failed), check:jsr (Success Dry run complete), pnpm exec tsc --project tsconfig.node.json --noEmit (exit 0).

Everything from the previous head's summary that is not amended above still stands — the #305 expression-prop correction, the error-mode split, the vocabulary collapse onto #289/#291/#298, and planning-loop exhaustion left open under #290.

@taras

taras commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

User authority now gates execution — head 545fd93a484828689f70bd1239ca3e5af600ebd0

Both findings were correct. Fixed at 545fd93, on top of 511776e.

1. The decision controls execution

<UserCheckpoint> now declares returns and binds a schema-validated decision — proceed plus the assessment fields a later prompt quotes — instead of returning prose a caller could only read. One decisionSchema serves both paths: <Elicit> binds it when the assessment reports a material choice, and an explicit <Parse> binds "proceed": true with its reason when there is none. Continuation is represented, never inferred from absent elicitation data, which is what keeps #290's "cannot become implicit approval" intact.

Every affected transition is gated: a declined handoff does not start Planning; a declined authorization does not start Implementation; a declined plan-review checkpoint neither revises nor accepts the plan; a declined PR-review checkpoint neither revises nor accepts the implementation; a declined acceptance finishes as rejected rather than falling into the accepted branch. In both loops the checkpoint is read before the verdict, so the user's decision outranks the planner's.

Nesting is not stopping, and the target says so rather than overclaiming. The run still expands to <Output> and completes; there is no clean halt at a stage boundary a later invocation resumes from, and no stop reason recorded for one. Classified as missing under #298 over #289.

2. Two engine limitations found while proving it

#328<Content /> is not substituted when nested. It resolves only at a body's top level or directly inside <Output>. Discovery and UserCheckpoint both wrote <Content /> inside a <Prompt>, so neither could expand at all. Cause read out of expand.ts:2668: substituteSegmentList returns non-Content component segments unchanged and never recurses into their children. Reproduced with a Markdown wrapper, a core function component, and a structural <Capture> — the wrapper's kind does not matter. Both components now take the material as a prop, which interpolates anywhere.

A projection asymmetry worth knowing. A binding made inside a Markdown component's projected content does not escape to its caller; the same binding inside a registered function component does. My first harness stubbed <Agent>/<Session> as Markdown components and produced a failure that looked like a defect in UserCheckpoint but was an artifact of the stub. Rebuilt with TypeScript stubs before drawing any conclusion.

3. artifacts.md inventory corrected

The claim that every listed result is JSON declared inline is gone. Three kinds now, and the difference is stated as load-bearing — only parsed results can gate a transition:

  • Prose, no schema: PlannerHandoff, ImplementationPlan. Planning branches on the separately parsed verdict, not on plan text.
  • Parsed JSON, each described using only fields its inline schema declares — PlannerVerdict is passed/review/revisionPrompt (evidence and user questions live inside review, they are not fields); ImplementationResult is changedFiles/commitMessage/report; PullRequestVerdict adds findings with disposition/title/description/evidence; UserInvolvementAssessment is requiresUser/assessment/question/options/recommendation.
  • A declared return: UserDecision, from the checkpoint. The actor and timestamp that would make it auditable are marked missing under Store WorkflowRuns and filtered journals in SQLite #291 — nothing in the returned value identifies who answered.

4. Executable discrimination over the shipped subset

Real UserCheckpoint, TypeScript stub agent recording every turn, <Answers> supplying the decision:

Scenario Agent turns proceed Stages that ran
Material choice, user declines 1 false none — both bindings stayed unresolved
Material choice, user authorizes 2 true PLANNING-RAN, IMPLEMENTATION-RAN
No material choice 2 true (explicit <Parse> branch) PLANNING-RAN, IMPLEMENTATION-RAN

The declining path records no subsequent agent turn. In the third scenario a declining <Answer> was in scope and never consulted, because <Elicit> never ran — the continuation came from the explicit branch. All four <Output> outcome branches were exercised separately and each renders its own outcome with no unbound-binding error, including rejected-at-acceptance.

5. Verification

Probes rerun: 9 frontmatters parse (UserCheckpoint now returnMode=value, the rest text), 5 embedded schemas compile, 21 shipped + 5 repository names resolve, 9 missing names unresolved. Vocabulary sweep clean.

Local gates: fmt, lint (0 errors), check, test (349 passed, 0 failed), check:jsr, tsc — all green.

All ten PR checks green: composability, deploy, jsr, lint, review, site, smoke, test-bun, test-deno, test-node.

@taras

taras commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

Authority now survives the component boundary — head 173e96aac33388b715ab9b4e1fff7e3cb50e4f13

All three findings were correct, and the <Issue> one was the serious one: a durable GitHub object created from the planner's proposed disposition before anyone approved it. Fixed at 173e96a, still based on 511776e.

Structured stage boundaries

Planning and Implementation are value components now. Each returns its prose (plan / report), the parsed verdict's fields, the complete UserDecision it resolved internally, and two derived control fields:

  • authorizedproceed && verdict.passed. A stage cannot approve its own advancement, and neither an approving decision over a failing verdict nor a passing verdict under a decline is enough.
  • terminalconverged, declined, or exhausted.

start.md gates on those: authorization is requested only after a passing plan verdict and an approving plan-review decision; acceptance only after a passing pull-request verdict and an approving review decision. It renders the human-readable reports from the returned fields rather than receiving them pre-flattened. An exhausted loop reports itself distinctly and fails closed — that is not an answer to what exhaustion should do, which stays unresolved under #290.

InstructionFiles and Discovery remain text components. The rule is stated where it matters: a controller that resolves a decision and returns a rendering of it has discarded the thing its caller needs.

Approval precedes durable effects

Deferred <Issue> creation moved inside the approved branch, after the checkpoint. proceed: true authorizes the exact transition and effects the checkpoint assessed and nothing more; the free-text response and rationale record the user's reasoning and nothing reads them to change which effects run, because an effect that has already executed cannot be amended by prose. proceed: false performs none of them.

Discrimination — real documents

Real Planning, Implementation, and UserCheckpoint; stubbed only the agent transport and the three deterministic components that do not exist. Targeting is by requiresUser, so exactly one checkpoint per scenario reaches the <Answer> and the rest take the explicit no-material-choice continuation.

Scenario Agent turns commit PR issues Result
Plan review declined 3 0 0 0 terminal=declined, authorized=false; no authorization question, no implementation turn
All approved, verdicts pass 8 1 1 1 accepted
PR review declined 7 1 1 0 implementation.terminal=declined; no acceptance question
Failing verdict, approved 12 1 1 1 revision turn runs; reaches implementation only after the later passing verdict
Exhaustion 20 0 0 0 terminal=exhausted, authorized=false; reaches neither

Mutations, each changing an observed result:

Mutation Effect
<Issue> moved ahead of its approval declined PR review goes issues 0 → 1 — the issue is created despite the decline
Caller gate bypassed (planning.authorizedtrue) declined plan review goes prompts 3 → 6 — advances to the authorization question
Planning's return reduced to text authorized / terminal / verdictPassed render as unresolved literals; the caller cannot gate at all

Correction to my previous report

I described the earlier probe as discriminating the gating. It stubbed the outer stages, so it never exercised any internal-decision path — the harness was built not to see exactly what you found. The scenarios above run the real documents.

Verification

Probes rerun: 9 frontmatters parse (UserCheckpoint, Planning, Implementation now returnMode=value), 5 embedded schemas compile, 21 shipped + 5 repository names resolve, 9 missing names unresolved. Vocabulary sweep clean. #328's missing-capability classification kept, and #327's.

Local gates: fmt, lint (0 errors), check, test (349 passed, 0 failed), check:jsr, tsc.

Docs updated for the new boundaries: start.md (flow, gate section, data-flow table), artifacts.md (StageResult added; the three-kind split now says only parsed results can gate), the specification, and the PR description.

All ten checks green: composability, deploy, jsr, lint, review, site, smoke, test-bun, test-deno, test-node.

@taras

taras commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

Sources returned, derived state removed — head 5b434db6f5da3576937d95fdd1c749ece9d6cee0

Both problems were real. Fixed at 5b434db, still based on 511776e.

1. The pull-request handle

Confirmed before correcting, not after. With <PullRequest> stubbed at #295's shape — number, url, headSha, baseSha, state, reviews, comments, checks — the head-173e96a Implementation failed:

Return validation failed for <Implementation />:
  - "/pullRequest" must be string

The mutation rerun makes the cost explicit: that failure lands after commit and ISSUE-CREATED have already executed. A conforming implementation would perform its durable effects and only then fail the stage.

pullRequest is removed from the declared return. start.md consumed none of it, and the artifact ledger records the effect and its handle independently (#291). report renders the named fields instead of interpolating the object:

#{pullRequest.number} ({pullRequest.state}) {pullRequest.url}
head {pullRequest.headSha} onto base {pullRequest.baseSha}

If a later caller genuinely needs the handle it is declared with #295's object schema — never a placeholder. The harness <PullRequest> stub now returns that object, and <Commit> returns a {sha, message} structure rather than a string, so a future string placeholder cannot pass again.

2. Duplicated control state

authorized, terminal, and StageResult are removed. Both stages return their prose, the parsed verdict's fields, and the complete UserDecision — the sources, nothing derived. start.md computes each gate where it uses it:

planning.decision.proceed && planning.verdictPassed
implementation.decision.proceed && implementation.verdictPassed

The mutation shows why the duplicate was unsafe rather than merely redundant: a record pairing decision.proceed: false with authorized: true validates — no return schema can hold a derived flag to agreement with its sources — and gating on it advances a declined plan review from 3 turns to 6.

The same two fields carry what terminal was labelling. After a loop, decision.proceed false is a decline; decision.proceed true with verdictPassed false is a review that reached its bound still failing. Neither passes the gate, <Output> tells them apart, and the eventual policy for the second stays unresolved under #290.

On StageResult: I coined it without asking. architecture.md's registry requires permission first. It is removed and not proposed.

<Issue> keeps its corrected position — after the checkpoint, inside the approved path.

Discrimination — real documents, effects stubbed at their specified shapes

Scenario Agent turns commit PR issues Result
Plan review declined 3 0 0 0 no authorization question, no implementation turn
All approved 8 1 1 1 accepted
PR review declined 7 1 1 0 no deferred issue, no acceptance question
Failing verdict, approved 12 1 1 1 revision turn; reaches implementation only after the later passing verdict
Planning exhaustion 20 0 0 0 proceed=true passed=false; reaches neither
PR-review exhaustion 24 5 5 5 proceed=true passed=false; acceptance never reached

Mutations, each changing an observed result:

Mutation Effect
Gate on a duplicated authorized field declined plan review advances, prompts 3 → 6
Declare the handle as string Return validation failed — after commit and issue effects have run
<Issue> moved ahead of its approval declined PR review goes issues 0 → 1

Verification

Probes rerun: 9 frontmatters parse, 5 embedded schemas compile, 21 shipped + 5 repository names resolve, 9 missing unresolved. Sweep clean, including StageResult. #327 and #328 classifications retained.

Local gates: fmt, lint (0 errors), check, test (349 passed, 0 failed), check:jsr, tsc. Docs updated: start.md, artifacts.md, the specification, the data-flow table, and the PR description.

All ten checks green: composability, deploy, jsr, lint, review, site, smoke, test-bun, test-deno, test-node.

taras added 28 commits August 22, 2026 13:15
Rebased onto 511776e. #326 closed #203 by proving the two projection
lifetimes with real modifiers — L8 for a projected `persist` resource and
Q14 for a projected daemon — which were the acceptance criteria this
target had the weakest evidence for. Projected content keeping its
caller's bindings while its live effects belong to the component
invocation is now stated as shipped, along with why it matters: a process
a stage starts stops before the workspace it ran in is removed, which is
the ordering `<Worktree>` (#293) is built on.

`architecture.md` is authoritative on the error model, so the `output`
row reads "fails the run; `<PrintErrors>` can print instead" as the
registry states it. The engine does not do that yet — an outer boundary
ends the run instead, whether the failure arose in the region itself or
in content projected into it — so the discrepancy is classified as
missing and filed as #327 rather than allowed to redefine the settled
contract. No stage writes `<PrintErrors>`, so nothing here depends on it.

The distinction the target does keep is the one the registry makes: a
`throw` decision is the mode a boundary cannot replace. Every stage's
parsing sits in documentation, so the final `<Parse>` is a gate for that
reason and not because printing is unavailable.
…entory

`<UserCheckpoint>` captured `elicitation.proceed` and then returned only
rendered prose, so a caller had nothing to branch on. `start.md` invoked
`Planning` and `Implementation` unconditionally and the two stages
branched on the planner verdict alone: a user answering `proceed: false`
could not stop anything. That contradicts the central user-authority
contract and #290's acceptance.

`<UserCheckpoint>` now declares `returns` and binds a schema-validated
decision — `proceed` plus the assessment fields a later prompt quotes.
One `decisionSchema` serves both paths: `<Elicit>` binds it when the
assessment reports a material choice, and an explicit `<Parse>` binds
`"proceed": true` with its reason when there is none, so continuation is
represented rather than inferred from absent elicitation data. Every
affected transition is gated — a declined handoff does not start
Planning, a declined authorization does not start Implementation, a
declined review neither revises nor accepts, and a declined acceptance
finishes as rejected rather than falling into the accepted branch.

Nesting is what the language supports; it is not stopping. The run still
expands to `<Output>` and completes, with no clean halt at a stage
boundary and no stop reason recorded for one. That is classified as
missing under #298 over #289 rather than described as enforcement.

Two engine limitations surfaced while proving this and are filed rather
than worked around silently. `<Content />` is substituted only at a body's
top level or directly inside `<Output>`, never nested inside a `<Prompt>`
(#328) — `Discovery` and `UserCheckpoint` used exactly that shape and
could not expand, so both now take the material as a prop. And a binding
made inside a Markdown component's projected content does not escape to
its caller, while the same binding inside a registered function component
does; the harness had to account for that to test the real components.

artifacts.md claimed every listed result was JSON declared inline. It is
corrected into three kinds: `PlannerHandoff` and `ImplementationPlan` are
prose with no schema and cannot gate anything; the four parsed results are
described using only fields their inline schemas declare; and
`UserDecision` is the checkpoint's declared return, with the actor and
timestamp that would make it auditable marked missing under #291.

Discrimination over the shipped subset, with a turn-recording stub agent:
a declined checkpoint records 1 agent turn and neither stage runs; the
authorized path records 2 and both run; a no-material-choice assessment
records 2 and proceeds on its explicit `<Parse>` branch while a declining
answer in scope is never consulted.
`Planning` and `Implementation` consumed their internal checkpoint
decisions and returned only prose, so authority leaked at the component
boundary. A declined plan review broke the planning loop, but `start.md`
still asked the authorization question and could reach `Implementation`;
a declined pull-request review still let the change be reported accepted.
`Implementation` also created deferred `<Issue>` effects before its
review checkpoint ran, so the planner's proposed disposition became a
durable GitHub object before the user approved it. The earlier probe
stubbed the outer stages, so it never exercised those paths.

Both stages are now value components. Each returns its prose, the parsed
verdict's fields, the complete `UserDecision` it resolved, and two derived
control fields the caller gates on: `authorized`, which is
`proceed && verdict.passed`, and `terminal`, which distinguishes
`converged` from `declined` and `exhausted`. A stage cannot approve its
own advancement, and an exhausted loop reports itself distinctly and fails
closed — which is not a decision about what exhaustion should do, still
unresolved under #290.

`start.md` gates on those returns: authorization is requested only after a
passing plan verdict and an approving plan-review decision, acceptance
only after a passing pull-request verdict and an approving review
decision. It renders the human-readable reports from the returned fields.

Deferred `<Issue>` creation moved inside the approved branch, after the
checkpoint. `proceed: true` authorizes the exact proposed transition and
effects; the free-text response records reasoning and never amends effects,
because an effect that has already executed cannot be amended by prose.
`proceed: false` performs none of them.

Discrimination against the real `Planning`, `Implementation`, and
`UserCheckpoint`, stubbing only the agent transport and the three
deterministic components that do not exist. A declined plan review: 3
agent turns, no authorization question, no implementation turn, no
effects. A declined pull-request review: no deferred issue, no acceptance
question. A failing verdict with approval performs the revision turn and
reaches the next stage only after a later passing verdict. Exhaustion
reaches neither. Moving `<Issue>` ahead of approval creates the issue
despite a decline; bypassing a caller gate advances past a declined
review; reducing either return to text leaves the gate fields unresolved.
Each mutation changes an observed result.
Two contract problems in the replacement for the authority leak.

`Implementation` declared `pullRequest: { type: string }` and returned the
raw binding. #295 specifies a structured handle — number, URL, head and
base identities, state, reviews, comments, checks — so a conforming
`<PullRequest>` would perform its durable effects and only then fail this
component's return validation. The previous harness stubbed the handle as
a string, which is exactly why the probe stayed green. `start.md` consumes
none of it and the artifact ledger records the effect and handle
independently (#291), so the field is gone and `report` renders the named
fields a reader needs. If a later caller needs the handle it gets #295's
object schema, never a placeholder.

Both stages also returned `authorized` and `terminal`, duplicating
`decision.proceed && verdictPassed` and a second reading of the same two
fields. A return schema can require both sources to be present but cannot
require a derived flag to agree with them, so a record pairing
`proceed: false` with `authorized: true` validated — and `authorized` read
confusingly next to the separate authorization checkpoint. Both are gone,
along with the `StageResult` name, which was coined without the permission
architecture.md's terminology registry requires; it is not being added
there.

`start.md` now computes each gate where it uses it, from the authoritative
pair. After a loop, `decision.proceed` false is a decline and
`decision.proceed` true with `verdictPassed` false is exhaustion; the gate
refuses both and the output tells them apart, while the terminal policy
stays unresolved under #290. Deferred `<Issue>` creation keeps its
corrected position after the checkpoint, inside the approved path.

Discrimination reruns against the real documents with effects stubbed at
their specified shapes. Declined plan review: 3 turns, no authorization
question, no effects. Declined pull-request review: no deferred issue, no
acceptance question. Fail-then-pass reaches implementation only after the
passing verdict. Planning exhaustion and pull-request-review exhaustion
each reach neither. Mutations: gating on a duplicated `authorized` field
advances a declined review (3 turns becomes 6); declaring the handle as a
string fails return validation after commit and issue effects have already
run; moving `<Issue>` ahead of approval creates it despite the decline.
Two inconsistencies left by the return-contract fix.

The migration to value components was described in some places and not
others. The specification still said `Planning` and `Implementation`
return derived `authorized` and `terminal` fields, which they stopped
doing. And the specification, `start.md`, and `primitives.md` all still
described every component body as split by `<Output>` into documentation
under `throw` and a region under `output`. That is true only of the text
components: a value component declares `returns`, renders nothing, cannot
contain `<Output>` at all, and runs its entire body fail-fast, binding
nothing when it fails. Each document now says which kind it is talking
about, and `Planning`, `Implementation`, and `UserCheckpoint` describe
their own bodies as value bodies.

The pull-request reviewer could not see what it was reviewing. #295
resolves reviews, comments, and checks alongside the identities, and the
prompt rendered only number, state, URL, head, and base — while
`runtime.md` denies agent network access, so the planner had no way to
recover the rest. An adversarial review that cannot see a failing check or
an existing objection is uninformed rather than adversarial. Every
category is now rendered explicitly into the review prompt and into the
checkpoint material the user reads, each collection iterated with `<Each>`
rather than stringified, and the prompt names the revision under review:
the diff at `headSha` against `baseSha`, with a verdict describing that
head alone.

The member field names inside reviews, comments, and checks are #295's to
settle; these documents depend on that schema rather than defining a
competing one. What is settled here is that the planner receives the
complete snapshot. The handle stays internal — `start.md` gates on the
verdict and decision — so the earlier claim that nothing consumes it was
wrong, and the PR description is corrected too.

Discrimination adds a structured `<PullRequest>` stub carrying non-empty
reviews, comments, and checks with distinct markers. It captures the
actual rendered planner prompt and the checkpoint material and asserts
every category, both head identities, and the diff instruction reach them.
Omitting any one category reddens exactly its own assertions and leaves
the others green. Nothing in the test uses agent network access. The six
gate scenarios and the three earlier mutations still hold.
…on text

Three claims did not match what the documents do.

`artifacts.md` still said nothing consumes the pull-request handle. The
boundary is now stated exactly: `Implementation` consumes all of it
internally, `start.md` never receives it — what crosses the stage boundary
is the verdict and the decision — and the artifact ledger records the
effect independently (#291).

The specification said both text components do their work in
documentation. Only `Discovery` does. `InstructionFiles` puts its `<Each>`
and `<File>` reads inside `<Output>`, and probing that case corrected a
second assumption in the same sentence: `<File>` prints its own failures,
so an unreadable instruction file is decided as a printed error before the
region's `output` mode sees it. The run continues; what stops the caller
is the binding rule, since `as` refuses a body holding a printed error and
`instructions` stays unbound. `start.md` gains the same qualifier, because
"an undecided error fails the run" is only true of undecided ones.

The user checkpoint did not carry what the PR claimed it did. The planner
prompt rendered each review's body; `checkpointMaterial` rendered only the
author, state, and head. A user approving a change has to read the original
objection, not the planner's summary of it, so the body is now in both. The
earlier probe missed this because it asserted the review *author* on the
checkpoint material and the *body* only on the prompt — an assertion too
weak to see the omission it was meant to cover.

Discrimination now asserts the body on both surfaces. Dropping any category
reddens both symmetrically, and dropping the body from the checkpoint alone
reddens the checkpoint while the prompt stays green — the exact shape of
the defect.
`checkpointMaterial` presented the pull request and the verdict but not
the two things approval actually sets in motion. When a verdict fails,
approval sends `verdict.revisionPrompt` to the implementor, and the
checkpoint never showed it. A `defer` disposition sends the complete
finding to `<Issue>`, including `finding.evidence`, and the checkpoint
showed only the title, disposition, and description.

That contradicts the document's own rule. `proceed: true` authorizes the
exact transition and the exact effects presented for assessment, which
means nothing if the material omits them: a user could approve revision
instructions and issue evidence they had never read. `Planning` already
showed its revision prompt; `Implementation` did not.

Both are now in the material, unchanged and unsummarized — the literal
`verdict.revisionPrompt`, and each finding's evidence rendered beneath it
with a nested `<Each>`. The explanatory text in the component and the
specification says why, rather than asserting that approval covers effects
it never displayed.

Discrimination gains distinct markers for both. The baseline asserts they
reach the checkpoint, that the revision prompt the implementor receives
carries the same value, and that `<Issue>` receives the same complete
finding — the stub now records what it was passed. Removing either field
from the checkpoint reddens exactly its own assertion while the effect
assertions stay green, which is the shape of the leak: the effect still
fires, the user just never saw it.
The target described a `<Workflow>`/`<Sandbox>`/`<Stage>` spine over a sidecar
Git ledger at `refs/xmd/runs`, an implicit worktree, an implementor that edits
files, and a `<PullRequest>` handle carrying reviews, comments and checks. None
of that is the architecture any more, and three of those constructs were
rejected rather than deferred.

The command selects the environment: `xmd workflow start` creates the run and
its one retained Workspace, and the root document is the workflow. Named
`<Repository>` and `<Worktree>` compose the checkouts from ordinary root props,
so nothing resolves through a hidden alias and no single worktree is implied.
Agents are read-only under that host ceiling, so the implementor returns
constrained XMD that `<Expand>` admits and expands as ordinary durable effects,
and `<Git.Add>`, `<Git.Commit>`, an explicit `<Git.Push>` and `<PullRequest>`
follow in that order. Waiting for the user is a durable suspension that `resume`
continues, which is what `<Stage>` was reaching for.

Retention is no longer speculative. #291 and #365 are shipped foundation: the
run retains its filtered journal, Workspace roots, and metadata, and one
expansion publishes its mutation, logical root and journal result in one
transaction. `JournalProvenance` (#425) is what makes that history evidence
rather than storage, and it is named where the documents discuss retained
evidence. Every remaining capability is marked as target behavior against
#218's dependency order.

Two gaps are now stated rather than assumed away. #295 moved reviews, comments
and checks out of the creation result, and no issue owns the forge read that
returns them to a network-denied reviewer; and the journal records which run
and expansion produced a decision but not which person answered.

Props are namespaced throughout (#305), `<Content />` projects at any depth
(#328), and the obsolete asymmetry notes are gone.
The living target still described `xmd workflow start` and `resume` as unbuilt
and told the reader to wait for them. They exist: a run is created from
committed Git bytes, streams its document in the foreground, retains its
filtered journal and its commands' results, and resumes from the frontier. So
does the retention beneath them, and namespaced props.

Marking them shipped exposes what is still missing above them, and the target
now says it: the run's Workspace holds no repository until named checkouts
exist, and a pinned definition installs no component search path, so the stages
beside it resolve to nothing under that command and are exercised under
`xmd run`. The nine names that resolve to nothing are listed individually, each
against the issue that owes it.

Three settled contracts arrive with them. Routing, retention and failure are
separate decisions about a command, and a bound block turns an exit status into
data that no public modifier middleware can manufacture. Content a caller
projects keeps its own region's error mode, so a component's declaration
governs the component's work and never the caller's text — while the outer
printing boundary that cannot print an `output` decision stays open and
distinct. And canonical core alone brings an execution or an expansion into
being: public middleware may inspect, narrow, refuse and delegate, and returns
nothing anyone reads.
The previous commit reported that a workflow run passes no repository component
search path, so the five stage components resolve to nothing under start and
resume, without saying who owed the fix. Architecture has assigned it to #301,
beside composing the supervised workflow those components belong to.

The three documents that carry the observation now carry the assignment with
it: #301 owes InstructionFiles, Discovery, UserCheckpoint, Planning and
Implementation reachable under start and resume, from component code the
trusted host authorized, accounted for by workflow-definition identity and
retained-history admission, so neither a resume nor a replay substitutes what a
mutable checkout holds now for the retained code.

What is stated is the required outcome, not how to reach it. Ordinary xmd run
resolution is unchanged and workflow execution gains no generic repository
component search path; the mechanism is #301's to design and to take through
architecture review.
The target still described the pre-#453 engine: `print` installed by the root,
so a failure the document did not decide was printed into its own text and the
run carried on. A text root now installs the fail-capable `output` mode for its
whole body, and `<Output>` selects which regions render rather than deciding
whether a failure counts.

The consequence the reader most needs is the instruction files. A failed
`<File>` read is still a component-owned printed error, but the stage cannot
hand back a body holding one: `InstructionFiles as="instructions"` binds
nothing, and the refusal is raised in the root's own body, where it is now the
run's outcome. Discovery never starts and nothing after it does, so an
unreadable AGENTS.md ends this workflow rather than being visible and survivable
as the documents claimed.

#327 stays as it was and stays distinct — an outer `<PrintErrors>` still does
not print a failure a callee's `<Output>` region decided, which is a limitation
of the printing boundary rather than of root settlement, and nothing here
depends on it. #446's ownership rule is untouched: a caller's projected failure
is still the caller's.
The flow's `<Output>` sat inside `<Repository>`/`<Worktree>`. Only a direct
top-level `<Output>` is a declaration — caller content cannot introduce one for
the document it is written in — so canonical structural preflight refused this
document before `<Repository>` was ever resolved, and the entry document could
not run even as far as its own first missing component.

The declaration now opens before `<Repository>` and closes after it, with the
composition, every stage invocation, every gate and the final report inside it.
Nothing else about the flow moves.

Wrapping the whole flow selects no more than it did: every element inside binds
with `as`, and a binding keeps a private buffer, so a stage invocation
contributes nothing to the document and the final-gate `<If>` tree remains the
only thing that renders. The prose around the rendered gate, the checkpoint
behaviour available today, and the root's error modes now say root declaration
rather than describing a nested one as selecting or buffering the root.
The flow bound its worktree with a lexical `<Worktree as="worktree">` and
expected that same invocation to render the stages inside it. Generic `as=` is
an ordinary private capture, and #293 settled that Repository and Worktree get
no exception to it — so that spelling would have captured the whole flow and
left the root's `<Output>` with nothing to emit.

The two existing forms compose instead: a self-closing `<Worktree … as>` creates
or restores the retained checkout and binds its path, and a lexical
`<Dir path={worktree}>` inside the same `<Repository>` makes that path cwd,
renders the stages and the final report, and restores the enclosing cwd. Agent
access is still `<Agent.AddDir>`; cwd never implies it.

`<Dir>` is not built either, so the unresolved inventory is ten names rather
than nine, and #293 owns the boundary this composition consumes.

Reading a run back is no longer future work. `xmd workflow status`, `list` and
`history` report immutable lifecycle snapshots — no lease, no replay, no
Workspace, no append — so the documents say so, and say what history actually
prints: an authored source position rather than a forkability reason it does not
have. Suspension, executor ownership, cancellation and deletion stay #367;
forks and forkability stay #368.
#290 settled the exhaustion behavior these documents still called an unresolved
product decision. Exhausting the bounded attempts is neither convergence nor
ordinary successful completion: it starts no implementation and no later durable
effect, and it asks the user for direction rather than answering on the stage's
own account. A composed workflow turns that request into a durable suspension,
which is #367's and unbuilt.

No return schema changes. The pair a stage already returns — `decision.proceed`
true with `verdictPassed` false — is what makes exhaustion distinct, and nothing
else produces it, so the rule against returning a second copy of a derivable
answer still holds. What changes is that the report asks instead of stopping:
the two exhaustion branches now await direction, while a decline still stops.

The proof #290 asks for begins beside the stages it exercises. `Agent.AddDir` is
#302's and does not exist, so a no-op stub sits in the test directory alone —
repository resolution is scope-local, so every name the inventory calls
unresolved still resolves to nothing from the workflow directory, which the
component probe confirms.

One of the seven acceptance criteria is proven so far: the exact instruction
paths and contents reach the planner prompt, constrained literally in the
scenario so a dropped or summarized instruction fails rather than matching.
Six of #290's seven criteria now execute. `InstructionFiles` reaches Discovery's
prompt and both of Planning's with the instruction paths and contents
constrained literally, so a prompt that dropped or summarized them fails. A
passing verdict returns true/true and sends no revision. A failing verdict sends
its exact review, revision prompt and complete checkpoint record back to the
implementor's own session, as that session's second turn and before the revised
plan. Both bounded repair loops stop where the documents say: an initial
malformed reply and two malformed corrections reach the final `<Parse>`, which
is what the assertion pins — the third reply is the one quoted in the failure,
so no third correction was sent. A material choice reaches the checkpoint, takes
the `<Elicit>` path, and an explicit decline stays `proceed: false`; no material
choice returns the documented continue record word for word.

The seventh is not proven, and the document says so where the coverage would be
rather than only here. Exercising all five failing iterations costs twenty agent
turns in one invocation — as many as every other test combined, which take
thirty-one seconds across seven timeboxes. A test's timeout is a fixed twenty
seconds with no prop that raises it. The scenarios for the run are written and
correct; what is missing is a way to run them. Lowering `max` would prove a
different document than the one the workflow ships.
#290's last criterion needs five failing iterations of `Planning` in one
invocation: five plan prompts, five verdicts, five checkpoint assessments and
the five revisions a failing round sends. Twenty ACP round trips do not fit a
test's fixed twenty-second timeout, and lowering `max` would prove a different
document than the one the workflow ships.

So the run moves to an integration test that keeps the document and replaces the
transport. The real `Planning` resolves from the workflow directory on the same
search path `xmd test` derives, under one execution and one contextual working
directory, against a synchronous stub root Agent provider installed through the
public `rootProvider` seam. It finishes in about half a second.

What it proves is the ordering as much as the outcome: exactly twenty calls,
five of each turn in the order the document writes them, each routed to the
session the document sends it to, every verdict validly failing and every
checkpoint explicitly continuing, and each round's review, revision prompt and
assessment carried into that round's revision. The returned pair is uniquely
exhausted, and the plan, review and revision prompt are round five's.

The root writes `start.md`'s own gate with a later effect behind it. That
prompt never runs, which is what the twentieth-and-final call count says.
Removing `verdictPassed` from the gate makes the count twenty-one and fails the
test, so the assertion discriminates the gate rather than restating it.
Collapsing the calls to one session per turn kind kept only the last write, so
any round but the fifth could have been routed to the wrong session and the
assertion would still have matched. The test compares the complete twenty-entry
`turn:session` sequence now. Routing round two's verdict to the implementor
fails it, and reports that entry — the case the previous shape could not see.

Both casts are gone, and neither needed to be there. `PromptOptions.session` is
`string | Session`, so a `typeof` check narrows it and `sessionKey` is reachable
without asserting a shape. The root's result is `Json`, so `isJsonObject` guards
it where the execution completes and the fields are read from a `JsonObject`
rather than from a shape the test claimed.
Three merges landed under this target, and the inventory moves twice.

#293 is complete. `<Repository>`, the self-closing `<Worktree>` and the lexical
`<Dir>` are registered by `@executablemd/workflow/composition`, so the
composition this document is written against executes under a workflow run. The
unresolved set is seven names, not ten, and each still names the issue that owes
it. What that leaves is a host distinction rather than a build one: registration
is scope-local and the workflow host installs it, so plain `xmd run` resolves
none of the three — evidence about which host ran a document, never about
whether a component exists. The documents say that in as many words, because the
alternative reads as a regression.

#367's second slice is here too. The executor lock owns lifecycle transitions,
so single-executor ownership, atomic begin and settle, cancellation and deletion
are shipped. Cancellation does not signal a live executor: it refuses without
mutation and tells the caller to interrupt the foreground process, and Ctrl-C
tears the scope down in order and leaves the run resumable. Durable suspension
and releasing the executor at a checkpoint stay open, which is still what keeps
a checkpoint answering inside the execution that asked.

The composition itself is untouched — the settled self-closing `<Worktree … as>`
followed by lexical `<Dir path={worktree}>`, with no binding exception — and
#301 still owns reachability for the five stage components.
The previous commit moved the inventories and left three passages behind, each
still describing the state before the merge it was reconciling.

`start.md` called single-executor ownership, atomic lifecycle transitions,
cancellation and deletion unbuilt. They are #466's, owned by the executor lock;
only durable suspension and releasing the executor at a checkpoint remain #367.

`runtime.md` said `xmd workflow cancel` asks an active executor to stop, which
is the one thing cancellation never does. It refuses without mutation against a
live executor and directs the caller to interrupt the foreground process;
without one, an eligible retained state transitions to `cancelled` under the
exact lock; a completed or failed outcome stays authoritative.

The specification called `<Repository>` and `<Worktree>` unimplemented alongside
`<Agent.AddDir>`, which conflated a shipped composition with an unbuilt Agent
boundary, and opened its lifecycle paragraph with "the rest of the lifecycle is
not built" before listing what #466 shipped. Both now lead with what is built
and end with what is not.

The sweeps carry each of those exact forms as a forbidden pattern, self-tested
against the text that produced them, so a regression fails there rather than in
review.
`start.md` now declares its five authored stages in its own frontmatter, and
that is the whole integration — a declaration, not a mechanism. #493 does the
rest: `xmd workflow start` and `resume` resolve exactly those names from the
blobs the pinned definition commit holds, each normalized to a canonical
repository-relative path and the blob's own object ID, and those entries join
workflow-definition identity and retained-history admission. Change what a stage
says and it is a different definition; a resume reconstructs the component from
its retained source, a same-named file beside the definition answers nothing,
and an undeclared name resolves to nothing at all. Workflow execution still
searches no repository directories, and ordinary `xmd run` is untouched.

That closes #301's component-bundle slice and nothing more. Reaching the stage
names is not running the workflow: scheduling the loop and continuing it
unattended are still owed, and this branch adds no scheduler, no automatic
resume, no Agent ceiling, no generated-XMD admission, and no push, pull request
or issue.

The inventory shrinks to five as a result of what else has landed.
`<Git.Switch>`, `<Git.Add>` and staged-only `<Git.Commit>` are built (#294), and
the shared Git-host reconciliation the remote effects are built over is too
(#297) — the surface, not the components over it. What still resolves to nothing
is what reaches a remote or a forge, plus the Agent ceiling and the constrained
evaluator: `<Agent.AddDir>`, `<Expand>`, `<Git.Push>`, `<PullRequest>`,
`<Issue>`.

Durable suspension and giving the executor lock back are shipped, and a typed
answer can be delivered to a run that is waiting — but delivery executes
nothing and no scheduler resumes anything, so the documents say the wait is real
and the continuation is still someone's explicit act.
…alls

The documents had swung from one error to its opposite. They once said durable
suspension was unbuilt; the last commit corrected that and overshot, saying an
`<Elicit>` under `xmd workflow` *becomes* a durable suspension and that these
checkpoints release the executor. Neither is true, and the two mistakes are the
same mistake: reading shipped substrate as a shipped integration.

What is shipped is `suspendFor()` — a run waits, gives its executor lock back,
and a typed answer can be delivered to it (#367, #300). It is an Api operation.
There is no v1 Markdown element that spells it, a suspension is a different
mechanism from `<Elicit>`, which asks inside the execution already running, and
this workflow installs no component or middleware that turns one into the other.
So no checkpoint here releases the executor, delivery executes nothing, and
nothing schedules a resume. Consuming that substrate is #301's supervised
composition, and the documents now say exactly that without inventing a spelling
or a bridge for it.

Two smaller corrections fall out of the same pass. `<Git.Add>` and
`<Git.Commit>` were still listed among names that resolve to nothing, when they
are two of the six registrations the workflow host supplies alongside
`<Repository>`, `<Worktree>`, `<Dir>` and `<Git.Switch>`. And the forge read
that hands a network-denied reviewer a pull request's reviews, comments and
checks is no longer unowned: `<Fetch>` reads over HTTP and retains what it read
(#456). No stage writes those fetches yet, and admitting `<Fetch>` inside
generated XMD stays #369's.

The component probe now asserts four exact categories rather than a set — 16
ordinary, 5 bundled stages, 5 issue-owned unresolved, 6 workflow-host-only — and
fails if any component the documents write goes unclassified or lands in the
wrong group.
Two passages still described `suspendFor()`'s work as missing, and my sweep let
them through because it only recognised the word "unbuilt". They said it another
way: "durable suspension, and releasing the executor at a checkpoint, remain
#367" in `start.md`, and "What is not built is the wait" in the specification.

Both are corrected the same way the rest of the target already reads. The wait
is built — a run suspends durably and gives its executor lock back (#367), and a
typed answer can be delivered to it (#300). What is not built is anything that
reaches or acts on it: no v1 Markdown element spells the operation, this workflow
calls nothing that suspends, delivery executes nothing, and no scheduler resumes
a run. `start.md`'s "waiting is a suspension" section now says that at the point
where a reader would otherwise conclude its checkpoints already suspend.

The sweep is broadened to the phrasings it missed — "remain #367", "not built is
the wait", "still missing is the wait", and an elicitation that "is to record its
pending request" — each self-tested against the sentence that produced it, and
each failing against the commit they were found in.
"Answered inside the execution that asked it" reads as a settled outcome, and it
is not one. The workflow host installs the same WebForm provider `xmd run` does
— the gate is `!mode.testing`, and the workflow path passes `testing: false` —
so an unanswered `<Elicit>` opens a loopback browser form and blocks the run.

A probe against a committed workflow root says so directly. `workflow start` on
a root with an unanswered `<Elicit>` printed a form URL and was still running
when the probe killed it at twenty-five seconds; its status stayed `running`,
its history recorded no suspension request, and it never settled `suspended` or
gave the executor lock back. The same root with an authored `<Answers>` value
completed normally.

So the four documents now say that: `<Elicit>` is ordinary live elicitation,
distinct from `suspendFor()`; `xmd run` and the workflow host both install
WebForm; an `<Answers>` region answers without a person; and an unanswered one
blocks rather than suspending, publishing neither a validated answer nor a
suspension request. The target stays explicit — #301's supervised composition
must consume `suspendFor()` without inventing a v1 Markdown spelling.

The sweep now forbids "answered inside the execution" and requires the provider,
the blocking, and the absent `suspended` settlement in its place.
#302 settles that a workflow Agent receives no Workspace checkout, no read-only
materialization of one, no Workspace or host path as cwd, no ACP
`additionalDirectories`, and no component that registers a directory. These
documents described the opposite in a dozen places: `<Agent.AddDir>` in four
stages, a `worktree` prop threaded from the root into three of them, prompts
telling agents to investigate a registered checkout, and an inventory listing a
directory component as work still to do.

All of it is gone. The stages take no `worktree`, the test stub is deleted, and
the prompts say what is true — each agent reasons over what its prompt renders
and says plainly what it cannot determine. `InstructionFiles` is the concrete
case that survives: host-authored file effects produce exact repository-relative
paths and contents, and that captured result reaches Discovery and both Planning
prompts as data. General repository observation stays the bounded XMD
request/result loop #302 and #369 owe, and this change invents no spelling for
it.

The boundary is now proved, not asserted. The root-provider stub records the
whole Agent-facing surface — factory options, agent and session selection,
prompt text and prompt options — and a root that reads real instruction files
proves the paths and contents arrive while the absolute directory they were read
from does not, along with no Workspace value, checkout path, registration input
or `additionalDirectories` field. Injecting that directory trips the guard, and
summarizing the instructions fails the arrival check.

`<Agent.AddDir>` leaves the inventory rather than moving down it: it is excluded
by architecture, not waiting on implementation. Four names remain owed —
`<Expand>`, `<Git.Push>`, `<PullRequest>`, `<Issue>` — and #293's Worktree
identity, replay and cleanup question moves from Open to Settled, where the
shipped contract belongs.
Seven capabilities merged under this document's base, and four of them change
what it may say in the present tense.

`<Git.Push>` and `<PullRequest>` are registered workflow-host components now
(#495, #500, #504), so they leave the unresolved inventory and join the built
list beside the composition and the local Git effects. Push publishes the
selected checkout's exact current branch and commit to the same branch at
canonical `origin`, with no props, no result, no force-push, no upstream change
and no implicit stage or commit. The pull request requires that Push's own
matching successful evidence and never publishes anything itself. Eight names
are host-registered; two — `<Expand>` and `<Issue>` — still resolve to nothing.

The upsert is settled, and this markup does not reach it. Without a number
`<PullRequest>` asks for one to exist; with a number it asks for that one to say
this. A revision iteration commits again, so the head it asks about is not the
head the existing pull request holds, and an unnumbered request over an open
pull request saying anything else is a deliberate conflict. Carrying the bound
number into the next iteration is a composition this document cannot write
today, so the markup stands and the gap is recorded under #301 rather than
spelled by guesswork.

#497 shipped the evaluator, for observation. Complete preflight inside the
admission, pinned identities that consult no directory or bundle, exact request
ceilings, one retained decision, and a continuation held to the ceilings it was
admitted under. What it does not admit is file mutation — so the prose that had
`<Expand>` publishing each admitted `<File>` now says which half is built, and
`<Expand>` itself stays a documented placeholder #369 owes.

#498 shipped explicit history forks, so forkability stops being described as
absent: `history --forkable` adds its two columns, and lineage, reasons,
changed-definition replay admission and retained Workspace-root copying come
with it. A run continuing another run's history is still a different thing from
an Agent provider continuing a session, and nothing schedules either.

A `<Test>` declares its own timeout (#503), so twenty seconds is a default
rather than a ceiling. The two comments that justified the TypeScript proof by
that ceiling now give the reason that actually holds: a stub provider makes the
whole twenty-call trace observable in under a second, where a long ACP scenario
would take longer to prove less. `<Execution host="run">` (#501) is inherited
and unmentioned — no document here discusses nested root execution.
#301's architecture amendment settles the composition the revision loop was
missing. An expression prop that evaluates to `undefined` is omitted before prop
validation and before the durable JSON boundary, so a document can seed a
binding with an empty object and say the same thing on every iteration.

The loop does that now. `let pullRequest = {}` is declared once, before the
loop; the single `<PullRequest number={pullRequest.number} …>` inside it reads
`undefined` on the first pass, so `number` is omitted and the unnumbered
create-or-adopt contract applies; the result replaces the seed through ordinary
`as` binding, and every later pass carries the retained positive number into the
numbered update contract for that exact pull request. What made this necessary
is unchanged: a revision commits again, so an unnumbered second request would
find a pull request holding a different head and refuse it as a conflict.

Two of the three pieces already work, and the probe checks both rather than
assuming them — a member read on the seed yields `undefined` in an expression
prop without failing, and an `as=` binding shadows the eval-declared name for
later props. The third does not: the engine refuses an undefined prop today
rather than omitting it. So the documents carry the omission as #301's, unbuilt,
and the probe fails if that ever silently becomes true without the prose
catching up.

The amendment is general, and the reference state says so: required props still
fail as missing, `null` stays an explicit value, and an unbound name still
fails. None of it is a `PullRequest` special case, and no engine code changes
here — Planning owes that proof before implementation is handed off.
Two paragraphs in each of three documents contradicted each other. One named a
third enforcement layer — "registered Workspace paths are presented as read-only
filesystem views" — and the next denied that any path is registered at all. The
third layer was the stale half: #302 settles that a workflow Agent receives no
checkout, no materialization of one, no Workspace or host path as cwd, and no
`additionalDirectories` over ACP, so there is nothing to present read-only. Two
layers enforce the ceiling, and the reason there is no third is now the sentence
that used to be the third.

The data-flow table said `worktree` is consumed by "every stage that registers a
directory with an Agent". No stage does. It is consumed by `<Dir>`, and through
`<Dir>` by XMD's own file and Git effects.

The executable inventory disagreed with its own classification. It said four
names in the loop body resolve to nothing and named the three components that
reach a remote or a forge — but only `<Expand>` and `<Issue>` resolve to
nothing. What blocks the stage is now separated by kind: two unresolved names,
one unbuilt engine capability (#301's omission of an expression prop evaluating
to `undefined`, which the seeded pull-request number needs), and two components
that are shipped and merely out of reach behind them. A shipped component behind
an unbuilt prerequisite is unreachable, which is not the same claim as unbuilt,
and the sweeps now hold that distinction open in both directions: the exact
stale sentences fail, and the corrected denials pass.
Four merged capabilities change what this target may say, and two of them change
what it writes.

`<Capture>` left the reserved set with no alias (#528), so every structural
invocation here is `<Let>` — nine of them across five documents, plus the prose
that named the removed directive. The pull-request loop's seed stops being
JavaScript: `<Let as="pullRequest" value={{}} />` binds the empty object
directly, which is the shipped form for exactly this. It does not close #301's
gap and the documents say so — `<Let value>` binds a value, and does not make an
ordinary component prop whose expression evaluates to `undefined` become
omitted, so the seeded `number={pullRequest.number}` still waits on the omission
nothing has built.

The deferred-issue markup was invalid against the shipped contract (#516). It
passed `pullRequest` and `finding` props that do not exist on a component whose
schema is closed. It now writes what #516 actually offers: an
`<IssueTracker url={props.tracker}>` naming the container, a paired
`<Issue title={finding.title} as="deferredIssue">` whose body is the
description, and a result read as exactly `{ url }`. The body carries what a
reader needs to know why the finding was deferred — the finding, its evidence,
the pull request it came from, and the review and checkpoint that classified it.
`tracker` is a new required root prop threaded to the stage; it selects a target
and grants nothing, because the ceiling lives beside the host's credentials.

That moves `<Issue>` out of the unresolved inventory and leaves one name in it.
`<Expand>` is the only component here that resolves to nothing, and the stage
still cannot reach the forge — which is unreachable, not unbuilt.

Authentication and crash inspection are recorded where the documents already
discuss the effects they belong to. Credentials are live host input acquired per
invocation and are not a prop, a retained value, a journal field or anything an
Agent sees (#532). A crashed run is inspected from a private recovered copy,
leaving the authoritative source byte-identical (#521).
@taras
taras force-pushed the agent/adversarial-implementation-workflow-wip branch from 6305c42 to 568f19a Compare August 22, 2026 17:28
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