Start hereTry itAgentsArchitectureMechanismsResultsGlossaryRelated workSpikeChangelogContributingSecurity

FireTrident architecture

FireTrident is a git platform for many coding agents working one repository at once, built on Cloudflare Workers and Artifacts. It coordinates agents the way colonies do: through signals left in a shared environment, local decisions, and parallel integration, instead of locks and one merge queue. The names the code borrows from natural systems (colony, field, antibody) are mapped to plain terms in glossary.md.

The model in one screen

Request flow (an in-platform agent)

  1. ColonyDO.registerAgent gives the agent an id.

  2. ColonyDO.take(agentId) builds candidates from open intents, reads the field from the relevant RegionDOs, and picks one with the response-threshold rule (core/threshold.ts). Three signals shape the choice:

    • Lateral inhibition: a candidate is skipped when other agents' live claims on its exact footprint files reach a threshold (half a fresh claim by default): their sum, or with FT_INHIBIT_STRONGEST the strongest single file's. Claims decay, so a silent agent's claims stop inhibiting after one half-life, about 3.5 minutes, for one shared file; summed over n shared files they hold for 5·ln(2n) minutes, by the strongest file one half-life however many overlap. There is no lock to release and nothing to deadlock.
    • Co-change repulsion (soft): TrunkDO mines trunk history into a Hebbian co-change map (core/cochange.ts, trunk/cochange.ts); candidates whose files usually change together with files other agents have claimed score lower.
    • Admission: RegionDO.admit checks the region's admission cap and deposits the agent's claim marks in one synchronous step, so concurrent takes cannot over-admit a region. Only then does PoolDO.lease hand out a workspace.

    The assignment also says what review will ask of the change (review, agents/preview.ts): the questions of up to five live antibodies whose path matchers cover the footprint, and the rules that cover it. An agent sees a pattern people rejected before it writes it. Security antibodies are left out: an agent whose change one of them returns is told only the class, never the question that would show how to slip past it.

    It also says why the code is the way it is (context): the latest landings, at most three, that changed the footprint's files, with their intents' titles and whys, from a per-path index the colony fills as landings arrive (no trunk call per take).

    And it says why the intent's latest change came back (lastReturn): the proposal, when, the paths it wrote and the reason its agent was given, unless a change for the intent has landed since. Direction returns and contest losses are skipped, and a security return names only the class. In-platform agents see it as fenced data to address first; MCP take and checkout return it as lastReturn. A change that came back because trunk moved its files names the landings that moved them (movedBy: the landing, its intent's title and why, its agent, the model its note names and the trunk commit), on the proposal, in the inbox, in take's lastReturn, in the redo prompt (fenced as data) and on the web cards, and its reason ends: keep what that landing did unless your intent says otherwise, and escalate if both cannot hold.

    • Import repulsion (FT_IMPORT_WEIGHT, off when absent or 0): claims on the direct imports and importers of a candidate's files repel it the way co-change neighbors do. TrunkDO keeps the import index (trunk/imports.ts, importLinks) current from landings; a take asks for it once per batch of files, cached, and fails open. In the simulator it cut rework from 31% to 16% at weight 1, for about 10% fewer landings a minute (bench results 2026-10-13-papers). Where nothing conflicts it is pure cost: on a preview, the Hono rule run (20 Gemma agents, 0 returns either way) took 199 s to zero at weight 1 against 137 s and 143 s without it, so it stays off unless a colony's work contends on hub files.
  3. The agent (AgentDO) clones trunk at lease.baseSha into a @cloudflare/computer Workspace using the read-only trunk token, reads its footprint's files and the files its intent builds on (each recorded in its read set with its blob id; the project tree its write prompt lists, up to 400 paths, and the modules its import check reads are not recorded), edits with an LLM, commits, and force-pushes its commit to main of its own workspace repo with the workspace write token. The fork's prior content never matters. (At very large scale agents read from trunk mirrors instead of trunk, to respect per-repo git request limits.) With virtual checkouts (FT_VIRTUAL_CHECKOUT: on or colony ids) an agent never clones: it reads the commit, the root tree, the subtrees along each wanted path and then the blobs through the ARTIFACTS binding at lease.baseSha, cached by id (agents/virtual.ts), and proposes an inline change: no commit and no push. A change over the inline limits, or with binary content, still goes the git way for that change.

  4. ColonyDO.propose records the proposal, computes the residual (paths changed outside the declared footprint), runs innate checks, and enqueues it in the lane of the proposal's primary region (RegionDO.enqueue).

  5. On each lane tick (RegionDO alarm, interval from core/batch.ts#laneIntervalMs), the region asks TrunkDO.blobsFor for current trunk blobs, validates proposals with core/occ.ts, selects a batch with core/batch.ts#selectBatch, and calls TrunkDO.land(batch). Stale proposals are returned to their agents with each stale path, the blob it read and the blob now on trunk.

  6. TrunkDO.land queues the batch. One cycle at a time takes every waiting batch (up to 200 proposals), re-validates each proposal against trunk and the batches ahead of it, reads each proposal's new blobs from its workspace repo (readBlob on the binding), or, for inline changes, their texts from the colony in one call per cycle (checking each blob id), builds one commit per batch on top of the previous one by replacing only the changed paths (FT-* trailers, a lineage note each), and pushes the tip once with the expected old value (compare-and-swap) (with asynchronous publishing, FT_ASYNC_PUBLISH, the cycle records its landings and moves the trunk writer's own main instead, and a publish loop beside the cycles pushes every commit recorded since its last push, in order: leases, certified and the lineage notes follow the published head, lanes validate against the recorded one, the recorded head stays within 2,000 landings or 60 s of the published one, a restart publishes first, and a take whose intent covers a file changed by an unpublished landing waits half a second instead of leasing a base without it); the notes follow in one notes commit on refs/notes/firetrident. The lane then reports each Landing to ColonyDO.onLanding, which updates intents, deposits trail marks, reinforces the agent's threshold for the region, and broadcasts events.

Durable Objects and their RPC contracts

All are SQLite-backed (new_sqlite_classes). Callers use RPC on stubs (env.COLONY.get(env.COLONY.idFromName(colonyId)).take(agentId)). Types come from @fire_trident/core.

ColonyDO (name: colonyId)

Owns colony config, intents, rules, agents, proposals index, review queue, antibodies, decision counters (the funnel), metrics, and the live WebSocket hub (hibernation API) for the UI. It also prices the colony's model calls (colony/spend.ts, core/pricing.ts): in-platform agents count calls and tokens per model in their journals, which the colony reads into its own tables (16 at a time, at most every 15 s, until an agent has stopped and been quiet 5 minutes), and review, the contest judge, the conflict resolver and antibody minting count theirs per model as they call. GET /api/colonies/:id/metrics and GET /api/colonies/:id carry spend: USD in total, by layer (agents, review, judge, resolver, other) and per landed intent, tokens by model, and the tokens of any model with no listed price, which count in no dollar sum; prices are Workers AI list prices on 2026-10-12. The top bar, ft status and the landing page's rule card show it as model calls this platform made. Tokens counted before the per-model counters existed are priced at the model their layer is set to use (review at Clef, second opinions at the dearest second-opinion model). Per intent (colony/intentspend.ts, FT_INTENT_SPEND): agents count each call under their job's intent in their journals, review and the contest judge count by proposal, and GET /api/colonies/:id/intents/:id/spend prices them by layer and model; the resolver and antibody minting are not counted per intent.

RegionDO (name: ${colonyId}:${region})

Owns the field for one region and that region's lane.

TrunkDO (name: colonyId)

The only writer of trunk. Holds a @cloudflare/computer Workspace with trunk cloned, an index of trunk paths to blob ids, and the lineage tables.

PoolDO (name: colonyId)

Keeps a warm pool of workspace forks of trunk across the ft-ws-* namespaces. Each namespace holds a seed repo that mirrors trunk main, and workspaces fork from their namespace's seed (forks cannot cross namespaces, and forking a repo just pushed fails about one time in five: spike/RESULTS.md facts 2 and 3). Workspaces are refreshed to the current trunk on lease and recycled after landing.

AgentDO (name: agentId)

An in-platform agent built on @cloudflare/computer (a git workspace) and Workers AI (or Claude when ANTHROPIC_API_KEY is set). It loops: take, work, propose, wait, repeat. Its model gets no tools: the loop reads the files and records the read set (request flow step 3). It journals each step so a killed agent resumes.

FireTridentMcp

An agents/mcp McpAgent exposing tools for external agents (any MCP client): join, sense, take, declare, checkout, read, propose, inbox, release, escalate, why, impact. Every tool call heartbeats the agent's claims, so a slow external agent keeps its work; an agent silent past the colony's limit (ten minutes unless a person sets one; in-platform agents keep ten) loses its intent to the colony, with a failure mark (see Closure ledger). take and inbox accept wait (the server waits for work or for the proposal's outcome). join registers the session as an external agent and returns a resumeKey once (the colony keeps only its SHA-256); another session acts as that agent only with agentId and that key. take returns git steps that need no shell state (no cd or export), and with checkout the questions review will ask of the change, the rules that cover it and the why of the latest landings on its files; escalate reports an intent that cannot be done as written (see Escalations); propose records the agent's notVerified statement for review; why (trunk/whywalk.ts) says whether each landing still holds on today's trunk (still as written, changed since by a later landing, or deleted), and the readers add the current certification of its covering tests (core/whyholds.ts); for a line it skips formatting-only landings (whitespace, quote style, trailing commas and semicolons, rewrapping: core/reformat.ts) and follows blocks of three or more lines moved from another file, bounded at 200 landings, 3 moves and 2 s and failing open, with the line's owner first; why trims each note to the proposals that wrote the path (otherProposals counts the rest). Session state tolerates parallel tool calls. A failed call returns isError with structuredContent.error and the same JSON as its text (mcp/errors.ts): kind (validation, auth, not_found, conflict, too_large, rate_limit, server, unavailable), message, retryable, retryAfterMs (a jittered 1 to 5 s for platform failures such as an overloaded object), status, and next where the next call is clear. declare remembers each call's fingerprint (title, why, sorted footprint, acceptance, priority, parent) in the session's object for ten minutes, and a repeat returns the same intent with replayed: true; take, declare, propose, release and escalate are annotated idempotent. impact lists a file's importers up to three levels (at most 200 files, each with the import it comes via), the live claims on them (agent, intent and claim age, read from at most 32 regions) and the whys of up to 10 recent landings on them; it labels agent-written text as data and fails open (the import index gets 3 s to sync, or the answer says it may be behind). It also answers on the public MCP for listed colonies, as GET /api/colonies/:id/impact?path= and as ft impact. With FT_MODEL_STEPUP (on or a list of colony ids), an intent whose changes came back twice since it last changed course is taken next with the stronger model class (default): the take says why, an in-platform agent uses it and its note's observed model shows the switch, and an outside agent gets it as advice; a third return still reaches the stuck strip. Beside MCP, with FT_A2A (on or a list of colony ids) an orchestrator hands a colony work over the Agent2Agent protocol (api/a2a.ts, colony/a2a.ts): a public agent card at GET /api/colonies/:id/.well-known/agent-card.json and JSON-RPC at POST /api/colonies/:id/a2a, where SendMessage declares an intent (the first line of the text is the title, the rest the why; data parts may carry the footprint and acceptance) and needs the admin token, GetTask and ListTasks follow intents as tasks without one (open is submitted, claimed or proposed is working, landed is completed with the commit and a link to the why, withdrawn by CancelTask is canceled, withdrawn otherwise or escalated and not approved is failed), and CancelTask withdraws (admin token). A twin answer returns the existing intent's task and says so. See docs/agents/a2a.md.

Artifacts layout

Measured in the spike (spike/RESULTS.md): import and fork are synchronous (about 1.3 s per fork, parallel even from one source); about 20% of forks issued within ~0.1 s of a push to the source fail with INTERNAL_ERROR and must be retried; git inside a Durable Object via @cloudflare/computer clones in ~350 ms, commits in ~90 ms, and pushes in ~300 ms; a commit built by tree surgery takes ~40 ms; forks copy every ref including notes (defaultBranchOnly is ignored); binding read calls need main or a full commit id (HEAD and refs/... return null); the server rejects pushes whose expected old value is stale (compare-and-swap) but accepts forced non-fast-forward pushes; the namespace push trigger starts a Workflow for every repo in the namespace (~0.5 s to instance, ~2.3 s to run()).

Facts the design relies on (from the docs and the spike): each repo is one Durable Object; git requests are limited per repo and control-plane calls per namespace; the binding reads but cannot write files, so writes go through git; there is no server-side merge, hook, or branch protection; Artifacts accepts non-fast-forward pushes, so only TrunkDO ever holds a trunk write token and it pushes with an expected old value.

Live events

ColonyEvent (see packages/core/src/types.ts) is the single event shape for the UI and for internal fan-out. The UI first loads ColonySnapshot, then applies events from the WebSocket.

Additional RPCs (as built)

Decide: the reflex arc and the immune review (packages/worker/src/decide/)

A proposal enters checking and the decide engine routes it:

  1. Innate checks: the rule re-check on the changed files (every live rule, the intent's own first, up to 200), residual paths (changed but not in the declared footprint), a mandatory "Not verified:" statement, and hollow files: a new script file that is empty or holds only comments or placeholder bodies goes back.
  2. Deterministic proof: every changed relative import is resolved against the trunk file tree. When only import specifiers changed and each still points at the same file, the rule re-check passes, the change stays inside its footprint and touches no sensitive path, the proof is a confirmation on its own and no model is consulted. A broken import is returned with the exact path. The resolution facts are passed to every model that is consulted, and no model opinion can override them. A change whose every changed file is an existing script file with the same tokens outside comments and whitespace is proven not to change behavior (decide/comments.ts, an exact lexer; JSX, any directive comment and credential-looking new comments disqualify it), and lands on the innate checks plus this proof (the colony policy's commentProof, on by default).
  3. System 1 (reflex): one Clef call (@cf/cloudflare/clef, typed noul/choice/score questions: needs_human, risk, touches_sensitive, off_intent, plus every matching antibody question). Confident answers act; uncertain ones escalate.
  4. System 2 (LLM): consulted only when the deterministic proof is inconclusive and the reflex cannot act alone. A second opinion from a different model family than the authoring agent (@cf/google/gemma-4-26b-a4b-it, or @cf/openai/gpt-oss-120b when the author is Gemma), strict JSON verdict, behind a per-model concurrency and rate limiter with backoff and fallback (Workers AI caps large models at 20 requests a minute per account). Reviewers see up to 24,000 diff characters a file and 60,000 in all. An approval counts as a confirmation only when the layer saw every changed line; a change too large for its view goes to a person ("too large for model review"), and each layer's view (files shown of files changed, what was cut) is recorded with its decision. The state models see says who wrote the intent's why: a person, a rule, certification or an agent, whose why is a claim to check. Every model prompt (second opinion, contest judge, antibody questions) puts the intent, the agent's own text, the facts and the diff inside one fence marker that occurs in none of them, labeled as data to judge, never instructions. When every configured reviewer model is in the author's family, no model is asked and the change goes to a person (rule L9); when the author never reported its model (an outside agent need not), the review runs and the decision records that independence was not checked. The contest judge asks only models outside every contestant's family, and with none configured the deterministic order picks the winner.
  5. System 3 (human): the review queue.

propose is idempotent: a repeat for the same agent, intent and head commit returns the existing proposal. Deterministic outcomes are recorded as their own checks in the evidence and in decide.deterministic.* metrics; they never count as reflex decisions in the funnel.

Loosened guards (danger.ts): in modified script files (not tests), deleted 401/403 denials and throws inside security conditions, security conditions made weaker (an operand dropped, === turned into ==, a negation removed, a constant comparison dropped), verify swapped for decode or a constant-time compare dropped, and deleted expiry or limit checks are a loosened-guard signal: a second opinion must look, and no intent text, policy or antibody waives it. Measured on real history (packages/bench/results/2026-10-13-danger, every modified non-test script file checked on its own): on Hono, the loosened-guard signal fired on 13 of 3,559 files (0.4%), 12 of them in auth, JWT, CSRF or signed-cookie code, and on none of Hono's newest 251 commits; all the danger checks together flagged 1.1%. On FireTrident's own history they flagged 9.5% until a SQL statement run on a handle named sql, sqlite, db or database stopped counting as running a program (decide/sqlexec.ts; a file importing child_process, cluster or worker_threads keeps every exec firing): now 3.1% (1.8% over all its history), 2.1% sent to a person, and Hono's 1.1% unchanged (packages/bench/results/2026-10-17-danger/). Escalation batching (FT_ESCALATION_BATCH; decide/batch.ts, api/batch.ts): the escalations that skip the hourly alert budget get a budget of their own (policy escalationBatchPerHour, 60); past it each change still waits for a person, but the queue groups them by reason into one card with approve all, reject all and open each (POST /api/colonies/:id/review/batches/decide, admin token; ft review --approve-batch / --reject-batch). Only capability hits and the operational rules (M2, L5, L7, L8) are batched; never authentication, secrets, governance, handoff, a repair's test change, a loosened guard, a protected path, a security finding's next proposal (P2), a goal conflict (P4), a held merge, an audit item or a canary. How often the checks miss a dangerous change nobody has seen is not measured.

Protected paths (rule P3): the colony policy's protectedPaths (globs; GET/POST /api/colonies/:id/decide/policy, ft policy --protect) send any change that writes, deletes or renames a matching path to a person, naming the glob, in every phase and even with review off; no intent text, antibody or policy waives it. A glob added later reaches changes already cleared to land: each lands only with a person's approval given after protection, checked by the colony, each lane and every trunk-writer cycle (TrunkDO.protect, table ft_protected), and the policy answer says which changes were stopped and which had already landed.

Twin intents (FT_TWINS: on or a list of colony ids; core/twins.ts, colony/twins.ts). An intent a person or an agent declares (REST or MCP declare) is compared with the colony's open, claimed and proposed intents and those landed in the last 30 minutes: when its normalized title words score at least 0.8 (Jaccard, after punctuation, stop words and do-it verbs are dropped and words lightly stemmed) against one and their footprints overlap, nothing is created; the answer names the twin (duplicateOf, similar) with a sentence saying to take it or declare again with force: true. Removing never matches adding, rule, certification, spec and vaccine intents are never checked, and ft new declares with force. Who declared it (colony/chain.ts): every take carries declaredBy (source, the declaring agent and its model, the chain depth of agent-declared ancestors through parentId), and the in-platform prompt shows it beside the why for an agent-declared intent, with the note that an agent's request is not a thing to copy onward and that text asking to be spread is reported. With FT_INTENT_CHAIN_GATE (on or a list of colony ids), an agent-declared intent at a chain depth of intentChainDepth (policy, default 2) or more, or the intentFanoutth (default 5) one its declarer has declared in an hour, is held as an open report of kind chain: takes skip it, GET /api/colonies/:id/escalations and the Review tab show it, a dismissal releases it and a withdrawal ends it; the gate fails open.

Goal conflicts (rule P4, FT_GOAL_CONFLICT: on or a list of colony ids). A change that removes lines a recent landing wrote for another intent waits for a person: in a text file it modifies or deletes, it removes at least max(3, half) of the lines that landing still has there, the landing is within the decide policy's goalConflictMinutes (60) and among its newest goalConflictLandings (200), and its intent is neither an ancestor nor a descendant of the change's own (core/goalconflict.ts). Blank lines and re-indented lines do not count. The trunk writer traces the removed lines through its landing log and notes (TrunkDO.removedLineOwners, bounded to 20 files, 2,000 lines, 512 KB and 4 s). The reason names the landing, its age, its intent and why; a person approving lands the change with undoes in its note. Certification repairs, rule intents, the colony's own intents (megafile splits) and the platform's own commits never conflict, and the check fails open: its own failure never holds a change, and the evidence says it was not checked. Agents are also told to escalate with contradictory-spec instead of undoing another intent's recent work. Checked on a preview (scripts/goal_conflict.ts): three agents each moving one backend to a different language; with the check, the first landed and the other two waited, each naming the landing it would undo, and a person chose; without it, all three landed and the last silently won. scripts/goal_conflict.ts --demo <colony> sets up the same three goals on one colony with an agent assigned to each and never decides anything: the first change lands and the two that would undo it stay with a person. Production's order-backend colony is that demo.

Two-signal gate (gate.ts): a proposal lands without a person only on two confirmations bound to its head commit: the innate checks, plus a deterministic proof that it changes nothing but what its rule asks, a second model's approval of every changed line, or earned autonomy with a confident, quiet reflex. On the proof path both confirmations are deterministic and rest on a rule a person wrote: the proof passes only when the innate checks pass, and p(needs_human) is not consulted (Clef's alarms are advisory there; an antibody hit still returns the change). A second model's approval and earned autonomy also need a low p(needs_human). Earned autonomy applies only to intents written by a person or a rule, and only when the reflex saw the whole change. A second opinion without a needs_human probability (missing or not a number) reads as 1, so it never lands a change on its own. Neither earned autonomy nor a deterministic proof (D3, M1) lands a destructive change alone (destructive.ts: a file deleted or renamed, an export removed, 200 or more net lines removed, or more than half of a file of 40 or more lines): it goes to the second opinion with the reason named, and the fact shows on the review card. Earned autonomy needs the fact computed: a file it cannot read to tell (binary, CommonJS, export *) leaves it unknown, and the reflex alone does not land the change. One signal returns it to its agent. Escalation to a human needs a detector hit plus damage or a capability change. Regulation: an alert budget caps human pages per hour; a detector marked not useful ≥10% goes advisory and >25% goes off. Danger patterns (danger.ts): hits for new process, network, eval or crypto use are waived only when an intent written by a person or a rule asks for them; an agent's or a repair's text never waives them. Files another tool runs or obeys (handoff.ts, one list: MCP client configuration, editor and dev-container settings, .envrc, git hooks, .gitmodules, nested .git, virtual environments, other agents' instruction files, and .gitattributes lines that add or alter a filter, diff or merge driver) are a must-human class, handoff, that no policy, antibody, intent text or earned autonomy waives, deleted ones too. Governance, handoff, the sensitive paths and protected paths all match case-folded and NFC-normalized (pathfold.ts). In a repair intent from certification, any test-file change beyond imports is a must-human hit no policy or antibody can waive, and node:test's skip and todo forms, and assertions that disappear (commented-out ones do not count), are test-weakening. Security returns: a return for a security finding tells its agent only the class; the full reasons stay in the decision record and the review view, the proposal route withholds them from readers without the operator token, and the intent's next proposal goes to a person. Antibodies: a human rejection mints an antibody (path matcher + yes/no question + threshold) only when no existing antibody fired on the proposal (otherwise it counts a recurrence on the one that did), committed to trunk as .firetrident/antibodies/<id>.json by TrunkDO.systemCommit with a lineage note; every later matching proposal is screened, and the return reason names the antibody's question. Antibody files are authoritative: a landing that edits, renames or deletes one reloads or retires it. Vaccination meter: POST /api/colonies/:id/vaccinate {count} injects sealed bad proposals of several threat classes through the normal path; they can never land, and recall per class is reported with a Wilson 95% interval (GET /api/colonies/:id/vaccinate). The report separates vaccines returned (stopped by a model or a check) from vaccines escalated (sent to a person, whose decision the meter does not measure), and a vaccine whose second opinion was unavailable is an error, not a catch. Review view: for every escalated proposal the engine keeps a masked view in decide_review (each file's hunks up to 24,000 characters, 60,000 in all, the danger hits with line numbers, the residual paths; lines that may hold a secret are masked), the review queue serves it, and the web card and ft review lead with the lines to look at first.

Quoted acceptance (FT_QUOTED_ACCEPTANCE: on or a list of colony ids; decide/quotes.ts, decide/acceptance.ts). For an intent with an acceptance list, the second opinion answers each line as met, not met or unclear, naming a file and quoting at least 8 characters of a changed line for each met; the acceptance list is fenced as data in the prompt. The platform looks for each quote, whitespace-collapsed, in the diff it showed the model (a quote it cannot find, or one from a removed line, counts as unclear), and the approval counts as a confirmation only when every line is met with a found quote. A line stating what must not be there has no line to quote: a met counts on the model's word, recorded as basis: absence. A line not met returns the change naming it (so does a keep line: the acceptance of the newest landing by another intent on the files the change modifies); an unclear one goes to a person. The decision, the lineage note, ft why, the Why tab and the public MCP why keep each line with its verdict and quote. Rule intents on the proof path, certification repairs and vaccines keep today's rule.

Review view (FT_REVIEW_VIEW: on or a list of colony ids; needs FT_QUOTED_ACCEPTANCE; decide/reviewview.ts, decide/keepcheck.ts, decide/tsexports.ts). Before tests before landing and any model, FireTrident checks each acceptance or keep line that names a file and what it exports (a function signature, an interface, a type alias, a constant, or that the file exists) by reading the files after the change and the trunk files the line names. A line it proves is met, marked checked by FireTrident on the review card and in the lineage note (basis: checked); a line it disproves sends the change back with the exact mismatch (rule A1), a keep line only when it held before the change. The second opinion is asked only the lines left open, sees each changed file whole where it fits the review budgets (24,000 characters a file, 60,000 in all; what it sees of the change is never less than the plain diff showed), a quote found in a changed file after the change counts, and its review receipt digests that whole-file view.

Thresholds (DEFAULT_POLICY, decide/policy.ts). One policy per colony; POST /api/colonies/:id/decide/policy (admin token) changes it, and mergePolicy clamps each number to its range. The gate's rules above read them by these names.

Setting Default What it does
enabled on Runs the reflex arc on every proposal; off, only protected paths still reach a person (P3)
deterministic on Lets a mechanical proof confirm a change with no model (D3, M1)
commentProof on Lets the comment-only proof (comments.ts) confirm a change to existing script files whose code is unchanged outside comments and whitespace
confidence 0.8 A layer acts alone only when the answers that decide are at least this confident (|2p − 1| for yes/no answers): R1, R3
hit 0.5 A yes/no detector answer at or above this probability is a hit (needs_human, touches_sensitive, off_intent, antibodies)
needsHuman 0.5 p(needs_human) must be below this for the reflex (R3) or the second opinion (L3) to land a change
alertsPerHour 30 Escalations to a person per rolling hour; past it, one below budgetRiskFloor goes back to its agent, except the rules marked as never bounced (P2, P3, P4, M2, L5, L7, L8)
budgetRiskFloor 2 Risk (0 none to 3 high) at or above which an escalation still reaches a person when the budget is spent
autonomyAfter 3 Landings in a region approved by a deterministic proof, the second opinion or a person since its last revocation (approvedLandings) before R3 may land there alone
probation 0.1, 0.25, 5 After 5 fires, a detector overruled (by a higher layer, a person's approval or feedback) at least 10% of the time is advisory (recorded, not gating), more than 25% off (not asked)
maxAntibodies 60 Matching antibodies asked per proposal (one Clef call takes 64 questions; 4 are the standard ones)
llmConcurrency 8 System 2 calls in flight at once, per colony (the contest judge's own limiter takes the same numbers)
llmPerMinute 120 System 2 calls started per minute, per colony and model
goalConflictMinutes, goalConflictLandings 60, 200 P4 (FT_GOAL_CONFLICT): a landing is recent for 60 minutes and while among the colony's newest 200 landings
intentChainDepth 2 FT_INTENT_CHAIN_GATE: an agent-declared intent with 2 agent-declared intents in its ancestry waits for a person
intentFanout 5 The same gate: agent-declared intents one agent may declare in an hour before the rest wait
reflexModel @cf/cloudflare/clef System 1
secondOpinionModels @cf/google/gemma-4-26b-a4b-it, @cf/openai/gpt-oss-120b, @cf/zai-org/glm-4.7-flash System 2: the first from a different family than the author's
juryModels @cf/google/gemma-4-26b-a4b-it, @cf/openai/gpt-oss-20b, @cf/zai-org/glm-4.7-flash FT_CONTEST_JURY: two or three outside the contestants' families sit in place of the judge
dangerAllow none Danger classes a person allows without a person (optionally under some paths); allowed hits stay danger signals, so a reviewer's approval is still needed
protectedPaths none Globs only a person may change (P3), at most 50
auditRate 0.03 The share of changes that landed without a person (proof, reproduction, a model or earned autonomy) sent to a person after landing, sampled per confirming layer, 0 to 0.2
canaryRate 0 The share of escalations accompanied by a sealed known-bad change in the human queue to measure what reviewers catch, 0 to 0.2; off by default

Routes: POST|GET /api/colonies/:id/vaccinate, GET /api/colonies/:id/immune, POST /api/colonies/:id/decide/policy, POST /api/colonies/:id/detectors/:did (useful / not useful feedback). ColonyDO: vaccinate, vaccineReport, immune, setDecidePolicy, detectorFeedback. Every decision is recorded (recordDecision) and emitted, so the UI funnel shows reflex / LLM / human counts. GET /api/colonies/:id/immune is public: each autonomy row gives the approved landings since the region's last revocation, that revocation's reason and when the region last moved (lastAt); the Review tab shows them latest first ("Autonomy earned in N of M regions"), and ft immune <colony> prints them with the antibodies and detectors.

Changing direction (chemotaxis)

A colony is steered the way cells follow a chemical gradient: when the gradient changes, cells re-orient. Here a human changes the gradient (amends an intent, withdraws it, retracts a rule, or sends an agent guidance), the field updates (needs evaporate, move or appear), and agents re-orient at their next step. Nothing is preempted by a lock or a queue. Agents also tell the colony what they are doing, so a human can see where to push. Pure helpers live in colony/direction.ts (colony side) and agents/direction.ts (agent side).

Amend (PATCH /api/colonies/:id/intents/:iid {title?, why?, footprint?, priority?, reason, by?}, ColonyDO.amendIntent, ft intent amend). The intent's revision goes up by one and the change is kept in intent_revisions (fields changed, before and after, reason, by, at). Need marks move with the footprint (strength = priority) and claims leave the paths it dropped; the holder's claim follows the paths it gained. A title, why or footprint change is substantive: changes in flight built on the older revision go back (intent revised: r1 → r2, why changed (...)), and the holder keeps the intent, claim and lease and redoes the work against the new revision. A priority-only amendment just moves the intent in the queue. GET /api/colonies/:id/intents/:iid/lifecycle (public; core/lifecycle.ts, ft intent show) returns each change's lifecycle strip: declared, claimed, proposed, confirmed (the layer and rule), landed, certified, taken back and merged by a person, each with its evidence and time, and where a change left the path (returned with the landing that beat it, escalated, withdrawn, reaped); the Why tab, the review card and the Intents tab draw it. The last two steps happen in the team's repository, so FireTrident never marks them done. Propose is idempotent per agent, intent and head, retries in flight included; over MCP, a retry after a lost reply returns the same proposal marked replayed and writes nothing. Only a person's amendment, a rule or the platform raises priority: an intent an agent declares (MCP declare, A2A SendMessage, POST /intents with source agent) takes at most the priority of a parent the agent holds or declared, or 1 otherwise (colony/priority.ts); what it asked is kept as requestedPriority, with priorityCap saying which cap applied, the lineage note records both, and the answer says what it got (priorityNote). Landed, withdrawn and sealed intents cannot be amended (409); GET /api/colonies/:id/intents/:iid (ft intent show) returns the intent with its revisions and directives.

Withdraw (POST .../intents/:iid/withdraw {reason, by?}, ft intent withdraw). The intent becomes withdrawn (kept, with the reason), its need marks evaporate and its claims clear, changes in flight go back (withdrawn: ...), and its holder goes idle and gives its lease back. Idempotent. Retract a rule (DELETE /api/colonies/:id/rules/:rid {reason, by?} or ?reason=, ft rule retract): the rule stays in history (retractedAt, retractedBy, retractReason) but is no longer checked (the decide arc and agents skip it), its open violations stop counting, and every intent it created that has not landed is withdrawn. Landed work is never touched.

Directives (POST /api/colonies/:id/agents/:aid/directive {message, intentId?, by?}, ColonyDO.direct, ft direct). Free-text guidance for one agent, about the intent it holds unless intentId names another. Revisions and withdrawals reach the holder the same way, as directives of kind revision and withdrawal. An in-platform agent gets them pushed (AgentDO.direct); external agents read them in their inbox (ColonyDO.inbox, which the MCP inbox tool calls): a directive entry lines up with a proposal entry (id, intentId, status: "directive", reason = what to do, createdAt). Reading a revision notice records that the agent saw that revision.

How an in-platform agent re-orients (loop.ts, applyDirection). Directives are kept in the agent's journal and acted on before its next step. A withdrawal ends the job (the colony already released the claim). A revision ends it too, and the next take hands back the same intent at the new revision. Guidance joins every later prompt for that intent (a "Directives from a human operator" block); work already produced is redone with it, after the agent recalls a proposal still in flight (ColonyDO.recall: returned as superseded: ..., intent, claim and lease kept). A directive that makes the step in flight moot (any withdrawal or revision of the job's intent, guidance while it produces or proposes) moves the agent's epoch, so the step halts at its next check and no more model calls go the old way.

When a change of direction takes effect. An amendment or withdrawal sends changes in flight back and dequeues them; ColonyDO.propose refuses (409) a change built on an older substantive revision (an external agent passing no intentRevision is held to the revision it last saw); the lane returns stale direction before TrunkDO.land. The commit point is the trunk writer: the colony records the new direction with TrunkDO.direction (table ft_direction), and each landing cycle reads it once before building. Recorded before that read, the change stops; after it, the change lands, and the call waits for that cycle (at most 15 s) and names the landing, which ft revert undoes. Responses give returned (changes stopped), landed (each with its landing and trunk commit) and confirmed (false when the trunk writer did not answer, so those changes may still land). A withdrawal that lost the race leaves the intent landed; an amendment that lost it keeps the intent live ("landed a change built on r1; r2 still to do"). Direction returns leave no failure mark, are not counted by the cascade detector, count as redirected (not returned), and never reopen the intent. scripts/direction_race.rb checks both outcomes on a deployment, with FT_TEST_PUSH_DELAY_MS holding each push open. Checked on a preview (2026-10-13) with the push held 6 to 10 s: 7 withdrawals that arrived after the cycle's check were each answered with their landing, and trunk agreed; 2 sent while the change waited in the writer's queue stopped it. The first queued try found that the colony recorded the withdrawal at the trunk writer only after telling the lanes, whose dequeue can wait behind a landing in flight, so the change's own cycle never saw it; the colony now starts TrunkDO.direction first.

Relay (POST /api/colonies/:id/agents/:aid/status {note}, ColonyDO.relay). An agent's one-line "what I am doing now" is stored on the agent (GET agents and ft agents show it in the NOW column), emitted live (at most one event per agent per second), and recorded with its next proposal. In-platform agents relay as they enter each step and when they act on a directive (at most every 3 s; synthetic agents at most once a minute).

Lineage. At propose time the colony records the direction a change was built under in its evidence (evidence.direction: intentRevision, the amendments up to it, the human guidance the agent had for the intent, its last status note), so it lands in the lineage note under refs/notes/firetrident. landContext(intentIds, agentIds, proposalIds) also hands the lane each proposal's recorded decide-arc decisions (layer, kind, p, action, at, the question, who answered, a one-line answer; the latest 8 per proposal), which TrunkDO writes into the note's decisions. A deterministic proof that decided alone appears in the note's evidence checks (deterministic: ..., gate). ft why and the Why tab show the revision history, directives, note and the decide arc.

Revert (POST /api/colonies/:id/landings/:ref/revert {reason, by?, redo?, proposal?}, ColonyDO.revertLanding, ft revert; ref is a landing id or a trunk commit of 7+ characters). A person undoes a landing as a new trunk commit that puts every path it wrote back to its blob from before (trunk/revert.ts plans it; TrunkDO.revert writes it through the serialized system-commit path). It is refused (409) when any of those paths has changed since, naming each path and the latest landing that moved it, or when it would delete a file that trunk files import, naming the importers; both checks run again inside the commit. The intents the landing completed come back as new intents (parent: the reverted intent, why: the reason first; redo: false declares none), and a rule's violations count again until they land. The revert commit carries FT-Revert-Of and a lineage note with revert {of, commit, by, reason}; certification runs on the result; a revert can itself be reverted, which withdraws the intents it declared again (ColonyDO keeps each revert in reverts). With proposal (ft revert <colony> <prp_...>), only that proposal of the landing is undone: its own paths (the landing's recorded paths, filtered by the proposal's write set in the note), with every guard applied to them, and only its intent comes back. The commit carries FT-Revert-Proposal, the note's revert names the proposal, and a trunk writer still on older code refuses the call (503) rather than undo the whole landing.

Landing notices. When a landing changes files that agents still working hold in their footprint (never its authors), each gets one directive of kind landing with the landing, the changed paths and their blobs before and after, and the landing intent's title and why: facts, never instructions. An in-platform agent that has not proposed yet rebases on trunk's tip and re-reads the changed files (at most twice); after proposing it does nothing, since the lane catches staleness. MCP agents read notices in their inbox. The directive event carries kind: "landing", the landing id and the paths.

The fence fails closed. When a cycle cannot read the direction record, or a row of it does not parse, the trunk writer holds the changes it could not check for the next cycle instead of letting them through: nothing lands or comes back, no failed attempt is counted, the lane rechecks after 1 s, and /metrics counts it (direction.unchecked, and trunk.directionUnchecked on the cycle). The protected-path check does the same (protect.unchecked): with its record unreadable no change lands, because a path protected a moment ago may be among those the lanes passed.

Events: amended, withdrawn, retracted, reverted, directive and status (plus intent with status withdrawn). The Events tab groups the first five under Direction; the Intents tab shows an rN badge on amended intents and groups intents by what asked for them, then by region, each rolled up by state and sorted most urgent first (packages/web/src/intentTree.ts).

Stuck work

colony/stuck.ts checks, from indexed queries, for work that stopped moving: an intent claimed with no change for 10 minutes (with the agent's own note), an intent whose changes came back 3 or more times (with the last reasons), certification red for 15 minutes (with where its repair stands), a report or a change waiting 10 minutes for a person, and an agent alive but not moving for 10 minutes. The snapshot and the metrics report carry stuck: { at, items, total }; each item says what to do (review the change, answer the report, look at the agent, amend or withdraw the intent, look at the tests), on the needs-you strip and in ft status. The colony's tick, at most every 30 s, publishes one stuck event per new item (stuck_reported keeps the keys); an item that clears publishes nothing, and one that recurs is reported again.

Escalations

An agent that finds an intent cannot be done as written (a wrong test, a contradictory spec, an impossible ask) reports it instead of working around it: the MCP tool escalate {intentId?, kind, report}, or an in-platform agent's ESCALATE <kind>: <report> reply. ColonyDO.escalate keeps the report on the intent (escalation: kind, report, agent, time), releases the agent's claim and lease, and keeps the intent out of take() until a person answers; at most one report waits per intent. A person resolves it with POST /api/colonies/:id/intents/:iid/escalation {decision: "amend" | "withdraw" | "dismiss", note, by?, amend?} (or ft escalation): amend applies the change through amendIntent and reopens the intent, withdraw withdraws it, dismiss reopens it unchanged. GET /api/colonies/:id/escalations and ft escalations list the open reports; the web Review tab shows them as cards. The repair intents of certification tell their writers to escalate a wrong test with kind broken-test. Checked on a preview (2026-10-13) with scripts/mcp_escalate.ts: an external agent escalated over MCP, no agent could take the intent while the report waited, and a person's dismissal reopened it.

Contest mode

An intent declared with contest: n (2 to 4; API, spec intents[].contest) is raced by up to n agents at once: contestants' claims on its files neither repel nor inhibit each other, and later contestants skip admission. Each contestant's change goes through the decide arc as usual; a contest change that reaches ready is held until every live contestant has one ready or ten minutes after the first (a contestant that gives back, stops or dies does not block). Then decide/contest.ts picks one: a deterministic proof first, then fewer residual paths, then no danger signals, then a pairwise comparison by a second-opinion model outside the authors' families, asked in both orders so a disagreement counts as a tie, then the smaller diff, then the earlier change. The winner goes to its lane; each other change goes back with supersededBy and the reason; contestants still working get a withdrawal notice; the winner's lineage note keeps the others as alternatives (proposal, agent, model, why it lost), and a contest event is published. If the winner does not land, a new round starts. Judge calls count in the colony's decide spend. Checked on a preview (2026-10-13): three in-platform agents raced one intent (a small formatting module and its tests); the colony held their ready changes, the judge (gpt-oss-120b) preferred whichever change it was shown first in both orders, so the comparison counted as a tie broken by the smaller diff, the winner landed about two minutes after the race began, and its lineage note keeps the two others as alternatives. In an earlier try with a harder task (wiring a new route into the router), none of the 49 changes the three agents proposed passed review, so nothing was compared: a contest only helps when at least one contestant can do the work. With FT_CONTEST_JURY (on or a list of colony ids) the pair goes to a jury of two or three small models from different families (policy juryModels), each asked in both orders at once within the judge's deadline; only a member whose two answers agree votes, the majority wins, and a split or no vote is a tie broken as before; the verdict names every vote, and scripts/contest_bias.ts measures the judge or the jury over N contests.

Agent-scale source control

Anthropic's account of Scion, its in-house version control system for 30,000 merges a day (2026-10-13), lists the principles of source control for agents. Where FireTrident stands on each:

Scion FireTrident
No merge queue; test after merge, before deploy Lanes land optimistically; certification runs after landing; certified moves only to a passing commit; a red trunk becomes a repair intent, and culprit finding names the suspects
Commits, merges and rebases on the server The trunk writer builds every trunk commit by replacing only the changed paths and moves trunk with compare-and-swap; agents never push to trunk
Changesets: changed files with content pointers, accepted without coordination; refs moved separately Inline changes carry their contents; with asynchronous publishing the trunk writer records landings and pushes git beside its cycles
Every client query is an API; jobs need no local repository why and blame per line, path history, landings, certifications, review, escalations and the MCP tools; agents read files through the server
Sparse and virtual checkouts Virtual checkouts: agents read only the objects they need, cached by id
Derived data through one mechanism Derived indexes: import index, co-change map, path history, why-context
Engineers tell agents "no"; people review a change's intent, not every line Rules and antibodies keep each "no"; the review card leads with the intent and the lines to look at
Permissions per directory; approvals that tell comment-only changes from logical ones Protected paths always reach a person; comment-only changes land on a deterministic proof
Component "superfans" watching telemetry The stuck-work detector, and the cascade detector

What differs: Scion leaves git and serves a repository with almost 100 GB of history, checked out on the order of 25 million times a day for CI; FireTrident stays git (any client can clone a colony and read its notes) and has run on repositories of up to 1,287 files. Scion lets concurrent changes conflict and fixes them afterwards; FireTrident also validates what each agent read before its change lands.

Scale

Every agent talks to one ColonyDO, so the take path is built to stay cheap per call:

Measured on a preview (packages/bench/results/2026-10-12-scale/) with synthetic API agents (no model calls, review off, no work time, 64 regions), 120 s runs:

Agents Landings/min Time to land p50 / p90 Platform errors
1,000 2,924 13.0 / 17.0 s 0
2,000 2,714 14.8 / 19.5 s 0
5,000 2,778 12.5 / 17.4 s 0 from load; 78 of 50,368 calls failed in the first 33 s: the load generator's own DNS and connection errors, and one reset when the preview's code was updated (live-n5000.json)

These runs predate the event record (see Replay). Their driver held a live feed open, so regions were already emitting field events to the colony; the record adds one batched insert per published batch. Before these changes, 1,000 agents reached 515 landings/min and the run stopped at 60 s with a 38% error rate. The next limit is TrunkDO, the single writer: every 3.4 to 3.9 s commit cycle hits the 200-proposal cap, so throughput no longer grows with agents, and about 6 s of queueing dominates time to land.

Round 4 (packages/bench/results/2026-10-13-round4/: preview p18, one colony with 64 regions, the same driver) measured the trunk writer's new modes at 5,000 agents. The git path landed 1,821 a minute: 3.9 s of each 5.1 s cycle went to reading the changed blobs from workspaces (2.2 s on 2026-10-12; the code is the same, Artifacts read latency is not). Inline changes remove that read: the cycle took 2.8 s, 3,842 changes landed a minute, and time to land fell from 19.1 to 10.7 s at p50. Asynchronous publishing moves the push out of the cycle (cycles of 1.8 to 2.5 s, below the 200-proposal cap, and pushes of 6 to 11 s beside them, each carrying 120 to 360 landings; median time to land about 8 s) but did not raise throughput (2,759 and 3,776 a minute): ColonyDO, which answers every take and proposal and the trunk writer's call for inline contents, became the limit, and it slows as the colony's history grows. Two of its costs were fixed on the way: with asynchronous publishing, every take's check for unpublished landings on its files sorted the landings table and parsed the newest 500; it now walks only the landings between the two heads, by an index on trunk_sha, and extends its last answer when only the colony's head moved (takes refused for room fell from 10,201 to 237 in a run).

The live bench (packages/bench/src/live.ts) leaves its colony and record in place and writes a run block: agents joined, trunk commits, changes and intents landed, the span from the run's first landing to its last, landings and changes a minute over it, and time to land at p50 and p95. Its agents call the REST API, which the per-session call budget does not cover (that budget applies to MCP sessions). Production's scale colony is such a run.

Replay

ColonyDO records every event it publishes (publish in ColonyDO, EventRecorder in colony/events.ts) into a capped events table: a sequence number, a time, the event type and the event, the newest 100,000 kept. Field marks are not recorded (the replay derives the field from intents, claims and landings); metrics are sampled every 3 s while events flow, plus one trailing sample. Recording happens where events are published, so it does not depend on who is watching or how live delivery is batched; regions therefore keep emitting while a colony works, even with no viewer.

GET /api/colonies/:id/events?after=<seq>&limit=<n> pages the record. A colony that ran before recording existed answers with a timeline rebuilt from what it stores (colony/replay.ts: rules, intents with their times, agents, proposals and their decide outcomes, decisions, landings with their files, revisions and directives), labeled reconstructed; claim times and the deaths of reaped agents are estimated. The web app's /c/:id?replay=1 feeds the record through the same reducer as live events, at 1× to 16× with a scrubber; director mode (?director=1) works on top of it.

The colony page holds thousands of agents: agents are canvas dots, a map of more than 2,000 files is laid out again at most once a second while files keep arriving, a seek backward in a record of more than 20,000 events waits until the scrubber rests (250 ms), and proposals are pruned once per batch of events.

New projects (ft new --spec)

A spec (see demo/new-project-spec.md) declares the project's intents, the files each one builds on, the spec sections every agent receives, an optional model per intent, the architecture rules, and acceptance tests. ft new creates a colony with an empty trunk, sets its policy first when the spec declares one (policy: protectedPaths, commentProof; POST /decide/policy, so no agent can change a protected path before it holds), adds the rules, and posts the intents in dependency order (parents and priorities make foundations land first). An in-platform agent waits until the files its intent builds on have landed, reads them, and writes files that must parse, keep every rule, use only TypeScript that Node can strip, and import only files and names that exist; each file gets up to three attempts with the failure as feedback. demo/run_new_project.rb runs the flow and verifies the result from a plain git clone.

Plain-git access

A colony's trunk can be read with stock git and no account: git clone https://<host>/git/<colony>.git, then git fetch origin refs/notes/firetrident:refs/notes/firetrident for the lineage notes (api/git-proxy.ts, mounted at /git/*).

Who authors the commits. By default the landing commit's author and committer are FireTrident, with a Co-authored-by line for each agent where FT_CREDIT_TRAILERS covers the colony. A colony can name a person instead (POST /api/colonies/:id/commit-author {name, email}, ft commit-author): that person is the author of every landing commit, FireTrident stays the committer, so signatures are unchanged, and agents get no trailers; the lineage note still names every agent, its model and the review receipt. A commit that lands an imported issue carries Refs: owner/repo#n (see "GitHub issues").

Measured on a preview (2026-10-12) with a Hono trunk imported at depth 200 (583 files, 2.4 MB pack) and one landing: clone 1.9 s median over 5 runs; clone, notes fetch and git log --notes=firetrident -3 together 2.9 s median.

Review from a chat client

POST /mcp/review is a second MCP endpoint, for people, so a person can work through escalations in Claude, ChatGPT or any MCP host (mcp/review/, mcp/apps/). It takes the admin token only: it refuses without FT_ADMIN_TOKEN configured (even with FT_OPEN_WRITES), refuses the MCP token, and refuses when the two tokens are the same value. Its tools are review_queue (what waits for a person, as GET /api/colonies/:id/review), review_card (one change: the lines to look at, the masked review view, and who said each thing, built by the same code as the web card) and decide (approve or return with a reason, through ColonyDO.decide). The two reading tools declare MCP Apps views (specification 2026-01-26: _meta.ui.resourceUri on a ui://firetrident/... resource of type text/html;profile=mcp-app): one self-contained page with an empty content security policy that writes agent text as text. A model never decides: review_card gives the view a single-use code in the result's _meta (an HMAC over colony, change, head, the decision allowed, a nonce and a 10-minute expiry), which never appears in the text or data a model reads; decide refuses without a valid code, and a spent code voids every older code for that change. A host without the view gets the full card as text and the web review link. Add it to Claude Code with claude mcp add --transport http firetrident-review https://<host>/mcp/review --header 'Authorization: Bearer ${FT_ADMIN_TOKEN}' (single quotes: Claude Code stores the variable's name and reads it when it connects).

GitHub issues

POST /api/colonies/:id/issues/import {repo: "owner/name", labels?, limit?, closeOnLand?, close?} (admin token; ft issues import <colony> owner/name) reads a repository's open issues from GitHub's REST API, without a token for public repositories or with FT_GITHUB_TOKEN, and declares one intent per issue, skipping pull requests and issues already imported (keyed by owner/repo#number). Issue text is written by strangers, so an imported intent's source is external: every gate reads it as an agent's request (it never waives a danger gate or earns autonomy), agents see its text quoted line by line under a line saying a stranger wrote it (at most 4,000 characters), and it records where it came from. The lineage note, the landing card and the Intents tab name the issue, and the landing commit carries Refs: owner/repo#n. Writing back is off unless the import set closeOnLand and FT_GITHUB_TOKEN can change that repository's issues: then FireTrident posts one comment linking the landing, and closes the issue only with close.

Trunk certification

Review decides whether a change may land; trunk certification checks that trunk still works. After landings the colony runs the project's own tests on trunk and records the result per commit (packages/worker/src/certify/).

Configuration and deployment