- What fAIctory actually is
- Meet the Crew
- A Run through the Factory
- The technical shape
- How I built it, in layers
- 1. Build the Studio and the floor
- 2. Put orchestration behind a service
- 3. Replace demo responses with a local Crew
- 4. Add the founder gate before generation
- 5. Stream visible progress without coupling the browser to the graph
- 6. Make Runs durable
- 7. Turn plans into Products, then distrust them
- 8. Make failure part of the workflow
- 9. Publish an intentional projection
- Installing fAIctory locally
- Starting and publishing a first Run
- Testing the Factory
- Security decisions and limits
- What fAIctory does not do yet
- Future developments
- Different models for different Agents
- A grilling before implementation
- Version the whole Crew, not only the code
- Better research evidence
- Branches and comparative Runs
- More Product adapters
- Budgets and operational visibility
- Deployment as another controlled Hand-off
- Less glamorous engineering work
- What I learnt building it
I’ve spent the last few days building something called fAIctory. The capitalisation is a bit ridiculous, I know, but naming things remains one of the two hard problems in computer science as we all know.
The short description is that fAIctory is an "autonomous product studio". One human founder writes a Brief and directs a permanent Crew of named AI specialists. Those specialists research the problem, frame the Product, challenge the weak bits, propose a design, plan the engineering, build an artefact, test it, and prepare it for publication.
That sounds dangerously close to “I put six chatbots together and called it a company”, so the longer description matters. fAIctory is not an unbounded swarm, and it is not a chat interface with theatrical job titles sprinkled over it. It is an attempt to build a traceable, recoverable, human-directed production system around local AI models.
The important part is not that the AI can generate something. The important part is that the Factory can show what happened, stop at the right moments, test what was produced, recover when it fails, and keep private working material out of the public Product.
This post is the full tour: the language, the Crew, the architecture, the decisions I made while building it, the security boundaries, the local setup, and the bits which are still deliberately limited. Make tea.
What fAIctory actually is
fAIctory has two surfaces. The first is the public Studio. This introduces the Factory and its Crew, displays published Run stories, provides a library of completed Products, and lets people open or download a verified Product. The second is the private Factory floor, where the founder writes Briefs, starts Runs, reviews evidence, approves or rejects a direction, follows the live Hand-offs, retries failed work, and decides what becomes public.
The public Studio introduces the Factory and provides the deliberately public route into its Crew, production line, Products, and Proof.
The two surfaces are intentionally separate. The Factory floor contains working material: the original Brief, intermediate artefacts, rejected candidates, recovery diagnostics, founder decisions, and the complete trace. The Studio receives a deliberately smaller projection containing only the material intended for an audience and the Product which actually passed Proof.
That distinction is easy to lose in an AI prototype. A model produces some HTML, the application jams it into the page, everybody applauds, and nobody asks whether the model also embedded its private instructions, a failed earlier attempt, or an unexpected network call. That is fun for a demo and less fun for an operating system.
I wanted the Factory to have a precise domain language too. Words shape architecture, and “a collection of bot messages” leads you towards a very different system from “a traceable product Run with Hand-offs and Proof”. In fAIctory:
The Factory is the operating system which coordinates the work, preserves evidence, and moves artefacts between specialists.
The Crew is the permanent set of named AI specialists.
An Agent is one member of that Crew, with a stable role, remit, and voice.
A Brief is the founder-authored ambition, its constraints, and what success must mean.
A Run is one traceable execution of a Brief through the Factory.
A Hand-off is an explicit transfer of an artefact and its context from one Agent to another.
Proof is the collection of tests, sources, critique, and observed results attached to a decision or Product.
A Product is the useful artefact released to real people, together with enough evidence to trust and operate it.
I avoid calling a Brief a prompt, a Run a chat, or Proof a confidence score. Those alternatives imply something disposable and conversational. fAIctory is meant to preserve decisions and evidence as work moves through a production process. It is a small distinction with rather large consequences.
Meet the Crew
The Crew is stable and opinionated. A Run does not need to invoke everybody merely because they have an avatar, but each Agent has a clear craft and owns particular Hand-offs:
Ada is the chief of staff and Run orchestrator. She finalises the completed Product and assembles its first-build Brief.
Miro is the product strategist. He turns research and founder intent into a testable framing, and revises it when the founder asks for changes.
Nia is the research lead. She begins the Run by finding the relevant audience, evidence, unknowns, and constraints.
Sol is the design director. She creates the experience direction after the founder approves the framing.
Kit is the principal engineer. He turns the direction into architecture, milestones, acceptance tests, and the Product bundle itself.
Rue is the red-team editor. She critiques framing and verifies the built Product against deterministic checks.
Buddy is the incident reporter and recovery specialist. He diagnoses failed Product candidates and gives Kit bounded repair context without being allowed to weaken the Proof.
Buddy is deliberately a little separate from the six Product-making Agents. He is part of the permanent Crew, but his purpose is operational: report the failure, explain what the evidence says, help the next repair attempt, and track the incident through to resolution. He is not permitted to “solve” a failing test by quietly deleting the test, which is the AI equivalent of taking the batteries out of a smoke alarm.
A Run through the Factory
The central workflow is a stateful graph. A new Run starts with the Brief, moves through research, framing, and critique, then pauses at a founder approval gate. Approval continues into design, engineering, build, verification, and finalisation. A request for changes goes back through revision and critique before returning to the same gate.
Brief
↓
Nia: research
↓
Miro: product framing
↓
Rue: critique
↓
Founder approval gate ───── request changes ───→ Miro: revision
│ │
│ approve └──→ Rue: critique ↺
↓
Sol: design direction
↓
Kit: engineering plan
↓
Kit: bounded Product build and repair loop (a.k.a. Ralph loop)
↓
Rue: Product Proof
↓
Ada: final Product and first-build Brief
↓
Founder: publish (optional)
A live Run on the private Factory floor. The Brief remains locked on the left while the trace, current Agent, engine, stage, and production status remain visible on the right.
The approval gate is an architectural invariant, not a decorative button. Sol and Kit cannot begin the first-build Hand-off before the founder has approved the framing, and a Run cannot finalise or publish without that decision. The founder can approve, request a revision with feedback, or cancel the Run entirely.
The founder gate after research, framing, and critique. The production line stops here until a human approves the direction, requests a revision, or cancels the Run.
Each successful critique creates an immutable framing revision. Revision zero is the original proposal. A founder decision annotates that revision rather than replacing it. If the founder requests changes, Miro and Rue create the next revision and the Factory floor can compare both versions side by side. The system keeps the complete history, because “the model changed it” is not a useful audit trail.
The Run state holds the Brief, stage, status, research, framing, risks, revision history, design direction, engineering plan, Product bundle, Proof checks, decisions, retry history, rejected candidates, cancellation record, engine label, and append-only trace. Crew output is validated before it enters that state. The graph is not merely deciding which chat response appears next; it is advancing a typed product record.
The technical shape
fAIctory is a self-hosted Next.js application running on Node.js. LangGraph owns orchestration and checkpointed state. Ollama runs the local Crew model. Zod validates every important boundary. Postgres stores durable Run checkpoints and public story projections. Docker provides an isolated place to test generated interactive Products.
The core dependencies are intentionally fairly small: Next.js 16, React 19, TypeScript, LangGraph, the LangGraph Postgres checkpointer, PostgreSQL, Zod, and a small ZIP library for Product downloads. Tailwind handles the presentation. Node.js v24.11.1 or newer is required.
Browser
├── Public Studio (/)
│ ├── Published Run stories
│ ├── Product library
│ └── Product preview and download
│
└── Private Factory floor (/floor)
├── Brief editor
├── Founder approval gate
├── Live trace and Hand-offs
└── Retry, cancel, and publish controls
Next.js on Node.js
├── Founder access boundary
├── Run API routes
├── Public story API
├── Factory service
│ └── LangGraph Run graph
├── Publication store
├── Product runtime
└── Incident reporter
Local services
├── Ollama
├── PostgreSQL
└── Docker Product sandboxThe most important boundary is the Factory service in src/factory/index.ts. LangGraph stays behind that module. Pages and route handlers call a narrow interface to start, stream, decide, retry, cancel, inspect, and publish Runs. They do not know how graph nodes, checkpoint routing, or interrupts are implemented.
I like that seam because orchestration libraries are infrastructure, not the product domain. The domain should be able to say “resume this Run after the founder approved revision two” without every route becoming fluent in graph internals. It also makes the Crew, checkpointer, Product runtime, publication store, and incident reporter injectable in tests.
Run state is a contract, not a transcript
One of the more important implementation details is that a Run is not stored as a bag of chat messages. The graph advances a typed state object. The real schema is larger, but the shape looks like this:
const FactoryState = new StateSchema({
runId: z.string(),
brief: BriefSchema,
status: z.enum([
"working",
"awaiting_human",
"completed",
"cancelled",
]),
stage: FactoryStageSchema,
revisionHistory: z.array(FramingRevisionSchema).default([]),
retryHistory: z.array(RetryAttemptSchema).default([]),
rejectedProductAttempts: z
.array(RejectedProductAttemptSchema)
.default([]),
incidentHistory: z.array(IncidentSummarySchema).default([]),
productBuild: ProductBuildSchema.nullable().default(null),
proofChecks: z.array(ProofCheckSchema).default([]),
engine: z.string().default("demo"),
trace: z.array(TraceEventSchema).default([]),
});That distinction matters because model output does not become trusted application state merely because it resembles JSON. Every Crew method returns a value which is parsed through the relevant Zod schema before the graph accepts it. Research must be a bounded list of non-empty findings. An engineering plan must contain architecture, milestones, and acceptance tests. An interactive Product must contain exactly the five expected files. Rejected candidates and incident summaries have their own schemas too.
The snapshot returned to the browser is another deliberate projection. It exposes the artefacts, trace, gate, revision history, and recovery flags needed by the floor without leaking LangGraph’s internal state representation into React. This is what allows the graph implementation to change without turning every route and component into a migration project.
How I built it, in layers
I did not start with the most complicated version of the Factory and attempt to make the whole thing work in one heroic commit. I built a visible slice, found the next place where a toy implementation would become dangerous or annoying, and then added the boundary needed to deal with it.
1. Build the Studio and the floor
The first layer was the public Studio: the visual identity, Crew profiles, manifesto, and the idea that completed work should be presented as a story rather than dumped into a generic chat transcript. The Factory floor followed as the private production surface.
Separating those surfaces early forced a useful question: which data belongs to the founder, and which data belongs to the audience? That later became the publication projection and prevented private Run state from leaking into the public site by convenience.
2. Put orchestration behind a service
Next came the first LangGraph Run. The initial graph proved the essential loop: Brief, research, framing, critique, human gate, and continuation. I kept it behind the Factory interface from the start. That meant later work could add streaming, recovery, persistence, cancellation, and publishing without teaching the UI about graph mechanics.
LangGraph uses the Run ID as its checkpoint thread identifier. The application can therefore inspect the latest state, discover which node is pending, and resume precisely where work stopped. A retry does not replay completed research and framing merely because Kit’s build failed much later.
3. Replace demo responses with a local Crew
The deterministic Crew is useful for interface development and tests, but the real local Crew runs through Ollama. Each Agent method asks Ollama for schema-constrained output and validates the result with Zod before the Factory accepts it. The adapter disables free-form thinking output, has a bounded request timeout, and keeps the configured model warm for a short period.
If Ollama is missing, unavailable, too slow, or returns invalid structured data, the Factory falls back to deterministic output. Crucially, it says so. The Run engine becomes demo (Ollama unavailable) rather than pretending that a local model completed the work. A graceful fallback is useful; a dishonest fallback is just an observability bug wearing a friendly hat.
4. Add the founder gate before generation
The founder gate sits after Rue’s critique and before design or engineering. This is where the human can inspect the proposed audience, value, scope, risks, and open questions while changing direction is still cheap. Once approved, the Run can spend more time and compute producing the first build.
This is one of the ideas behind fAIctory which I care about most. Human-in-the-loop should not mean asking somebody to rubber-stamp a completed artefact after all the consequential decisions have already been made. Put the decision where the human still has leverage.
5. Stream visible progress without coupling the browser to the graph
A Run can take a while when several local model calls are involved. Returning one JSON response at the end makes the Factory look frozen and loses valuable progress if the final step fails. The Run routes therefore support content negotiation.
The Factory floor requests application/x-ndjson and receives one complete Run snapshot after each Agent step. An ordinary API caller receives the final JSON snapshot. The first streamed frame reveals the Run ID before Nia’s first model call, so even an early failure leaves the browser with an identity it can inspect or recover.
Accept: application/x-ndjson
{"runId":"...","stage":"research","status":"working",...}
{"runId":"...","stage":"framing","status":"working",...}
{"runId":"...","stage":"critique","status":"working",...}
{"runId":"...","stage":"approval","status":"awaiting_human",...}The browser parses those lines incrementally, updates the production-line display, and retains previous valid snapshots if the stream ends with a structured error. The adapter also disables buffering and proxy transformation where the surrounding infrastructure respects those headers.
6. Make Runs durable
The zero-configuration mode uses LangGraph’s in-memory checkpointer and a memory publication store. It is convenient for tests and a quick look around, but everything disappears when the Node process restarts. The application says exactly that rather than making vague promises about persistence.
Add DATABASE_URL and the runtime selects PostgresSaver for Run checkpoints plus a separate PostgreSQL publication store. Those are deliberately distinct stores, even though they share one database in the standard local setup. The private Run is the system of record; a public story is a smaller projection with different visibility and lifecycle rules.
The browser only keeps the latest Run ID in localStorage. That value is a pointer, not persistence. When the floor opens, it asks the API for the checkpoint. If a memory-backed server restarted and the Run no longer exists, the stale pointer is removed.
The ownership boundary is easier to see as a table:
State | Owner | Survives a restart? |
|---|---|---|
Private Run, trace, revisions, and rejected candidates | LangGraph checkpointer | Yes with PostgreSQL; no with MemorySaver |
Published Product and public story | Publication store | Yes with PostgreSQL; no with the memory store |
Latest Run ID in the browser | localStorage | Yes, but it is only a pointer |
Active execution and AbortController | Current Node.js process | No |
Founder session | Signed browser cookie | Yes, until expiry or an access-key change |
Model files | Local Ollama installation | Yes |
The awkward row is active execution. A durable checkpoint can restore a paused Run after a restart, but the in-flight model request, shared abort signal, and process-local duplicate-execution lock cannot survive the Node process disappearing. Durability is specific, not magical.
7. Turn plans into Products, then distrust them
Producing strategy and engineering prose is interesting, but fAIctory is meant to make Products. Kit therefore generates a bounded website bundle. The current interactive adapter accepts exactly five files: index.html, styles.css, logic.js, app.js, and test/logic.test.js.
logic.js contains pure domain behaviour. app.js wires that behaviour to the browser. The generated Node test file exercises the domain operations. Splitting them this way makes meaningful automated Proof possible without pretending that a screenshot or a model saying “looks good” is a test suite.
Before any generated code runs, deterministic checks inspect the bundle. Remote assets, network primitives, embedded contexts, inline handlers, submission targets, unexpected files, and malformed bootstrap tags are rejected. Declared tests must contain an assertion signal, and the suite must call every statically exposed ProductLogic operation. An empty test which passes is technically green and practically useless.
Only then does the Product runtime create a temporary directory and launch a fresh Docker container. The bundle is mounted read-only. The container has no network, no Linux capabilities, a read-only root filesystem, a non-root user, bounded CPU, memory and process counts, and a thirty-second timeout. The command is fixed by the Factory: generated code cannot choose an arbitrary command or install packages.
This is the actual invocation, with $workspace standing in for the temporary directory containing the generated bundle:
docker run --rm \
--pull=never \
--network=none \
--read-only \
--cap-drop=ALL \
--security-opt=no-new-privileges \
--pids-limit=64 \
--memory=128m \
--cpus=0.5 \
--user=65534:65534 \
--tmpfs=/tmp:rw,noexec,nosuid,size=16m \
--volume="$workspace:/workspace:ro" \
--workdir=/workspace \
node:24.11.1-alpine \
sh -lc \
"node --test test/logic.test.js \
&& node --check logic.js \
&& node --check app.js"--pull=never is important: a Proof run cannot quietly fetch code from a registry. The numeric user is the unprivileged nobody account, the generated workspace is read-only, and the only writable location is a small tmpfs. Even the shell command is supplied by fAIctory rather than by the generated Product. It runs the tests first, then checks both JavaScript files for syntax errors.
The runner combines the Run cancellation signal with its own thirty-second timeout, retains only the final 100,000 characters of output, and removes the temporary workspace in a finally block. If Docker is unavailable or the pinned image has not already been installed, that becomes a bounded Proof failure rather than an excuse to weaken the sandbox.
Generated candidate
↓
Schema and file-shape validation
↓
Static security checks
↓
Test adequacy checks
↓
Networkless, read-only Docker execution
↓
Node tests and syntax checks
↓
Verified Product bundle
↓
CSP-injected sandbox previewStatic Products render in an iframe with no sandbox capabilities. A verified interactive Product receives allow-scripts, but never allow-same-origin, and still runs under a default-deny Content Security Policy. The same assembly and iframe policy is used on the private floor, the public Studio, the Product library, the launch page, and the full-screen preview.
Verification is not a vibe. That is the magic.
8. Make failure part of the workflow
Generated Products fail. Pretending otherwise merely moves the failure somewhere less visible. fAIctory gives Kit a bounded number of candidates inside one build node: three by default, configurable from one to ten.
When a candidate fails, the Factory retains its escaped source and Proof output as private, non-previewable Run evidence. Buddy receives that exact source and verifier result, writes a structured diagnosis, and Kit receives the rejected bundle plus the diagnosis for the next repair. The repair is therefore grounded in what actually failed rather than a vague summary reconstructed several model calls later.
Before repeating Docker Proof, the Factory compares the repaired files with the rejected candidate. If Kit changed only the Product metadata or returned identical source, the attempt fails as “Repair made no source changes”. It does not get to burn another container execution while claiming progress.
Buddy cannot alter acceptance criteria, disable verification, or recommend a construct which the Proof explicitly forbids. If a later candidate succeeds, the incident becomes resolved and the Run continues. If all configured attempts fail, the Factory checkpoints the candidates and diagnostics, reports the incident, and offers the founder a retry. That retry resumes Kit’s pending build node; it does not replay Nia, Miro, Rue, and Sol for dramatic effect.
A real bounded build failure. Buddy records each intervention, the final incident remains visible, and all three rejected Product attempts are retained as private evidence for inspection and retry.
Active cancellation follows the same philosophy. A shared AbortSignal reaches LangGraph, the Crew provider, Ollama, and the Docker runner. The Factory writes the terminal cancellation checkpoint before allowing the aborted stream to finish, so a late model response cannot resurrect work the founder stopped. Cancellation is terminal and audited.
9. Publish an intentional projection
Completion and publication are separate actions. A completed Run remains private until the founder selects Publish to Studio. The Factory rejects publication unless the Run is complete, finalised, and contains a verified Product.
The resulting story contains a stable slug, audience, public summary, selected Proof, Agent attribution, publication time, and the verified Product bundle. It does not contain rejected candidates, hidden reasoning, credentials, Buddy’s private repair instructions, or the full founder context.
Re-publishing the same Run is idempotent. Published Products receive a launch page, a full-size sandboxed preview, and a ZIP download containing only the verified original files. The download excludes Proof metadata and all private Run state. Public does mean public, of course, so a founder must still avoid publishing Product source which contains sensitive data.
Installing fAIctory locally
The supported installation is deliberately local and self-hosted. You need Node.js v24.11.1 or newer (there is an `.nvmrc` file provided for the nvm users), npm, and Docker Desktop or another working Docker installation with Compose. Ollama is recommended for the real Crew, but the visibly labelled deterministic mode means you can launch the Factory before downloading a large model.
Clone the JamesNock/fAIctory repository or download and extract its GitHub source ZIP, then run:
npm ci
npm run setup
npm run devOpen http://localhost:3000 for the public Studio or http://localhost:3000/floor for the Factory floor. On a new installation, setup generates a founder access key, prints it once, and stores it in the local .env file. Do not commit that file, obviously.
The setup script is cross-platform Node rather than Bash. It checks the Node version, creates .env without overwriting an existing one, verifies Docker and Compose, starts the bundled PostgreSQL service, pulls the sandbox image if necessary, initialises LangGraph checkpoints and publications, checks Ollama readiness, and prints the next command. It is idempotent, so running it after an interruption is fine.
The default Ollama model is qwen3.6:35b. Setup does not silently begin that rather substantial download. Pull it yourself:
ollama pull qwen3.6:35bOr explicitly permit the installer to fetch it:
npm run setup -- --pull-modelIf you want deterministic output for UI work, tests, or a smaller machine, set FACTORY_CREW=demo. If you use another model already installed in Ollama, change OLLAMA_MODEL in .env.
The manual setup, so nothing is hidden
I like guided installers, but I do not like mysterious installers. The equivalent manual process is:
npm install
cp .env.example .env
ollama pull qwen3.6:35b
docker pull node:24.11.1-alpine
docker compose up -d --wait
npm run factory:setup
npm run devThe Compose file starts PostgreSQL 17 Alpine, binds it only to 127.0.0.1:5432, and stores data in the faictory_postgres_data named volume. The local credentials are deliberately simple development defaults. They are not an invitation to expose that database to the internet and hope nobody notices.
npm run factory:setup calls LangGraph’s Postgres schema setup and creates the separate factory_publications table. It is safe to run again against an existing local database. A configured DATABASE_URL does not help if the database exists but its required tables have never been initialised.
Important configuration
The example environment file documents the complete runtime surface:
FACTORY_CREW=ollama
OLLAMA_BASE_URL=http://127.0.0.1:11434
OLLAMA_MODEL=qwen3.6:35b
FACTORY_SANDBOX_IMAGE=node:24.11.1-alpine
FACTORY_PRODUCT_MAX_ATTEMPTS=3
FACTORY_ACCESS_KEY=
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/faictory
INCIDENT_WEBHOOK_URL=
INCIDENT_WEBHOOK_TOKEN=FACTORY_ACCESS_KEY protects the floor and all Run mutation or inspection APIs. Leaving it empty keeps a local prototype unlocked. FACTORY_PRODUCT_MAX_ATTEMPTS controls the bounded ralph loop and accepts an integer from one to ten. The incident webhook is optional; without it Buddy writes structured lifecycle records to the application log.
Checking the installation
The doctor command checks the services and configuration:
npm run doctorIt checks Node, .env, Docker, the sandbox image, Postgres initialisation, and Ollama readiness. Missing Ollama is a warning because the demo fallback works. Missing Docker or durable storage is a required-service failure for the full setup.
For later starts, the usual sequence is simply:
docker compose up -d --wait
npm run devStart Ollama as well if the Crew uses it. Stopping the PostgreSQL container preserves the named volume. Avoid docker compose down -v unless deleting every local checkpoint and publication is genuinely the plan. The -v is small, efficient, and spectacularly unsentimental.
Starting and publishing a first Run
Once the services are running, open the Factory floor and authenticate with the generated founder key. Write a Brief which describes the ambition, audience, constraints, and evidence of success. This is worth doing properly. An autonomous production line is still perfectly capable of building the wrong thing with tremendous efficiency.
Start the Run and watch the live trace. Nia’s research, Miro’s framing, and Rue’s critique appear as structured Hand-offs. At the gate, inspect the framing and risks. Approve it if the direction is sound, or request a revision with useful feedback. The revision history will remain available rather than being flattened into the latest answer.
After approval, Sol produces the design direction and Kit creates the engineering plan and Product candidate. The floor shows active Crew identity and progress while the request advances. If Product Proof fails, the bounded Buddy and Kit loop runs inside the same build stage. If it eventually needs founder intervention, retry resumes that pending work. You can also cancel active or paused work, which records a terminal founder decision.
When Rue’s Proof passes and Ada finalises the Run, inspect the Product and its evidence. Completion still does not make it public. Select Publish to Studio when you are satisfied. The Product then appears in the public story ledger and Product library with its launch page, sandbox preview, Proof, and downloadable verified files.
Job done.
Testing the Factory
The automated suite covers the graph contract, founder approval and revision paths, recovery and cancellation, Ollama parsing, access control, response streaming, Product adapters, sandbox rules, publications, ZIP downloads, incident reporting, rendered HTML, and local setup helpers.
npm run lint
npm testnpm test builds the production Next.js application and runs the Node test suite. It writes the verification build to .next-test instead of .next, which matters if the development server is still running. Sharing the same build directory can leave the live server pointing at stale CSS or JavaScript chunks. That was the sort of bug which makes you question causality for twenty minutes, so the isolation is now explicit.
There is also an opt-in golden Run:
npm run test:goldenThis is the real-service smoke test. It requires PostgreSQL, Ollama, Docker, the configured model, and the sandbox image. It bypasses the deterministic fallback, creates and approves a fixed Brief through the Factory interface, requires Docker Proof, assembles the preview, and restores the completed Run through a second Postgres checkpointer. It leaves the Run in PostgreSQL as operational evidence and prints its ID.
Unit and contract tests tell me that the boundaries behave. The golden Run tells me that the local services, model, checkpointing, sandbox, and full orchestration path can work together. Both matter.
Security decisions and limits
fAIctory currently has a pragmatic single-founder access system, not a multi-user identity platform. When FACTORY_ACCESS_KEY is configured, the floor redirects to login and protected APIs require a signed session. The supplied key is compared through SHA-256 digests using a constant-time comparison. The resulting cookie is HttpOnly, SameSite Strict, valid for seven days, and Secure when the request uses HTTPS. Changing the access key invalidates existing sessions.
That is suitable for a private, self-hosted founder tool behind normal network controls. It is not accounts, roles, recovery, throttling, or a comprehensive audit platform. A remotely reachable deployment needs HTTPS, a reverse proxy, strong secrets, non-default database credentials, backups, firewalling, and a private Ollama endpoint.
The Product runtime also has a deliberately narrow threat model. It builds bounded website artefacts, not arbitrary repositories. There are no package installs, user-selected commands, network access, or general filesystem access. Supporting a new Product type should mean adding another adapter with its own schema, isolation, and Proof, not gradually turning the existing adapter into “run whatever the model wrote and cross your fingers”.
What fAIctory does not do yet
This is a working first slice, not a claim that an autonomous software company has been solved over a weekend. The current graph produces bounded website Products. It stops before deployment and does not run arbitrary generated commands. Authentication is for one founder. Active cancellation coordination belongs to the current Node process. The automated suite uses in-memory dependencies and does not provision PostgreSQL, which is why the golden Run exists.
The model can still misunderstand a Brief, produce weak research, or build an uninspiring Product which happens to satisfy its technical checks. Proof makes specific claims more trustworthy; it does not turn taste, usefulness, or product judgement into a solved equation. The founder remains accountable for the direction and the decision to publish.
Future developments
It is worth being candid about the first version of fAIctory: I built it very quickly. That was useful because it forced the idea into something real before I could spend six months designing the world’s most elegant imaginary Factory. It also means some of the current decisions are good starting hypotheses rather than conclusions handed down on stone tablets. It is not battle-tested and certainly not "launchable" as-is. It's no more than a play-around, really.
The architecture has boundaries, tests, recovery, and a fairly healthy suspicion of generated code, but the next stage should involve more experimentation and evidence. I do not merely want to add more Agents, more stages, and more flashing lights. I want to find out which parts genuinely improve the Products and which parts are expensive theatre.
Different models for different Agents
At the moment, the Ollama Crew uses one configured model across its specialist roles. That made sense for getting the complete system working, but it is unlikely to be the best long-term arrangement. Nia’s research task, Miro’s product framing, Rue’s adversarial critique, Sol’s design direction, and Kit’s code generation do not require exactly the same strengths.
I should spend considerably more time trying different models for different Agents. A smaller, faster model may be perfectly adequate for structured orchestration or summarisation. Another may be much better at source-grounded research. A coding-focused model may produce stronger bundles and repairs, while a model inclined to challenge assumptions may make a better Rue. There is no good reason to pay the latency and memory cost of the largest model for every Hand-off if a smaller specialist does the work as well or better.
That experimentation needs to be more rigorous than swapping a model name, running one entertaining Brief, and declaring victory. fAIctory needs a repeatable evaluation harness: a set of representative Briefs, expected structural outcomes, human quality judgements, Proof pass rates, repair rates, latency, token usage, and resource cost. Model choice should become explicit Run configuration and remain in the trace so that two Runs can be compared properly.
The aim is not to find one universally “best” model. I do not think such a thing exists in any useful sense. The aim is to find the best fit for each responsibility, on the hardware available, within an acceptable time and cost budget.
A grilling before implementation
The current founder gate happens before design and engineering, which is the right place to approve the Product framing. I think the Factory probably needs another deliberate pause before implementation too: a grilling.
After Sol has proposed the experience and Kit has written the engineering plan, the Crew should interrogate the proposed build before Kit starts generating it. Rue could lead, but this should be broader than another generic critique. Each relevant Agent should challenge the plan from their own position: does it still solve the researched problem, does the experience match the approved framing, are the acceptance tests strong enough, which assumptions remain unproved, what failure modes have been ignored, and is the scope realistic for the Product adapter?
The output should be a short, structured set of objections and answers rather than an endless committee meeting conducted by language models (a chilling vision). Resolved questions would strengthen the implementation Brief. Material unresolved questions would return the Run to the founder or the appropriate Agent. Only then would Kit build.
This would add some time before generation, but that is rather the point. The Factory can produce code quickly. Its job is not to keep the expensive-looking machinery moving at all costs; its job is to increase the chance that the machinery produces the right thing.
Version the whole Crew, not only the code
A reproducible Run needs more than a Git commit and a Run ID. Model versions, Agent instructions, schemas, sampling settings, Product adapter versions, Proof rules, and Factory configuration all influence the result. Those inputs should be recorded as a Crew manifest attached to every Run.
That would make it possible to replay a Brief against a new Crew configuration, compare the results, and understand whether a change genuinely improved anything. It would also stop “we changed the prompt at some point” becoming the official root-cause analysis.
Better research evidence
Nia’s research should eventually carry stronger source provenance. Claims should link to captured sources, identify when they were retrieved, distinguish evidence from inference, and remain available to later Agents without dumping an entire research archive into every model context.
That opens another useful role for Proof. The Factory could detect unsupported claims, stale evidence, contradictory sources, or a Product decision which has drifted away from the research it supposedly follows. It would not make research automatically correct, but it would make weak foundations much easier to spot.
Branches and comparative Runs
At present, a Run has one revision history and one route towards a completed Product. It would be useful to branch from an approved framing and explore two genuinely different approaches without repeating the research or losing their common origin.
The founder could compare two designs, engineering strategies, model combinations, or Product candidates alongside their Proof and resource use. The important constraint would be to make branching an explicit experiment, not an excuse to generate twenty alternatives and choose whichever one has the nicest gradient.
More Product adapters
The current website adapters prove the Product runtime seam, but fAIctory should eventually make more than bounded websites. Possible adapters include command-line tools, small APIs, documentation packages, data transformations, and installable web applications.
Each adapter would need its own strict schema, fixed build commands, isolation, acceptance criteria, resource limits, and publication rules. I would rather add these slowly than create one general-purpose adapter which can execute an arbitrary generated repository. “It can build anything” and “it can safely build something” are very different claims.
Budgets and operational visibility
Runs should have explicit budgets for elapsed time, model calls, repair attempts, context size, and compute. The Factory already bounds Product candidates, but the same discipline should apply across the complete graph. Autonomy becomes much easier to trust when it has a visible fuel gauge and a brake pedal.
The floor could then show where a Run spent its time, which Agent consumed the most context, which model calls fell back, how often Buddy intervened, and which stages tend to create revisions. That evidence would help improve the process instead of optimising whichever delay happened to be annoying most recently.
Deployment as another controlled Hand-off
The current Factory stops before deployment, and I think that is the correct limit for the first version. Eventually, deployment could become another adapter-backed Hand-off with its own founder gate, environment policy, health checks, rollback plan, and post-release Proof.
A Product passing tests is not the same as a Product operating successfully. A mature Run should be able to attach observed release evidence, monitor an agreed set of signals, and roll back through a controlled mechanism. It should never acquire broad production credentials merely because an Agent asked nicely.
Less glamorous engineering work
There is plenty of necessary work which will not look especially exciting in a Studio screenshot: reconnect cursors for long streams, durable background execution, versioned database migrations, backup and restore tooling, stronger identity and roles, rate limiting, fuller audit records, retention policies, accessibility testing, and more incident destinations.
That work matters. The Factory will not become more trustworthy simply by adding cleverer models to a fragile operating system. Each new capability should extend an existing boundary, bring its own Proof, and remain honest about what survives a restart or a failure.
There is a lot to explore. The useful next move is to turn these ideas into measured experiments, keep the ones which improve outcomes, and cheerfully delete the ones which merely make the diagram more impressive.
What I learnt building it
The easy part of an AI product studio is calling a model. The harder part is everything around the call: deciding what context it should receive, validating what comes back, preserving state, showing progress, placing human decisions, containing generated code, proving behaviour, surviving failure, and publishing only the right material.
Named Agents and a polished Studio give fAIctory character, and I am rather fond of them. But the Factory becomes useful because the characters operate inside constraints. Nia cannot silently rewrite the Brief. Kit cannot skip the founder gate. Buddy cannot make a failure disappear by weakening Proof. A rejected Product cannot become the public preview. A demo fallback cannot pretend to be Ollama. A browser pointer cannot pretend to be durable storage.
Autonomy without boundaries is not a production system. It is just uncertainty happening more quickly.
fAIctory is my quick attempt to build those boundaries into the product from the beginning: local where practical, typed at the edges, resumable by design, sceptical of generated code, explicit about human authority, and honest about what passed and what did not.