Skip to content

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.json is 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 a git pull of 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:

json
{ "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:

json
{ "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:

json
{ "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:

json
{ "review": { "tool": "none" } }

Change the screen sizes the browser check uses ​

Widths in CSS pixels:

json
{ "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:

json
{ "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:

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):

json
{
  "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:

  • verifySlots is how many setup and verify runs go at once, across every repository. The rest wait in a queue, and the result reports the wait as queuedMs. Two suits a machine with 8 cores. Raise it if runs rarely wait; lower it if checks start to time out under load.
  • scope runs each setup and verify in a systemd user scope, where systemd-run --user works (most Linux machines). A lower cpuWeight lets your interactive sessions stay responsive while checks run. memoryMax makes 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.
json
{ "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.

KeyTypeDefaultWhat it sets
paths.checkoutstring"~/workspace/mexxi"The mexxi clone the hosts are linked to. The installer refuses to run from any other clone.
paths.worktreeRootstring"~/.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.

KeyTypeDefaultWhat it sets
hosts.opencodebooleantrueInstall for OpenCode (~/.config/opencode).
hosts.claudebooleantrueInstall for Claude Code (~/.claude).
hosts.codexbooleantrueInstall 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.

KeyTypeDefaultWhat it sets
models.opencode.generalstring or nullnullOpenCode's built-in general subagent.
models.opencode.explorestring or nullnullOpenCode's built-in explore subagent.
models.opencode.uiDesignerstring or nullnullThe shared ui-designer agent.
models.opencode.reviewerstring or nullnullThe shared code-reviewer agent.
models.opencode.crossFamilyReviewerstring or nullnullA 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.uiDesignerstring or nullnullThe shared ui-designer agent.
models.claude.reviewerstring or nullnullThe shared code-reviewer agent.
models.claude.crossFamilyReviewerstring or nullnullA 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.uiDesignerstring or nullnullThe shared ui-designer agent.
models.codex.reviewerstring or nullnullThe shared code-reviewer agent.
models.codex.crossFamilyReviewerstring or nullnullCodex 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.

KeyTypeDefaultWhat it sets
workflow.branchPrefixstring"agent/"Prefix for task branches; a repository instruction may still ask for another with --prefix.
workflow.viewportsarray[1440,768,390]Viewport widths in CSS pixels that rendered QA covers: desktop, tablet and mobile.
workflow.ports.firstinteger4300Lowest 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.lastinteger4999Highest port ibni assigns; at least first.
workflow.ports.blockSizeinteger10Ports 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.warmCachesbooleantrueCopy 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.verifySlotsinteger2How 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.enabledbooleantrueRun 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.cpuWeightinteger50CPUWeight 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.memoryMaxstring"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.portVariablesobject{}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.

KeyTypeDefaultWhat it sets
review.toolstring"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.

KeyTypeDefaultWhat it sets
browser.playwrightMcpVersionstring"0.0.83"Exact @playwright/mcp version.
browser.executablePathstring or nullnullBrowser 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.outputDirstring or nullnullWhere 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.

KeyTypeDefaultWhat it sets
guard.denyarray["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.protectedBranchesarray[]Branches the guard refuses to push to, in addition to each repository's default branch.
guard.blockUnquotedGlobsbooleanfalseRefuse 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.repeatedFailureLimitinteger3How 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.

KeyTypeDefaultWhat it sets
claude.bashMaxTimeoutMsinteger900000Longest 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.contextNudgePercentinteger70Context 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.

KeyTypeDefaultWhat it sets
forge.hostsobject{}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.

KeyTypeDefaultWhat it sets
jev.enabledbooleanfalseRun triage at all.
jev.stagestring"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.modelstring"typesafe/jev-1.14"Pinned Jev model; not the ~typesafe/jev-latest alias, so a release cannot shift answers mid-calibration.
jev.endpointstring"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.ownersarray[]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.repositoriesarray[]Repositories (owner/name, as in the push remote's github.com URL) triage may run in.
jev.maxPayloadBytesinteger32768Each request stays below this many bytes; larger text puts Jev in a slower, less accurate long-context mode.
jev.timeoutMsinteger5000How long one request may take before triage gives up and the run carries on.
jev.zeroDataRetentionbooleantrueAsk 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.

KeyTypeDefaultWhat it sets
segwi.enabledbooleanfalseInstall and run segwi at all.
segwi.bind.hoststring"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.portinteger4280Port to listen on.
segwi.runnerstring"claude-code"Which coding agent segwi starts. Only Claude Code exists so far.
segwi.modelstring""Model every run uses, such as sonnet. Empty uses your coding agent's own default, which may cost more than a run needs.
segwi.repositoriesarray[]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.extensionsarray[]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.modestring"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.headerstring"Tailscale-User-Login"Request header that carries the signed-in identity in tailscale-header mode.
segwi.auth.web.allowedarray[]Identities allowed in tailscale-header mode. Empty means nobody.
segwi.auth.web.hostnamestring""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.concurrentRunsinteger2Runs that may work at the same time.
segwi.limits.runsPerDayinteger20Runs that may start in one day.
segwi.limits.maxTurnsinteger80Agent turns one run may take before it stops.
segwi.limits.idleTimeoutMinutesinteger30Minutes a run may sit without activity or a reply before segwi stops it.
segwi.limits.hardTimeoutMinutesinteger120Minutes a run may last in total.
segwi.limits.approvalTimeoutMinutesinteger60Minutes an approval waits for you before it counts as denied.
segwi.limits.taskMaxCharsinteger8000Longest task text a run accepts.
segwi.limits.inputMaxCharsinteger4000Longest reply a run accepts.
segwi.canary.modelstring"haiku"Model the canary run uses. A small one keeps the nightly cost to a few cents.
segwi.canary.maxTurnsinteger6Agent turns the canary run may take before it stops.
segwi.pushbooleantrueSend a push notification when a run needs you, finishes or fails.
segwi.vapidSubjectstring""Contact address sent with every push, as a mailto: address or an https:// URL you own. Empty leaves push off.
segwi.service.systemdUserUnitbooleanfalseLet 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.

KeyTypeDefaultWhat it sets
retro.outputstring"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.transcriptRetentionDaysinteger90Days 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.

KeyTypeDefaultWhat it sets
modsarray["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.

KeyTypeDefaultWhat it sets
verifystring"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.
setupstring or nullnullThe 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.
ciCheckstring or nullnullThe 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.
forgestring or nullnullgithub 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.

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.

HostDestinationSource
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