Agent-builder toolkit
The REPL's agent-builder surface — 15 tools, 10 slash commands, and the safety floors that keep it honest. Introduced in @declaragent/[email protected]; fleet + monitoring tools rounded it out through 0.6.0.
Builder tools load only when the host process opts in with
DECLARAGENT_BUILDER=on. Production agents shipped as dependencies
never see them.
For a walk-through of the flow, see:
- Conversational tour — recommended starting point, full two-agent fleet in ~15 minutes.
- Build an agent through conversation — single-agent transcript-style deep dive.
Tool catalog
Every tool is permission-gated under the builder's scope root. The
scope is resolved at session startup in this order: nearest
fleet.yaml → nearest agent.yaml → cwd.
Authoring
| Tool | Purpose |
|---|---|
DeclaraAddSkill({ name, description, body, inputs?, outputs?, agentPath?, addToAgentYaml?, confirmOutsideScope? }) | Author a skill. Writes <agentPath>/skills/<name>.md + surgically appends the ref to agent.yaml (preserves comments). Refuses bodies that look like leaked secrets. |
DeclaraAddSecret({ ref, provider, usedBy?, tenantScope?, agentPath? }) | Reserve a secret slot. Never accepts a value — derives a DECLARA_* env-var name, appends a commented block to .env.example, and returns an actionable hint. Validates the provider is declared in secrets.yaml for non-env providers. |
DeclaraAddSource({ type, id, config, agentPath? }) (0.4.0) | Author an event source on the agent's event-sources.yaml. Round-trips through core's loader + the in-process webhook/cron/file-watch adapters; rolls the file back on validation failure. External-broker types (kafka/nats/sqs/amqp/mqtt) pass through structurally and surface as external: true so the REPL can hint the user to install the matching adapter. |
DeclaraAddChannel({ type, id, config }) (0.4.0) | Register a user-facing channel (slack / telegram / discord / whatsapp) in the user-global ~/.declaragent/channels.json. Scope note: channels are shared across agents, not per-scaffold — the tool's hint makes this explicit. Inline structural validation; adapter-level checks stay in declaragent channels validate. |
DeclaraAddMCP({ name, command, args?, env?, protocolVersion? }) (0.4.0) | Register a stdio MCP server in the user-global ~/.declaragent/mcp-servers.json. Mirrors declaragent mcp add; refuses duplicates by name. Tools contributed by the server appear as mcp__<name>__<tool> after the next REPL restart. |
DeclaraAddPlugin({ pluginPath, confirmOutsideScope? }) (0.4.0) | Install a local plugin into the user-global ~/.declaragent/plugins.json. Reads + validates <pluginPath>/plugin.json via core's loader, records the manifest's declared permissions as consentedPermissions — the user's /yes on the surrounding proposal IS the consent. |
DeclaraFleetAdd({ template, id?, force?, fleetRoot? }) | Scaffold a new agent into the current fleet from a named template. Wraps the same addAgentFromTemplate the CLI verb uses. |
DeclaraAddPeer({ agent, transports, fleetRoot? }) | Append or merge a peer entry in rpc-peers.yaml. Preserves comments via yaml.parseDocument; validates the final file against core's peersConfigSchema. |
DeclaraAuthPlaybook({ provider }) | Return a concise markdown playbook for a supported provider. Pure lookup. Supported: anthropic, openai, github, slack, vault. |
Plan / apply
| Tool | Purpose |
|---|---|
DeclaraProposeChange({ summary, steps: [{ kind, description, preview?, payload }], requiresExplicitYes? }) | Register a plan with the session's proposal registry and await the user's decision. Blocks until /yes, /no, /edit, or the 15-minute TTL expires. Returns { proposalId, confirmed, finalSteps, reason }. |
DeclaraApplyChange({ proposalId }) | Walk a confirmed proposal's steps. Captures git HEAD before mutating; on any step failure halts + attempts a scoped git checkout rollback. Emits one tool_call audit record per step + a summary. |
Read-only inspection
All four are readonly: true, parallelSafe: true — they auto-approve
in default permission mode and can run concurrently.
| Tool | Purpose |
|---|---|
DeclaraEventsTail({ last?, kind?, correlationId?, sinceMs? }) | Read the last N entries from the session event store. Payloads are truncated to 140 chars with a … suffix to keep responses bounded. |
DeclaraFleetStatus({ history?, historyLimit?, fleetRoot? }) | Return the full FleetStatusReport for the current fleet — agents, envs, capabilities, peers, optional deploy history. |
DeclaraAuditVerify({ tenant? }) | Verify the audit-chain's hash continuity. Returns { ok, totalEntries, verifiedEntries, violations } — inspect ok before reporting. |
DeclaraDlqShow({ sourceId?, limit? }) | List rejected events in the session store — the client-side analog of the daemon-side broker DLQ. For broker-level DLQ entries, run declaragent dlq show <sourceId> <entryId> outside the REPL. |
Slash commands
| Command | Behavior |
|---|---|
/plan <description> | Ask the builder to draft a proposal without executing. No args: still aliases /mode plan for backwards compatibility. |
/yes [<phrase>] | Confirm the active proposal. Deploy / audit-erase / scope-breach proposals require the exact phrase shown in the prompt (e.g. /yes deploy). |
/no | Reject the active proposal. |
/edit <n> <replacement> | Revise step <n>'s description. Payloads are not user-editable via slash — ask the model to re-propose if the structured args need reshaping. |
/diff [<path>] | Show git diff scoped to the builder's scope root (or the given path). |
/scope | Print the current builder scope root. |
/fleet graph [mermaid|dot|json] | Render the fleet's peer graph inline. Default: mermaid. |
/undo | Revert the last DeclaraApplyChange via scoped git checkout. Requires git + the apply captured a HEAD. Single-step in v0.2; stacked undo lands in v0.3. |
/history [<limit>] | Render recent builder actions from the audit chain (default: 50 entries, newest first). |
Safety model
Four floors operate automatically — each is scoped to a specific failure mode the plan's §5 calls out.
Secret-leak redaction (pre-turn)
Every user message flows through redactSecrets() before the
model, the transcript, or the SQLite session store see it. Seven
pattern detectors cover the common cases:
- Anthropic / OpenAI-project / OpenAI-user API keys (
sk-ant-…,sk-proj-…,sk-live-…) - GitHub PATs + OAuth tokens (
ghp_…,gho_…) - npm tokens (
npm_…) - Slack tokens (
xoxb-…,xapp-…, etc.) - AWS access key ids (
AKIA…) - JWTs (three dot-separated segments with
eyprefix)
Matches are replaced with <redacted:label> in the redacted message.
The original value is discarded — not stored anywhere in the session.
A system line tells the user a redaction happened; the model is
instructed never to echo the marker back.
Scope confinement
Every file-writing tool asserts its target path lives at or below
the session's scope root via path.startsWith(scopeRoot + sep) (the
classic /foo vs /foo-bar prefix bug is guarded explicitly).
Reaching outside scope requires confirmOutsideScope: true, which
the proposal flow routes through a requiresExplicitYes confirmation.
Silent cross-repo writes are not possible.
Deploy deny floor
Bash:declaragent deploy* and Bash:declaragent fleet deploy* are
bottom-of-stack deny rules in the permission gate. A rogue model
calling Bash with declaragent deploy trips the gate rather than
the live site.
To actually deploy:
- Let the model propose via
DeclaraProposeChange({ summary: "deploy …", requiresExplicitYes: true }). - The user types
/yes deploy(exact phrase derived from the summary). - The user switches to
/mode bypass+ runsdeclaragent deploy …themselves, or exits the REPL.
The builder never drops the deny rule on its own.
Plan-then-execute
Every multi-file change flows through
DeclaraProposeChange → explicit user /yes → DeclaraApplyChange.
The proposal is a plan, not an action — the file system is untouched
until confirmation. Direct DeclaraAddSkill / DeclaraAddSecret
calls stay available for trivially-scoped single-step work.
Audit chain
Every DeclaraApplyChange emits:
- One
tool_callrecord per step withtool: 'Declara:<stepKind>'. - One summary
tool_callrecord withtool: 'DeclaraApplyChange'.
All records share a correlation id so /history threads them. The
hash-chain stays verifiable through DeclaraAuditVerify({ tenant })
or declaragent audit verify outside the REPL.
REPL input UX (0.4.1)
Four conversational-flow helpers make long prompts + proposals feel natural:
- Bracketed paste. Paste a multi-line prompt into the REPL and every line lands in the input atomically. The REPL enables
CSI ?2004hon startup and owns a parallel stdin listener that watches for the paste markers; embedded\nmid-paste no longer fires submit. Works out of the box in every modern terminal (iTerm2, Terminal.app, Alacritty, Ghostty, VS Code's integrated terminal, tmux). Terminals without bracketed-paste support degrade gracefully. /prompt <path>. Read a file and submit its contents verbatim as your next user message. Useful for prompts that outgrow a single paste buffer, or for reusing a saved brief.@<path>file refs. Mention a file inline (please summarise @README.md) — the REPL inlines the file contents in a fenced block before sending to the model. Supports absolute, relative (resolved from cwd), and~/paths. Dedupes repeated tokens; truncates attachments at 256KB; leaves emails ([email protected]) alone.- Bare-letter proposal shortcuts. When a proposal is pending, a bare
y/yes/n/nosubmission routes as/yes//no. Explicit-yes proposals (deploys, audit erasures) still require the full phrase — the shortcut bows out safely. The typed/edit <n> <replacement>path stays unchanged for revising steps.
Environment variables
| Variable | Purpose |
|---|---|
DECLARAGENT_BUILDER | on enables the builder toolkit. Default: off. |
The scope root is resolved from process.cwd() at session start; no
env var overrides it. Use /scope to confirm.
Deferred to v0.3+
- Multi-step
/undo— stack the last N applies. - Snapshot fallback for non-git projects — phase 6 mandates git; a homegrown snapshot layer for non-git workflows is tracked.
/monitorpane — bottom-of-screen Ink live-tail for events. The four read-only tools cover the "check state" need; the pane adds ambient awareness, which needs daemon-side push first.builder.*audit kinds — v0.2 reuseskind: 'tool_call'with aDeclara:-prefixedtoolfield. A dedicatedbuilder.apply/builder.addSkillunion lands when the core schema next bumps.- Multi-model builders — the propose / plan step could run on a cheaper model; execution stays on the session model.
Related
- Build an agent through conversation — end-to-end transcript.
- CLI reference — underlying verbs the builder wraps.
agent.yamlschema — the schemaDeclaraAddSkillround-trips through.- Fleet reference — context for
DeclaraFleetAdd+DeclaraAddPeer.