mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
🤖 ## 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>
154 lines
7.8 KiB
Markdown
154 lines
7.8 KiB
Markdown
# 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.
|