Content Agent
What is the content agent?
accent agent runs a budgeted, resumable, human-in-the-loop LLM session
over your site’s own content. You give it a goal in plain language; it reads
pages, searches content, and proposes edits through the same content API the
server uses. Every write above the draft line pauses for your approval before
it touches a file.
Unlike the MCP server – which exposes your content to an external assistant like Claude Desktop – the content agent runs in-process, drives an LLM endpoint you choose, and steers from your terminal. It is a local authoring tool, not a network service.
The command is free in every license state, including the unlicensed “Core” tier: it fires no license check. Local authoring is the free tier; you pay only to serve in production. You bring your own LLM endpoint and key.
Running a session
The minimum invocation is a goal plus a provider, endpoint, and model:
accent agent "Fix typos in the getting-started guide" \ --provider anthropic \ --endpoint https://api.anthropic.com \ --model claude-opus-4-8
There is no default provider. The endpoint and model are always explicit
input – the agent never guesses a provider or reads a generic vendor
variable like ANTHROPIC_API_KEY or OPENAI_API_KEY. If a required setting
is missing, the command fails immediately with a message naming the field,
before it loads any content or opens a connection.
Choosing a provider
Two provider kinds are built in:
anthropic– the Anthropic Messages API.openai– any OpenAI-compatible/v1/chat/completionsendpoint, including local servers such as Ollama, llama-server, and vLLM.
Remote endpoint (Anthropic)
accent agent "Add a summary paragraph to /blog/release-notes" \ --provider anthropic \ --endpoint https://api.anthropic.com \ --model claude-opus-4-8 \ --api-key sk-ant-...
Local endpoint (OpenAI-compatible)
A local server needs no key:
accent agent "Tighten the intro on the home page" \ --provider openai \ --endpoint http://127.0.0.1:8080 \ --model llama-3.1-8b-instruct
The API key
The key is resolved from --api-key first, then the
ACCENTCMS_AGENT_API_KEY environment variable. No generic vendor variable is
ever consulted – the agent’s key is Accent-namespaced by design, so a stray
OPENAI_API_KEY in your shell can never silently configure an outbound call.
Keep the key out of config.yaml; prefer the environment variable:
export ACCENTCMS_AGENT_API_KEY="sk-ant-..." accent agent "..." --provider anthropic --endpoint https://api.anthropic.com --model claude-opus-4-8
Provider defaults in config.yaml
To avoid repeating --provider/--endpoint/--model on every run, put them in an
optional agent: block. A CLI flag always overrides the config value, and any
setting supplied by neither the flag nor the block is an error:
agent: # `enabled` mounts the server-side agent surfaces (the admin chat panel # below). The CLI is opt-in by invocation and is NOT gated by this flag; # it defaults to false. enabled: false llm: provider: anthropic endpoint: https://api.anthropic.com model: claude-opus-4-8
The agent: block is optional and default-off: omitting it entirely is never
an error or a warning, and it changes nothing about accent serve or
accent build. With the block in place, the earlier command collapses to:
export ACCENTCMS_AGENT_API_KEY="sk-ant-..." accent agent "Fix typos in the getting-started guide"
Budget
Every session runs under a hard budget. It stops at the first limit reached:
| Flag | Default | Meaning |
|---|---|---|
--max-steps | 25 | Maximum tool dispatches |
--max-tokens | 200000 | Maximum total tokens across the session |
--max-seconds | 900 | Wall-clock limit |
--max-completion-tokens | 4096 | Tokens per single completion |
accent agent "Normalise heading capitalisation across /docs" --max-steps 40 --max-seconds 300
Approval flow
Reads run freely. Any write at or above the published line pauses for your approval: the agent first performs the edit as a dry run and shows you the preview, then waits. You reply on stdin:
approve <id>– re-dispatch the write for real.deny <id> <reason>– skip it with no side effect; the reason is fed back to the agent so it can adjust.
Anything else you type is treated as steering – free-text guidance the agent folds into its next step. A denied write leaves no trace on disk.
[tool] get_page
[tool] update_body
[approval needed] reply with: approve c3 | deny c3 <reason>
approve c3
...
[done] Fixed 4 typos across getting-started.md; awaiting further instructions.
Session log and crash resume
Each session appends to a JSONL log (default .agent-session.jsonl, override
with --session-log). Pass --resume to rebuild a crashed session’s history
and budget from that log before continuing; without it, a restarted run starts
fresh (the log is still appended to either way).
The session-log format is disposable. It is not a stable, versioned artifact yet: replay compatibility is not promised across upgrades. Treat the log as scratch state for the current run, not as durable history. A future release will lock the format.
Cross-referencing the git audit trail
On a production author-role deployment with git.enabled: true, every
approved write already creates a git audit commit (see
git deployment). Agent writes are no exception, and the
session log records the connection: the log entry for a committed write
carries the commit’s hash in an audit_commit field, so you can go from a
session-log line to the exact commit git log shows for the same mutation –
and back – when reviewing what an agent changed. Everywhere else (dev serve,
the free CLI, publisher-role deployments) no commit is created and the
field is simply absent: the log bytes are identical to a build without the
stamp. Like the rest of the session log, the field is not promised across
upgrades until the format locks.
The admin chat panel
The same agent can be driven from the browser. With the panel enabled, every admin screen shows an Agent chat button in the top bar (and the dashboard shows an entry card): a slide-over panel where you type a goal, watch the run stream live, steer it mid-flight, and approve or deny writes in a dialog.
Two switches gate the panel, and both must be on:
- A Core+ (or higher) license. The panel lives inside the admin shell
(
/_admin/), which requires a license key in development and production alike. Without one, no agent surface exists. agent.enabled: truein theagent:block, plus the sameagent.llmprovider settings andACCENTCMS_AGENT_API_KEYthe CLI uses.
admin: enabled: true agent: enabled: true llm: provider: anthropic endpoint: https://api.anthropic.com model: claude-opus-4-8
The surface is inert and reversible by default: with agent.enabled
false (or absent) the admin renders no panel, no launcher, and no agent
routes – byte-for-byte the same HTML as a build without the agent. Flipping
the flag off and reloading the config (SIGHUP or POST /_admin/reload)
removes the surface immediately; a run that is mid-flight winds down at its
next approval boundary instead of being killed.
How a browser session works
- First message = the goal. Sending starts the session; later messages steer it, exactly like typing free text into the CLI session.
- Approvals are a dialog. When the agent proposes a write at or above the published line, the panel raises a modal showing the exact call preview with Approve and Deny (plus an optional reason) buttons. Choosing Later keeps the pause – the write stays pending server-side and a Review pending write button brings the dialog back whenever you are ready; the dialog is never re-raised over what you are typing. A pending approval survives page navigation and reconnects.
- Questions land inline. If the agent asks a clarifying question, it appears in the transcript and your next message answers it. Like an approval, an unanswered question survives page navigation: the panel re-shows it on the next screen.
- The launcher shows a dot when the agent is waiting. A pending approval or unanswered question marks the Agent chat button (and the dashboard card) with an attention dot, so a waiting run is visible even while the panel is closed.
- Connection loss heals itself. If the event stream drops, the panel reconnects and re-reads the server’s run state – including the final answer of a run that finished while you were away. If your admin session expires (for example, a dialog left open overnight), the panel says so instead of pretending the click worked: sign in again and retry.
- One session per login. Each admin login drives at most one run; signing out winds the run down so it cannot outlive the credential that started it.
Roles still apply
The panel inherits the server’s role-derived capabilities. On a
publisher-role deployment the agent is read-only: writes are denied
outright by the capability gate – no approval dialog is ever raised, and
nothing touches disk. Author and developer deployments get the full
approval flow.
Notes
- The agent operates through the same content API and validation the server uses, so its edits respect your document models and frontmatter rules.
- On Unix, an unchanged edit is a no-op: re-writing identical content skips the file write, cache invalidation, and audit commit. Crash-resume therefore never applies the same change twice.
accent agentproduces no rendered HTML of its own; it edits the markdown your theme already renders. The worked example above is the reference for how a session looks.