This commit is contained in:
+15
-71
@@ -5,7 +5,17 @@ This is the reference for the piece YAML format consumed by
|
||||
`/api/pieces` HTTP layer (`src/bridge/pieces-api.ts` `validatePiece`).
|
||||
|
||||
Field names are snake_case in the YAML; the engine maps them to
|
||||
camelCase internally (see `Movement` in `src/engine/agent-loop.ts`).
|
||||
camelCase internally (see `Movement` in `src/engine/agent-loop/types.ts`).
|
||||
|
||||
> **Tool / edit / SSH availability is NOT declared in pieces.**
|
||||
> A piece defines only the *movement flow* (persona, instruction, transition
|
||||
> rules). Which tools an agent may call, whether `Write`/`Edit` are available,
|
||||
> and which SSH connections it can reach are all decided by the **workspace tool
|
||||
> policy** (Settings → Tools / SSH), resolved once per job by the worker
|
||||
> (`resolveWorkspaceTools`) and applied uniformly to every movement. The legacy
|
||||
> fields `allowed_tools`, `edit`, `shared_tools`, and `allowed_ssh_connections`
|
||||
> were removed in the tool-consolidation work — if present on a submitted piece
|
||||
> they are tolerated but ignored.
|
||||
|
||||
## Top-level
|
||||
|
||||
@@ -17,89 +27,24 @@ camelCase internally (see `Movement` in `src/engine/agent-loop.ts`).
|
||||
| `initial_movement` | string | yes | must reference a `movements[].name` |
|
||||
| `triggers.keywords` | string[] | no | classifier hint only |
|
||||
| `required_mcp` | string[] | no | `[a-z0-9_-]{1,64}` server slugs |
|
||||
| `shared_tools` | string[] | no | piece-level tools merged into **every** movement's effective `allowed_tools` (union). See below. |
|
||||
| `model` | string | no | preferred LLM model |
|
||||
| `movements` | Movement[] | yes | non-empty array |
|
||||
|
||||
### `shared_tools`
|
||||
|
||||
Tools listed here are added to every movement's allowed set, so they don't have
|
||||
to be repeated in each `movements[].allowed_tools`. The effective set for a
|
||||
movement is:
|
||||
|
||||
```
|
||||
shared_tools ∪ movements[].allowed_tools ∪ META_TOOLS
|
||||
```
|
||||
|
||||
`prepareMovementContext` (`src/engine/piece-runner.ts`) unions the first two
|
||||
via `mergeToolNames` (de-duplicates, preserves order) into
|
||||
`movement.allowedTools`; the always-on `META_TOOLS` are added later, when
|
||||
`getToolDefs` (`src/engine/tools/index.ts`) builds the tool list.
|
||||
|
||||
The per-movement gates still apply on top of the union:
|
||||
|
||||
- A movement with `edit: false` never exposes `Write`/`Edit`, even if listed in
|
||||
`shared_tools`.
|
||||
- SSH tools (`SshExec` etc.) still require the movement's own
|
||||
`allowed_ssh_connections` — a shared SSH tool is rejected at runtime in any
|
||||
movement that has not declared connections (defense in depth).
|
||||
- Unknown tool names are silently dropped by `getToolDefs` (`name in allDefs`).
|
||||
|
||||
Handled by two complementary layers (mirrors `required_mcp`): file-backed
|
||||
pieces are cleaned leniently at load time by `normalizeSharedTools`
|
||||
(`piece-runner.ts`, drops bad entries + warns), while API writes
|
||||
(`POST`/`PUT /api/pieces`, `CreatePiece`/`UpdatePiece`) are validated strictly
|
||||
by `validatePiece` (`pieces-api.ts`, rejects a non-array or non-string entry).
|
||||
|
||||
## Movement
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|-------|------|----------|-------|
|
||||
| `name` | string | yes | unique within the piece |
|
||||
| `edit` | boolean | yes | when true, Write/Edit are exposed |
|
||||
| `persona` | string | yes | system-prompt persona |
|
||||
| `instruction` | string | yes | the movement's task description |
|
||||
| `allowed_tools` | string[] | yes | tool names; `'mcp__*'` wildcard allowed |
|
||||
| `allowed_commands` | string[] | no | Bash command allowlist (overrides default) |
|
||||
| `allowed_ssh_connections` | string[] | conditional | see below |
|
||||
| `rules` | Rule[] | yes | transition rules; may be empty |
|
||||
| `default_next` | string | no | engine-internal fallback (sentinel-friendly) |
|
||||
| `max_consecutive_revisits` | number | no | loop-detection threshold override |
|
||||
|
||||
## `allowed_ssh_connections`
|
||||
|
||||
Per-movement SSH connection allowlist (Phase 4 of the SSH tool integration
|
||||
|
||||
| Value | Meaning |
|
||||
|-------|---------|
|
||||
| `undefined` (field omitted) | SSH tools reject with `no_allowed_connections_declared`. |
|
||||
| `[]` (empty array) | SSH tools reject with `no_allowed_connections_declared`. The empty form is preferred over omission when the movement intentionally denies all connections (intent is explicit). |
|
||||
| `['<connection-id>', ...]` | Only listed connection IDs may be passed to SSH tools. |
|
||||
| `['*']` | Any registered connection may be passed. Still subject to ownership and grant checks (defense in depth). Use sparingly — typically only `ssh-ops`-style pieces. |
|
||||
|
||||
**Required**: If a movement's `allowed_tools` contains any of `SshExec`,
|
||||
`SshUpload`, or `SshDownload`, then `allowed_ssh_connections` MUST be
|
||||
present. `validatePieceDef` and `validatePiece` both reject pieces that
|
||||
omit it for SSH-using movements.
|
||||
|
||||
**Format**: each entry must be `'*'` or a lowercase hex/hyphen id with
|
||||
8+ characters (loose match against `randomUUID()` output).
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
movements:
|
||||
- name: ops
|
||||
edit: false
|
||||
persona: ops-operator
|
||||
instruction: Run health checks on production hosts.
|
||||
allowed_tools: [SshExec, Read]
|
||||
allowed_ssh_connections:
|
||||
- 6f9619ff-8b86-d011-b42d-00c04fc964ff
|
||||
- 7a8b9cde-1234-4567-89ab-cdef12345678
|
||||
rules:
|
||||
- condition: all checks pass
|
||||
next: COMPLETE
|
||||
```
|
||||
Tool availability, `Write`/`Edit` (edit) permission, and SSH connection scoping
|
||||
are runtime values on the in-memory `Movement` (populated from the workspace
|
||||
policy in `prepareMovementContext`), not YAML fields.
|
||||
|
||||
## Rule
|
||||
|
||||
@@ -112,8 +57,7 @@ movements:
|
||||
`COMPLETE` / `ABORT` / `ASK` — those are reachable only through the
|
||||
`complete` tool (status: `success` / `aborted` / `needs_user_input`).
|
||||
`default_next` does accept the terminal sentinels because it is an
|
||||
engine-internal fallback (context overflow, ASK limit, SpawnSubTask
|
||||
unavailable).
|
||||
engine-internal fallback (context overflow, ASK limit).
|
||||
|
||||
## Validation paths
|
||||
|
||||
|
||||
Reference in New Issue
Block a user