Drift
Policy fades repo by repo
Branch protection missing here, secret scanning off there, a public repo with no licence. Each gap is small. Together they are the org’s real posture, and nobody chose it.
A steward bot for a GitHub organisation
Ion watches every repository in your organisation. It spots drift from your policy, finds repos that belong together or apart, and writes up each change as an exact plan: the evidence, the commands it would run, the way back, and how much of the org it would touch. Then it stops. A maintainer approves that exact plan, or nothing happens. Only then does ion write, through capped, logged requests, with a circuit breaker that halts any run touching too much at once. Archiving a repo always needs its own yes. Ion never deletes, never force-pushes, and never merges its own pull requests.
Each repository gets care. The organisation around them does not, because no person has the whole map in their head.
Drift
Branch protection missing here, secret scanning off there, a public repo with no licence. Each gap is small. Together they are the org’s real posture, and nobody chose it.
Duplicates
Code copied between repos, commits landing in both on the same days, readmes pointing at each other. The split made sense once. The evidence that it stopped making sense sits unread in the history.
Stale weight
Experiments and one-off tools stay active for years because archiving feels risky and nobody owns the decision. New people cannot tell what is alive.
Fear of the fix
Merging two repos without losing history, splitting one safely, proving nothing was dropped: doable, fiddly, and easy to get subtly wrong. So it does not happen.
Ion’s answer is a division of labour. The machine does the watching, the measuring and the careful writing. People keep the deciding. The next panel is that bargain as a working model.
Every ion run walks the same six stages. The first three only read and write words. The fourth belongs to a person. The fifth is the only stage that can change anything, and it re-checks the fourth before it moves. Run the model, then try to break the rules: execute without approving, approve and then change the plan, or widen the blast radius past a quarter of the org.
A teaching model with invented repos and a short stand-in fingerprint. The rules are ion’s real ones: approval binds to the plan hash (SPEC §2.4), the breaker guards are the shipped defaults (guide), and the executor re-derives approval from the record instead of trusting a stored flag.
Collect
Metadata comes from one batched GraphQL query under a points budget. File contents come from bare git mirrors on the machine, never from repeated API calls.
Analyze
Health, policy drift, coupling, cohesion, lifecycle and tag boundaries. All deterministic. A language model may later help word a proposal. It cannot invent one, add a repo, or raise a score.
Report
An “Org health” issue lists what waits, what is queued, what is held and what just finished. Ion edits it in place and never comments. A short digest goes to your notifiers, only when something changed.
Each proposal is one issue in a private tracker repo. Its body holds the evidence, the ordered steps, the exact commands, the rollback, the blast radius, and a fingerprint of the plan: plan-sha256: …, a hash that changes completely if one command changes.
A maintainer can answer in three ways. All three are checked the same way: who acted, whether they maintain every repo involved, and which fingerprint was on display when they acted.
Channel 1
Add ion:approved, ion:rejected or ion:hold. Ion reads the issue timeline to find who applied it. A decision label from a non-maintainer is recorded as ignored and taken off again, with no comment.
Channel 2
Send ion approve P-0007 over the Divan Bus from an approved seat. The signature is verified, then ion mirrors the decision onto the issue as the label, so the public record stays on GitHub.
Channel 3
Tick the approve or hold box on the Org health issue. Ion checks the edit history: every edit since its own last write must be by a maintainer, or the tick fails closed and counts for nothing.
Change the plan and the fingerprint changes. The old yes stops counting, the label comes off, and ion asks again.
This reviewer-shaped gate is the heart of ion, and it is why the project sits with the amp team: the work is reviewing. The machine prepares a case worth deciding. A reviewer decides it. See why the name fits.
Even a fully approved run passes through four more checks before and during writing. Each one can stop it without touching anything.
Typed writes
One file, ion/writes.py, is allowed to call a mutating GitHub API. A test enforces that. Each request has a known type, a checked shape, and a per-run cap. Over the cap, work waits for the next run instead of failing. Pushes may only target ion/* branches, never a default branch.
5 PRs · 1 archive
Default caps per run for the two loudest write types. Yours to set.
Circuit breaker
Before writing, the run totals its own blast radius. During the run, every write is checked again. Trip any guard and the run aborts, says so in the digest, and records the trip where ion status shows it.
25% · 2 · 150
Defaults: share of repos touched, archives per run, total writes. Any branch-protection weakening also trips it.
Exact verification
A merge is accepted only when the imported tree matches the source tree exactly and the source tip is an ancestor of the result. A split must match the rewrite tool’s commit map, commit for commit, plus tree equality. Every moved commit carries an Ion-Origin: org/repo@sha trailer saying where it came from. Counting commits is not a check, so ion never does it.
Ledger and kill switch
ion pause stops every executor and tracker write. Reads still work. Each step writes a before and after row, so a crashed or interrupted run resumes without repeating finished steps. ion explain P-0007 shows the whole history of one proposal.
Underneath all of it: nothing irreversible. No deleted repos, branches or history. No force push, in any spelling, ever. Archive instead of delete. A maintainer merges ion’s pull requests. Ion never merges its own.
| Guard | Default | What happens when it trips |
|---|---|---|
| Share of org touched | over 25% | Run aborts before writing. Digest and status record it. |
| Archives in one run | over 2 | Run aborts. Archiving still needs its own approval each time. |
| Total writes in one run | over 150 | Run aborts. Per-type caps usually defer work long before this. |
| Weaker branch protection | any | Run aborts. Ion may strengthen protection. It may not lower it. |
| Kill switch | ion pause | All executors and tracker writes stop. Reads continue. |
Defaults from the shipped guard config. Every value is yours to change in ion.yml, and ion plan-config previews what a change would do before you adopt it.
A candidate must persist across repeated snapshots before it becomes a proposal, so one odd day never files an issue. Structural kinds carry ordered steps: a later step refuses to run until the pull request before it has landed.
| Kind | What it means | Typical evidence |
|---|---|---|
merge | Fold repo A into repo B, full history kept, then a pointer PR in A | Coupling score and its five signals |
split | Move a directory out into a new repo, history kept, removal PR after proof | Separable clusters inside one repo |
move_code | Move code between existing repos, with the same two exact checks | Imports and co-change across the pair |
archive | Put a finished repo away. Always its own approval | Staged lifecycle: active, stale, archive-proposed |
rename · new_repo | Name and homekeeping for splits and merges | The parent proposal that needs them |
policy_fix | Bring a repo back to policy: protection, alerts, scanning, required files | Drift from ion.yml, with an action level per class |
health_fix | Missing readme, licence, CI or description | Per-repo health checks |
autofix | One small change across many repos, as a single proposal of exact diffs | ion autofix preview diffs over the mirrors |
Five signals per repo pair, combined so that one saturated signal alone just reaches the merge threshold of 0.6. Weak signals need company. The name-prefix signal is deliberately tiny: repos called protean-* share a naming scheme, not a life, and ion’s first live run proved the point with 265 pairs that were mostly that.
| Signal | Weight | Hygiene |
|---|---|---|
| Cross-references | 0.6 | Manifests, imports, git URLs, readme links |
| Duplicated code | 0.6 | Identical files plus near-duplicates. Vendored, lock and generated files ignored |
| Co-change | 0.6 | Shared commit days over average commit days. Huge commits, bots and ion itself excluded. Half-weighted unless a content signal agrees |
| Shared dependencies | 0.3 | Overlap of the two dependency sets |
| Shared name prefix | 0.05 | Counts only when another signal is already at least 0.15 |
Tag rules add boundaries: mark repos with topics such as scope:app, say which scopes may depend on which, and cross-scope merges are blocked or flagged before they are ever proposed. Two fleet tools ride on the same mirrors: ion grep searches every repo with no API calls, and ion autofix previews a change everywhere, then files one proposal for exactly those diffs.
--applyRun the checks yourself: uv run pytest -q and uv run python scripts/check_licenses.py. The licence guard fails the build if any dependency falls outside MIT, BSD, Apache-2.0, ISC, PSF or MPL-2.0. No GPL dependency, ever.
Michael Faraday coined the word in 1834, from the Greek ἰόν, “going”, for charged particles that move through a solution toward an electrode. Four threads tie the name to the work:
Going
Ion travels the whole org on each run and carries a signal from where the evidence sits to where the decision happens.
Lattices
In a crystal, ions sit on the points of a lattice and the pull between them holds it together. Ion watches how repos relate and keeps that structure sound. The lattice is also home ground: it runs in the same family as the incentre lattice and Exo.
Charge
Charge does work only along a path. Ion’s paths are the three approval channels. Off those paths, no current flows and nothing changes.
The amp team
Current is measured in amperes, and ion is owned and reviewed by the amp team, whose craft is reviewing. That fits the design: ion’s centre of gravity is a review gate. In the live Aska Digital deployment, proposals are prepared for a reviewer’s yes or no, and the reviewer’s word is what turns a plan into current.
Ion’s mechanisms come from other projects’ documented behaviour, re-implemented in its own words. Nothing is vendored. The typed-write broker follows GitHub Agentic Workflows’ safe outputs and actions/stale’s per-run caps. The breaker follows Peribolos. Plan-bound approval follows policy-bot. The dashboard follows Renovate’s layout. Coupling follows Adam Tornhill’s change-coupling method. The full credit table names all twenty-five, each with its licence, checked on 2026-10-13.
Security problems go through private reporting, described in SECURITY.md. Be kind, per the code of conduct.