Factory
The factory is the controller that turns a labelled issue into runs, narrates progress on the issue, meters each run’s tokens and owns the kill switch. It is the only component that creates runs, so every run has a task and a budget. This page describes the design; what runs today is on the status page.
Source: docs/architecture/ai-platform.drawio, page 5.
From issue to merge
| Step | What happens |
|---|---|
| Intake | A maintainer adds factory/ready to an issue. The factory takes a snapshot of it, so a later edit does not change a task already under way, and comments with the run id, branch, budget and a watch link. A task already running refuses a second label |
| Sponsored intake | A second intake kind, api, takes a task from a machine principal: a ZITADEL machine user declared under api.sponsored, never from a request body. Each principal names a sponsor (a maintainer, named in the issue’s footer and on the Task, accountable for what it files), a daily dollar ceiling and a per-task maxCostUSD; every request must carry max_cost, and the Task records who asked, the sponsor, the person it acts for and the cap. The runs are charged to the principal. The Slack App (Slack) and RunLore (RunLore) are its clients; RunLore’s link is off by default. Past caps.tasksPerDay, POST /v1/tasks answers 429 daily_task_cap |
| Triage | Once per task: which class it is (for example docs-links), which team of roles works it, and how big a budget it gets. A refusal is explained in a comment. What triage predicts only chooses the team and the budget; risk is enforced on the diff at merge time |
| Teams | Roles run as sequential AgentRuns in one room, on one agent/<task> branch: an implementer, then a reviewer, with a tester in larger teams. Only the implementer writes |
| Revise | A GitHub review with “Request changes” becomes the input of a new run on the same branch. /factory retry tries again after a failure |
| Merge gate | policy-bot posts a status, the factory’s gate decides, the merger App merges. A low-risk class merges with no human (docs-links and revert on cloud-native-ref, at most once a day, behind a revert breaker); every other pull request merges on one maintainer’s approval of its current head. How it decides and the classes: the merge gate; drills and recovery: run auto-merge safely |
| Scope | loops.<class>.scope is off, steering or scoper; docs and scripts run scoper, every other class is off. steering adds the class’s note (.github/agent-factory/steering/<class>.md) to the implementer’s brief. scoper first runs a read-only triager that turns the issue into a bounded task with acceptance checks, or asks the author the question it could not answer (the user guide). Three counters by repository and class: scoper outcomes, steering reads (used, absent, invalid) and bounce exits (relabel, retry, expired) |
| Split | loops.<class>.split is set on scripts only, with anyTier: true (example (the reference host)). A row needs scope: scoper on the same row and never applies to docs-links, revert or an oracle class. Its keys: maxChildren (2 to 5, required), familyUSD (the cap on the parent and every part together; default four of the child tier’s task caps, $20.40 on standard) and childTier (default standard). A split is only proposed for a task the triage classifier puts on frontier, and parts open one at a time after a maintainer’s /factory split (user guide). Two counters by repository and class: maku_factory_splits_total (by outcome) and maku_factory_split_children_total (landed, dropped, reverted) |
| Test-first tester | Per class, testFirst: true (it needs checks: true) runs the tester before the implementer: its tests, failing on the base, become the contract the implementer works against. merge.contractCheck names the host’s Test contract 🧪 check, required on a task that has a contract (row 2b) and report-only otherwise; row 2c refuses a head that changed the contract. Reasons: contract_invalid, contract_disputed, contract_modified. holdoutChecks on a live or shadow class names the checks the implementer cannot read. Metrics: maku_factory_contracts_total and maku_factory_class_without_holdout. testFirst is false by default (runbook 21 turns it on); trio is the implementer and the reviewer until the bench. The roles: roles |
| Kill switch | One task: the label factory/stop. Every task: the stop ConfigMap, or a label on a pinned control issue |
The developer’s view of the same journey is in the user guide.
Two clients reach the same intake without a label. Slack drafts the issue in a thread and a maintainer’s check mark files it; RunLore hands the factory an incident and checks, after the fix merges, that the alert is gone.
Source: docs/architecture/ai-platform.drawio, page 6.
Dependency bumps
A class that declares author.adopt takes Renovate’s pull requests instead of authoring bumps: a Task
with no run, decided by the same gate. deps-patch and deps-minor run in shadow on cloud-native-ref.
What is checked: adopted classes.
After a merge
A monitored class’s merges stay under watch, whoever merged them, and a regression or a follow-up fix
counts against the breaker like a revert. With merge.approveToMerge, a maintainer’s approval of the
current head is the merge. The monitor: after a merge;
the approval: approve-to-merge.
Components and software
| Component | Software | What it does | Why this software |
|---|---|---|---|
| Task controller | A Go controller (controller-runtime) | Turns a labelled issue into a task: snapshot, a room, the team’s runs on its branch; narrates on the issue. Starts a reviewer after the implementer, and turns “Request changes” into a new run | The only component that creates runs, so every run has a task and a budget |
| Admission | Kueue | Queues sandboxes so a burst of tasks waits instead of overloading the node pool. Until it lands, the factory’s own caps bound concurrency | The Kubernetes-native job queue, with quotas |
| Run meter and kill switch | Part of the controller | The run meter revokes any run, hand-launched ones included, at its token cap. The agent-factory-stop ConfigMap pauses intake and stops every task; so does a label on a pinned control issue | Controls that act from outside the sandbox |
| Merge gate | policy-bot, the factory’s gate and a merger GitHub App | policy-bot posts policy-bot: main; the factory’s gate decides each head; the merger App (ogenki-agent-merger[bot]) merges the head the gate decided or a maintainer approved; only the factory’s own revert PRs use GitHub’s auto-merge. A separate App holds the right to merge, and only it | The policy lives in the repository and is reviewable; the right to merge is isolated from everything else |
| Admission policy | Kyverno | Denies AgentRun creation to anyone but the factory | One path in, so no run escapes its budget |
Controls
| Boundary | Mechanism |
|---|---|
| Spend | Per-run deadline; the factory’s run meter revokes a run at its token cap; token budgets at the gateway; at most 20 tasks a day |
| Merge | Only low-risk classes (docs-links, revert) merge themselves, live since v2.0, with a revert breaker; everything else merges on one maintainer’s approval of the current head (merge gate) |
| Stop | kubectl -n agent-system create configmap agent-factory-stop pauses intake and stops every task; one label on a pinned control issue also refuses new runs and revokes every running one |
The sandbox, identity and GitHub boundaries are on Runtime → Security boundaries. The factory design has the full merge policy, gate paths and the five-layer kill switch.