# The Web Designer — a core Astroagent console update

**Status:** Implemented · **Date:** 2026-07-24
**Related:** [../agents/console/PLAN.md](../agents/console/PLAN.md) · [../agents/console/SETUP.md](../agents/console/SETUP.md) · [../brand/BRAND.md](../brand/BRAND.md) · [../.claude/skills/brand/SKILL.md](../.claude/skills/brand/SKILL.md)

---

## What it is

The **Web Designer** is a new capability of the **astroagent console** — the base's
in-site authoring engine (`agents/console/server.mjs`). It is not a separate OS agent; it
is a new *mode* of the existing confined runner (`carlos-arias-agent`), reached through the
already-gated `/devconsole/` nginx prefix.

You hand it a **brief for any page** — a prompt/instructions, a draft, uploaded images, and
links to short videos — and it does real design work (edit a section, restyle a component,
lay out a new page) and publishes. Briefs are **queued**; the agent drains the queue one task
at a time.

This is a **core engine change**, not site content — a fresh SeedProject clone that runs the
migration + `configure.mjs` inherits the whole thing. (See the site-coupling caveat below for
the one thing that is not yet fully clone-clean.)

It coexists with two sibling surfaces built earlier in the same arc:
- the **in-page console** (the ▲ handle) — freeform, contextual, preview→publish by hand;
- the **project builder** (`/admin/projects/new` → `/devconsole/build-project`) — the
  *structured* path that writes a `cja_projects` row.

The Web Designer is the *freeform, queued* path for everything else.

## Architecture

```
/admin/designer  ──create task──▶  cja_tasks (queued)     [PHP: admintasks controller]
      │  poll status                       ▲
      └──kick──▶ /devconsole/tasks/run ──  drainTasks() loop   [Node runner]
                                            │  claim next   (php api/cli/tasks-next.php)
                                            │  run design agent: claude -p (skills + brief, Write/Edit)
                                            │  build → git commit LIVE   (auto-publish)
                                            │  mark finished (php api/cli/tasks-finish.php)
                                            └─ next task …
```

The Node runner is deliberately dependency-free (no DB driver), so it reads/updates the queue
by shelling out to small **PHP CLI helpers** — keeping MariaDB behind the single PHP data
layer. The design work itself is a `claude -p` spawn, the same harness as the in-page console.

### Locked decisions

| Question | Decision |
|---|---|
| Scope of the agent | **Full builder** — may edit any page and create new pages (updating the nav in `Header.astro` + `Footer.astro`). |
| Publish model | **Auto-publish & continue.** Each task builds, commits live, and the queue advances hands-off. Safety net = git history (one revert per task) + a build-must-pass gate. |
| Skills | **CLI-native.** Skills live under `.claude/skills/` so `claude` auto-discovers them; the persona also reads them explicitly for headless reliability. |
| Queue store | **DB table `cja_tasks`** (durable, survives restart), not the in-memory `jobs` Map. |
| Runner ↔ DB | **PHP CLI bridge** (`tasks-next.php` / `tasks-finish.php`); the runner stays dependency-free. |
| Progress UX | **Polling** `admintasks/list`, not SSE — sidesteps the drifted server/DevConsole event contract. |

## Skills (the CLI-native design brain)

`.claude/skills/` is new and is what makes the agent design rather than merely edit:
- `brand/SKILL.md` — the sumi-e brand system distilled from `brand/BRAND.md` (the seal rule,
  palette tokens, type scale, radii, do/don'ts). **This file is the site-specific piece** — a
  clone swaps its contents.
- `ui-ux/` — a symlink to the existing generic `app/.astroagent/skills/ui-ux` (reusable).

Before my change the console passed **no** skill wiring at all; the skills array in
`astroagent.config.json` was inert. The runner still passes no `--skill` flag — discovery is
by location (`.claude/skills/`) plus an explicit "read these SKILL.md files" instruction in
the design persona.

## Safety model

- **Clean-tree guard.** A task refuses to run if the git scope (`app brand api/db api/cli`) is
  dirty — otherwise the auto-publish `git add` would sweep unrelated WIP into the task's
  commit. Uploaded assets under `app/public/media/` are exempt (they *are* the task's images
  and get committed with it). This guard exists because exactly that hazard bit during the
  build: an uncommitted file was swept into a task commit and a revert then deleted it.
- **Build-must-pass gate** before any commit; a failing task reverts its own edits
  (`git checkout` + `clean`) and the queue continues.
- **Scoped commits** — `git add` never touches secrets/config, only `app brand api/db api/cli`
  (+ media). The agent runs confined, Write/Edit within the repo only.
- **Single-flight** — the queue shares the runner's global `busy` lock, so a draining queue and
  the in-page console can never run at once (the console returns 429 while draining).

## Files

| File | Role | Core / site |
|---|---|---|
| `agents/console/server.mjs` | `buildDesignPrompt`, `runDesignTask`, `drainTasks`, `POST /devconsole/tasks/run` | **core** |
| `api/db/migrations/011_create_cja_tasks.sql` | queue table | **core** |
| `api/public/controllers/admintasks.php` | create / list / get / cancel | **core** |
| `api/cli/tasks-next.php` | atomic claim (queued → running) | **core** |
| `api/cli/tasks-finish.php` | mark published / failed + result | **core** |
| `app/src/pages/admin/designer.astro` | submit brief + poll the queue | **core** |
| `app/src/layouts/AdminLayout.astro` | "Web Designer" nav link | **core** |
| `.claude/skills/ui-ux` (symlink) | generic design skill | **core** |
| `.claude/skills/brand/SKILL.md` | this site's brand | **site-specific** |

## Site-coupling caveat (not yet clone-clean)

The prompt-builders in `server.mjs` — both the new `buildDesignPrompt` and the pre-existing
`buildPrompt` — **hardcode** "the Carlos Arias website" and "sumi-e" directly in the engine.
This site identity was already baked into the console before this change; the Web Designer
matches that pattern rather than adding new debt. For a truly clone-clean base, those strings
should be sourced from `site.config.json` / `astroagent.config.json` + the `brand` skill,
leaving the runner site-agnostic. Deferred — flagged, not done.

## How to run it

1. Sign in to the admin (secret link), open **/admin/designer**.
2. Submit a brief: pick a page (or "New page"), write instructions, optionally a draft,
   upload images, paste short-video links → **Add to queue & run**.
3. The queue drains automatically; each row shows `queued → running… → published`
   (with a live link + commit) or `failed` (with the reason).
4. Undo any task with `git revert <commit>` on `main`, then rebuild.
