The agent setup behind this site
forwarddeployed.eu is a plain Jekyll site published from docs/ on GitHub Pages, with no CI and no custom build step. What’s less plain is how its content and structure get written: by a small team of Claude Code agents, defined in .claude/agents/ in the repo, coordinating over GitHub issues and pull requests. This post describes that setup as it exists today — not an aspiration, the actual files.
The four agents
conductor (model: opus, tools: Agent, Read, Glob, Grep, Bash) is the entry point. It doesn’t build the site or write content itself — it reads the task or issue, sets up a feature branch, splits the work between the other two builder agents, and takes the result through to a reviewed PR. It’s explicitly barred from editing anything in docs/ itself; even a small fix gets delegated.
layout (model: sonnet, tools: Read, Write, Edit, Glob, Grep, Bash) owns Jekyll site structure: _config.yml, layouts, includes, styles, navigation, and the pages that render content. It’s responsible for keeping docs/CNAME intact, using only plugins GitHub Pages supports, and passing internal links through relative_url/absolute_url. It renders from the content sources the writer fills — posts and the “from the web” data file — rather than hard-coding text, and it doesn’t write articles itself.
writer (model: sonnet, tools: Read, Write, Edit, Glob, Grep, WebFetch, WebSearch — this is the agent producing the post you’re reading) owns content: blog posts in docs/_posts/ and “from the web” entries in docs/_data/from_the_web.yml. Its core rule is to check sources before summarizing them — fetch the linked video or article, don’t invent quotes or numbers — and, when it’s asked for a sample or placeholder entry it can’t fully research, to label it clearly as a draft rather than pass it off as finished. It stays out of layouts, includes, styles, _config.yml, and docs/CNAME.
reviewer (model: opus, tools: Read, Glob, Grep, Bash) checks a PR against its issue’s acceptance criteria and against GitHub Pages validity — front matter conventions, that docs/CNAME and _config.yml are intact, that referenced layouts and includes exist, that no build output got committed. It’s read-only: it can run read-only gh commands like gh pr view and gh issue view, but it never edits, commits, pushes, merges, approves or closes anything. Its output is a fixed format: a verdict of “approve” or “changes needed,” a checklist of acceptance criteria marked met, partially met or missing, and any required fixes labeled by which agent owns them.
The end-to-end workflow
- Read the issue. The conductor runs
gh issue view <N>and treats its intent and acceptance criteria as the definition of done. If the task is genuinely ambiguous, it asks the user rather than guessing. - Branch. It creates a feature branch from an up-to-date
origin/main— never working directly onmain— named for the intent of the work, and keeps using that branch for the rest of the session. - Delegate, layout before writer. Structure and styling go to layout; posts, entries and copy go to writer. When a task needs both, layout runs first so the writer fills structures that already exist. Each agent gets a self-contained brief — the issue’s intent, the specific acceptance criteria it owns, and the relevant conventions — because the sub-agents don’t see the conductor’s conversation.
- Check the result. The conductor reads the changed files itself and confirms every acceptance criterion is covered before moving on.
- Commit, push, open the PR. The conductor commits with a descriptive message, pushes, and opens or updates the PR via
gh pr create/gh pr edit, includingCloses #<N>for the linked issue. - Review. The conductor spawns the reviewer with the PR number. If the verdict is “changes needed,” it routes the concrete fixes to whichever agent owns them, then commits, pushes, and re-reviews. It repeats this for at most five fix rounds — if it still fails after the fifth, it reports what’s left rather than retrying indefinitely.
- Report. The conductor tells the user the PR link, the final review verdict, and anything that wasn’t verified — for example, a Jekyll build it couldn’t run locally.
How it gets started
There’s no dashboard or scheduler behind any of this. A session starts with claude --agent conductor and a task like “work on issue #6” — the conductor takes it from there. Each agent can also be invoked directly for narrower work, bypassing the conductor entirely.
Guardrails
A few constraints run across the agent definitions and CLAUDE.md:
- No force-push, no merging, no closing issues. This rule lives in conductor.md: the conductor is explicit that merging a PR is the user’s call, not something an agent does on its own.
- Only GitHub-Pages-supported plugins. Since the site builds natively on GitHub Pages with no CI, layout can’t reach for custom Ruby plugins or anything outside what
github-pagesships. - The writer fetches before it summarizes. No quotes, numbers or claims get written up without checking the source first, and if it can’t access a source, it says so and writes only what it can support — anything unverifiable is left out rather than invented.
- The reviewer is read-only by instruction, not by tooling alone. Its tool list is Read, Glob, Grep, Bash — no Write or Edit — but Bash itself can write, commit and push. What actually keeps it from doing that is its prompt: only read-only
ghcommands (gh pr view,gh pr diff,gh pr checks,gh issue view), a rule to never edit, commit, push, merge, approve or close anything, and a PR comment only when its caller explicitly asks. - A committed allowlist backs some of this up.
.claude/settings.jsonpre-approves only read-onlyghcommands (pr view/diff/checks/list,issue view/list) and read-onlygitcommands (status,log,diff,show,ls-tree, branch listing). Any other Bash command — a push, a commit, a file write through the shell — still needs a permission prompt unless the session’s permissions allow it. - Path ownership stays fixed. Posts and data files are the writer’s;
_config.yml, layouts, includes and styles are layout’s; the conductor edits neither directly. Those content paths and field names are kept consistent across the writer, layout and reviewer prompts specifically so they don’t drift apart.
Trade-offs, honestly
Some of this is genuinely enforced, and some of it isn’t. What Claude Code actually enforces is each agent’s tools: allowlist in its frontmatter: the writer has no Bash at all, and the conductor and reviewer have no Write or Edit — so, for example, the reviewer literally cannot call a file-editing tool, whatever its prompt says. .claude/settings.json adds a second, committed layer on top: it pre-approves only read-only gh and git commands, so anything else run through Bash — a push, a commit, a shell-based file write — still needs a permission prompt rather than running silently.
What isn’t enforced this way is finer-grained: which specific paths under docs/ an agent should touch is prompt-level, not tool-level — layout could technically call Write on a file in _posts/, and nothing path-specific stops it: any permission check it hits is the same one that covers its legitimate writes to layouts and styles. The agents that carry Bash — conductor, layout and reviewer — aren’t kept from running something like gh pr merge or git push --force by their tool lists either; those commands simply aren’t in the .claude/settings.json allowlist, so running them still triggers a permission prompt. For the conductor and reviewer, what stops them is that prompt plus an explicit instruction never to merge or force-push — and, for the reviewer, never to push at all. Layout’s prompt says nothing about merging or pushing, so for layout the permission prompt is the only check there is; a user approving it, or a session with permissive settings, removes that check with nothing behind it.
Separately, any of the agents also reads content from outside the conversation — issue text, PR comments, a fetched article — and a CaMeL-style design would treat that as untrusted input that shouldn’t be able to trigger actions on its own. Today that boundary is informal: agent prompts, file-path conventions, permission prompts, and a human deciding whether to merge.
The five-round review limit is also a real trade-off: it caps how long the conductor will iterate before handing a problem back to the user, which keeps sessions bounded but means some issues need a manual pass rather than getting fully closed out by the agents alone. The cap used to be one round, and it moved to five for a mundane reason: while this post was being drafted, the second review of its own PR still found issues after the single allowed fix round, so the user raised the limit rather than absorb every leftover fix by hand.
A meta note
This post is itself an example of the workflow it describes: it resolves issue #6, “New Blog Post: Current Agent setup,” and was produced by the conductor delegating to the writer, with the reviewer checking the result before it went out. Every claim about the setup is drawn directly from CLAUDE.md, the four agent definitions in .claude/agents/, and .claude/settings.json as they exist in this repository — nothing here is aspirational. The one exception is the cap-history detail above, which comes from this post’s own review history rather than from those files.