Skip to content

Orgabot is in private alpha. Request access, or join our Discord.

← All documentation
Reference

Invariants

The load-bearing safety and correctness properties. Each one was written after something broke silently, and none of them may be weakened by a change.

These are the properties a change must never weaken. Most exist because something failed silently: HTTP 200, nothing in the logs, nothing visibly broken, and the wrong outcome shipped.

Reviewer is never the producer

The independent reviewer must never be the same agent that produced the change.

Evidence over confidence

A verification gate passes only with command exit-code evidence. A diff touching at least one file (including a binary add, delete, or rename) must never be treated as "no changes produced": the no-changes heuristic counts changed files, not inserted or deleted lines.

A related trap: a shell operator inside a verify command can make the gate measure nothing at all. cd x && npm test once ran /usr/bin/cd, exited 0, and the gate reported PASS having tested nothing. Two PRs merged on fabricated evidence before it was caught.

No authority from natural language

Workers act only through brokered, policy-checked tools. The mission brief labels every piece of context by trust level. Editing a branch is auto-approved; opening a PR, merging, or deploying require policy or explicit approval.

Draft PRs are intentional

Pull requests open as drafts. Auto-merge paths promote a draft to ready in the shared merge path before merging. Do not stop opening PRs as drafts.

Pending records are reaped from observed facts

pending.json is built-but-undelivered work. A record is deleted only when its PR is observed merged (by any route, not just Orgabot's own ship path), or when its branch exists neither locally nor on the remote. "Actionable" is always derived from those facts, never from the record's own say-so.

  • Deletion needs a first-hand, repo-scoped fact. PR status is keyed by repo root, never by bare branch name across repos.
  • The dashboard pass reaps only pr_merged and invalid_branch. branch_gone is left to orgabot pending reap, which reads git live, because the dashboard's branch lists are a TTL cache and a cached negative is not an observation.
  • A record may never name the base or default branch. That is refused at the write, and again at the storage boundary, because orgabot ship would then target main.
  • A record's identity is the repo-scoped (project, branch) pair. Branch names are unique per repository, never globally, so the scoping lives in the lookup rather than in a caller's filter.
  • Writes are atomic and lock-guarded, so the dashboard reaper cannot clobber a concurrent write: temp plus rename while pending.json is authoritative, one SQLite transaction once the domain has switched.
  • Uncertainty keeps a record. An unreadable source is not evidence of absence, and a branch list read before a record was written is not evidence about that record. A write refuses rather than rewriting the store from a list it could not read, and an empty query result is an answer about what was asked for, never "the record is gone".

Salvage-commit hygiene

On worker death or timeout, the salvage path commits work in progress but must exclude untracked build-artifact directories (.build/, node_modules/, dist/, target/, DerivedData/) and untracked files over 5 MB. Already-tracked files are still committed. Normal, non-salvage commit paths are unchanged.

Session-limit death is a failure, not a success

A Claude Code session or usage-limit message in the stream maps to a distinct worker failure. It must never become a silent "completed / no changes produced".

Durability is proven, not asserted

Restate resume tests assert that journaled steps do not re-execute (the worker is opened once and its cost is not re-charged), and that a mid-flight worker resume restarts at the exact next event index.

Mission state needs one authority

New lifecycle work must make legacy mission records, organization mission records, objective nodes, inbox items, and dashboard cards projections or compatibility references, never competing sources of truth.

Interrupts are acknowledged transitions

Escape or another UI action requests interruption. The UI must not report a mission interrupted until the runtime acknowledges it. When escalation is required, the worktree, session, branch, and recovery evidence are preserved.

Autonomy uses the org chart

Agents consult and route through roles and capabilities before escalating. No agent may self-grant authority. Humans enter only where policy, risk, spending, reserved authority, or configuration requires them.

An org-owned mission is owned by a role

The generic unattributed worker is sanctioned only for a project no organization maps, or when the operator passes --no-org. When an org maps the project and no role can be auto-selected, the mission holds and prompts for a role. It must never silently fall back to the generic worker.

Local macOS MVP first

Local macOS remains the zero-cloud default and critical path. Later accepted decisions add cross-platform secret custody, hybrid providers, isolated hosted tenant control planes, and the published shared GitHub App without weakening that profile. Providers still marked planned in the generated deployment matrix, plus mobile and cross-device interaction, remain outside the delivered boundary.

The PR body is the only closing path

No Orgabot-composed pull-request title or merge-commit message may carry a GitHub closing keyword. A keyword in a title links nothing while the PR is open, but GitHub reuses the title as the default-branch commit message on merge, and there it closes the referenced issue and bypasses the deliberate Closes / Refs decision made in the body. Titles say Fix issue 1234, never Fix #1234; the body's single footer is the one intentional closing keyword.

That decision itself is never a bare "the diff passed its gates": Closes #N requires the issue's own acceptance criteria to be MET by what the mission delivered, an independently confirmed tip (verified and reviewed), and no structural block (an epic/design/P0 label or title, an open child, or a PR summary that reads as a partial delivery). Anything less writes Refs #N and leaves a comment on the issue naming what was and was not delivered. See Shipping and review for the full rule. One acceptance evaluation decides both this footer and whether the mission itself may terminate completed, so the two can never disagree.

A workflow may not drop a gate

Missions execute a workflow definition resolved organization, then project, then mission, and policy is checked at resolution, fail-closed: a workflow omitting a policy-required gate is refused before a run exists, an unreadable policy blocks rather than reading as "no policy", and a policy-locked gate cannot be disabled by configuration at any level. Evidence-over-confidence, reviewer-is-never-the-producer, and draft-PRs-are-intentional are gate properties now, so weakening one weakens every gate. A refused gate decision is recorded with its reason, never dropped: a pass the rules did not admit must not read like one they did.

The operational store holds no authority

The local SQLite operational store serves listings and change notifications, and it is the only store for the peripheral domains (approvals, inputs, pending records, organization state, the observation stores, acknowledgments) and for mission records. The lifecycle journal stays mission authority and the event spine stays event authority. Those domains were moved into the store behind proven equivalence (ADR 0063) and the dual-write phase is complete: there is no JSON copy, leftover JSON is unread, and no path falls through to it, including error paths. An unavailable store refuses.

Connectors that observe never act

The Firebase connector polls App Hosting rollouts read-only so a failed rollout becomes a dashboard incident; it never deploys, rolls back, or mutates a backend. A failed or partial poll keeps the previous observation and reports the error, because replacing the list with an empty one is a false all-clear, the exact failure the connector exists to prevent.

Recovery acts only on live, first-hand evidence

The recovery queue may resume a stranded Orgabot PR, but every decision re-reads the PR's live state first: a record is a cached belief, and an unreadable state holds. A resumption re-enters the ordinary mission path under the original mission's org and role, so every verify, review, and merge gate still applies. A fork's head branch is not proof of Orgabot's authorship, a cancelled owner is an operator decision no sweep overrides, and nothing reachable from an unauthenticated read endpoint may spawn a worker or spend a credential.

Dashboard actions repaint before the network

A state-changing handler applies its change locally, repaints, sends the request, and rolls back on refusal. await fetch(...) followed by a poll with nothing painted in between is the shape that made the dashboard feel laggy.

GitHub is polled at a bounded rate

Never once per UI poll. A per-batch cap is not a rate limit: a caller that polls every few seconds starts a fresh batch as soon as the previous one finishes, so a large backlog drains at poll speed. That once exhausted a 5,000/hour GraphQL quota in about 14 minutes with no missions running.