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

ToolWhat it does
readRead 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.
writeWrite 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
editEdit 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_linesReplace 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_rewriteRewrite 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.
grepSearch 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.
findBounded 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).
jqQuery 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.
statFile metadata (mode, size, mtime, entry_count for directories) Replaces command: stat(1).
wcCount 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_dirDirectory 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.
chmodChange approval bits on repo-relative paths. Replaces command: chmod(1).
deleteRemove files and empty directories within write scope Replaces command: rm(1).
copyCopy files within write scope (files only; never use command cp). Replaces command: cp(1).
moveRename files within write scope (atomic rename; never use command mv). Replaces command: mv(1).
mkdirCreate directories within write scope (never use command mkdir). Replaces command: mkdir(1).
chownChange file ownership within write scope (never use command chown). Replaces command: chown(1).
diffUnified diff between two repo-relative file paths (read-scope only; never use command diff).
extract_archiveUnpack .zip or .tar.gz into a write-scoped directory (replaces unzip/tar xzf; HITL ask by default).

Git

ToolWhat it does
git_statusGit 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_diffGit diff JSON (available, diff hunks, or files stat rows; available:false on non-git projects). Replaces command: git diff.
git_logRecent git commits JSON (available, commits with hash/subject/author/date; available:false on non-git projects). Optional path filters history for one file.
git_commitStage 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_showShow commit or file at ref JSON (available, show with kind diff/blob). Replaces command: git show.
git_blamePer-line blame JSON (available, lines with commit/author/date/content). Replaces command: git blame.
git_restoreRestore 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_refResolve refs to SHAs JSON (available, refs with ref/sha/peeled). Replaces command: git rev-parse.
git_branchesList local branches JSON (available, branches with name/current). Replaces command: git branch.

Meta

ToolWhat it does
request_toolsLoad 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)
verifyRun 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_repoCross-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.
summarizeSummarize 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_readRead a skill’s instructions or one of its listed resources; no arguments lists the loaded catalog.
surface_notePost 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

ToolWhat it does
render_viewRasterize 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/. Tool result includes kit catalog.
capture_pageOne-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_pageMeasure 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_openOpen 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_actDrive a held page (page_open id); page stays alive so auth persists.
page_snapshotUniversal {state, snapshot, log, artifact} for a held page (does not close it).
page_closeTear down a held page; optional final snapshot.

Terminal

ToolWhat it does
terminal_openLive 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_sendWrite 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_readRead incremental pty output since the last read (idle/timeout bounded).
terminal_snapshotUser-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_closeTear 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.