Agent manifest
Each Covenant agent is a subdirectory of $COVENANT_HOME/agents/ containing an agent.toml manifest. The router walks agents/, picks up each subdirectory's agent.toml, and resolves agent.entry against that package directory. Flat *.toml files at the top of agents/are silently skipped. The manifest declares the agent's identity, runtime, package-relative executable path, required capabilities, resource budget, sandbox requirement, and optional settlement configuration.
Example
[agent]
id = "research"
name = "research"
version = "0.1.0"
runtime = "rust-bin"
entry = "research"
[capabilities]
required = ["tool.web_search"]
optional = ["memory.write"]
[resources]
cpu_ms_per_task = 30000
memory_mb = 512
disk_mb = 100
network = "outbound-https-only"
[sandbox]
required = true
backend = "linux-gvisor"
filesystem = "read-only-package"
[settlement]
budget_credits_per_hour = 1000
priority = "normal"Schema
[agent]
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | yes | Stable identifier. ASCII [A-Za-z0-9_.-]+; @ is rejected at parse time. Used as the routing key and the audit-log matched_agent value. The daemon synthesises<id>@agent as the agent's AgentId.display for budget keying. |
name | string | yes | Display name; appears in CLI listings. |
version | string | yes | SemVer recommended. |
runtime | enum | yes | rust-bin, python3, node, or hermes. The first three exec entry as a subprocess; hermes delegates to a configured Hermes HTTP endpoint and ignores entry. |
entry | string | yes | Path to the binary (for rust-bin) or the entry script (for python3 / node). Resolved relative to the manifest's parent directory unless absolute. Ignored when runtime = "hermes". |
[capabilities]
| Field | Type | Default | Notes |
|---|---|---|---|
required | list of action strings | [] | Every action in this list must be present in the issuer's active capability set or the dispatch is rejected. |
optional | list of action strings | [] | Recorded for visibility but not enforced. |
Action strings live in reserved namespaces: intent., memory., identity., tool., agent.. The daemon validates that required and optional actions sit in one of these namespaces.
[resources]
| Field | Type | Default | Notes |
|---|---|---|---|
cpu_ms_per_task | u64 milliseconds | 30000 | CPU budget. The runtime preempts the process when the projection tick flags projected overshoot and kills it at the elapsed cap as the backstop. |
memory_mb | u64 MiB | 512 | Advisory today; enforced by sandboxed runtimes. |
disk_mb | u64 MiB | 100 | Advisory today. |
network | enum | outbound-https-only | off, outbound-https-only, or full. |
[sandbox]
| Field | Type | Default | Notes |
|---|---|---|---|
required | bool | false | When true, the manifest must name a sandbox-grade backend. Trusted-local subprocess execution is rejected. |
backend | enum | trusted-local | trusted-local or linux-gvisor. The runtime crate has a gVisor runner and the daemon supports the linux-gvisor backend; live Linux CI coverage runs on sandbox-runtime path PRs via gvisor-live.yml. Promoting that workflow to a required check and broadening sandbox policy enforcement remain planned. |
filesystem | enum | read-only-package | read-only-package, ephemeral, or host. The field is parsed now and enforced by sandboxed runtimes. |
[settlement]
| Field | Type | Default | Notes |
|---|---|---|---|
budget_credits_per_hour | u64 | 0 | Soft cap; tolerated as 0 until budget and settlement enforcement are configured for the agent. |
priority | enum | normal | low, normal, high. |
[hermes]
Optional block consulted when runtime = "hermes"; ignored otherwise. Hermes manages its tool allowlist server-side today, so both fields are documentary — they pin the contract the agent author expects, surface in operator listings, and act as the enforcement seam once Hermes exposes per-run controls.
| Field | Type | Default | Notes |
|---|---|---|---|
tools_allowed | list of strings | [] | Tools the agent expects the run to invoke. Names match Hermes's tool-registry slugs (e.g. terminal, read_file, web). Operators can spot a manifest that over-asks before granting capabilities. |
approval_policy | enum | operator-prompt | How the runner should handle Hermes approval.request events when no operator is online. operator-prompt blocks until an operator answers via the console; auto-deny short-circuits to a denied response; auto-once accepts a single approval and stops. Reserved — runtime enforcement lands once the Hermes runner learns to post /v1/runs/{id}/approval. |
Runtime contract
At dispatch, the runtime spawns the agent according to runtime and entry:
runtime = "rust-bin" → exec entry directly
runtime = "python3" → exec python3 entry
runtime = "node" → exec node entry
runtime = "hermes" → POST to a configured Hermes HTTP endpointThe first three runtimes communicate over stdin/stdout. The agent reads exactly one JSON line from stdin:
{
"id": "uuid",
"text": "the user's intent",
"issuer": { "display": "user@local", "pubkey": "…" },
"issued_at": 1714938000000,
"priority": "normal",
"parent": null
}And writes exactly one JSON line to stdout:
{
"text": "…",
"sources": ["…"]
}Stderr output is captured by the daemon's tracing subsystem and surfaces in operator logs. The agent process must terminate within resources.cpu_ms_per_task; the runtime preempts the process via SIGTERM/grace/SIGKILL when the periodic projection tick observes that the process is on track to exceed the cap, and falls back to the wall-clock kill at the cap if preempt did not fire. Either path produces a dispatch error. Successful processes with malformed stdout are rejected as runtime failures, not accepted as successful dispatches. The current subprocess runner is trusted-local. If sandbox.required is true, it fails closed instead of silently running the agent without sandbox-grade isolation.
Validation rules
The manifest parser rejects manifests that:
- omit or leave empty any of
agent.id,agent.name, oragent.version; - omit or leave empty
agent.entrywhenruntimeispython3,node, orrust-bin(runtime = "hermes"ignoresagent.entryentirely); - contain characters in
agent.idoutside ASCII[A-Za-z0-9_.-]+—agent.idflows into the daemon's synthesisedAgentIddisplay and round-trips through that charset filter on every JSONL replay; - declare an
agent.entrythat is absolute or contains../ root / drive components for a subprocess runtime — entries must be relative paths inside the agent package directory; - declare a
requiredoroptionalcapability action outside the reserved namespaces; - set
sandbox.required = truewhile keepingbackend = "trusted-local"; - fail to parse as TOML.
Unknown top-level sections are tolerated for forward compatibility; subsequent releases may attach meaning to them.
Manifest discovery
On startup the daemon walks $COVENANT_HOME/agents/ and loads each subdirectory that contains an agent.toml; flat *.toml files at the top of agents/ and subdirectories without an agent.toml are silently skipped. The loader sorts the returned cards by agent.id so routing tie-breaking between equal-scoring agents is deterministic across hosts regardless of filesystem read order (APFS, ext4, and ntfs all return read_dir entries in different orders). Online registration is not supported; the daemon must be restarted after a new manifest is added. Existing manifests may be edited in place and are re-read on the next daemon start.
Related
- Concepts — agents in context.
- Capability tokens — what the
requiredlist refers to. - Security model — what the resource budget protects.