From b5f4d38dc25f14684dcfd257666edfee87752068 Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Thu, 6 Aug 2026 13:18:14 -0400 Subject: [PATCH 1/2] fix(skills): sync the embedded agentfield-use mirror The workspace-handle section added in #885 landed in skills/ only, leaving the embedded copy the control plane actually serves 41 lines behind. Three skillkit tests have been failing on main since that merge. Co-Authored-By: Claude Fable 5 --- .../skill_data/agentfield-use/SKILL.md | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/control-plane/internal/skillkit/skill_data/agentfield-use/SKILL.md b/control-plane/internal/skillkit/skill_data/agentfield-use/SKILL.md index 1e54edae2..05f49f1fd 100644 --- a/control-plane/internal/skillkit/skill_data/agentfield-use/SKILL.md +++ b/control-plane/internal/skillkit/skill_data/agentfield-use/SKILL.md @@ -210,6 +210,44 @@ Long-running agents can take tens of minutes — poll with backoff (start ~5s, settle at ~30s) and tell the user what is in flight. For live progress, stream Server-Sent Events from `GET /api/v1/executions//events`. +### If the result carries a `workspace_handle`, you can read the files + +Some agents (SWE-AF) mirror the workspace they are building in, so you can open +the actual files instead of reasoning from the summary — including uncommitted +edits and untracked files that no git push would carry. You do not ask whether +this is available and there is nothing to configure: the handle is in the result +when it works and absent when it doesn't. + +```json +"workspace_handle": {"v":1, "remote":"ssh://host:port"|"dir:/path", + "namespace":"...", "key":"<64 hex>", "token":"..."} +``` + +Use it only if `furrow` is on PATH (and, for an `ssh://` handle, `furrow-dial`). +Otherwise ignore it silently and work from the result as usual. + +```bash +# ssh:// handle — furrow-dial carries the protocol; nothing else changes +export FURROW_SSH_COMMAND=furrow-dial FURROW_DIAL_TOKEN= FURROW_DIAL_INSECURE=1 +FURROW_RECOVERY_KEY= furrow clone / ./run-workspace --no-watch + +# dir: handle (same machine) — clone rejects directory remotes, so pair instead. +# The path is the handle's remote with the "dir:" prefix removed; don't append +# anything to it. +git init -q run-workspace && furrow --repo run-workspace watch --no-daemon +furrow --repo run-workspace pair --name --key +furrow --repo run-workspace sync --pull --bootstrap +``` + +`furrow --repo run-workspace sync --follow` keeps it current while the run +works. Read and diff freely. Treat it as a mirror, not a shared drive: it is +one-writer, and edits go back as a merge (`furrow merge --check ""`), +so change files between issues or on a fork rather than while the agent writes. + +`get_workspace_handle` re-fetches a handle mid-run: +`POST /api/v1/execute/.get_workspace_handle` with `{"input":{"run_id":"..."}}`. +`{"available": false}` means no mirror — carry on without it. + **Several at once:** `POST /api/v1/executions/batch-status` with `{"execution_ids": [...]}`. Terminal entries embed the FULL result payload — responses can be large (100KB+), so write to a file and parse from there; never @@ -280,6 +318,9 @@ is enabled), and verify offline with `af verify audit.json`. ## Hard rules - Every call goes through the control plane — never POST to an agent's own port. + The one exception is a `workspace_handle`: its `ssh://` endpoint is a furrow + transport, not the agent's HTTP port, and the per-run token in the handle is + what authorizes it. Reading files there is not an agent call. - Kwargs live under `"input"`. Empty input is `{"input": {}}`. - Async + poll for anything that might exceed a few seconds; sync is for quick lookups only. Independent async calls go out together, not one at a time. From 7e221703d36ab51cab2bc21d9f7a3ecdf2a12e57 Mon Sep 17 00:00:00 2001 From: Abir Abbas Date: Thu, 6 Aug 2026 13:18:35 -0400 Subject: [PATCH 2/2] docs(skill): resolve furrow from where AgentField installs it furrow has no release channel today, so "use it only if furrow is on PATH" silently disabled the workspace handle for every caller. Point the lookup at `~/.agentfield/bin` (where provisioning puts it) and at a node's own vendored copy, and keep the silent-skip when neither exists. Provisioning itself belongs in the install path, not in this document. Co-Authored-By: Claude Fable 5 --- .../skill_data/agentfield-use/SKILL.md | 32 ++++++++++++++----- skills/agentfield-use/SKILL.md | 32 ++++++++++++++----- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/control-plane/internal/skillkit/skill_data/agentfield-use/SKILL.md b/control-plane/internal/skillkit/skill_data/agentfield-use/SKILL.md index 05f49f1fd..122ab272b 100644 --- a/control-plane/internal/skillkit/skill_data/agentfield-use/SKILL.md +++ b/control-plane/internal/skillkit/skill_data/agentfield-use/SKILL.md @@ -223,23 +223,39 @@ when it works and absent when it doesn't. "namespace":"...", "key":"<64 hex>", "token":"..."} ``` -Use it only if `furrow` is on PATH (and, for an `ssh://` handle, `furrow-dial`). -Otherwise ignore it silently and work from the result as usual. +`furrow` is rarely on PATH. AgentField installs it to `~/.agentfield/bin/`, and a +node that ships its own copy keeps it inside the installed package. Resolve it +from those; do not try to install it yourself. + +```bash +os=$(uname -s | tr A-Z a-z) +furrow_bin() { # $1 = furrow | furrow-dial + command -v "$1" 2>/dev/null && return + for c in ~/.agentfield/bin/"$1" ~/.agentfield/packages/*/{bin,go/bin}/"$1"-"$os"-*; do + [ -x "$c" ] && { echo "$c"; return; } + done +} +FURROW=$(furrow_bin furrow) DIAL=$(furrow_bin furrow-dial) +``` + +A `dir:` handle needs only `$FURROW`; an `ssh://` handle needs `$DIAL` too. If +either is missing, say so plainly and carry on from the result — the mirror is +fine, this machine just has no client for it. ```bash # ssh:// handle — furrow-dial carries the protocol; nothing else changes -export FURROW_SSH_COMMAND=furrow-dial FURROW_DIAL_TOKEN= FURROW_DIAL_INSECURE=1 -FURROW_RECOVERY_KEY= furrow clone / ./run-workspace --no-watch +export FURROW_SSH_COMMAND="$DIAL" FURROW_DIAL_TOKEN= FURROW_DIAL_INSECURE=1 +FURROW_RECOVERY_KEY= "$FURROW" clone / ./run-workspace --no-watch # dir: handle (same machine) — clone rejects directory remotes, so pair instead. # The path is the handle's remote with the "dir:" prefix removed; don't append # anything to it. -git init -q run-workspace && furrow --repo run-workspace watch --no-daemon -furrow --repo run-workspace pair --name --key -furrow --repo run-workspace sync --pull --bootstrap +git init -q run-workspace && "$FURROW" --repo run-workspace watch --no-daemon +"$FURROW" --repo run-workspace pair --name --key +"$FURROW" --repo run-workspace sync --pull --bootstrap ``` -`furrow --repo run-workspace sync --follow` keeps it current while the run +`"$FURROW" --repo run-workspace sync --follow` keeps it current while the run works. Read and diff freely. Treat it as a mirror, not a shared drive: it is one-writer, and edits go back as a merge (`furrow merge --check ""`), so change files between issues or on a fork rather than while the agent writes. diff --git a/skills/agentfield-use/SKILL.md b/skills/agentfield-use/SKILL.md index 05f49f1fd..122ab272b 100644 --- a/skills/agentfield-use/SKILL.md +++ b/skills/agentfield-use/SKILL.md @@ -223,23 +223,39 @@ when it works and absent when it doesn't. "namespace":"...", "key":"<64 hex>", "token":"..."} ``` -Use it only if `furrow` is on PATH (and, for an `ssh://` handle, `furrow-dial`). -Otherwise ignore it silently and work from the result as usual. +`furrow` is rarely on PATH. AgentField installs it to `~/.agentfield/bin/`, and a +node that ships its own copy keeps it inside the installed package. Resolve it +from those; do not try to install it yourself. + +```bash +os=$(uname -s | tr A-Z a-z) +furrow_bin() { # $1 = furrow | furrow-dial + command -v "$1" 2>/dev/null && return + for c in ~/.agentfield/bin/"$1" ~/.agentfield/packages/*/{bin,go/bin}/"$1"-"$os"-*; do + [ -x "$c" ] && { echo "$c"; return; } + done +} +FURROW=$(furrow_bin furrow) DIAL=$(furrow_bin furrow-dial) +``` + +A `dir:` handle needs only `$FURROW`; an `ssh://` handle needs `$DIAL` too. If +either is missing, say so plainly and carry on from the result — the mirror is +fine, this machine just has no client for it. ```bash # ssh:// handle — furrow-dial carries the protocol; nothing else changes -export FURROW_SSH_COMMAND=furrow-dial FURROW_DIAL_TOKEN= FURROW_DIAL_INSECURE=1 -FURROW_RECOVERY_KEY= furrow clone / ./run-workspace --no-watch +export FURROW_SSH_COMMAND="$DIAL" FURROW_DIAL_TOKEN= FURROW_DIAL_INSECURE=1 +FURROW_RECOVERY_KEY= "$FURROW" clone / ./run-workspace --no-watch # dir: handle (same machine) — clone rejects directory remotes, so pair instead. # The path is the handle's remote with the "dir:" prefix removed; don't append # anything to it. -git init -q run-workspace && furrow --repo run-workspace watch --no-daemon -furrow --repo run-workspace pair --name --key -furrow --repo run-workspace sync --pull --bootstrap +git init -q run-workspace && "$FURROW" --repo run-workspace watch --no-daemon +"$FURROW" --repo run-workspace pair --name --key +"$FURROW" --repo run-workspace sync --pull --bootstrap ``` -`furrow --repo run-workspace sync --follow` keeps it current while the run +`"$FURROW" --repo run-workspace sync --follow` keeps it current while the run works. Read and diff freely. Treat it as a mirror, not a shared drive: it is one-writer, and edits go back as a merge (`furrow merge --check ""`), so change files between issues or on a fork rather than while the agent writes.