← Back to overview
What was built

Systems

One production system delivered and handed over, one running daily, the apparatus that grew around them, and the failures that shaped both. Every figure on this page comes from a store or a git history rather than from memory.

Team Brain

built · deployed · handed over

A multi-source SEO analytics platform an agency team used daily. Fifteen people, five live data sources, one 8 GB VPS — a textbook Anthropic SDK deployment where the interesting part is the cost and routing discipline, not the model.

93.9%
tool success over 1,352 real calls
15
daily users, none of them engineers
49.4M
input tokens, Feb–Jun
$30
Claude API per month
Stack, by layer
Runtime
Node.js 20 · TypeScript 5.9
Backend
Express 5 · PM2 cluster · Nginx 1.26 · Let's Encrypt
Frontend
Next.js 16 static export · React 19 · Tailwind 4
Database
PostgreSQL 17 · ~150 MB
Time-series
TimescaleDB 2.26
Vector
pgvector 0.8 + pgvectorscale 0.9 · all-MiniLM-L6-v2, 384d
Graph & text
Apache AGE 1.5 · pg_trgm
AI
Anthropic SDK 0.71+ · Sonnet 4.6 / 4.5 · Haiku 4.5 / Opus 4.6
Five sources, one query surface
Ahrefs
23 tools · 500k API units/month, budget-aware with a check_budget tool
Search Console
JWT service account, 3 domains · nightly top-1,000 snapshots over 90 days
Analytics 4
18 tools — landing pages, attribution, funnels, cohort retention
CrUX
real-user Core Web Vitals, not lab scores
Live rendering
headless Chromium, the default for technical audits
Six specialists, model chosen per role
AuditorsonnetTechnical SEO audit, AEO, SERP analysis, Core Web Vitals
AnalystsonnetAhrefs + GSC + GA4 joined, competitor gap analysis
WritersonnetArticle writing, editorial, brand voice
Briefsonnet-highContent briefs, research, strategy
IdeahaikuKeyword ideation, brainstorming
BatchhaikuMulti-domain reports, bulk operations

~45 sub-agents sit behind the six as loadable context, plus custom agents the team creates from chat and stores in the database. Model choice is per specialist: Haiku where the work is generative and cheap, Sonnet where it is analytic.

Routing — five signals, in order
  1. 1@mention — explicit selection
  2. 2Action path — 29 actions across 8 categories
  3. 3Complexity — multi-domain or cross-source signals
  4. 4Keyword — registry score from each agent's declared keywords
  5. 5Base model — no specialist, plain conversation

Deterministic, not a model guessing which agent to be.

Four chat modes
default
standard response
extended
depth and thoroughness
fast
parallel tool calls, no preamble
confirm
explicit approval before any state-changing action
Night shift — nine autonomous jobs on cron
Daily 01:30
activate pending memories older than 7 days
Daily 06:30
health probe, posted to the team feed
Mon 05:00
weekly digest
Wed 01:30 / 02:30
GSC snapshot · CrUX snapshot
Sun 03:00
article scraper across 3 domains
Monthly
memory decay · knowledge re-embedding · sitemap crawl
What it cost to run

49.4 M input tokens over five months of daily use by fifteen people — about $30 a month in Claude API spend at Sonnet input pricing, roughly 14k input tokens per message. The cost control is architectural: snapshots answer first and the live API is the fallback, Haiku takes the generative work, and a budget tool stops a large query exhausting the plan.

Decisions worth defending
One Postgres, not a separate OLAP store

TimescaleDB hypertables and continuous aggregates in the database that was already there for the app and the vectors. No second system to keep in sync, no cross-store consistency problem.

The model writes SQL; Node runs it

One query tool against the data layer instead of a tool per question. A question nobody anticipated can still be answered.

A sandboxed execution tool, so the model does not do arithmetic

Aggregations, joins and percentiles over large result sets run as code in a VM. A language model computing a median across 5,000 rows is a wrong answer waiting to happen.

Every number carries its age

Freshness badges in the UI and in tool results. An analytics answer without a timestamp invites a decision made on last month's data.

61 agent definitions collapsed to 6

Nobody could tell which to reach for; the names were accurate and useless. The narrow prompts became loadable context. A capability taxonomy is a UX decision, not an architecture decision.

Instructions bound to a worked example

Every instruction references a concrete tool call, so a stale instruction breaks loudly instead of quietly misleading.

Handover

Delivered with nine handover documents — system overview, admin guide, dev guide, user guide in two languages, data model, workflow, and a two-tier access model. A system only its author can operate is not delivered; it is lent.

Home — actions and agent picker
Home — actions and agent picker
Chat with streaming and tool results inline
Chat with streaming and tool results inline
Tool calls, expanded with their results
Tool calls, expanded with their results
Specialist selection
Specialist selection
User profile and access tier
User profile and access tier
Express 5TypeScriptPostgreSQL 17TimescaleDBpgvectorApache AGENext.js 16React 19Anthropic SDKPM2Nginx

Factorium

running daily · pre-release

It started as a harness for my own work and became the thing the work was about: every session with a model began from zero, and everything learned in it was lost when the window closed. Factorium is the answer to that — one store the human and both models read and write, so the next session starts informed.

Where v01 stands

The compile gate is not the blocker. Build passes at 2,911 modules, tsc clean, 23 of 23 smoke tests, nine apps in place. What remains is UI tuning and one untested button — not architecture.

What it is for

The plan is to ship v1 as an ordinary application. Not a repo with a README and a docker-compose that assumes you are an engineer — an app a knowledge worker installs. The whole stack (Postgres with three extensions, a vector store, a local model server, the agent runtime) is packaged behind one install, and getting that to compile was most of the work. The point is that this level of human–AI symbiosis should not require a systems background to reach.

What it is made of
186
TypeScript source files
563
commits
23
tool modules
9
apps
2,911
modules in a passing build
23/23
smoke tests
Store
One Postgres instance: pgvector for embeddings, Apache AGE for the knowledge and code graphs, TimescaleDB for the activity hypertable. Qdrant alongside for seven collections. One connection, one backup, no cross-store consistency problem.
Memory
Records carry importance, decay and Hebbian strengthening — reaching a record strengthens it, unused ones fade, anchors never decay.
Agent runtime
23 tool modules behind one registry. The local model gets a deliberately smaller surface than the HTTP registry: two hive verbs instead of a tool per source, because every extra schema is prompt tax on a 12B model.
Retrieval
Six layers queried in parallel and fused by rank (RRF, k=60) rather than concatenated. Asymmetric embedding prefixes and diacritics folding at the single point both queries and documents pass through.
Watchers
File changes ingest into the code graph and re-compute spatial tiers 30s after a batch settles; any markdown written to the store is indexed automatically. No manual re-index step exists.
Plan
Every task, decision and finding is a node in the same database as everything else and vector-indexed — a decision from May is retrievable in August by describing the situation rather than remembering its name.
Model layer
llama.cpp on Metal behind a mutex that makes concurrent loads impossible. A liveness probe separates a wedged model from a loading one, because /health returns 200 for both.
Nine apps
Chat · Stream
The local agent, as a thread with automatic chapters rather than a list of questions
Search · Find
One box across knowledge, code, docs and the plan
LIP · Plan
The plan itself — nodes, status, weights, dependencies
Graph · Verse
Knowledge, code and docs as one graph: 2D nodes or a 3D crystal you fly through
File · Browser
A file tree plus tabs the agent opens as it works
Git · Repos
Commit log and working tree across repositories
Watch · Ops
Service health, activity feed, backup state
Project · Run
Start and watch a dev server from inside the environment
Browse · Web
A real browser the agent can drive and read back
What the local agent can do
  • ·Run shell commands and keep services alive across a session, then verify from outside with a real request
  • ·Read, write and surgically edit files across any project, including bootstrapping a new one
  • ·Drive a browser — screenshot, console, network, rendered page — so it can check its own UI work
  • ·Search knowledge tier-aware, so a CORE result outweighs a peripheral tangent
  • ·Walk the graph several hops from an entity
  • ·Search indexed code by meaning rather than by string
  • ·Read and update the plan, and read durable memory
One model in production, two on the bench
In production · Gemma-4 12B · Q5_K_M · ~7.7 GB resident

The only tier used in production. Q5 for reliability where tool calling is zero-tolerance, and at 7.7 GB it leaves enough of the 24 GB free to load vision and audio projectors alongside it — which a larger model does not.

26B · QAT UD-Q4_K_XLexperimental, on hold — mixture-of-experts, and at 16–17 GB it costs the rest of the machine
E2B · UD-Q4_K_XL, ~3.9 GBheld for Factorium Pocket, the planned mobile companion to the desktop app

No two chat models run concurrently on 24 GB. The switcher is strictly stop-then-start — reproduced live, not assumed.

Quantisation policy is deliberately not uniform. On the MoE model, routing is a discrete choice, so quantisation noise in the router logits sends a token to the wrong expert — a semantic error rather than a smooth loss of precision, which is why only training-aware builds were considered there. On the dense 12B the argument does not transfer and two grounding channels contradicted each other, so it stayed at 5-bit: not switching on contradictory evidence.

Open source at release

Open source from release, and meant as a starting point rather than a product — the way editors get forked and specialised. Factorium began from a VS Code base and kept roughly a fifth of it; the rest was removed or rewritten. Other variants should be able to do the same to this.

The knowledge graph as a 3D crystal
The knowledge graph as a 3D crystal
Orbital layout — distance from centre is importance tier
Orbital layout — distance from centre is importance tier
Degree-scaled repulsion: hubs push apart instead of imploding
Degree-scaled repulsion: hubs push apart instead of imploding
Force layout, labels on
Force layout, labels on
Knowledge graph, shell view
Knowledge graph, shell view
The same graph without shells
The same graph without shells
The code graph — entities and import edges
The code graph — entities and import edges
A disconnected code cluster, visible at a glance
A disconnected code cluster, visible at a glance
An isolated knowledge cluster
An isolated knowledge cluster
TypeScriptHonollama.cpp / MetalGemma-4 12BQdrantApache AGEpgvectorbge-m3TimescaleDBReact 19three.js

What was tried, and what it cost

the useful part

Limits first: one machine, roughly six months, no team, no budget. Everything below is a stored measurement, and all of it was found the same way — by checking a thing whose answer was already known.

Parallel agents and heavy orchestrationremoved

Tried in both directions — multi-agent orchestration, parallel runs, elaborate harnesses, several configurations. What survived is one cloud model, one local model, one shared store, a hand-written runtime. On 24 GB every added layer competes for the same memory, and every added abstraction removes a place to look when something breaks.

No agent frameworkheld since May

Own runtime; the vendor SDK only for the cloud model. A framework injects prompt content you did not write, which is fatal when the thing being measured is how a 12B model responds to instructions. The same logic kept Zod out of 22 tool modules — a hand-written validator rejected 5 of 5 probes — and put it in the one place where the input is a file from disk we do not control.

An embedding model used outside its contractweeks

nomic-embed requires asymmetric prefixes; served through llama.cpp nothing adds them, so short irrelevant text outranked long relevant text and nothing errored. 10,676 records re-embedded — and the first repair script read only the body field while the writer had embedded title + body, which would have silently stripped titles from 95% of vectors.

The failures were almost never in the modelthree days

Spent on retrieval quality before the actual defect surfaced: the answer was not in the index. 58% of one collection were duplicates of a single turn, and the local model's own conversations had never been indexed at all.

Silent failure as a class, not a bugnine found in one sweep

Input accepted, matched against nothing, discarded, operation reports success. A validator that skipped unknown keys meant every optional-parameter typo the local model made was ignored. Five mutations — including a token revoke — treated a non-2xx as a rejection rather than a failure.

A speech-to-text stage that should never have been addeda week

Whisper was wired in before testing whether the model could take audio directly. It could. Removing it restored acoustic nuance the model actually uses.

What the measurement said about the model, not the codethe finding

Rules retrieved by their own wording: 1 in 5. Stored as the situations that trigger them: 10 in 10. Asked afterwards which of its rules carried real information, the model misclassified 7 of 8 — including three that had changed its decision minutes earlier. Value is measured by behaviour, never by asking.

Games that teach a value system

three prototypes

One idea, three attempts: a scoring rule that teaches a value system rather than a skill. Aimed at learning — children included — which is why none of them can be finished.

MPC arcadempcmcpinside Factorium · not open source yet

While flying the 3D knowledge graph, typing mpcmcp wakes a shooter. The crystals use the same shape-language as the graph nodes, and the scoring is inverted on purpose.

Daemons — shoot them, +score

The opposites of the system's values and its bad habits: sycophancy, skipping verification, treating the model as a calculator, giving up, entropy. Popping one lands as having actually got rid of a bad habit.

Angels — avoid them, −score

The real values, pulled live from the hive's own lexicon, so the game always speaks the current Codex. Hit "Primacy of Truth" or "stop-and-ask" and it costs you. The supreme axiom never spawns at all.

cybernetic-genesisplay →source →

Two canvas games, plain HTML and vanilla JS. In Entropy Harvest the combo grows while you keep clearing — the mechanic rewards sustained order rather than one good move.

neon-shiftsource →

A playable prototype generated from a single sentence, spending an entire free tier in one sitting. A test of how far one prompt goes with an agent that writes and deploys.