Files
buzz/crates/buzz-acp/TASKS.md
Logan Johnson ea1e97e65f feat(acp): run one prepared task from a file or stdin (#7851)
🤖
## Summary
Scripts, scheduled jobs, and other automation need to give an agent one
prepared task and wait for a result without starting a long-running chat
listener. This adds `buzz-acp run`: it reads a task from a local file or
stdin, runs one fresh agent session with the configured tools and
instructions, cleans up, and exits with a machine-readable status.

```sh
buzz-acp run --task ./task.json
cat task.json | buzz-acp run --task -
```

- The versioned JSON task specifies a task ID, intended agent public
key, prepared prompt, and execution time limit. Invalid input or an
identity mismatch is rejected before agent launch. Credentials,
executable, tools, and permissions remain controlled by trusted launch
settings, not the task document.
- Each invocation uses the normal agent equipment: base/persona/team
instructions, enabled memory, tools, model and effort settings,
permissions, and Git identity, credentials, and signing. Both task and
conversational modes obtain these capabilities from one shared runtime
owner; new shared setup belongs there rather than in separate entry
points.
- Launch configuration reads have a separate ten-second bound and can be
cancelled even when a prompt file blocks. A deadline covers adapter
startup through the task turn. Interrupts and termination signals
trigger bounded cleanup. Stdout contains one terminal JSON record;
diagnostics stay on stderr. A normal turn ending is not proof that the
task achieved its goal.
- Normal invocation remains the conversational service. Task mode does
not start chat subscriptions, presence, heartbeat turns, or a persistent
agent pool. Tasks are trusted executable instructions, not sandboxed
input. Reusing a task ID does not prevent duplicate execution, and
uncertain outcomes must not be blindly retried.

See [the task contract](crates/buzz-acp/TASKS.md) for the document
format, exit codes, limits, and examples. URL retrieval, a task server,
and a persistent execution ledger are outside this change.

### Related issue
None found in the existing issue and PR search.

### Testing
- At `4e86bbc97`: 964 unit tests and 22 integration tests passed, with
three existing ignored tests. The package suite ran serially with
inherited harness/Git options cleared. A final test-only shutdown-result
assertion was then checked with the focused memory test and all-target
Clippy.
- All eight real-binary task tests independently passed on the exact
candidate, including blocked configuration reads, file/stdin validation,
session equipment, deadlines, cancellation, and cleanup. The memory test
passed 20 repeated runs. Removing the oversized-input guard fails the
new error assertion; restoring it passes.
- Package all-target Clippy, formatting, file-size checks, and pre-push
Rust/Tauri checks passed. All executed hosted checks passed on the
updated head (54 successful, 28 skipped). The separate Codex review
comment still requests authorization for this exact range.
- Earlier revisions passed the release build and a real Goose 1.51.0
tool-use smoke test. No new release artifact or installed Janet runtime
is claimed for this feedback revision.
- Repository-wide `just ci` was previously attempted; workspace Clippy
passed, then the desktop dependency check stopped on a local pnpm fetch
failure. Hosted checks cover the applicable PR lanes.

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
2026-09-24 10:58:15 -04:00

154 lines
7.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Local task runner
`buzz-acp run` runs one prepared task in one fresh agent process and ACP session,
then exits. Scripts and automation can use it without starting a conversational
`buzz-acp` service or sending a chat message.
```sh
buzz-acp run --task ./task.json
cat task.json | buzz-acp run --task -
```
Put command options after `run`. Use the normal trusted launch settings for
identity, relay, adapter, model, effort, permissions, prompts, MCP tools, and
memory. `buzz-acp run --help` lists them. The working directory is the launch
working directory, not the task file's parent. The task cannot change it.
## Task document, version 1
Supply exactly one UTF-8 JSON object, at most 1 MiB including whitespace:
```json
{
"version": 1,
"taskId": "example-001",
"agentPubkey": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"prompt": "Inspect the current workspace. Report your findings using the task's specified destination.",
"maxDurationMs": 120000
}
```
The example public key is illustrative; use the public key of the configured
agent. Never put a private key in a task document.
| Field | Contract |
| --- | --- |
| `version` | Integer `1`. Other versions fail before agent launch. |
| `taskId` | Nonblank string, 1–256 UTF-8 bytes, no control characters. Correlation only, not a claim or deduplication key. |
| `agentPubkey` | Exactly 64 lowercase hexadecimal characters. Must equal the configured agent's public key. |
| `prompt` | Nonblank prepared task string. Include relevant context, original evidence, and reporting instructions here. |
| `maxDurationMs` | Positive unsigned 64-bit integer. The shorter of this limit and the trusted `--max-turn-duration` cap wins. |
All fields are required. Unknown fields, duplicate fields, wrong types, trailing
JSON documents, and malformed JSON are rejected. File/stdin input has a ten-second
read limit. Close stdin after the document. The input limit is separate from the
execution deadline.
Launch configuration is the authority for credentials, executable, tools,
permissions, and relay. A document is **executable task input**, not a sandboxed
message: only feed tasks that are trusted to use that agent's capabilities. The
identity check prevents accidental execution by another agent; it does not
verify the document's author. File/stdin input does not need a signature.
## Session behavior
- Load normal base, persona, and team instructions. Add the task-session context
instead of a conversational session description. Modern adapters receive
standing instructions through session setup; legacy adapters receive them with
the first prompt. `--no-base-prompt` and custom base files still work.
- Load enabled core memory for the resolved owner and identity before session
creation. `--no-memory` skips this. No owner means no memory namespace. The
normal three-second fetch bound applies. Confirmed absence supplies the normal
onboarding hint; a failed read supplies no hint, not a false empty-memory claim.
- Apply model, supported effort and permission settings through the existing
session setup. Unsupported settings retain the existing adapter fallback
behavior. Pass configured identity and relay to the child and MCP tools; keep
the inherited authorization and other launch environment. The shared runtime
prepares Git identity, scoped credentials, and signing helpers for both task
and conversational sessions. Temporary key material lives until adapter
cleanup is complete.
- Submit one prepared task, allowing multiple model/tool exchanges. There is no
initial-message turn, heartbeat turn, conversation history, automatic channel
context, or second task. Context must be supplied or fetched with tools.
- Do not start ordinary relay subscriptions, a standing pool, presence, typing,
a setup listener, observer publication, or an isolated-turn socket. Inherited
service-only options cannot enable these features. Shared option parsing still
validates launch settings.
- Do not discover, borrow, stop, or reconfigure an existing host. Memory, files,
credentials, and external side effects may be shared with concurrent sessions.
Session isolation is not a security sandbox or a concurrency lock.
- Do not automatically publish final text. Use task reporting instructions and
tools. Do not retry the task after failure, timeout, or uncertain execution.
Launch configuration preparation (including prompt-file reads) has a separate
ten-second bound. A blocked configuration read reports `configuration_timeout`
with exit 2; SIGINT/SIGTERM during that read reports `cancelled` with exit
130/143 and a null `taskId`. No adapter is started during this phase.
The execution deadline covers adapter startup, memory loading, session setup,
and the task turn. SIGINT/SIGTERM cancel the invocation. If a prompt is active,
allow up to five seconds for cooperative cancellation, then kill and reap the
child, with a further five-second reap bound. Startup and setup cancellation do
not wait for a session to become ready. On Unix, cleanup kills the process group,
including ordinary descendants. Processes that deliberately escape that group
are outside this guarantee. On non-Unix platforms cleanup uses the existing
ACP direct-child cleanup; descendant containment is not promised.
## Terminal output
For each attempted invocation, stdout contains one JSON line (except help/version
output). Diagnostics and child stderr stay on stderr. No prompt, private key,
agent final text, or raw adapter error is included in the terminal record.
```json
{"version":1,"taskId":"example-001","status":"completed","stopReason":"end_turn"}
```
`taskId` is null until the document is validated. `status` is one of `completed`,
`failed`, `invalid`, `timed_out`, or `cancelled`. `stopReason` is present only for
normal completion: `end_turn`, `max_tokens`, `max_turn_requests`, or `refusal`.
Failures include a stable, sanitized `error` code when available, such as
`agent_identity_mismatch`, `invalid_task_json`, or `agent_spawn_failed`.
| Exit | Meaning |
| --- | --- |
| `0` | Turn returned normally. Inspect `stopReason`; this is not proof of task success. |
| `1` | Startup/execution failure, adapter cancellation, or terminal-output failure. |
| `2` | Invalid arguments, launch configuration, or task input/read. |
| `124` | Execution deadline exceeded. |
| `130` | SIGINT; bounded cleanup ran. |
| `143` | SIGTERM; bounded cleanup ran. |
A killed host or broken stdout can leave no terminal record. A record is not a
persistent result ledger. Launching the same task twice can execute it twice.
## Future work
URL input and task-server retrieval are **not implemented**. URL-shaped sources
are rejected, not fetched. A future design can GET a supplied URL and validate
the same task document, with explicit authentication, size/time bounds, and
redirect rules. This project adds no HTTP task endpoint or job queue.
## Shared runtime ownership
`runtime::AgentRuntime` prepares shared capabilities and owns their lifetime.
Both entry points obtain adapter configuration and prompt context from it;
add new shared capabilities there rather than in either entry point. Session
creation, tool configuration, model/effort/permissions, and legacy prompt
framing use the existing pool code. Mode-specific code owns task input and
terminal output or conversational intake and scheduling, not agent equipment.
## Build and verification
```sh
. ./bin/activate-hermit
cargo build --release -p buzz-acp
cargo test -p buzz-acp
```
`tests/run_task.rs` invokes the actual binary with a deterministic ACP peer. It
checks stdin/file parity, fresh process/session setup, legacy and modern standing
instructions, tools, identity, model/effort/permissions, memory reads and opt-out,
invalid input, clean JSON, deadlines, signals, and Unix descendant cleanup.
These checks do not require a model provider or production relay.