Agents
An agent is a YAML persona plus a markdown prompt. Packs ship both, and a project can override either. This page covers the persona and its capability boundary; the prompt side has its own page. The reject contract is agent-contract.md.
Persona YAML
# agents/plan-writer.yaml
id: plan-writer
name: Plan Writer
description: Creates and updates blueprint markdown under .paintedwolf/blueprints only
tool_profile: plan_write_only
exec_mode: none
tools: [read, write, edit, grep]
topology_roles: [worker]
system_prompt_template: agents/plan-writer.md
The persona names what the agent is for; its tool_profile is the enforcement boundary.
Two optional fields select skills. skills: is what the agent may read — all for the full catalog, or an exact name list; omit it for none. skills_index: narrows the ambient prompt listing to a curated subset of that set.
Tool profiles
A profile is the capability ceiling — which tools may reach the agent at all:
# tools/profiles/plan_review_readonly.yaml
id: plan_review_readonly
description: Read-only plan review — read and grep on plan paths
tools:
read: sticky # sticky = always available; true = deferred via request_tools
grep: sticky
deny_tools: [write, edit, command, delegate_*] # wildcards allowed
Native tools are engine-implemented — you can’t add one from a pack. The extension paths are: profiles, coordinator surfaces, and MCP for genuinely new tools.
Native tools
These are the ids a profile, a persona’s tools:, or a rule’s selector.tool may name. Generated from the engine’s tool manifest:
Show the full native tool catalog (46)
Filesystem
| Tool | What it does |
|---|---|
read | Read a file as JSON (project-relative, or an absolute path the user supplied). Unscoped large-file reads return a structure digest. Narrow with mode / offset / limit / ranges / symbol for literal content. Replaces command: cat / head / sed -n. |
write | Write file contents (creates or replaces, making missing parent folders; append: true appends to an existing file) within profile write scope; on scope violation returns Code: WRITE_SCOPE_DENIED with allowed globs |
edit | Edit a file in write scope. Anchor structurally: symbol replaces a whole definition; insert_before/insert_after add adjacent code (the tool owns line boundaries — no line numbers). Or replace an exact old_string with new_string (one occurrence, or every occurrence with replace_all). Scope violation returns Code: WRITE_SCOPE_DENIED. Replaces command: sed -i / perl -pi in-place edit. |
replace_lines | Replace lines start_line..end_line (inclusive: both boundary lines are deleted; re-emit kept lines in new_content). Preferred for long files after read(offset, limit); profile write scope |
code_rewrite | Rewrite one code shape at every site in a single call — beats read+edit per file when a change recurs (rename, signature, API swap). $VAR=one node, $$$VAR=a sequence, matched by syntax not text. path or paths[] (+recursive). dry_run previews. Search-only: grep structural: true. Replaces command: ast-grep / comby / gofmt -r structural rewrite. |
grep | Search files with RE2 text patterns or tree-sitter structural patterns. Recurses under path with optional path_glob, searches gitignored files, and skips engine/VCS metadata. Wide results may return a view digest. Replaces command: rg / grep -r. |
find | Bounded recursive directory walk — canonical for ** name_glob. Default type=any returns files and directories together. Wide unscoped walk at path="." (2000+ paths) returns view digest (selected=0) with distribution + FIND_OVERFLOW_NARROW — pass name_glob when you can name a filename pattern. A concrete path (not “.”) or name_glob returns literal results[] (same as list_dir targeted). Explicit max_results or offset also forces a literal page. Replaces command: find(1) recursive walk, tree(1). |
jq | Query a structured document (JSON, YAML, or TOML) with a jq program (gojq, sandboxed — no shell, env, or network). YAML/TOML decode via yqlib; values from non-JSON formats are normalized structure, not source-verbatim bytes. Unbounded look returns JSON shape plus sample values; narrow the query or add limit/offset for literal values[]. Replaces command: jq, yq. |
stat | File metadata (mode, size, mtime, entry_count for directories) Replaces command: stat(1). |
wc | Count lines, bytes, and words in files or recursively in directories. bytes is the true file size; truncated marks counts taken from a capped scan. Replaces command: wc -l. |
list_dir | Directory survey. Unscoped list_dir at . (repo root) with no max_depth, max_entries, or offset returns layout summary (tree, digest, highlights). Any narrowed path or scope arg returns flat entries[] listing. Replaces command: ls -la. |
chmod | Change approval bits on repo-relative paths. Replaces command: chmod(1). |
delete | Remove files and empty directories within write scope Replaces command: rm(1). |
copy | Copy files within write scope (files only; never use command cp). Replaces command: cp(1). |
move | Rename files within write scope (atomic rename; never use command mv). Replaces command: mv(1). |
mkdir | Create directories within write scope (never use command mkdir). Replaces command: mkdir(1). |
chown | Change file ownership within write scope (never use command chown). Replaces command: chown(1). |
diff | Unified diff between two repo-relative file paths (read-scope only; never use command diff). |
extract_archive | Unpack .zip or .tar.gz into a write-scoped directory (replaces unzip/tar xzf; HITL ask by default). |
Git
| Tool | What it does |
|---|---|
git_status | Git working tree status JSON (available, branch, dirty, counts; available:false on non-git projects). Informational only — dirty is normal; does not require cleanup. Replaces command: git status. |
git_diff | Git diff JSON (available, diff hunks, or files stat rows; available:false on non-git projects). Replaces command: git diff. |
git_log | Recent git commits JSON (available, commits with hash/subject/author/date; available:false on non-git projects). Optional path filters history for one file. |
git_commit | Stage and commit paths with a message. Success JSON: available, hash, paths, and uncommitted_paths/uncommitted_count for dirty files this commit left alone. Failure JSON: available=false, error, optional hook_failed, output_tail (last hook/CLI lines), and hint. Empty paths stages only files this session wrote (max 20) and leaves the rest of a dirty tree untouched; name paths explicitly to commit work this session did not write. Replaces command: git commit. |
git_show | Show commit or file at ref JSON (available, show with kind diff/blob). Replaces command: git show. |
git_blame | Per-line blame JSON (available, lines with commit/author/date/content). Replaces command: git blame. |
git_restore | Restore paths from HEAD into index and worktree. Discards uncommitted edits — only when the user explicitly asked to revert named paths. Replaces command: git checkout – / git restore. |
git_ref | Resolve refs to SHAs JSON (available, refs with ref/sha/peeled). Replaces command: git rev-parse. |
git_branches | List local branches JSON (available, branches with name/current). Replaces command: git branch. |
Meta
| Tool | What it does |
|---|---|
request_tools | Load deferred tool schemas by name; activated tools become callable with full schemas from your next turn (names come from the deferred tool catalog in your Tools section) |
verify | Run the project’s declared verification host-side; the host owns the verdict. Omit command to run the declared command — passing a different one is rejected where a project declares its gate, because only the declared command counts as evidence. Use command() for narrower checks while iterating. Result outcome is passed | failed | unverifiable: unverifiable means the run reached no verdict about the work (the sandbox stopped it), so it says nothing about the code and substituting another command will not help. declared reports whether this project declares a verify command at all. When false, pass the relevant documented build, test, lint, or validation command explicitly; its settled receipt is evidence for the exact current source revision, and any later mutation makes it stale. Pass cwd to change directory; cd and env.PWD are ignored. Verify receives no listener or loopback-connect authority; start or exercise a local service with command and the corresponding typed capability, then run a non-server verify command where the project supports one. |
survey_repo | Cross-cutting catalog map/audit — returns a Tier 1 digest (selected=0), not a briefing. Use only when you cannot name a path yet, or the ask matches a bundle (ssot_drift / api_routes). Prefer list_dir(".") before layout_overview. For explain/orient over a known file/package/dir, use summarize(path=…) instead — never survey_repo for depth on a named path. Digest shape:/drill: are next-hops; drill with summarize or natives. |
summarize | Summarize a repo file or dir — like read, but returns a structured briefing pack (identity/skeleton/substance tiers) + line-cited anchors[] not raw lines; synthesize your answer from the pack. Primary arg is path (file or dir); task is optional focus. Example: {“path”:“README.md”} or {“path”:“lycaon/internal/session”,“task”:“how does abort work?”}. |
skills_read | Read a skill’s instructions or one of its listed resources; no arguments lists the loaded catalog. |
surface_note | Post a concise, grounded mid-turn note for the user, then keep working. Needs at least one cited_evidence or cited_urls entry, and every citation must resolve — a note has no advisory pass. Not a question (use ask_user), not a final answer (closeout), not routine progress narration. |
Visual
| Tool | What it does |
|---|---|
render_view | Rasterize SVG or HTML into an authored visual: UI/product mockups (especially before building), diagrams, comparisons, timelines, or charts. Use when spatial layout improves clarity; never as runtime evidence — use capture_page for observed UI. Host injects an offline design kit (OFL fonts, Lucide-adapted icons, theme tokens, spacing/type scales, media placeholders). No CDN links, scripts, or external https assets — use kit font-family names, #kit-* icons, and –kit-* CSS variables. Project images/CSS load offline via http://lycaon.asset/ |
capture_page | One-shot open→act→snapshot→close; prefer page_* for live/authenticated state. Use project_dir for a static tree or url plus process_handle for a loopback app. Targets are exclusive. Returns structure, console, and screenshot; use filmstrip with actions for sequence claims. |
measure_page | Measure selector geometry in CSS pixels. Use id for an existing live page, or one exclusive project_dir/loopback url target. annotate adds boxes; page_geometry remains the numeric evidence. |
page_open | Open a live page; returns an id held across turns (cookies/auth persist). Prefer capture_page for a single check. Bindings match capture_page (project_dir or url). |
page_act | Drive a held page (page_open id); page stays alive so auth persists. |
page_snapshot | Universal {state, snapshot, log, artifact} for a held page (does not close it). |
page_close | Tear down a held page; optional final snapshot. |
Terminal
| Tool | What it does |
|---|---|
terminal_open | Live host terminal (pty): run a command-class argv command. Prefer command when you only need exit code/tail for yourself. Use this tool when the session must be held or the user should see the screen — then terminal_snapshot. Returns an id held across turns plus the requested observation (screen by default). Never supply secrets via later terminal_send — use env/credential helpers. A terminal receives no listen authority; start servers with command and an approved capability request instead. |
terminal_send | Write literal text and/or named control tokens ({Enter}, {Ctrl-C}, …) to a held pty, then return the settled vt grid. Never send passwords, tokens, or passphrases. |
terminal_read | Read incremental pty output since the last read (idle/timeout bounded). |
terminal_snapshot | User-visible photograph of the held terminal session (CLI output or TUI), parallel to capture_page for pages. Returns one bounded VT screen and a receipt, never a duplicate main-stream log. Use for any held session — not only full-screen TUIs. Settles on output quiescence; does not advance terminal_read’s cursor. |
terminal_close | Tear down a held pty (graceful then kill). It returns lifecycle acknowledgement only; use terminal_read or terminal_snapshot before closing when output matters. |
bash is not in this manifest by design. It is surface-gated —
only the phases whose coordinator
surface lists it can reach it at all — and every command is separately
allow-listed and confined before it runs; see
Security & privacy. Profiles may still deny it
explicitly, and most read-only work has a native tool that replaces it.
Tools an MCP server contributes are named
mcp_<server>_<tool> and can be referenced from profiles and
selectors with the same syntax as the ids above.
preview_overlay reports whether current source verification would let the overlay land. Promotion completes only when that verification has passed, then lands as one recoverable transaction. Each worker overlay ends with one promote_overlay or reject_overlay call.
The agent contract
Rejected tool calls come back as a structured reject ending in a stable Code: (often Rejected / Why / Instead / Code:). Design your prompts to branch on codes — the codes are agent-public stable; the message text is not. The codes themselves are emitted by Open Agent Rules.