feat: post-parse handoff to the build-schema agent skill (ADR-0010) - #185
Open
yuanwen-tian wants to merge 5 commits into
Open
feat: post-parse handoff to the build-schema agent skill (ADR-0010)#185yuanwen-tian wants to merge 5 commits into
yuanwen-tian wants to merge 5 commits into
Conversation
The workflow was parse -> hand-author a schema -> extract. The middle step is now offered: after a parse completes on a real terminal, ade asks whether to build an extraction schema with an AI-agent session running the build-schema skill (landingai-ade plugin), collects the extraction intent plus optional baseline schema / ground truth, launches the agent seeded with the parsed job item, and on return names the delivered deliverable/schema.json and offers to run the extract. Also exposed standalone as `ade schema build JOB_ITEM_ID` so any stored parse can reach the same flow. Agents are adapters, not one agent: claude and codex are detected on PATH, a menu choice is remembered as agent.default in config.json, and a custom agent.command entry launches anything else. ade stays network-free -- non-native agents are told to use a local copy of the skill or clone the marketplace repo themselves; ade never manages skill versions. A missing agent CLI fails legibly with install instructions (non-fatally on the offer path, where the parse already succeeded). Gating keeps the machine contract byte-identical: the offer never fires under --json/--id-only, on any non-tty stream, inside an agent host or CI, or with schema_prompt: false. stdout stays payload-only; every prompt rides stderr. Subprocess launch and PATH lookup are injected ports (run_agent, which), so the suite drives the whole handoff offline -- no process is ever spawned in tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an agent-driven schema-authoring handoff between parse and extract.
Changes:
- Adds
ade schema buildand the gated post-parse offer. - Adds agent detection, configuration, prompting, and subprocess ports.
- Documents and tests the workflow and shared CLI re-execution.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/ade_cli/agents.py |
Defines agent adapters and prompts. |
src/ade_cli/schema_build.py |
Implements schema-build orchestration. |
src/ade_cli/parse.py |
Adds the post-parse offer. |
src/ade_cli/main.py |
Registers the schema command. |
src/ade_cli/ports.py |
Adds PATH and subprocess ports. |
src/ade_cli/config.py |
Adds atomic configuration persistence. |
src/ade_cli/update.py |
Exposes shared re-exec arguments. |
src/ade_cli/view.py |
Uses the shared re-exec helper. |
src/ade_cli/help.py |
Documents command and output shape. |
tests/test_schema_build.py |
Tests schema-build behavior. |
tests/test_parse.py |
Tests post-parse gating. |
tests/conftest.py |
Adds subprocess and PATH fakes. |
tests/test_update.py |
Tests shared re-execution. |
tests/test_telemetry.py |
Adds schema-build telemetry coverage. |
README.md |
Documents schema building. |
SKILL.md |
Guides agents toward direct skill use. |
docs/reference/help.json |
Regenerates command reference data. |
docs/adr/0010-post-parse-agent-handoff.md |
Records the handoff design. |
Suppressed comments (4)
src/ade_cli/schema_build.py:267
- In
--jsonmode this attached child still inherits stdout (ports.py:56-60), so output from Claude, Codex, or a custom agent is emitted before the JSON object. That breaks the repository's one-object stdout contract; the recorder used by this test suite cannot expose it. Route the agent child's stdout to stderr in JSON mode while keeping it attached to the terminal, or reject--jsonfor this flow.
exit_code = ports.run_agent([*agent.command, prompt], cwd=workspace)
src/ade_cli/schema_build.py:296
extract_hintis documented as ready to run, but the schema path is interpolated without shell quoting. An ADE home or explicit workspace containing spaces makes the emitted command split the path into multiple arguments. Build the hint with platform-appropriate command-line quoting.
extract_hint = f"ade extract {ref} --schema {schema_path}"
src/ade_cli/schema_build.py:248
- A relative
--workspaceis also passed as the child'scwdand embedded unchanged in the seeded prompt. From inside that child, a workspace such asruns/demothen resolves toruns/demo/runs/demo, so the skill can write outside the directory that this code later checks. Normalize the workspace to an absolute path before creating it and building the prompt.
workspace.mkdir(parents=True, exist_ok=True)
src/ade_cli/schema_build.py:259
- CLI-supplied and interactively entered schema/ground-truth paths may be relative to the caller's directory, but the agent runs with
cwd=workspace. Passing those paths unchanged makes the agent resolve them under the workspace and miss valid input files. Resolve both paths before placing them in the prompt.
existing_schema=existing_schema,
ground_truth=ground_truth,
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…s seam Unit tests (offline) for the paths the first pass left uncovered: workspace slugging and reuse, --workspace, URL-sourced parses, the --existing-schema/--ground-truth flags, a mistyped optional path, non-default environments in the seeded prompt, an unlaunchable agent, extract exit-code propagation, both menu fallbacks (raw-mode failure and TERM=dumb), agent_ambiguous under --json, and unknown ids. Integration tests drive the one seam the offline suite fakes: a real agent executable found on PATH and spawned attached to a real pty, then ade's own re-exec running the delivered schema against production. The chained extraction reuses the direct extract test's schema and parse item, so it dedups to that item and the added coverage bills nothing. POSIX-only (pty); skipped on Windows. Fixes a bug these found: run_build read config.json unguarded, so an unreadable or corrupt file crashed the handoff -- offer_after_parse already guarded it. Both now go through one tolerant reader: the agent preference and the prompt opt-out are optional, so a bad config costs the preference, never the handoff or a completed parse. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Building a schema was the only door the offer opened, but the other thing a human does with a fresh parse is look at it, and a bare "Exit" taught nothing. The menu now offers three choices: build a schema, open the viewer, or exit. Exit stays last and default, so bare Enter still costs no keystroke of the pre-offer behavior -- but it is no longer a dead end. Exiting (and aborting with Esc, and finding no agent installed) restates both commands with a runnable short id, because by then the summary's next: line has scrolled behind a menu and the person who chose Exit is exactly the one who needs them back. The viewer choice rides the same subprocess-of-self re-exec as the extract handoff, so it inherits the terminal and the run ends as `ade view` ended. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`schema build` looks free -- it makes no API call itself -- but the session it launches does: the skill proves every schema version with a real extract run, so a converging loop costs roughly one extraction per iteration per document, not one at the end. That was only discoverable by reading the skill or the invoice. Now stated on every surface a user or agent meets before spending: `ade schema build --help`, the published result note in `ade help --json`, the `workflow` topic, and -- the moment it actually matters -- the launch notice printed just before the agent session starts. The final offered extraction is called out as a normally-free cache hit so the disclosure is not read as double-billing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…alone Copilot review catch (#185): the default workspace was <slug>-<date>, so two different documents both named invoice.pdf on the same day shared one workspace -- and the second run could find the first one's deliverable/schema.json and report schema_built for a schema its agent never produced. The item id (a content hash of source x content x params) now keys the default: <slug>-<item-id[:8]>-<date>. Different documents can never collide however they are named; the same document re-parsed maps to the same id, so resuming an interrupted run still works. An explicit --workspace remains the caller's own choice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lai-bianca
added a commit
that referenced
this pull request
Aug 11, 2026
> **Do not merge until [landing-ai/docs#377](landing-ai/docs#377) is live.** Eight links in this README point at `docs.landing.ai/cli/...`, which 404 until that PR deploys. ## Why The README was a 2,767-word manual with a section per verb, written when it was the only documentation the CLI had. `docs.landing.ai` now has an eight-page CLI tab, and its reference page regenerates from this repo's `docs/reference/help.json` on every release. That left two copies of the same material, one of which regenerates and one of which does not. The hand-maintained copy had already drifted: the version-pin example said `ADE_CLI_VERSION=0.2.1` against a shipped 1.0.5. ## What this does Trims the README to what this repo owns and a GitHub visitor needs, and links the docs for depth. **Kept** - What `ade` does, in one paragraph. - **Install, in full.** The installer scripts live in this repo, so this stays the canonical home: platform installers, where the app lands, `ADE_HOME`, `ADE_CLI_VERSION`, `ADE_CLI_INSTALL_DIR`, the never-`rm -rf ~/.ade` warning, `ade update`, `ADE_NO_UPDATE_CHECK`. - A three-command first run. - One table, one line per command, no prose. - `For agents`: `ade help --json` and the `SKILL.md` pointer. - The `agentic-doc` lineage, contributing, license. **Moved to the docs** (auth details, job items and the guarantee, parse and extract prose, view/crop/find prose, `--json` and piping, help topics). Each now has a link to the page that owns it. **Two defects fixed** - Two sections were both titled `## History`, one for the command and one for the repo lineage, so their anchors collided and the rendered nav showed "History" twice. The second is now `## Project history`. - The top documentation link pointed at the docs root rather than the CLI documentation. 2,767 words to 821. 335 lines to 133. ## Verification - All 18 commands in `docs/reference/help.json` appear in the command table, and no others. - Every link returns 200 except the eight `docs.landing.ai/cli/` paths noted above. I confirmed each of those eight matches a real page filename in the docs PR, so they resolve on deploy. - `ADE_CLI_VERSION` example now matches the shipped version. - No duplicate headings. - `LICENSE`, `SKILL.md`, and `CONTRIBUTING.md` all exist at the paths linked. ## Notes for review - **This conflicts with #185**, which adds a 19-line `## Build a schema` section to the same file. Whichever merges second resolves it. In the new structure that section belongs after `First run`. - I deliberately did not document `ade schema build` here. It is not on `main`, and the skill it links in #185 lives in `landing-ai/claude-skills`, which is private, so a public README linking it would 404 for every reader. - This does not depend on #166. That PR adds `docs/agents/writing-style.md` but does not touch `README.md`. I wrote this against those standards anyway. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
The ADE workflow is three steps, but ade only shipped two of them:
Authoring a schema by hand means guessing field names and descriptions, running an extraction, eyeballing the output, and repeating — with no measurement of whether it actually got better. Meanwhile the build-schema skill (the
landingai-adeplugin, mirrored fromschema-loop) already does this properly: it drives this very CLI through measured iteration — real extractions, reviewer-confirmed ground truth, per-field accuracy targets — and stops only when converged with numbers or blocked with evidence. It delivers the final schema at<workspace>/deliverable/schema.json.The two just didn't know about each other. A user had to know the skill existed, install the plugin, and wire up the handoff themselves.
What this PR does
Closes the loop, in both directions, so the middle step is offered rather than assumed.
The three choices are the two things a human actually does with a fresh parse, plus the way out. Exit is last and the default, so bare Enter behaves exactly as before this PR — but it no longer teaches nothing: exiting (and aborting with Esc, and finding no agent installed) hands back both commands with a runnable short id, because by then the summary's
next:line has scrolled behind a menu.Everything above is also reachable directly as
ade schema build JOB_ITEM_ID, so a parse from last week gets the same flow.How you actually use it now
Before this PR:
After — the guided path. Just parse, and say yes:
After — the deliberate path. Same thing, on any stored parse, with inputs supplied up front:
ade schema build 849a8c60 \ --intent "invoice number, total, and the line-item table" \ --existing-schema ./v1.json \ --ground-truth ./golden.csvNothing changes if you already have a schema.
ade extract --schemais untouched, and the offer never appears in scripts, pipes,--json, CI, or inside an agent session.Command usage
ade schema build JOB_ITEM_ID--intent TEXT--json.--existing-schema PATH--ground-truth PATH--workspace PATH<ade-home>/schema-runs/<source>-<date>/; pointing at an existing one resumes that run.--agent NAMEclaude,codex, or a config-declared name). Defaults to the configured or only available one.--jsonade parse …ade schema build <ref>in thenext:hints.ade view <id> --openade extract <id> --schema <deliverable/schema.json>What it costs
ade schema buildmakes no API call itself, but the session it launches does — and per iteration, not once. The skill validates every schema version with a realade extractrun, so a converging loop costs roughly one extraction per iteration per document. The extraction offered at the end is normally a free cache hit, since the loop's last iteration already ran it.That was previously only discoverable by reading the skill or the invoice. It is now stated on every surface a user or agent meets before spending:
ade schema build --help, the publishedresultnote inade help --json, theworkflowtopic, and the launch notice printed immediately before the agent session starts.ade history listitemises every run the session billed.Configuration (
~/.ade/config.json){"agent": {"default": "claude"}}{"agent": {"name": "aider", "command": ["aider", "--message"]}}{"schema_prompt": false}ade schema buildstill works.Result payload (
ade schema build --json){ "status": "schema_built", "job_item_id": "849a8c60463ea7db", "agent": "claude", "workspace": "/Users/you/.ade/schema-runs/invoice-2026-08-10", "schema": "/Users/you/.ade/schema-runs/invoice-2026-08-10/deliverable/schema.json", "extract_hint": "ade extract 849a8c60 --schema /…/deliverable/schema.json", "extracted": false }extractedis alwaysfalse: the extraction is offered interactively, never performed silently on your behalf.Which agents work
claude)landingai-adeplugin. Best experience.codex)SKILL.md.agent.commandinconfig.json.Detection is a PATH lookup. If both known agents are present you get a one-time menu; the choice is remembered. ade never fetches, pins, or updates the skill — that stays the agent's job, so this PR adds no network surface and no version coupling. No agent installed at all fails legibly with install instructions (and non-fatally after a parse — the parse already succeeded and its
next:line still serves).Why it can't surprise a script or an agent
The machine contract is byte-identical. The offer fires only when all of these hold:
--json, not--id-onlysurface.pyreports no agent host and not CI — an agent runningade parsemust never meet a menuschema_promptis notfalseBare Enter selects Exit (last in the list), so the default costs zero keystrokes of the old behavior. Esc/Ctrl-C declines and the parse still exits 0. stdout stays payload-only; every prompt, notice, and menu rides stderr. A door that cannot open (no agent CLI installed) is non-fatal — the parse already succeeded and billed; a door that opens and then fails ends with that command's exit code.
Implementation notes for review
run_agent,which) on the existingPortsdataclass, so the offline suite drives the entire handoff without spawning anything.view._reexec_argv(frozen-binary aware) was hoisted toupdate.reexec_argvand is now shared with the viewer daemons.deliverable/schema.jsonis the only thing ade assumes about the skill's output — the single place to follow if that layout ever changes.agents.pyis pure (no typer, no I/O), so the seeded prompt is unit-testable as a plain function.Testing
786 passed, 10 skipped (was 765/8 on main) — 21 new offline tests, 2 new integration tests.
Offline unit coverage (
tests/test_schema_build.py,tests/test_parse.py): the seeded prompt's contents and per-agent skill-acquisition wording; agent detection, the remembered default, custom commands, and--agentprecedence; workspace slugging, reuse/resume, and--workspace; URL-sourced parses; the optional-path flags and a mistyped path re-prompting; non-default environments; deliverable found/missing; the--jsoncontract; extract accepted/declined and exit-code propagation; an unlaunchable agent; both menu fallbacks (raw-mode failure andTERM=dumb);agent_ambiguous; unknown/wrong-kind ids; and every gate on the post-parse offer (--json,--id-only, non-tty,CLAUDECODE=1,CI=1,schema_prompt: false, Esc, corrupt config). The offer's three choices each have coverage: the schema handoff, the viewer re-exec and its exit-code propagation, and Exit restating both commands with an id that still resolves. Two tests pin the credit disclosure — that it reaches stderr before the agent launches, and that both help surfaces carry it.Integration coverage (
tests/integration/test_production.py): the one seam the offline suite fakes. A real agent executable is placed on PATH and spawned attached to a real pty, the deliverable is written by that real process, and ade's own re-exec runs the delivered schema against production. The chained extraction deliberately reuses the direct extract test's schema and parse item, so it dedups to that item — the added coverage bills no extra credits. A second test covers the PATH-lookup failure. Both are POSIX-only (pty) and skip on Windows.ruff check src testsanduvx ty check srcare clean;docs/reference/help.jsonis regenerated.A bug the new tests caught
run_buildreadconfig.jsonunguarded, so an unreadable or corrupt config crashed the handoff — even thoughoffer_after_parsealready guarded it. Both now go through one tolerant reader: the agent preference and the prompt opt-out are optional, so a bad config costs the preference, never the handoff or a completed parse.Docs
docs/adr/0010-post-parse-agent-handoff.md— the decision and its constraints (gating, adapter table, config shape, subprocess-of-self extract, network-free posture), citing ADR-0002 for the menu pattern.SKILL.md— tells agents to invoke the build-schema skill directly rather than shelling out toade schema build.README.md— a user-facing "Build a schema" section.ade help workflowandade help schema buildboth teach the new step.Known gap
The
codex "<prompt>"launch shape follows its documented CLI but is untested against a real Codex install — I had none available. Claude Code's path is verified end-to-end. Worth a smoke test from anyone who has Codex before we advertise it broadly.🤖 Generated with Claude Code