Building an Agent¶
Build a Raven agent when you need a separate identity, tool profile, and execution policy on the shared runtime. Connecting an existing external product instead is covered by Agent Integrations.
Scaffold before customizing¶
Configure a host model provider first. Preview the files, then create an agent:
raven agents new example-agent --dry-run
raven agents new example-agent
The second command writes a new folder under $RAVEN_HOME/agents/example-agent.
It validates the manifests, compiles generated Python, scans discovery, and
by default launches an ACP initialize smoke check. The check can start the
runtime and write its state; it is not a full model task.
Names use lowercase kebab-case and cannot collide with shipped or discovered
agent identities. --display changes the display name. --here writes under
./agents/; --no-smoke skips launching; --register explicitly pins the row
in host config. The scaffold currently supports only --kind acp even though
the runtime can dispatch other backend kinds.
Do not re-run creation over an existing folder to upgrade it. Inspect the generated README and edit that definition. A reported refusal for missing provider credentials is different from a protocol or plugin-loading failure.
Understand the generated files¶
| File | Responsibility |
|---|---|
subagent.json |
Dispatch identity, description, ownership, launcher command |
run.py |
Render config and launch the installed Raven ACP server |
config.json |
The agent's settings on top of the shared runtime |
.env.example |
Document secret slots and storage overrides |
install.py |
Optional pinning for a custom location or fixed roster row |
plugins/<id>/raven-plugin.toml |
Declare the agent's runtime contributions |
| Plugin Python package | Implement tool and hook factories |
README.md |
Instructions specific to the generated agent |
Discovery substitutes {PYTHON} and {SUBAGENT_DIR} in the manifest. Keep
those placeholders in the portable definition rather than hard-coding this
machine's checkout. Fill the generated description/ownership TODOs: the
dispatching model uses those fields to decide when to select this agent.
Do not change name or memory identity just to rename a UI label: existing
sessions and recorded calls refer to those identities.
Configuration and state¶
The launcher renders a private config under the agent's state root. Keep the
publishable definition free of secrets; use the documented environment or
.env slots and keep private files out of git. Without a per-agent model key,
the launcher can inherit the host provider configuration. In own-key mode,
configure its provider/model endpoint too; a key alone is not a complete model
binding.
Keep three locations distinct:
- Working directory: task files supplied by the caller.
- State root: rendered configuration and product working state.
- ACP home: the child engine's identity, sessions, and skill pool.
Default storage is outside the definition folder. The child's ACP home must not be swallowed by a working directory the host passes to it; use the generated storage overrides only after checking that boundary.
Plugin-specific values belong under plugins.config["<plugin-id>"]. The
launcher injects the plugin roots; do not add a second discovery path by
guessing a relative directory. Optional ACP modes are declared profiles for
future turns, not a replacement for tool permission configuration.
Case study: Raven-Code¶
Raven-Code specializes without a second core loop. Its launcher renders
config and discovers agents/raven-code/plugins/code-flow/. The plugin
contributes tools, a loop hook, and a session observer through the contracts
in Building a Plugin.
| Responsibility | Seat inside the plugin's code_flow/ package |
Boundary |
|---|---|---|
| Project instructions | flow.py iteration hook |
Bound-workdir files and remaining prompt budget |
| Read-version tracking | tools/read_state.py and tools/filesystem.py |
Session ledger, not a global read grant |
| Persistent checklist | tools/todo.py and flow/session lifecycle |
Saved record is authoritative; bind the actual session |
| Concurrency notice | sessions.py and iteration hook |
Shared-directory evidence, not exclusive ownership |
| Git report | manifest.py into raven.harnessManifest metadata |
Unknown/shared facts cannot become “ready for integration” |
| Session cleanup | Manifest's session observer | Remove only the affected session's state |
The manifest deliberately replaces names such as read_file and edit_file.
Expose the actual replacement parameter schemas, not documentation for the
old tool face.
Two config gates are independent: plugins.config["code-flow"].enabled for
notices/reports, and tools.enabled inside that same slice for replacement
tools. Both default false in the plugin model; the Raven-Code launcher enables
its own render. Checklist/read-ledger binding remains necessary when notices
are disabled but tools remain enabled.
The flow appends repository instructions without replacing host identity or
rewriting the user's inbound text. The launcher's CODE_PROJECT_FILES selects
project files; off selects none. Trusted instruction loading must not become
arbitrary access outside the bound project.
For a new coding agent, begin with the scaffold and a small reviewed tool set. Reuse a contract rather than copying the product. Test:
- edits before a read and after another writer changes the file;
- two sessions sharing a directory;
- checklist restoration after a new turn or context trimming;
- session deletion and runtime replacement;
- unavailable Git facts and non-Git directories;
- sandboxed execution, where the host-exec replacement must decline.
Relevant tests include tests/test_agents_code_flow_read_state.py,
test_agents_code_flow_todo.py, test_agents_code_flow_project_files.py,
test_agents_code_flow_manifest.py, and test_agents_code_tools_plugin.py.
The usage guide
explains what operators can infer from these mechanisms.
Add tools and behaviour¶
Start from the generated greeting tool and hook factory. A tool implements
name, description, parameters, and async execute. Declare it in the
plugin manifest and test it in isolation before exposing it to a model.
The generated hook demonstrates the loop timing contract. Current shipped
agents also use the narrower AgentParticipant contract for per-turn
judgements; choose the seam that fits the behaviour rather than copying a
whole agent loop. See Building a Plugin.
Keep contributions scoped to this agent's config. Importing a plugin in the host must not accidentally enable the agent's tools in unrelated sessions. Do not put product-specific imports into the shared runtime to make discovery work; Raven treats the definition directory as data and loads contributions through the plugin seam.
Package an engine when needed¶
For a separately released harness, preview the wheel-shaped scaffold:
raven agents new example-engine-agent --engine-wheel --dry-run
Without --dry-run it creates an agent definition and an engine project.
The engine manifest lives inside its Python package, with an entry in the
raven.plugins entry-point group. The agent declares the engine package and
distribution; readiness stays false until that package is importable by the
child interpreter.
Manage dependencies with uv in the intended project/environment. Resolve
Raven from your verified checkout or distribution; the unqualified raven
name on a public package index can refer to a different project. Do not copy a
bare package-install command from an older design note. See
Development for repository environment setup.
Verify, discover, and operate¶
- Validate the generated manifests and import every factory with the same interpreter that will launch the agent.
- Check ACP initialize and the expected tool roster, not just process exit.
- Run a bounded read-only task, then test the specific mutation, output, or session continuation the agent needs.
- Test refusal paths: missing credentials/engine, permission denial, cancellation, and plugin activation failure.
- Restart the resident host after changing a definition, then verify that it can select and dispatch the agent.
A home-tree definition is discovered without pinning. For a definition outside that tree, use the scaffold's registration option or its generated installer deliberately. A pinned row may continue pointing at an old directory after a move; update the pin rather than assuming rediscovery fixes it.
Repository evidence lives in agents/BUILDING.md,
raven/cli/agents_commands.py, and the schema exports under schemas/.
Useful maintainer checks:
uv run pytest tests/test_cli_agents_commands.py tests/test_subagent_registry.py -q
These tests exercise scaffolding and registry behaviour; they do not replace a real acceptance task for your new agent.