- FRAMEWORK.md: the host + pluggable-agent contract, the roster, and the
clone+assign flow
- configure.mjs: ensure a default agents roster on a fresh clone
- .memory/{qa-agent,webdesigner}.md: framework notes pointing at the module layout
Verified: a scratch clone with a different site.config.json yields a different
identity + roster from identical core code.
4.4 KiB
AstroAgent — the standalone agent framework
A generic host that runs pluggable agents, cloneable into any SeedProject site, where
each project enables the agents it needs via config. The host knows nothing site-specific;
identity comes from config, brand from the per-site brand skill.
Module layout (agents/console/)
| File | Role |
|---|---|
host.mjs |
The generic runtime — site-agnostic. Shared primitives + route registry + scheduler + core routes (/ping, /auth, /logout). |
site.mjs |
The only place identity is read. Derives name / url / host / model / agentUser / home / role / qaEmail / qaRoutes / agents from site.config.json + astroagent.config.json. |
server.mjs |
Bootstrap: load host → register the enabled agents (with their settings) → host.listen(). |
agents/<name>.mjs |
One agent each — in-page-console, project-builder, web-designer, qa. Site-agnostic; identity via site.*, brand via brand/BRAND.md + the brand skill. |
Agent module contract
Each agents/<name>.mjs exports a manifest and a register:
export const manifest = {
name: "qa", title: "QA Agent", class: "runtime",
description: "…",
triggers: [ { type:"endpoint", method:"POST", path:"/qa/run" },
{ type:"schedule", settingKey:"heartbeatMin" } ],
tools: "Read Skill", skills: ["qa"],
tables: ["cja_qa_runs","cja_qa_findings"], // migrations this agent needs
cli: ["qa-start","qa-finish","qa-routes","qa-autofix"], // api/cli/*.php bridges it needs
};
export function register(host, settings = {}) {
host.route("POST", "/qa/run", (req, res) => { /* uses host.* */ });
if ((settings.heartbeatMin ?? 60) > 0) host.everyMinutes(settings.heartbeatMin, fn);
}
register receives the host + this agent's per-project settings. It wires routes/triggers
and uses only host.* primitives — never globals — so an agent is a self-contained unit.
Host API (host.*)
route(method, path, handler)— register an HTTP route (method:"ANY"matches any verb).runClaudeJson({prompt, tools, model})→{ok, result}— one-shot Claude.runClaudeStream({prompt, tools, model, resume, onMessage, …})— streaming Claude (SSE).build(extraEnv),git(args),phpCli(args)— build / git / the DB bridge.everyMinutes(min, fn, {bootDelayMs})— scheduled trigger (heartbeat).newJob/emit/jobs— SSE job model.readBody/json— HTTP helpers.state.busy— the shared single-flight lock (mutable across agent modules).gitScope,REPO,APP,PREVIEW_DIR,DEFAULT_MODEL,AGENT_TOOLS— framework constants.- Extension points agents attach, e.g.
host.drainTasks(web-designer) which QA autofix calls.
The roster — assign agents to a project
astroagent.config.json:
"agents": {
"enabled": ["in-page-console", "project-builder", "web-designer", "qa"],
"settings": { "qa": { "heartbeatMin": 60, "autofix": true } }
}
server.mjs loads only enabled agents. Disable one → its routes 404 and its triggers stop.
A restaurant site might enable ["web-designer","qa","menu-updater"]; a law firm
["web-designer","qa","seo","intake-triage"] — same framework, different roster.
Clone + assign
- Clone the base →
scripts/new-site.sh(or editsite.config.json+node scripts/configure.mjs) — stamps identity (name, url, agentUser, timezone…).site.mjspicks these up automatically. - Set
astroagent.config.json → agents.enabledto the roster this project needs; tunesettings. - Provision each enabled agent's dependencies — its
tables(migrations), itscli(api/cli/*.php), itsskills(.claude/skills/*or MCP). The manifest lists them;node agents/catalog.mjs→agents/agents.json, diff it against a clone to find gaps. - Start the console service → the host loads exactly the enabled agents.
Add a new agent
Drop agents/console/agents/<name>.mjs exporting manifest + register, add its name to
agents.enabled, provide its migrations/CLI/skills, run node agents/catalog.mjs, restart.
What stays site-specific (never in the core)
site.config.json + astroagent.config.json (identity + roster), the brand skill +
brand/BRAND.md (visual rules), the cja_* content, and the built site. The registry
(agents/agents.json, generated) unifies pipeline (agents/scripts/*) and runtime (console)
agents so one manifest diff covers a whole clone.