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/completions endpoint, 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:

FlagDefaultMeaning
--max-steps25Maximum tool dispatches
--max-tokens200000Maximum total tokens across the session
--max-seconds900Wall-clock limit
--max-completion-tokens4096Tokens 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:

  1. 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.
  2. agent.enabled: true in the agent: block, plus the same agent.llm provider settings and ACCENTCMS_AGENT_API_KEY the 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 agent produces 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.