Skip to content

feat(cli): surface the harness golden path (af wait, catalog, golden_path help, JSON envelopes)#816

Merged
AbirAbbas merged 8 commits into
mainfrom
feat/cli-golden-path
Jul 22, 2026
Merged

feat(cli): surface the harness golden path (af wait, catalog, golden_path help, JSON envelopes)#816
AbirAbbas merged 8 commits into
mainfrom
feat/cli-golden-path

Conversation

@AbirAbbas

Copy link
Copy Markdown
Contributor

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 and
    introspection but never execution: its quick_start omitted af call and
    af tail entirely.
  • af call --async printed a bare run-id string even under -o json, so a
    parser asking for JSON got a non-JSON token.
  • af call / af ls / af tail against a down control plane emitted a raw Go
    dial error with no guidance, while af agent commands already appended a
    reachability hint.
  • af skill install with no argument installed only the first catalog entry —
    the agentfield build skill — never agentfield-use, the drive skill
    that documents the golden loop.
  • There was no CLI way to browse installable nodes; the only curated catalog
    lived in the desktop app.

What changed

  • golden_path in af agent help — an ordered array of {step, command, purpose} covering doctor → catalog → install → secrets → run → ls/discover →
    call --schemacall --asyncwait/tail. af call and af tail
    added to quick_start.
  • af wait <run_id> [--timeout <sec, default 600>] — polls the run overview
    (the same /api/v1/agentic/run/:run_id API af agent run --id uses) until the
    run is terminal, prints final status + result as JSON, and maps exit codes:
    0 succeeded, 1 failed/cancelled, 2 timeout.
  • af call --async JSON envelope — under an explicit -o json/-o yaml it
    emits {"run_id": "...", "status": "accepted"}. The default/pretty path keeps
    the bare run-id line scripts capture via RUN_ID=$(af call ... --async).
  • Shared control-plane-unreachable hint — a transport failure through the
    shared makeRequest now surfaces Control 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 the
    desktop app's curated list (name, description, install source, docs URL).
    Supports -o json/-o yaml; the human table ends with the hint
    af install <source>.
  • af skill install with no name installs the whole catalog — both the build
    and drive skills, via a new skillkit.InstallAll. Explicit af skill install <name> is unchanged; the interactive picker copy now names both skills.

Validation Contract

Behavior (intent) Test
af agent help JSON contains golden_path with the ordered driving loop; quick_start includes af call and af tail TestAgentHelpDataShape (agent_helpers_test.go)
af wait on a succeeded run exits 0 and prints JSON with status and result; --timeout produces exit 2 TestRunWait (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 json output parses as JSON with run_id TestRunCallSyncSchemaAndAsync (trigger_commands_test.go)
Unreachable control plane on call/ls/tail/wait mentions af server TestUnreachableControlPlaneHintAcrossCommands + TestControlPlaneUnreachableErrorMentionsServer (connection_hint_test.go)
af catalog -o json yields ≥5 entries each with name/description/source TestRunCatalogJSON (catalog_test.go)
Bare af skill install (non-interactive) installs both skills TestInstallAllInstallsEveryCatalogSkill (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 — pass
  • GOFLAGS=-buildvcs=false go build ./... — pass
  • go vet ./... — pass
  • gofmt -l on changed files — clean
  • golangci-lint run on changed packages — no new findings on changed files
  • go test ./internal/cli/... ./internal/skillkit/... — pass

Note: 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/main checkout and has no dependency on the packages changed here, so it
is a pre-existing environment artifact, not a regression from this PR.

🤖 Generated with Claude Code

AbirAbbas and others added 6 commits July 22, 2026 15:22
`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>
@AbirAbbas
AbirAbbas requested a review from a team as a code owner July 22, 2026 19:25
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

📊 Coverage gate

Thresholds from .coverage-gate.toml: per-surface ≥ 84%, aggregate ≥ 85%, max per-surface regression ≤ 1.0 pp, max aggregate regression ≤ 0.50 pp.

Surface Current Baseline Δ
control-plane 86.90% 87.40% ↓ -0.50 pp 🟡
sdk-go 92.50% 92.00% ↑ +0.50 pp 🟢
sdk-python 93.82% 93.73% ↑ +0.09 pp 🟢
sdk-typescript 91.05% 90.42% ↑ +0.63 pp 🟢
web-ui 84.75% 84.79% ↓ -0.04 pp 🟡
aggregate 85.54% 85.75% ↓ -0.21 pp 🟡

✅ Gate passed

No surface regressed past the allowed threshold and the aggregate stayed above the floor.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

📐 Patch coverage gate

Threshold: 80% on lines this PR touches vs origin/main (from .coverage-gate.toml:thresholds.min_patch).

Surface Touched lines Patch coverage Status
control-plane 227 94.00%
sdk-go 0 ➖ no changes
sdk-python 0 ➖ no changes
sdk-typescript 0 ➖ no changes
web-ui 0 ➖ no changes

✅ Patch gate passed

Every surface whose lines were touched by this PR has patch coverage at or above the threshold.

AbirAbbas and others added 2 commits July 22, 2026 15:41
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>
@AbirAbbas
AbirAbbas merged commit d6197f0 into main Jul 22, 2026
25 checks passed
@AbirAbbas
AbirAbbas deleted the feat/cli-golden-path branch July 22, 2026 20:22
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