Configuration
mexxi works without any configuration. You change it when your setup differs from the defaults: another forge, fewer agents, a stricter guard. Settings live in two files, so that how you work never has to be committed to a repository, and what a repository needs travels with it.
~/.config/mexxi/config.jsonis how you work, on your machine: which agents to install for, which models to use, which commands the guard watches for. Nobody else sees it, and agit pullof mexxi never conflicts with it. Decision 0002 explains why..mexxi.json, committed to a repository, is what that repository needs, whoever runs the task: its verify command, its setup command, its CI check, its forge. It is read from the default branch, so a task branch cannot change the rules it is checked by. Decision 0010 and decision 0014 explain why.
After changing config.json, run node scripts/install.mjs again from your mexxi clone, so the agents pick up the change. A change to .mexxi.json takes effect once it is on the default branch.
Common changes
Each recipe is a few lines of ~/.config/mexxi/config.json, unless it says .mexxi.json. Keys you leave out keep their defaults.
Use GitLab or a self-hosted forge
GitHub and GitLab on github.com and gitlab.com work as they are; sign in with gh or glab. For a forge on your own domain, map its host:
{ "forge": { "hosts": { "git.example.com": "gitlab" } } }A single repository can instead name its forge in its .mexxi.json: { "forge": "gitlab" }.
Install for Claude Code only
Switch off the agents you do not use. The installer removes what it gave them before:
{ "hosts": { "opencode": false, "codex": false } }Get a reviewer from another model family
A reviewer from the builder's own family shares its blind spots. On Claude Code, with Codex installed, name a Codex model and ibni cross-review runs the review on it:
{ "models": { "claude": { "crossFamilyReviewer": "<a Codex model>" } } }On OpenCode, set models.opencode.crossFamilyReviewer to a provider/model from another family. Codex has one family, so its review is always same-family.
Run without ocr
The reviewer can read the whole diff itself instead of having OpenCodeReview pick the files and rules:
{ "review": { "tool": "none" } }Change the screen sizes the browser check uses
Widths in CSS pixels:
{ "workflow": { "viewports": [1280, 834, 375] } }Block your own deploy command
To keep production deploys for a human, add your deploy command to the guard's list as a backup to your own rules. Setting the list replaces the defaults, so keep the ones you still want:
{ "guard": { "deny": ["npm publish", "gh pr merge", "gh release create", "git push --force", "git push -f", "npm run deploy"] } }Use another verify command, or reuse a CI run that already passed
In the repository's .mexxi.json:
{ "verify": "make check", "ciCheck": "verify" }Run several tasks at once on one machine
Two tasks that check their work at the same moment can fight over ports and starve each other of CPU. The defaults already handle most of it, and these keys tune it.
Each workspace gets a block of ports, and ibni hands it to setup and verify as PORT and MEXXI_PORT_0 to MEXXI_PORT_9. A repository whose test servers read their own variables, with a fixed fallback port, can have those variables mapped to the block in config.json, so it needs no change. Key the mapping by the folder name of the repository's checkout, with each variable's offset in the block (offset 0 is PORT):
{
"workflow": {
"ports": { "first": 4300, "last": 4999, "blockSize": 10 },
"portVariables": {
"my-web-app": { "E2E_PORT": 1, "E2E_MOCK_API_PORT": 2, "STORYBOOK_PORT": 3 }
}
}
}Two more settings keep parallel checks from overloading the machine:
verifySlotsis how many setup and verify runs go at once, across every repository. The rest wait in a queue, and the result reports the wait asqueuedMs. Two suits a machine with 8 cores. Raise it if runs rarely wait; lower it if checks start to time out under load.scoperuns each setup and verify in a systemd user scope, wheresystemd-run --userworks (most Linux machines). A lowercpuWeightlets your interactive sessions stay responsive while checks run.memoryMaxmakes the kernel stop a runaway check rather than an agent session. When the run ends, the scope also stops any server or browser the command left behind. Elsewhere, such as on macOS, runs are plain child processes without these limits.
{ "workflow": { "verifySlots": 2, "scope": { "enabled": true, "cpuWeight": 50, "memoryMax": "50%" } } }warmCaches, on by default, copies your checkout's build and test caches into each new worktree, so its first check does not start cold. Set it to false if a tool in your repository misbehaves with a copied cache.
Settings in config.json
Every key is optional; the tables give the defaults. config.example.json spells them all out. To use another file, name it in MEXXI_CONFIG.
paths
mexxi has to know where its own clone is and where to put each task's copy of a repository.
Where mexxi lives and where it creates task worktrees.
| Key | Type | Default | What it sets |
|---|---|---|---|
paths.checkout | string | "~/workspace/mexxi" | The mexxi clone the hosts are linked to. The installer refuses to run from any other clone. |
paths.worktreeRoot | string | "~/.local/share/mexxi/worktrees" | Directory under which ibni creates a folder per repository, holding each task's worktree and its evidence directory. |
hosts
You may use only one or two of the three agents, and mexxi should touch only those.
Which coding-agent hosts the installer links the workflow into. Switching one off removes what an earlier install gave it: links, settings and hooks entries, and Claude Code mods.
| Key | Type | Default | What it sets |
|---|---|---|---|
hosts.opencode | boolean | true | Install for OpenCode (~/.config/opencode). |
hosts.claude | boolean | true | Install for Claude Code (~/.claude). |
hosts.codex | boolean | true | Install for Codex (~/.codex or CODEX_HOME, plus ~/.agents/skills). |
models
Different jobs suit different models, and a reviewer from another model family catches what the builder's family misses.
Model per role and host. null means the role inherits the host's own model.
| Key | Type | Default | What it sets |
|---|---|---|---|
models.opencode.general | string or null | null | OpenCode's built-in general subagent. |
models.opencode.explore | string or null | null | OpenCode's built-in explore subagent. |
models.opencode.uiDesigner | string or null | null | The shared ui-designer agent. |
models.opencode.reviewer | string or null | null | The shared code-reviewer agent. |
models.opencode.crossFamilyReviewer | string or null | null | A reviewer model from a different family to the implementer's, as provider/model, because a reviewer from the builder's own family shares its blind spots. Rendered as the code-reviewer's model in place of reviewer. null falls back to the reviewer's model. |
models.claude.uiDesigner | string or null | null | The shared ui-designer agent. |
models.claude.reviewer | string or null | null | The shared code-reviewer agent. |
models.claude.crossFamilyReviewer | string or null | null | A Codex model that ibni cross-review runs the code-reviewer on through codex exec in a read-only sandbox, so the reviewer is not the implementer's family and does not share its blind spots. null, or Codex not installed, falls back to the reviewer's model and says so in the pull request's Evidence. |
models.codex.uiDesigner | string or null | null | The shared ui-designer agent. |
models.codex.reviewer | string or null | null | The shared code-reviewer agent. |
models.codex.crossFamilyReviewer | string or null | null | Codex has only one family, so this is simply used in place of reviewer as the code-reviewer's model; null falls back to the reviewer's model. |
workflow
Branch names, screen sizes and free ports differ from one team and machine to the next.
Defaults the workflow applies to every repository.
| Key | Type | Default | What it sets |
|---|---|---|---|
workflow.branchPrefix | string | "agent/" | Prefix for task branches; a repository instruction may still ask for another with --prefix. |
workflow.viewports | array | [1440,768,390] | Viewport widths in CSS pixels that rendered QA covers: desktop, tablet and mobile. |
workflow.ports.first | integer | 4300 | Lowest port ibni assigns. Each workspace gets its own block of ports from this range, so two tasks can run their servers and checks at once. |
workflow.ports.last | integer | 4999 | Highest port ibni assigns; at least first. |
workflow.ports.blockSize | integer | 10 | Ports in each workspace's block. ibni passes them to setup and verify as PORT (the first) and MEXXI_PORT_0 to MEXXI_PORT_<blockSize-1>, so one workspace can run a dev server and several test servers at once. |
workflow.warmCaches | boolean | true | Copy build, type-check, lint and test caches (.next/cache, *.tsbuildinfo, .eslintcache, node_modules/.vite/vitest) that the control checkout has into each new worktree after setup, up to 1 GB each, so its first verify run is not a cold one. Each tool checks its cache against the sources, so a stale copy only costs a rebuild. |
workflow.verifySlots | integer | 2 | How many setup and verify runs ibni lets run at once on this machine, across every repository; the rest wait in a queue and report the wait as queuedMs. Two suits a machine with 8 cores: raise it if runs rarely wait, and lower it if checks start to time out under load. |
workflow.scope.enabled | boolean | true | Run setup and verify in a transient systemd user scope when one can start. Stopping the scope afterwards ends any server or browser the command left running. |
workflow.scope.cpuWeight | integer | 50 | CPUWeight of the scope. Below systemd's default of 100, a run gives way to interactive sessions when the CPU is busy, and still uses every idle core. |
workflow.scope.memoryMax | string | "50%" | MemoryMax of the scope: bytes with an optional K, M, G or T suffix, a percentage of physical memory, or infinity. Past it the kernel reclaims the run's cache and then stops the run, not an agent session. |
workflow.portVariables | object | {} | Variables a repository already reads for its ports, mapped to an offset in its workspace's block, keyed by the folder name of the repository's checkout. ibni sets each one as the block's first port plus the offset for setup and verify, so a repository's test servers move to the block without a code change. Offset 0 is PORT. |
review
The reviewer needs a way to choose what to read, and you may not have ocr installed.
How the independent code-reviewer selects what to review.
| Key | Type | Default | What it sets |
|---|---|---|---|
review.tool | string | "ocr" | ocr runs OpenCodeReview's delegation mode (the ocr CLI) to choose the files and rules, and the reviewer judges them; it needs ocr installed. none has the reviewer read the diff itself, read-only, with no extra tool to install. |
browser
The browser check should behave the same on every agent and every run, so the browser tooling is pinned to one version.
The Playwright MCP server every host uses for rendered QA.
| Key | Type | Default | What it sets |
|---|---|---|---|
browser.playwrightMcpVersion | string | "0.0.83" | Exact @playwright/mcp version. |
browser.executablePath | string or null | null | Browser executable Playwright drives, such as /usr/bin/chromium. null uses Playwright's own Chromium for both the MCP server (--browser=chromium) and the rendered-QA sweep; install it once with npx -y @playwright/mcp@<playwrightMcpVersion> install-browser chromium. |
browser.outputDir | string or null | null | Where Playwright may write screenshots and recordings; null means paths.worktreeRoot, so evidence directories are writable. |
guard
A stuck agent occasionally runs something destructive, so a hook backs up the rules by stopping the commands you list.
What the shared guard hook blocks on every host.
| Key | Type | Default | What it sets |
|---|---|---|---|
guard.deny | array | ["npm publish","gh pr merge","gh release create","git push --force","git push -f"] | Command prefixes refused on every host: publishing, merges, releases and force pushes by default. Add your own production deploys, such as firebase deploy, npm run deploy, gcloud run deploy, vercel --prod or netlify deploy --prod; setting the list replaces the default, so keep the default entries you want. The installer also writes each as a Bash(<prefix>:*) permissions.deny rule in Claude Code's settings.json; the guard hook repeats them as patterns, because a prefix rule is sidestepped by cd x && <command>. |
guard.protectedBranches | array | [] | Branches the guard refuses to push to, in addition to each repository's default branch. |
guard.blockUnquotedGlobs | boolean | false | Refuse unquoted --include=* and --exclude=* globs, which zsh expands (and aborts on when nothing matches) unless setopt no_nomatch reaches the agent's shell. Leave off once a fresh Claude Code session shows grep -rn x --include=*.ts . searching; turn on if it fails with 'no matches found'. |
guard.repeatedFailureLimit | integer | 3 | How many times the same shell command may fail in one agent session, with no file edited in between, before the loop breaker tells the agent to stop retrying and re-diagnose. |
claude
Claude Code only
Claude Code has settings the other agents lack, such as its limit on how long a shell command may run.
Claude Code-only settings: the ones the installer merges into Claude Code's own settings.json (alongside the deny rules and hooks it owns; everything else in that file stays yours), and the ones the mods read.
| Key | Type | Default | What it sets |
|---|---|---|---|
claude.bashMaxTimeoutMs | integer | 900000 | Longest a foreground Bash command may run (BASH_MAX_TIMEOUT_MS), in milliseconds. Set it above your slowest repository's verify run; the default is 15 minutes. |
claude.contextNudgePercent | integer | 70 | Context window fill, in percent, at which the run pane's band suggests a fresh session and ibni adopt for follow-ups. |
forge
mexxi has to know whether a repository lives on GitHub or GitLab to open its pull requests and read its CI.
Which code forge a repository's pull requests, CI state, issues and media URLs come from: GitHub through gh, GitLab through glab. github.com and gitlab.com are known; a repository can name its own with "forge" in its committed .mexxi.json, which wins over this group. A remote on any other host has no forge, and the steps that need one fail or skip with the reason.
| Key | Type | Default | What it sets |
|---|---|---|---|
forge.hosts | object | {} | Self-hosted forges by the host in the push remote's URL (with its port for an http(s) remote), as github or gitlab, such as {"git.example.com": "gitlab"}. An SSH host alias (git@github-work:owner/repo) needs no entry: ibni resolves it through ssh config, as gh does, and ssh.github.com counts as github.com. |
jev
Experimental, and off by default
Jev is an outside decision model, reached through OpenRouter. This tests whether it would choose a task's checks better than the agent, while only recording its answers.
Jev triage through OpenRouter; the API key is read from OPENROUTER_API_KEY, never from this file.
| Key | Type | Default | What it sets |
|---|---|---|---|
jev.enabled | boolean | false | Run triage at all. |
jev.stage | string | "shadow" | What triage may do with its answers. Only shadow exists: it records answers beside the agent's own gate choices and changes nothing. Escalate (add a gate on a confident answer) and skip (drop the independent review on a confident mechanical answer) come only once shadow data shows agreement. |
jev.model | string | "typesafe/jev-1.14" | Pinned Jev model; not the ~typesafe/jev-latest alias, so a release cannot shift answers mid-calibration. |
jev.endpoint | string | "https://openrouter.ai/api/alpha/decisions" | OpenRouter's Decisions API, which takes the question schema triage sends. It receives the API key, so it must be HTTPS; plain HTTP is allowed only on loopback (localhost, 127.0.0.1, [::1]), where a local stand-in never sends the key over a network. |
jev.owners | array | [] | GitHub accounts and organisations that are yours. A listed repository owned by anyone else is skipped, since its content is not only yours to share. Jev runs only on github.com: a repository on GitLab or a self-hosted forge is skipped. |
jev.repositories | array | [] | Repositories (owner/name, as in the push remote's github.com URL) triage may run in. |
jev.maxPayloadBytes | integer | 32768 | Each request stays below this many bytes; larger text puts Jev in a slower, less accurate long-context mode. |
jev.timeoutMs | integer | 5000 | How long one request may take before triage gives up and the run carries on. |
jev.zeroDataRetention | boolean | true | Ask OpenRouter to route only to providers that retain no data (provider.zdr). If none serves the model, calls fail and are recorded with the reason. |
segwi
Experimental, and off by default
A run takes minutes to an hour and stops whenever it needs a reply or a permission, so without segwi you have to be at the terminal to keep it moving.
segwi, a local web app and MCP server for starting and following mexxi runs from your phone.
| Key | Type | Default | What it sets |
|---|---|---|---|
segwi.enabled | boolean | false | Install and run segwi at all. |
segwi.bind.host | string | "127.0.0.1" | Interface to listen on. Loopback only: the server refuses any other host, so reach it from other devices through tailscale serve. |
segwi.bind.port | integer | 4280 | Port to listen on. |
segwi.runner | string | "claude-code" | Which coding agent segwi starts. Only Claude Code exists so far. |
segwi.model | string | "" | Model every run uses, such as sonnet. Empty uses your coding agent's own default, which may cost more than a run needs. |
segwi.repositories | array | [] | The only repositories a run may start in, each with a name, the path of its control checkout and the actions allowed there (build, ready or ask). |
segwi.extensions | array | [] | Local folders whose page and API segwi serves at /ext/<folder name>/, behind the same sign-in, and links from its menu. Each holds a server.mjs and a public folder; docs/segwi.md describes the contract. An extension runs inside segwi with your permissions, so list only code you trust. |
segwi.auth.web.mode | string | "localhost-only" | localhost-only trusts anyone on this machine. tailscale-header trusts the identity header that tailscale serve sets, and only on loopback. |
segwi.auth.web.header | string | "Tailscale-User-Login" | Request header that carries the signed-in identity in tailscale-header mode. |
segwi.auth.web.allowed | array | [] | Identities allowed in tailscale-header mode. Empty means nobody. |
segwi.auth.web.hostname | string | "" | This machine's name on your tailnet, such as my-machine.example.ts.net, as tailscale serve status shows it. In tailscale-header mode segwi answers only to it and to loopback names, so a web page on another site can't reach segwi by pointing its own name at this machine (DNS rebinding). Required in that mode. |
segwi.limits.concurrentRuns | integer | 2 | Runs that may work at the same time. |
segwi.limits.runsPerDay | integer | 20 | Runs that may start in one day. |
segwi.limits.maxTurns | integer | 80 | Agent turns one run may take before it stops. |
segwi.limits.idleTimeoutMinutes | integer | 30 | Minutes a run may sit without activity or a reply before segwi stops it. |
segwi.limits.hardTimeoutMinutes | integer | 120 | Minutes a run may last in total. |
segwi.limits.approvalTimeoutMinutes | integer | 60 | Minutes an approval waits for you before it counts as denied. |
segwi.limits.taskMaxChars | integer | 8000 | Longest task text a run accepts. |
segwi.limits.inputMaxChars | integer | 4000 | Longest reply a run accepts. |
segwi.canary.model | string | "haiku" | Model the canary run uses. A small one keeps the nightly cost to a few cents. |
segwi.canary.maxTurns | integer | 6 | Agent turns the canary run may take before it stops. |
segwi.push | boolean | true | Send a push notification when a run needs you, finishes or fails. |
segwi.vapidSubject | string | "" | Contact address sent with every push, as a mailto: address or an https:// URL you own. Empty leaves push off. |
segwi.service.systemdUserUnit | boolean | false | Let the installer add systemd user units for the server and its nightly check. Linux only. |
retro
Session transcripts are private, so retrospectives need a home that no public repository ever commits.
Where session retrospectives go and how long transcripts are kept for them.
| Key | Type | Default | What it sets |
|---|---|---|---|
retro.output | string | "retro/" | Where /irrifletti writes its reports. A relative path is inside the clone, where retro/ is gitignored; the command refuses a folder in a Git repository unless it is gitignored there with nothing tracked, so reports never reach a public repository. |
retro.transcriptRetentionDays | integer | 90 | Days of host transcripts a retro reads. |
mods
Claude Code only
Claude Code can load two optional extras: the run pane, which shows where a run stands beside the chat, and the command fixer, which repairs shell commands that would otherwise fail.
Which Claude Code mods the installer installs.
| Key | Type | Default | What it sets |
|---|---|---|---|
mods | array | ["run-pane","command-fixer"] | Which Claude Code mods the installer installs. |
Settings in .mexxi.json
Some settings belong to a repository rather than to whoever runs the task, so they are committed with it.
Optional settings a repository commits in .mexxi.json at its root. mexxi reads them from the default branch, so a task branch cannot change the rules it is checked by.
| Key | Type | Default | What it sets |
|---|---|---|---|
verify | string | "npm run verify" | The command that says whether the repository works, with a pass or a fail. A repository with one reliable command already, such as make check, names it here instead of adding an npm script. |
setup | string or null | null | The command that installs the repository's dependencies in a new worktree. Leave it out to use the root lockfile's own install: npm ci for package-lock.json, pnpm install --frozen-lockfile for pnpm-lock.yaml. Name it for Yarn, several lockfiles or a bootstrap script; with neither, nothing is installed. |
ciCheck | string or null | null | The CI job that runs the full verify command, such as verify on GitHub Actions. When it succeeded on exactly the commit a task starts from, the task cites it instead of running verify again. |
forge | string or null | null | github or gitlab, for a repository whose remote mexxi cannot place, such as one on a self-hosted forge. It wins over forge.hosts in config.json. |
What the installer links
The installer links one set of files into each host's home, so a change to the workflow reaches every agent. It records what it links and touches nothing else; decision 0003 explains why. Destinations are under your home directory. <checkout> is paths.checkout, and <generated> is ~/.local/share/mexxi/generated, where the installer renders each host's files from your config.json. implementation-workspace is ibni's old name, kept for one release.
| Host | Destination | Source |
|---|---|---|
| opencode | ~/.config/opencode/opencode.json | <generated>/opencode/opencode.json |
| opencode | ~/.config/opencode/AGENTS.md | <checkout>/AGENTS.md |
| opencode | ~/.config/opencode/commands | <checkout>/commands |
| opencode | ~/.config/opencode/skills | <checkout>/skills |
| opencode | ~/.config/opencode/agent | <checkout>/agent |
| claude | ~/.claude/CLAUDE.md | <generated>/claude/CLAUDE.md |
| claude | ~/.claude/commands | <checkout>/commands |
| claude | ~/.claude/skills/<name> (14 links) | <checkout>/skills/<name> |
| claude | ~/.claude/skills/verifika | <checkout>/node_modules/verifika/skills/verifika |
| claude | ~/.claude/agents/<agent>.md (2 links) | <generated>/claude/agents/<agent>.md |
| codex | ~/.codex/AGENTS.md | <checkout>/AGENTS.md |
| codex | ~/.agents/skills/<name> (14 links) | <checkout>/skills/<name> |
| codex | ~/.agents/skills/verifika | <checkout>/node_modules/verifika/skills/verifika |
| codex | ~/.codex/agents/<agent>.toml (2 links) | <generated>/codex/agents/<agent>.toml |
| shared | ~/.local/bin/ibni | <checkout>/scripts/ibni/ibni |
| shared | ~/.local/bin/implementation-workspace | <checkout>/scripts/implementation-workspace |