feat(cli): surface the harness golden path (af wait, catalog, golden_path help, JSON envelopes)#816
Merged
Merged
Conversation
`af call --async` printed a bare run-id string on stdout for every output
format, so a harness parsing `-o json` got a non-JSON token. Under an
explicitly requested machine format (-o json/-o yaml) it now emits
{"run_id": "...", "status": "accepted"} so parsers get valid JSON/YAML.
The default and pretty paths keep the bare run-id line that shell scripts
capture via RUN_ID=$(af call node.reasoner --async), so that contract does
not regress.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The machine-friendly `af agent help` payload taught discovery and
introspection but never execution: quick_start omitted `af call` and
`af tail` entirely, so a harness could find agents but was never shown how
to run one.
Add a `golden_path` field — an ordered array of {step, command, purpose}
covering the full driving loop (doctor → catalog → install → secrets →
run → ls/discover → call --schema → call --async → wait/tail) — and add
`af call` and `af tail` entries to quick_start.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`af skill install` with no argument resolved to Catalog[0] and installed only the `agentfield` build skill, never `agentfield-use` — the drive skill that documents the discover → call → wait loop. A first-time user therefore never got the golden-loop docs. Add skillkit.InstallAll, which installs every catalog skill into the resolved targets, and call it from `af skill install` when no skill name is passed. Explicit `af skill install <name>` is unchanged. The interactive picker copy now names both skills (build + drive). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`af call`/`af ls`/`af tail` emitted a raw Go dial error when the control plane was down, with no guidance — while `af agent` commands already appended a reachability hint. Every CLI command that talks to the control plane routes through makeRequest, so wrap a transport-level failure there with a shared, actionable hint: Control plane not reachable at <url>. Start it with `af server` or launch the AgentField desktop app. A cancelled context (Ctrl-C / caller-handled timeout) is passed through unwrapped so only genuine connectivity failures get the hint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`af call --async` returns a run_id, but there was no first-class way to block until that run finished — a harness had to poll or tail. Add `af wait <run_id> [--timeout <sec, default 600>]`: it polls the run overview (the same /api/v1/agentic/run/:run_id API `af agent run --id` uses) until every execution is terminal, prints the final status and result as JSON, and maps outcomes to exit codes — 0 on succeeded, 1 on failed/cancelled, 2 on timeout. A 404 (records not yet written after an async accept) is treated as "not ready" so a freshly-accepted run keeps polling. The command also exercises the shared control-plane-unreachable hint, covered here by a cross-command test over call/ls/tail/wait. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was no CLI way to discover installable nodes — the only curated catalog lived in the desktop app (desktop/src/shared/catalog.ts). Add `af catalog`, backed by an in-binary catalog seeded from that same curated list (name, description, install source, docs URL), so a harness can browse nodes before `af install` and works offline. Supports `-o json`/`-o yaml`; the human table ends with the hint `af install <source>`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
📊 Coverage gateThresholds from
✅ Gate passedNo surface regressed past the allowed threshold and the aggregate stayed above the floor. |
Contributor
📐 Patch coverage gateThreshold: 80% on lines this PR touches vs
✅ Patch gate passedEvery surface whose lines were touched by this PR has patch coverage at or above the threshold. |
Add behavior tests for the previously-untested command wiring and error paths surfaced by the coverage patch gate: - af wait: pretty output, invalid-format/empty-id (exit 2), a control plane 5xx (exit 3), the nil-opts default path, end-to-end command execution, and rootExecutionResult (explicit root, last-execution fallback, non-JSON, empty). - af catalog: end-to-end command execution under -o json. - af skill install <name>: the explicit-name path stays single-skill. - makeRequest: a cancelled context is passed through unwrapped, not relabeled as an unreachable-control-plane error. Raises control-plane patch coverage back over the 80% floor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Problem
The "golden path" an AI harness follows to drive AgentField agent nodes —
discover → schema → call async → wait → result — exists as primitives but was
never surfaced as one coherent flow:
af agent help(the machine-friendly interface) taught discovery andintrospection but never execution: its
quick_startomittedaf callandaf tailentirely.af call --asyncprinted a bare run-id string even under-o json, so aparser asking for JSON got a non-JSON token.
af call/af ls/af tailagainst a down control plane emitted a raw Godial error with no guidance, while
af agentcommands already appended areachability hint.
af skill installwith no argument installed only the first catalog entry —the
agentfieldbuild skill — neveragentfield-use, the drive skillthat documents the golden loop.
lived in the desktop app.
What changed
golden_pathinaf agent help— an ordered array of{step, command, purpose}covering doctor → catalog → install → secrets → run → ls/discover →call --schema→call --async→wait/tail.af callandaf tailadded to
quick_start.af wait <run_id> [--timeout <sec, default 600>]— polls the run overview(the same
/api/v1/agentic/run/:run_idAPIaf agent run --iduses) until therun is terminal, prints final status + result as JSON, and maps exit codes:
0succeeded,1failed/cancelled,2timeout.af call --asyncJSON envelope — under an explicit-o json/-o yamlitemits
{"run_id": "...", "status": "accepted"}. The default/pretty path keepsthe bare run-id line scripts capture via
RUN_ID=$(af call ... --async).shared
makeRequestnow surfacesControl plane not reachable at <url>. Start it with af server or launch the AgentField desktop app.for call/ls/tail/wait.A cancelled context is passed through unwrapped.
af catalog— a new command backed by an in-binary catalog seeded from thedesktop app's curated list (name, description, install source, docs URL).
Supports
-o json/-o yaml; the human table ends with the hintaf install <source>.af skill installwith no name installs the whole catalog — both the buildand drive skills, via a new
skillkit.InstallAll. Explicitaf skill install <name>is unchanged; the interactive picker copy now names both skills.Validation Contract
af agent helpJSON containsgolden_pathwith the ordered driving loop;quick_startincludesaf callandaf tailTestAgentHelpDataShape(agent_helpers_test.go)af waiton a succeeded run exits 0 and prints JSON withstatusandresult;--timeoutproduces exit 2TestRunWait(wait_test.go) — subtests "succeeded run exits 0…" and "times out with exit code 2…" (plus failed→exit 1 and mid-run polling)af call x.y --async -o jsonoutput parses as JSON withrun_idTestRunCallSyncSchemaAndAsync(trigger_commands_test.go)af serverTestUnreachableControlPlaneHintAcrossCommands+TestControlPlaneUnreachableErrorMentionsServer(connection_hint_test.go)af catalog -o jsonyields ≥5 entries each with name/description/sourceTestRunCatalogJSON(catalog_test.go)af skill install(non-interactive) installs both skillsTestInstallAllInstallsEveryCatalogSkill(skillkit/install_all_test.go)Test run
Ran the CI gates locally (from
.github/workflows/control-plane.yml):control-plane/web/client:npm ci && npm run build— passGOFLAGS=-buildvcs=false go build ./...— passgo vet ./...— passgofmt -lon changed files — cleangolangci-lint runon changed packages — no new findings on changed filesgo test ./internal/cli/... ./internal/skillkit/...— passNote: one unrelated test,
internal/server/TestStartAndStopCoverAdditionalBranches,fails in this local WSL environment (it binds an admin gRPC server and dials a
stub health endpoint that hangs locally). It reproduces identically on a pristine
origin/maincheckout and has no dependency on the packages changed here, so itis a pre-existing environment artifact, not a regression from this PR.
🤖 Generated with Claude Code