Tayler Erbe · Personal Project · System Design

Agent Architecture
How my agents actually work together

I run 29 AI agents across Skyrule, a personal product project, and my other personal projects. They share one job queue, one memory, one decision log and an ownership map that tooling enforces, on a single workstation with one GPU. This page is the system design: who the agents are, how work moves, what stops them, and what still needs me. Agent City, the 3D town, is how I watch it.

Status
Running daily · planned parts are marked as planned
Role
Designer and builder of the whole system
Job queue live since
Oct 2, 2026
Numbers
As of Oct 19, 2026
01The agent roster 02The work lifecycle 03Coordination and handoffs 04Models and the GPU 05Quality and governance 06Observability: Agent City 07Known limits
29
Agents with written charters (Oct 19, 2026)
1,060
Jobs finished by the queue since Oct 2 (Oct 19, 2026)
1,496
Product tests passing of 1,499 run (Oct 19, 2026)
294
Founder decisions logged since Sep 30 (Oct 19, 2026)

Who the agents are

I organised the agents like a small company, because the hard problems in multi-agent work are the same ones a company has: who owns what, who may decide, and how work passes between people without getting lost. Every agent has a written charter that says what it is for and, just as important, what it is not for and who owns that instead.

There are three groups. The product organization is twelve departments of Skyrule. Four cross-cutting agents check, route and block across all of them. The rest serve my other personal projects and the machine itself. All 29 run on the same rails.

Me Level 3 decisions only Product organization · 12 Office of the Founder Program management Domain practice Relational science Editorial Product design Platform and data AI and performance Data science Brand and marketing Legal and trust Finance and operations Cross-cutting · 4 Principal Architect Quality and compliance Security and IP Overseer (hourly) integrate and route block with a written reason check, never act Personal and operations · 13 Voice writer Writing partner Post menu Recon chief (news) Video studio GPU keeper Kissaten DJ (music) Site keeper Foreman (paused) 4 personal admin dashed: paused, or not detailed here Shared rails every agent runs on Job queuelanes, gates, budget Memory and recall555 sourced facts Decision logappend-only, 294 entries Ownership mapwrite scopes, enforced Agent Cityreads all of it
Scroll sideways to see the whole diagram
The 29 agents by function, above the shared rails (counts as of Oct 19, 2026)
AgentLane: what it ownsMayMay notDefault model
Office of the FounderDecision-record drafts, conflict briefs, dated handoffsDraft records for me with a consequences table per departmentMake any ruling or ratificationClaude Sonnet · local admin seat
Program managementCalendar, gate tracker, risk register, backlogSay where and when; number decision records; re-derive totalsDecide what gets built, or any technical choiceClaude Sonnet · local admin seat
Domain practiceThe product's domain rules and their sourcesWrite rule specs, source registries and their checkersDecide what a rule means (that is mine), or write proseClaude Sonnet · local admin seat
Relational scienceThe relational frame: roles, goals, relation kindsTest any department's proposal against the consent ruleDomain rules, product voice, the engineClaude Sonnet · local admin seat
EditorialThe content library, voice guide, rubricVerify citations, argue against a draft, prepare gradingGive the final grade or write the proseClaude Sonnet · local prose seat
Product designApp surfaces and their specs, accessibilitySpec the views and their CI assertionsCopy of any kind, the API or engine, pricingClaude Sonnet · local code seat
Platform and dataEngine, API, data, job execution, CI, backupsBuild and run the pipeline and its run recordsModel choice, test policy, what may be quotedClaude Sonnet · local code seat
AI and performanceExtraction, scoring contract, grading instrumentsBuild instruments, run its own tests, plan GPU workDomain rules, the engine, public claimsClaude Sonnet · local code seat
Data scienceTracking plan, calibration study design, the weekly numbersDesign studies, power and interval arithmeticDecide which rules are true; what may be loggedClaude Sonnet · local admin seat
Brand and marketingBrand system, claims log, marketing planDraft claims and check them against decisionsPublish, post or upload anythingClaude Sonnet · local prose seat
Legal and trustPrivacy, rights and promises to customersDraft policies and briefs for counsel; veto with reasonsGive a licensed lawyer's opinionClaude Sonnet · local admin seat
Finance and operationsPricing model, budget arithmetic, bookkeeping setupDraft the numbers and questions for an accountantArchitecture, product scope, tax adviceClaude Sonnet · local admin seat
Principal ArchitectCross-department coherence and integrationFind figures that disagree, route findings, audit ownership, mergeDecide or arbitrate a question a department ownsClaude Sonnet · local code seat
Quality and complianceCI gate, protected tests, release checklistBlock a merge, release or batch on quality, in writingMake the release decision itselfClaude Sonnet · local code seat
Security and IPThreat model, secrets hygiene, dependency checksBlock a merge or release that weakens a control, in writingBuild the code or give legal opinionsClaude Sonnet
OverseerAn hourly round of every agent and the machine roomRead evidence; ask each agent on a local model whether its last run workedQueue, restart or delete anythingScript · local model
Voice writerDrafts of public-facing sentences in my voiceDraft; I approve each onePublishQwen3 32B AWQ (local)
Writing partnerBriefings on AI news, then argues the other sideInterview me and organise my wordsWrite my essaysClaude Sonnet
Post menuA weekly menu of post and comment ideasDraft options for me to pick fromPost, comment or connectClaude Sonnet
Recon chiefNews desks that read public feeds for AI news and trendsFetch feeds and summarise themSpend Claude usage (it runs on the cpu lane)Script · local model
Video studioImage and video generation on my GPURun while the studio is upHold the GPU against running workComfyUI (local)
GPU keeperKeeping the GPU from sitting idleQueue the next GPU jobs when the GPU lane is emptyRestart the paused large regenerationClaude Sonnet
Kissaten DJMusic suggestions in the town's caféPropose a change to its taste notebookSave anything until I press SaveLocal model
Site keeperKeeping this website in step with my workDraft changes on a branchPublish without my approvalClaude Sonnet
ForemanWaking quiet departments on a local modelQueue small tasks; big ones wait for my approvalRun while paused (it is paused today)Local vision model

Four more personal admin agents (research scouts and an inbox reader) run on the same rails and follow the same rules; I leave them out of this table. "Local seat" is the model the department sits on when it runs locally instead of on Claude (section 04).

How work moves

All unattended work goes through one job queue that I wrote in Python. A job is a small JSON object: an id, a lane, a command or a prompt, and optional gates. The queue is the only thing that launches work. Agents, the town and I can add jobs, but only the queue decides when they start.

Why one queue: with several schedulers, nobody can answer "why did this run now?" With one, every launch is a single logged decision with its evidence. The cost is that the queue becomes critical infrastructure, so I kept it small, file-based and covered by its own tests.

Added Queued Gate check, every tick 1. dependencies are done 2. not-before time has passed 3. Claude lane: approval note 4. Claude lane: no usage pause 5. Claude lane: budget left 6. the lane has a free slot 7. start condition exits 0 any gate closed: stays queued, silently Running Exit code andfinish check Done usage limit hit: lane pauses until the stated reset, attempt not counted failed check, attempts left: retry (3 by default) Failed no attempts left Cancelled ReviewedDone log,Overseer, merge
Scroll sideways to see the whole diagram
The life of a job. "Reviewed" happens outside the queue.

Lanes and concurrency

Jobs run in lanes, and each lane has its own concurrency limit. A lane is a statement about a shared resource: what would break, or cost too much, if too many of these ran at once.

LaneWhat runs thereLimit (Oct 19)Why that limit
cpuScripts, checks, merges, the Overseer, the news desks4Cheap and independent; spends no model usage
vllmDrivers for the local vLLM server: extraction, generation, grading1One GPU. Speed inside a run comes from batched requests to the server, not from parallel jobs
ollamaLocal department agents on Qwen1Only one local seat fits on the card. The lane exists; no queued job has used it yet
claudeHeadless Claude Code agents, one charter each22Raised from 16 on Oct 18 for a release push, to return to 16 after. The code default is 6
The tick
Windows Task Scheduler runs a tick every 15 minutes with no session open. Each finished job also triggers a tick at once, because on Oct 5 the GPU sat idle between ticks. A lock file makes sure two ticks never launch the same job, and a lock older than five minutes is treated as a crashed tick and cleared.
Gates
Of the 1,129 jobs added since Oct 2, 247 waited on other jobs, 61 had a not-before time, 120 had a start condition, 151 had a finish check that decides "done" by evidence rather than by exit code, and 373 carried a priority.
Attempts and retries
A job gets 3 attempts unless it says otherwise. A wrapper records the exit code; if the wrapper itself vanished (a reboot), the job goes back to the queue. Across 1,141 launches the queue made 65 automatic retries. As of Oct 19, 7 jobs ended failed and 49 were cancelled.
The launch budget
The Claude lane spends my own usage, so it is opt-in per job: a job without an approval note never starts there. A weekly budget caps launches (130 this week, set by me), so at least half my weekly usage stays free for my own work. When Claude reports a usage limit, the whole lane pauses until the reset time it states, and the stop does not count as a failed attempt.
Self-scheduling
Two hooks add work on their own. The GPU keeper checks every hour whether the GPU lane is empty and, if so, queues one planning job to line up the next GPU work; it has done that 173 times. A periodic hook queues the hourly Overseer round and the six-hourly news fetch, but only from an allowlist of scripts and arguments, at most hourly, and only with an approval note. Editing the config cannot make it run an arbitrary command.

Handoffs, memory and ownership

Agents do not talk to each other in real time. They coordinate through files with owners: dependencies in the queue, dated handoff documents, a shared memory, and a decision log. I chose this over agent-to-agent chat because every exchange then leaves a record that a new session, a reviewer or I can read later, and nothing depends on two agents being awake at once.

Dependencies
A job can name the jobs it waits for. That is how one department's output becomes another's input without either agent polling: the queue holds the second job until the first is done.
Handoffs
Each department writes dated handoff documents in its own folder, named by sender, receiver and topic: 539 so far across 11 departments. Documents supersede by date, never by version number, so "which one is current" always has one answer.
Decision log
My decisions go into an append-only log: 294 entries since Sep 30. Questions reach me as multiple choice with a recommended option, often on my phone from a morning brief page; a tap appends a line, and a later line for the same question supersedes the earlier one. Agents search the log before asking me anything, and memory recall always shows my recorded decisions first.
Shared memory
555 one-fact memory files across the departments. Each department writes only its own memories and reads everyone's. The tool enforces the rules a model will not reliably hold on its own: every memory names a source file that exists (its hash is stored), the body is capped at 1,200 characters, and near-duplicates are refused. Search runs on a small embedding model kept on the CPU, so recall still works while the GPU is busy.
Memory sleep
Every night a mechanical pass retires memories whose source is gone, hides ones whose source changed until an agent re-checks them, flags snapshots older than seven days, and flags duplicates. A second pass, where a local model checks memories against my decisions, is written but switched off until it passes its own evaluation.
Session continuity
A mission file and an open-threads register are the first things every session reads, so a fresh agent picks up where the last one stopped.

Code ownership, enforced in layers

A machine-readable ownership map assigns every folder and code path to one of the 15 product agents: each department's own folder plus 62 owned code paths, with 5 paths reserved for me. A sync script writes each agent's scope from that map, so the scopes are never edited by hand and a check mode fails if any agent file drifts.

Enforcement comes in three layers, because no single layer can see everything.

Departmentagent 1. Scope guard every file tool call writes only inside scope any error is a denial exact; fails closed 2. Shell guard write-like commands that name out-of-scope paths git push: always denied heuristic: blind to scripts 3. Ownership audit each changed file against its owner, by the architect, pre-merge catches what 2 missed Integrationbranch denied, with the reason, back to the agent (or to the owner) Reading is open: every agent may read the whole repository. Only writes are confined.
Scroll sideways to see the whole diagram
Least privilege as enforcement, not instruction

One detail I am glad I caught: on Windows, a hook that blocks by exit code does not reliably block, because PowerShell collapses every non-zero exit to 1, which the agent runtime treats as a warning. So the scope guard always exits 0 and denies through an explicit "deny" decision in its output. A control that silently becomes a warning is worse than no control, because it looks like one.

Models and the GPU

Two kinds of model do the work. Claude runs the agents that plan, engineer, review and write code. Open models on my own GPU do the bulk work: extraction, generation, grading, local agents and every draft of public-facing text. The split follows three things: usage (Claude is budgeted; the GPU is mine to fill), privacy (sensitive user data never goes to a cloud model), and fit (bulk work wants throughput, review wants judgment).

WorkWhere it runsWhy
Engineering, review, planning, research synthesisClaude Code agents on the claude laneJudgment and tool use; budgeted per week
Extraction, generation, grading in bulkvLLM on the local GPUThroughput on a long run; no per-call usage
Drafts of public-facing textA local Qwen model; I approve every draftOne consistent voice, under my review
Overseer questions, news summaries, the café DJA local modelRoutine and frequent; should cost no Claude usage
Anything with sensitive user dataLocal onlyA rule, not a preference

Of the 754 Claude-lane jobs since Oct 2, 458 ran on Sonnet, 270 on Opus and 26 on Fable. The model is chosen per job, so a routine status check does not cost what a hard design review costs.

Seats: model roles, measured

Each department sits on a named local "seat", and the mapping lives in one editable file; no department is tied to a model in code. I picked the seats by measuring on this card, not by reputation.

SeatBase modelTemperatureUsed for
codeQwen3 Coder 30B0.15Engineering departments. Stayed fully on the GPU at a 32k context (about 30.5 GB)
proseQwen3 32B0.7Editorial and brand. The only seat allowed to draft public-facing text
adminQwen3 32B0.4Planning, legal, finance, research: same base, lower temperature
alt-codeDevstral Small 2 24B0.15Opt-in only. It spilled about 18% to the CPU on the same task, so it is not the default

One big model at a time: the GPU lease

The workstation has one RTX 5090 with 32 GB. A vLLM server reserves 94% of the card when it starts and keeps it. A 32B Qwen seat on Ollama needs about 20 GB. They cannot share, and when they collide Ollama does not fail loudly: it spills to the CPU and runs about ten times slower. So I made the GPU a lease with exactly one holder: idle, a vLLM server (three profiles: an 8B generation model, Qwen3 32B AWQ, and Qwen3 VL 32B for vision and extraction), or the local Ollama seats.

32 GB card: one holder vLLM server: 94% reserved at start, held until it stops Qwen3 32B seat on Ollama: about 20 GB together they do not fit: Ollama spills to CPU Switch requestvllm, ollama or idle GPU work running?live process markers yes Refusedask again next tick no Stop or unloadthe current holder Start targetwait for health Verify: nvidia-smiand health checksnew holder, or report FAILED
Scroll sideways to see the whole diagram
The GPU lease: never two big models, never interrupt running work, never assume a switch worked

The rule the lease will not break: a switch never interrupts running GPU work. If a generation, extraction or grading process is live, a switch away from it is refused and asked again on the next tick. Generation can resume, but a half-written output file is not worth a race for the sake of a schedule. After every switch the result is checked against the card's actual memory use and the servers' health, not assumed. A separate restore script hands the card back to the vision model after a temporary switch, with the same refusal when work is running.

I also wrote time windows for who should hold the card at each hour of the day (generation overnight, local agents in the daytime). That scheduler runs as a dry run today: it logs what it would do, and switching happens by explicit request from a job or from me.

Quality gates and authority

Autonomy is only useful if I can trust what comes back without re-checking all of it. So review is split across independent checks, each with a narrow job, and I sit at the end only for the decisions that are truly mine.

The Overseer
Every hour it goes round every agent and the machine room. It looks for failed runs, runs that look stuck (over 3 hours on the Claude lane, 6 on cpu, 14 on the GPU lane), processes that vanished, missed scheduled runs and offline runtimes, and asks each agent on a local model whether its newest run did its job. Verdicts are up, waiting, watch or down. It checks and never acts: nothing is queued, restarted or deleted. At 08:30 on Oct 19 it had checked 28 agents: 19 up, 5 waiting on me, 4 to watch, none down.
Review gates
The quality agent can block a merge, a release or a content batch on quality, and the security agent can block one that weakens a control, each with a written reason. I can override either. The Principal Architect integrates changes and runs the ownership audit first.
CI and protected tests
CI runs the product suite on Ubuntu and Windows (the deploy target and the development machine catch different failures), then proves that 14 protected tests actually ran and passed. A green suite is not enough: a deleted or silently skipped test leaves the build green and the guarantee gone. Removing a protected test takes a diff with a name on it. A pressure test of 84 probes runs after the suite.
Test counts
On Oct 19 the product suite ran 1,499 tests on my working checkout: 1,496 passed and 3 failed, all three in one module that is mid-change. Those three are open, not hidden. The job queue has its own tests for the periodic hook (10, passing), and Agent City has its own suite (section 07).
Findings are classified
Every finding is one of four kinds: a safety or evidence blocker, an integration blocker, a publication-only blocker, or a later improvement. A new blocker must name the failed requirement, the evidence, the owner and the fix. Nicer writing is never a blocker.

Authority levels: what needs me

On Oct 18 I wrote down which decisions actually need me, so that agents stop asking about things they can safely decide. Only Level 3 comes to me. For example, if the editorial check finds a problem that an existing rule already prohibits, engineering fixes it and quality verifies it; I am not asked again.

1
Agents act without asking
  • Fix isolated bugs, update tests, repair formatting
  • Improve wording without changing meaning
  • Implement requirements already approved
  • Integrate changes that pass the defined checks
2
A department head approves
  • Changes to internal checking mechanisms
  • Better implementation approaches
  • Bounded technical changes within policy
  • Never silently widen claims or weaken a control
3
Only me
  • The evidence standard and domain rules
  • Product promises; sensitive data and external access
  • Significant spending; release scope; acceptance criteria
  • Public deployment; authorising a full generation run

The release boundary. No agent publishes. The shell guard denies every git push, nothing generated is published automatically, and a full generation run or a public deployment needs my explicit go. This page is an example: an agent prepared it on a branch, and it goes live only after I approve it.

Agent City reads the real state

With 29 agents and hundreds of jobs, logs stopped being enough to answer "who is working, who is stuck, and who is waiting on me?" Agent City is my answer: a town in the browser, first in 2D and now in 3D with three.js, where each agent has a character and a building. It is a projection of the system above. It owns no scheduling and runs no work.

Sources (read-only) Jobs and job states Queue config and log Agent charters Model and GPU probes World builderpure: files andprobes in, a dict out State projectionone visible state peragent, fixed precedence Eventssnapshot diff,numbered, last 2,000 browsers poll every 5 s with a since cursor 3D town 2D city The Desk Done log Chat log Commands (the only way back) Button in a viewidentity-checked Preview, then confirmidempotency key, 24 h Queue's own lockqueued jobs only Job queuestill the only launcher
Scroll sideways to see the whole diagram
Agent City is a read-mostly projection; its few writes go through the queue's own lock

A deterministic projection. Each agent carries a few authoritative fields (availability, runtime, run state, attention, next scheduled start), and one pure function turns them into the one state the town shows, with a fixed precedence:

blocked waiting on me working queued offline paused scheduled soon sleeping idle

Two rules keep it honest. An agent whose runtime is unreachable shows as offline, never as asleep or done. An agent with nothing scheduled is idle, not sleeping. Tests check every conflicting pair, so "working" means a job is actually running, never that an agent looks busy.

Polling with a cursor
Browsers poll every 5 seconds and pass the sequence number of the last event they saw. They get a full snapshot on load and after any gap. Events come from diffing two snapshots, and the first run invents no history. I chose polling over server push because it is simple and robust on a standard-library server at this scale, and push can be added later without changing the event format.
Run now never bypasses a gate
"Run now" sets aside one date gate on one queued job and records the original. It is refused for approval flags, dependencies and start conditions, and it never overrides a usage pause or the launch budget. A button in a town must not be a way around the rules.
Idempotent commands
Every command can be previewed without changing anything. Commands that spend usage, override a gate, pause or cancel need an explicit confirm. The same idempotency key within 24 hours returns the stored result and never creates a second job, so a double tap on a phone is harmless. Running jobs are never touched.
The Done log
The queue's own records can be pruned or rewritten, so the first time the town sees a finished job it appends one line to an append-only ledger: who finished what, when, for which project, a one-line summary and the files the report names. 1,060 finished jobs as of Oct 19: 669 for Skyrule, 273 routine operations runs and 118 for my other personal projects.
The chat log
Every conversation I have had with any agent, and every board-room session, newest first, read-only. It never writes, renames or deletes a chat file.
Access
The town is reachable only over my private network with identity-checked access. It never listens on the public internet.

What I would do differently

Parsing text for signals
The queue detects a Claude usage limit by reading the job's output. On Oct 9 an agent's own report mentioned the limit, the pattern matched, and the lane paused for a day. I anchored the pattern to the start of a line and judge only the current attempt's output, but the real fix is a structured exit signal, not text matching.
Shell writes are guarded by heuristics
The file tools are guarded exactly. The shell is not: a script can write somewhere its command line does not name. The ownership audit catches that after the fact, before merge. I would rather run each agent in a sandbox where out-of-scope paths are simply not writable.
Per-job work contracts are planned
Today an agent's limits come from its charter and scope, not from the job. The next step, written down on Oct 18, is a machine-readable contract per job (allowed paths and tools, inputs, definition of done, required tests, budget, escalation) and a release controller that enforces existing policy. Neither is built yet.
JSON files and a lock file
The queue keeps 1,129 jobs in one JSON file and one state file per job, guarded by a lock file. That is easy to inspect and fine on one machine, but it rewrites the whole job list on every change. If I started again I would use SQLite from day one.
Partly built
The GPU time windows run as a dry run. The memory judge is switched off until it passes its evaluation. The foreman is paused, because an agent permission check refused its unattended job creation, and I have not yet decided on that rule. The local-agent lane exists but no queued job has used it. The town's own test suite ran 340 tests on Oct 19 with one error still open.
What is not measured
Claude cost is tracked only as launches against the weekly budget, not as tokens. The queue also has no native recurring schedule: a weekly job queues its own successor.

For the product side of this system, see the Skyrule case study. For how the town itself was made, see Agent City.

Personal ProjectMulti-Agent SystemsClaude Code SubagentsPythonJob SchedulingLeast PrivilegevLLMOllamaQwen3EmbeddingsGitHub ActionspytestThree.jsObservabilityRTX 5090
← Back to Portfolio