Three layers, one shape
xCoder has three independent layers. Each is mechanically enforced — no LLM in the path of decision-making.
| Layer | When it runs | What it prevents |
|---|---|---|
| Hooks | PreToolUse / Stop (kernel level, every tool call) | Edits on integration branch, commits without typecheck, commits without issue ref, sessions ending without a PR. |
| FlowEngine | On each phase exit (checked by xCoder TS code) | Leaving BRANCH without a tracking issue, leaving SPEC without a spec file, leaving COMMIT without a conventional message, leaving PR without a PR number. |
| Merge gate | Before PR merge (verdict-driven) | Removals without stated intent, kernel-zone changes without human review, security regressions, broken preview smokes. |
Phases
The FlowEngine drives a task through 12 named phases. Most of them have engine-side acceptance checks (preconditions to leave). Two of them — implement and test — have no engine check; the iteration loop between them is the agent's responsibility.
idle → scope → issue → branch → spec → implement → test → commit → qa → pr → review → merge → idle
↑ ↓
└──── iter ──────┘| Phase | Acceptance check | Strength |
|---|---|---|
| idle | (entry/exit; no check) | n/a |
| scope | (no engine check) | n/a |
| issue | (no engine check) | n/a |
| branch | I-3 trackingIssueExists, I-4 branchMatchesPrefix | strong |
| spec | I-5 specFileExistsIfRequired | medium |
| implement | (no engine check; iter loop) | n/a |
| test | (no engine check; iter loop) | n/a |
| commit | I-7 conventionalCommitFormat | strong |
| qa | (no engine check) | n/a |
| pr | I-9 prOpenedBeforeIdle | strong |
| review | (no engine check) | n/a |
| merge | (no engine check; merge-gate runs externally) | n/a |
Invariants — the contract
The 15 invariants are xCoder's flow-adherence contract. Each invariant has a mechanism (where it's enforced), a strength (how unbypassable it is), and a logging shape (what event it emits).
Strong vs. medium vs. weak
Strong: mechanically blocked unless an explicit, named, logged bypass is in effect. Medium: default-on, with an obvious config opt-out (e.g. workflow.specsRequired: false). Weak: advisory — emitted as a warning event, not a block.
See the invariants reference for the full table.
Bypass discipline
Every overridable check has exactly one or two named bypass paths. Each bypass:
- Names the invariant being overridden (e.g.
I-3). - Scopes to a single phase or, for env-var bypasses, a single shell invocation.
- Logs a
flow.bypassevent containing the reason, the source (env / config / command / flag), and the timestamp.
That last property is what makes the system honest. You can't quietly skip a gate — you can only loudly skip it.
The four bypass sources
| Source | Form | Use case |
|---|---|---|
| env | XCODER_ALLOW_INTEGRATION_EDIT=1 git ... | Hotfix on main; one shell invocation only. |
| config | workflow.commitDirectly: true | Repo opts in to direct-commit (e.g. solo dev, throwaway repo). |
| command | xc flow override I-7 --reason "..." | Recorded ad-hoc bypass for the active task. |
| flag | git commit --no-verify | Standard git escape hatch; logged via post-commit event. |
Asymmetry — autopilot vs. interactive
xCoder treats the two modes differently because their guarantees differ:
| Mode | Who decides | Strongest guarantee available |
|---|---|---|
| Autopilot | xCoder TS code (deterministic) | Strong — engine acceptance + xCoder evaluates every transition itself. |
| Interactive | LLM (the coding agent) | Strong-mechanical for hooks (kernel-level block); medium for engine acceptance (engine observes; agent moves). |
What the LLM can do
A motivated LLM can set XCODER_ALLOW_INTEGRATION_EDIT=1 if you let it run shell commands. xCoder doesn't try to outsmart the LLM — that's a losing game. What it does:
- Make the bypass visible (named env var, logged event).
- Make compliance the path of least resistance.
- Make non-compliance auditable — you can read the bypass log and see exactly when discipline was abandoned.
Why not just prompt the agent harder?
Prompts decay. The agent's context window churns through tens of thousands of tokens; "always create a feature branch first" is a whisper that gets drowned out by the immediate task. Prompts also fail silently — the agent thinks it followed the rule but didn't.
Mechanism doesn't decay. The no-edit-on-integration policy fires every single time git commit is invoked. The FlowEngine evaluates acceptance every time a phase transition is requested. There's no "I forgot."
Pairing prompts and mechanism
xCoder still uses prompts (skills, the boot context, the flow-discipline framing). They're great for positive direction — telling the agent what to do. Mechanism handles negative direction — refusing what shouldn't happen. The two layers don't compete; they compose.