Flow-Through

Flow Through Logic/Published work/Autonomous operations platform

Autonomous operations platformIn production

Multi-agent system running unattended

Fifty operational modules across eighteen scheduled jobs that plan, implement, test and document work overnight with no operator present.

  • 50 modules · 18 scheduled jobs
  • Policy at the tool boundary
  • Human approval queue

The problem

One person runs the company, and the work that keeps it healthy happens after hours: the review nobody reached, the maintenance that keeps sliding to next week. Hiring solves that at a cost a company of one carries badly. Handing it to software raises the question that stops most people from trying. At three in the morning, with nobody awake, what keeps an automated worker from pushing something broken into a live system or emailing a customer by mistake?

What it does

Fifty operational modules sit behind eighteen scheduled jobs on the host. Seven of those wake an agent on its own clock with a written brief describing one mode of work, and the rest run a single script on a timer or hold a service open. A build job claims the top item from a task queue on disk, opens a branch, implements one scoped unit, runs the repository's own tests, and writes a handoff so the run an hour later resumes with an accurate picture rather than starting again. A separate run then verifies that work against the acceptance criteria written into the task, and it is a different run on purpose, because a session that wrote the code is the worst judge of whether the code is right.

Other jobs carry the parts of an operations team that stay awake. One observes overnight health and composes a single honest sentence about it, and that sentence is required to be able to deliver bad news, because a summary that is only ever reassuring stops being read: the status reports it replaced were measured at zero readership and deleted. One triages the inbox into drafts. One audits the live sites against what the search index has been told about them. A weekly job works the sensitive surface the build job stays away from, meaning authentication, payments, webhooks and secrets handling, and it is authorised to do so precisely because the furthest it can reach is a branch.

The control plane is the half that took the most care, and it is what makes the rest safe to schedule. Every run's reach is decided before it starts. Anything that would change a live system stops at a queue and waits for a person to press approve, and anything that would leave the machine is refused to an unattended run outright. Every change is rehearsed against every other change already waiting, in a throwaway copy of the repository, using that repository's own test suite, and a combination that goes red is refused at the moment it is created rather than at the moment somebody presses.

How it works

A run is a short-lived process with a fixed brief, a fixed set of tools, a turn limit and a wall-clock timeout. It reads shared state from disk, does one unit of work, writes what it did back to disk, and exits. Everything durable lives in files rather than in the run, which is what lets an hourly job pick up where the last one stopped and what lets a run be killed at any moment without losing the thread.

  • The briefEach of the agent jobs is a markdown file naming its mode, its limits, the order it picks work in, and what a finished unit looks like. A scheduler tool writes the matching operating-system launch entry from that file, so creating a job and describing a job are one action.
  • The allowlistThe runner selects a tool allowlist before the process starts, expressed as explicit patterns for the shell commands and file operations that job is expected to need. The narrow jobs are named individually and the rest draw a default set. An observation job is given reading tools and its own health script, which leaves version control and package managers outside the set.
  • The guardUnderneath the allowlist sits a deterministic hook that inspects every tool call and blocks it by exiting with a reason on standard error. Universal rules cover everyone at the keyboard, and a stricter set applies while the autonomous flag is set: a push must target a titan branch, dependency installs stop, external sends stop, and secrets stay away from anything that reaches the network.
  • Shared stateThe task queue, the per-task handoffs, the approval queue, the proposals and a growing library of operational lesson notes all live as plain files the next run reads first. A backup module writes a dated archive holding that state, the logs, the narrative memory file and a full repository bundle, and the bundle inside the first archive was extracted and cloned back to prove it restores.
  • The rehearsalStaging a merge merges every card already waiting for that repository, in queue order, into a throwaway worktree taken from the current base, then puts the new branch on top and runs the repository's own suite over the merged result. A conflict or a red suite refuses the stage and names the offending card and file.
  • The approval queueA finished, executable thing becomes a card that carries the real diff alongside a plain-language title and the one-line consequence of ignoring it. Approving carries the merge out by itself, after a 600-second hold in which the card can still be killed, and every executed merge records the exact command that reverses it.
  • The lineEach run closes by writing one sentence about what actually happened, which is the first thing read the next morning. It goes stale after eighteen hours and the app falls back to counting, so a run that dies before writing one leaves no stale sentence behind pretending to be current.

What was hard

A queue of merges that were each safe alone

Staging validated a branch against the base as the base stood that day, and nothing revalidated afterwards. On 2026-08-09 eleven cards waited for one repository, so the queue held eleven merges each rehearsed against a different base, and the only signal of trouble would have been a failed press days later with nobody holding the context. Merging all eleven in queue order into a throwaway worktree found the eleventh colliding with a card staged two days earlier, in a file they both rewrite, and behind the collision sat a fault that neither branch's tests could see, because each branch was correct alone: one made a failed invitation retry on every sweep while the other raised its channel flag on a single lane, so a tenant with revoked mail credentials would send, be refused, alert and roll back once per pending member every fifteen minutes without end. The rehearsal is now part of staging. It merges the whole pending stack and runs the repository's suite over the merged result, which turns a broken press days from now into a failure for the run that caused it. Two later defects in the same mechanism surfaced afterwards and were fixed: a card whose work had already reached the base by another route still counted as a press that moves something, and the test step read the manifest from the repository as it sat on disk rather than from the merged worktree it was about to test, which left an entire repository's stack reported as unproven while the merged tree carried a working suite.

A policy that the thing it governed could edit

The guard began as rules on shell commands, which is the obvious place to put them and an incomplete one. A run holding file-editing tools could have rewritten the guard script, or the settings file that registers it, or the runner that selects the allowlists, and then proceeded under rules it had written for itself moments earlier. The fix is recorded in the guard's own comments: the hook is registered for the file-editing tools as well as the shell, and while the autonomous flag is set it refuses writes to the guard, to both settings files, to the runner, and to the approval state that only the approval tool is allowed to touch. Interactive sessions keep the ability to edit all of them, which is how a person applies a fix like this one. The same principle put approving, killing and executing behind the guard rather than behind good intentions, so a run can fill the queue and has no path to drain it.

Safe to leave running

The reach of a run is decided before the run exists. Every job draws a tool allowlist, written out as explicit patterns in the runner, so the widest thing a job can attempt is fixed at launch rather than argued about later. The narrow jobs are named one at a time and the rest share a default set. A read-only job is given reading tools plus the scripts it calls, which leaves version control, package managers, deletion and file writes outside the set entirely.

Beneath that sits the guard, a small shell script the harness consults before every tool call. It reads the call as data and blocks by exiting with a reason, so its behaviour is the same on every run regardless of what the model concludes it ought to do. Recursive force deletes and force pushes are refused for everyone at the keyboard. Autonomous runs additionally have their pushes confined to a titan branch, their dependency installs refused, their external sends refused, and any command that would carry secrets toward the network refused.

Anything that changes the outside world stops at the approval queue, and a run may fill that queue while having no route to drain it. Staging is a validation gate rather than a formality: it refuses a branch absent from the remote, a repository with uncommitted changes, a base that already carries the work, and a branch introducing nothing. What reaches the card is the real diff, captured at stage time. Approving starts a 600-second hold during which the card can still be killed, and the detached process that carries out the merge re-reads the queue inside a lock and stands down when the card has left it. Presses that land seconds apart take turns rather than colliding in the same worktree, and every completed merge records the exact revert command beside it.

Correctness is checked against the repository rather than asserted. A merge is rehearsed on top of every other pending merge and the repository's own suite runs over the combination, and a stack that goes red fails the run that caused it. Where a suite is absent or its dependencies are missing, the rehearsal says the stack ran unproven instead of implying a pass, which matters more than it sounds: reading a host with nothing installed as a suite that passed is exactly the shape of failure this system is built to avoid.

Reporting is treated as a safety control in its own right. The overnight sentence is required to name the crash and the unfinished thing, because a summary that is only ever reassuring gets skimmed past on the one morning it matters. Claims are audited against a tool result before they are written down, and anything unconfirmed is labelled unverified rather than smoothed over. The notifier reads the HTTP status of every post it makes, after a period where a discarded status let every message claim success against a dead endpoint. Monitors page once after a threshold that clears transient blips, and latch the already-paged flag only after delivery actually succeeds, so a page that failed to send retries instead of going quiet.

Properties are re-derived rather than trusted to stay true. A card is a claim made at one instant about two moving references, so the listing re-checks at read time whether each staged branch has already reached the base by another route, marks the ones that have, and drops them from the count of presses that move anything. That listing fetches nothing and changes nothing, and a repository the machine cannot reach is simply left unmarked.

Evidence

  • 50 modules · 18 scheduled jobs
    The modules are the fifty files in the repository's scripts directory, counted from the directory itself, which is everything sitting there apart from the two npm manifests: the command-line tools, the schedulers, the monitors, the shell helpers the jobs call, and the seven suites that test them. The eighteen jobs are the scheduled agents inventoried in MIGRATION.md, one table row each, naming the agent alongside the command behind it and the schedule it keeps, captured by reading the host's own launch entries.
  • Policy at the tool boundary
    Two artifacts in the tree, both readable end to end. The allowlists are literal strings in the cycle runner, a case statement that names the narrow jobs one by one and hands every other job a default set. The backstop is a pre-tool-use hook registered in the harness settings file against the shell and file-editing tools, which refuses a call by exiting with a status the harness treats as a block and a reason on standard error, printed to the run log as the rule that was hit.
  • Human approval queue
    The queue is a JSON file that one tool writes and the guard keeps a run from touching directly. Running the listing prints what waits, how long each card has waited, and how many presses actually move the base. The tool's own suite builds throwaway repositories and a temporary queue so the live queue stays untouched, and it printed 82 checks passed when it was last run on 2026-08-25, covering the staging refusals, the cascade rehearsal, the hold elapsing into a merge, and a kill landing before the deadline that stops it.
Every system