A small Go service that watches a bot account's GitHub notifications in an explicit repository whitelist for commands. It invokes the authenticated Codex or Claude CLI for the work and uses Git and the GitHub API for publication. The checked-in config.json contains sample settings; copy it to the ignored config.local.json for a real deployment.
Minion should start one AI-assisted action from a configured command, without free-form instructions in the command. Only collaborators with write or stronger access should be able to request work, and only in repositories explicitly selected by the operator or automatically registered from invitations by pinned owners. GitHub text and repository files are untrusted input; the default anti-prompt-injection skill should help the models keep them separate from operator instructions. AI-assisted work should run in sandboxes. Application logic, rather than AI instructions, should choose and limit GitHub operations, destinations, and publication states.
The security boundary is the operator-controlled configuration and mechanical checks on repository scope, requester permissions, and permitted actions, together with the AI CLIs' native sandbox restrictions and application-controlled GitHub writes. Minion requests sandbox modes; each CLI enforces its own restrictions. These controls must hold even when the model follows an injected instruction. Deployment isolation validation remains a public-release follow-up.
The embedded builtin://anti-prompt-injection skill is enabled by default for both model roles. GitHub titles and descriptions are quoted as untrusted task data in model prompts. Quoting and skill instructions are best-effort defenses against prompt injection; they do not enforce the security boundary. The skill tells each model to use untrusted content as evidence for the selected task, including relevant requirements and prior decisions, without letting instructions inside it change the selected action, authorization rules, permissions, publication destination, or publication state.
Minion does not use a separate prompt-injection classifier: the operator judges its false refusals and operational complexity to outweigh its benefits (decision #86).
The defaults below use @linkdataminion; substitute the configured bot_login. The commands mapping defines the available names and AI skills; each repository's actions list selects which names it accepts.
| Command | Result |
|---|---|
@linkdataminion review |
On a pull request, the main model uses the ponytail-review skill to review its exact head against its base. The bot submits a comment review; it never approves. |
@linkdataminion fix |
On an issue, the main model uses the ponytail skill to edit a pinned checkout of the default branch. The bot pushes a deterministic branch to its own fork and gives the requester a compare link to open the pull request themselves. |
@linkdataminion audit |
On an issue or pull request, the comment action uses the ponytail-audit skill for a read-only source audit and posts its report in the requesting thread. |
@linkdataminion security-audit |
On an issue or pull request, the main model uses Cloudflare's security-audit skill. The bot validates its report artifacts and posts the findings summary in the requesting thread. |
Every action supplies a usable local Git repository with complete history, including historical file contents, branches and tags. Models can inspect local history through the supplied .git metadata. Remote-tracking refs such as origin/<name> remain visible; pull requests also have refs/remotes/minion/pr-head; no remote is configured and no GitHub credentials are supplied. Minion does not authorize model fetches or publication; the orchestrator performs authenticated GitHub writes. Symlinks and submodule working trees remain omitted as declared in source.json; symlink blobs and gitlink entries remain in history. External LFS objects and other submodule repositories are not included. Pull requests retain pinned comparison context. History transfer is subject to the complete-bundle limit in Action boundary.
Fix replies record the expected branch head. Before enabling fix for a repository, create a fork under bot_login with the same repository name, disable GitHub Actions on that fork, and verify that no external CI, webhooks, or installed apps run its branch pushes with privileged credentials. Minion verifies the fork's parent and visibility, checks that its Actions are disabled, and refuses private fork PR settings that send base secrets or write tokens. The bot credential needs access to the base repository and its private fork PR settings; if the fork or settings cannot be read, fix does not publish; retries follow the failure policy. Private fork PR workflows may need to be enabled on the base repository to run CI after the requester opens the PR; keep secrets and write tokens disabled. Review any base pull_request_target workflows before enabling fix: that trigger can receive base credentials and must not execute untrusted fork-head code. Minion cannot prove that those workflows or external CI are safe.
The requester must open the pull request from their own account after reviewing the diff, so GitHub can apply any independent-review rule to that requester. The branch remains mutable afterward; protect minion/fix-* in the fork from updates by other writers and compare its current head with the recorded commit before merging. This private repository's current plan cannot enforce those protections or required reviews, so a second-person review remains a manual requirement here.
Before generating or pushing a fix, Minion reads effective push rulesets on the fork, monitored repository, and fork-network root. It allows active push rules only when GitHub exposes an explicitly empty bypass list, so the bot cannot bypass a restriction another fork would enforce. An unreadable or incomplete ruleset response prevents fix publication. An HTTP 403 before model work follows infrastructure retry/backoff without consuming an attempt or guaranteeing a reply; a 403 on the pre-push recheck is a publication failure. A saved complete result retries publication without another model attempt; an unsaved failure follows the failure policy. GitHub currently returns 403 for this private repository's rulesets on its plan, so fixes keep retrying until a trustworthy result is available. Branch rulesets on a base repository do not apply to its forks; push rulesets and their root bypass permissions do.
Each request must come from an account with effective repository write, maintain, or admin permission. An optional allowed_actors list narrows that group. Only the first nonblank line can start a command, with no inline arguments; a command inside a quote, fence, HTML comment or indented code block cannot start one. A later line that is exactly an enabled, installed command makes the request ambiguous, even inside a fence, HTML comment or indented block; >-quoted lines do not count. Inline arguments and ambiguous directives receive no bot reply; ambiguities are logged. Notes after an authorized command reach the main model as quoted task guidance within the selected action; they cannot change the bot's application-enforced limits. A review also includes the pull request title and description as untrusted context, even when the pull request author cannot run Minion. Fixes and audits use relevant thread descriptions as untrusted context. The bot checks the whitelist and command author's permission again before every action.
max_github_context_bytes limits the combined UTF-8 bytes of trimmed title, description, and requester notes included in an action (32 KiB when omitted or 0; configurable from 1 to 67,108,864 bytes). It counts these fields before prompt quoting, not the complete model prompt or repository files. Text at the limit passes this check; a larger total gets a fixed refusal before either model starts. Titles for the review, comment and security-audit actions exceeding the action title limit before trimming get the same refusal. fix omits the title and counts GitHub's rendered issue text. Notes supplied in an issue or pull request body also count as part of its description. Shorten the text and post a new command.
Edited command sources are ignored. Post a new comment to request work after editing text.
Before generating or pushing a new fix, Minion reads GitHub's rendered issue text and checks the issue's edit time against the request. If the issue changed at or after the request, it refuses new work and sends one best-effort fixed reply through terminal cleanup; review the current issue and post a new fix comment. A previously pushed branch remains available if publication later fails. With a saved complete result, Minion can reconcile its recorded SHA and retry the reply without rerunning the model; otherwise operator reconciliation may be needed.
After accepting an authorized command, the bot adds a 👀 reaction to the exact issue, pull request, comment, or review that contains it, before starting the main model. If the reaction fails, the service retains the original acceptance and retries after the recovery poll interval.
An enabled command used on the wrong supported thread type gets a fixed reply; see the commands table.
The security-audit action requires Codex as main, with Cloudflare's skill, the standard profile, independent agents, and its six-phase reporting workflow. With Claude as main, commands mapped to this action are disabled at startup and ignored without a reaction or reply. Claude can serve as its critic. Source remains read-only; validated report artifacts return as data and are retained in a new directory under work_root/audits/, with its path logged in journalctl. Returned audit metadata identifies the target as owner/repository@commit rather than an executor-local path. The GitHub reply includes the audited commit and a parent-rendered summary of structured findings and coverage, not a downloadable attachment. Retained Markdown is executor narrative for the operator, not the text posted to GitHub or a mechanically verified account of the audit. Audit agents are instructed to inspect source and use the skill's report validators, leaving repository code, builds, tests, fixtures and live services unexecuted. The sandbox permits local commands, so the source-only execution rule currently relies on those instructions. Minion checks the validator scripts and Node's permission model before invoking the audit model, then runs the validators under those restrictions: file reads are limited to the installed skill and copied validation files, child processes are denied, and the environment has a scratch HOME and restricted PATH, plus the profile, temp and system-root variables needed on Windows. The publishing host independently runs its installed validators on copied report data before retaining a complete audit or posting a reply. Node 20–24 does not restrict network access through this model, so the installed skill source on both hosts must be trusted. Claims needing runtime evidence remain needs_validation. Agent IDs, review coverage and completion in returned artifacts are executor claims; the publishing host checks their structure and cited paths against the assigned source, not whether a separate reviewer actually ran. Audit agents are instructed to disclose missing tools or time limits as incomplete coverage. max_runtime_minutes caps the action; expiry cancels the direct executor command and cleans its workspace. Collected partial audit artifacts are labelled incomplete and are not published. Validator errors identify the failed script and exit cause without copying report-derived stderr into the journal.
Complete REPORT.md must contain non-ignorable text, including when it is only a code sample. The publishing host validates structured findings and coverage against the assigned source, then renders the public summary from those records. The installed skill asks agents to fill applicable detail Markdown files, but their emptiness does not block publication: the structured records carry evidence, blockers and validation plans, while nonempty prose would not establish that the detail is true. The host does not infer truth from free Markdown prose.
A failed request gets at most three execution attempts, with these exceptions. The table distinguishes another execution attempt from replaying publication of a saved complete result. Once an assignment is delivered, a failed worker outcome is terminal even if no model-call start was acknowledged; only parent-proven failures before delivery can receive another execution attempt. An HTTP worker's failure classifications are diagnostic claims, not proof of an exception.
The saved terminal outcome and service log distinguish executor inactivity from the max-runtime cap expired, user or earlier parent canceled stops, and lost outcomes such as unverified execution or discarded acceptance. A missing required call receipt remains lost even if the worker claims a limit. The worker can reach its conservative runtime cap just before the parent's deadline; the parent labels that bound, checkpointed outcome expired. Worker limit reports cannot override a parent stop or change retry and capacity policy. The fixed GitHub failure reply remains generic.
| Failure | Policy |
|---|---|
| Unknown execution or outcome, including joined cancellation, URL transport or response-body read/decode errors | No further attempts; never automatically repeat paid model work. |
Graceful SIGINT/SIGTERM cancellation before an acknowledged model call; unsafe work-root admission; executor workspace preparation EACCES/EROFS; GitHub API HTTP 401/403/408/429/5xx or request transport errors (*url.Error) before publication |
Before assignment delivery, and unless a model-call start was acknowledged or execution has an unknown outcome, retry on later polls without consuming an attempt. An unsafe-admission request stays queued without a failure reply until admission passes. A local worker lost before assignment delivery also refunds its attempt; loss after delivery has an unknown outcome and is not retried. |
| Action timeout; unsupported or oversized source; executor workspace preparation (directories, source/context/repository materialization, private index) or initial review-evidence save failure, except missing executables, EACCES/EROFS, or ENOSPC/EDQUOT; source-root anchoring failure; invalid job, prompt, main proposal, critic verdict or result; invalid collection/import or audit report validation; oversized model/fix output; supplied Git metadata or frozen critic input mutation; critic snapshot/review-history preparation failure after model work; unfinished critic reviews; a fix producing no commit; post-call usage failure; invalid or conflicting private evidence or retained audit content | No further model execution attempts. A committed result can retry only the publication and storage failures below. |
| Other provider, missing executor Git, Git transport, audit-tool prerequisite, response-body read/decode or initial/pre-call usage save failures; executor workspace preparation or initial review-evidence save ENOSPC/EDQUOT | Before assignment delivery, and unless a model-call start was acknowledged, consume an attempt. A failed delivered assignment is terminal. Fixed replies and refusals follow the pre-publication rules. |
| Commit, push or publication failure after an acknowledged model call, without a committed complete result | No further model attempts. An issued task comment or review leaves a durable write record for marker reconciliation and suppresses a second reply. Fix pushes require a committed complete result and follow the next row. Other failures retain the fixed failure reply. |
| Retryable publication failure with a committed complete result, such as a GitHub reply error or an unavailable publishing audit validator | Retry publication from the saved result after source revalidation. Reconcile an issued task comment or review before any new write. An issued task comment or review is checked for its verified bot marker during a fixed grace; an absent marker never authorizes a second POST. Record the observed or uncertain remote effect separately from the first terminal decision. Never rerun the model. Each recovered publication call has an additional max_runtime_minutes cap but cannot extend the original accepted deadline. An issued fix push is reconciled against its exact recorded branch and commit SHA; a missing or mismatched branch never authorizes a second push and requires operator inspection. If no push was issued, no fix checkpoint exists, and no branch exists, Minion may build and push once from the saved result. A SHA-only checkpoint with a missing branch requires operator inspection. |
| Host I/O failure while retaining private worker diagnostics, review evidence or a completed audit after a complete result was committed | Retry retention and publication from that result after storage recovers; never rerun the model. An existing completed audit directory that differs from the committed result also retries for operator inspection; other content or integrity mismatches remain terminal. |
| Permanent publication error with a committed complete result, such as a fix that produces no commit | Stop publication retries and post the fixed failure reply without rerunning the model. |
| Final usage save failure after successful publication | Log a warning and retain the last durable counters; the published action stays successful. |
A committed saved result keeps its action accepted until publication succeeds or the original deadline or stop resolves it. An issued task comment, review or fix push remains recorded for remote-effect reconciliation after terminal cleanup, without blocking new notification discovery. Source revalidation blocks publication if the source or repository is no longer eligible, including an edited or deleted command, lost requester access, an archived or default-branchless repository, or a changed allowlist. Minion discards the result without a reply if ineligibility still holds when it resolves the failure; if eligibility returns, it may post the fixed failure reply.
When GitHub access prevents an accepted terminal reply, terminal_decisions records an unresolved_access disposition with the original repository, source key, available source URL, reason, reply state and operator action. GitHub can omit the display URL; the source key still identifies the request. not_issued means Minion did not start a reply POST. unknown means an issued POST could not be verified by the end of its fixed grace; inspect the marked reply manually. The first terminal outcome does not change, and Minion does not rerun the action or repost an uncertain reply. An issued reply retains the original source key and available URL in publication_writes while reconciliation continues.
If an existing completed audit directory differs from the saved result, recovery keeps that directory and retries; inspect it before removing it to let publication continue.
Minion waits for each direct command to exit and bounds pipe draining. The AI CLIs own their model-tool processes. Minion does not track their descendants or enforce per-command process and memory limits. Run Minion inside a VM or container if hard resource isolation is required.
Before acknowledging a model-call start, the orchestrator saves a no-repeat marker with the job attempt. After that acknowledgement, Minion never automatically repeats model execution, including after service restart. A selected worker's complete result is saved and committed before its upload is acknowledged; publication can retry from that result without another model call. Without a committed result, a failed assigned action is terminal and the requester can post a new command after inspecting the failure. An untrusted worker cannot make its own error flag decide whether to repeat paid work.
If the service context is canceled before a selected assignment is delivered, execution returns context.Canceled, and no model call was acknowledged, the interruption refunds the attempt even if the error also carries ErrTerminalJob, unless execution has an unknown outcome. An action-only timeout or internal cancellation still consumes an attempt. After default signal handling is restored, another signal can terminate shutdown immediately; normal crash/hard-kill accounting then applies.
A complete selected-worker result received after cancellation is refused, even if the original deadline has not passed. The worker receives no success acknowledgment; the selected worker is quarantined and the service exits for its supervisor to restart it. A saved result can replay publication only after its acceptance is committed, while the original deadline remains open and no terminal decision has been recorded.
After identifying a terminal job, Minion attempts one best-effort fixed reply. Pre-model refusals use the same path and retain the failed or expired action outcome even when the reply succeeds. During that reply attempt, Minion checks the live source, requester, repository identity, and verified bot marker. A failed or canceled reply check skips the reply and finishes the original terminal outcome. A verified existing marker also skips the reply. Minion saves an issued-write checkpoint before the one POST; an uncertain response is reconciled by marker without another POST. Post a new command after fixing the cause.
Minion runs on Linux, macOS, and Windows. Linux and macOS allow Codex or Claude roles. On native Windows, Claude cannot sandbox commands, so the required CLI setting failIfUnavailable makes it exit; configure Codex roles or run Claude inside WSL2. Minion does not gate Claude by OS; it checks Bubblewrap only for the Linux Unix-socket opt-in. CI runs Go tests, vet and build on macOS and Windows. It does not verify the installed AI CLIs' runtime sandboxing or login; validate those CLIs on the deployment host. The bundled systemd unit is Linux-only, and the cron example for one-pass runs is for Unix hosts. Minion does not include launchd or Windows Service integration.
Build Minion with Go 1.27 or newer. Install Git 2.45.1 or newer on both the orchestrator and executor hosts, and each selected AI CLI on the executor host (codex by default). For a local worker these are the same host. The orchestrator needs gh when it uses saved gh auth instead of GH_TOKEN or GITHUB_TOKEN. The selected CLI must be able to run Git and its helpers; Minion's host Git check does not prove access from model tools. Security audits also need Node.js 20 or newer on both the executor and publishing service host for the permission model used by the skill's report validators, plus a main model with sub-agent support for that skill. Minion selects the supported permission flag at runtime. Keep configuration and saved provider credentials outside source snapshots and role runtime directories, and protect them with host account permissions. Minion filters credential variables from model-command environments but does not prove that host files are unreadable to model tools. Copy the sample configuration, set its bot login and repository whitelist, then authenticate as that bot in the same operating-system account or service environment. Add invitation_owners only if the bot should accept invitations from those owners:
cp -n config.json config.local.json
chmod 600 config.local.json
# Edit config.local.json before continuing.
mkdir -p bin
go build -buildvcs=false -o bin/minion ./cmd/minion
mkdir -p var/work
chmod 700 var/workOn the orchestrator, run gh auth status only when using saved gh login; GH_TOKEN and GITHUB_TOKEN need no gh login. On a local worker, run codex login status when using saved Codex login, then ./bin/minion -config ./config.local.json -install-skills for the selected skills. With a remote worker, install its CLIs and skills on the worker host as described below. Finally, run ./bin/minion -config ./config.local.json -check on the orchestrator.
On native Windows, use PowerShell to copy the config, create bin and var/work, and run go build -buildvcs=false -o bin/minion.exe ./cmd/minion; then run bin/minion.exe with the same flags. Install the standalone codex.exe; npm's codex.cmd launcher cannot be started directly by Minion. The Codex CLI selects and enforces its native Windows sandbox backend; Minion requests a read-only or writable workspace. An elevated backend may allow broad same-user reads, including saved credentials, so choose the service account and host with that access in mind. CI runs Go tests, vet and build on Windows x64; it does not exercise the Codex sandbox. Set adversarial to "codex" for a Codex critic, or leave it empty to disable the critic. Native Windows fixes preserve existing Git executable modes, including case-only renames, but cannot create or change executable modes or preserve them across other renames; use Linux or macOS for those fixes. Place config.local.json and provider logins outside source snapshots. The operator manages the service account and work-root ACL; Minion validates paths but does not audit host directory permissions. Windows file ACLs, rather than Unix chmod, protect these paths.
Run -check while the persistent service is stopped; it takes the state lock. If the service uses environment-based credentials, provide the same protected environment to this check.
Normal startup and -check verify Git's minimum version and run a bounded private synthetic offline bundle/import/index round trip under work_root, before AI or GitHub preflight. The check uses Minion's production Git paths with synthetic local files; it reads no project repository, fetches nothing and runs no model. A failed version or capability check stops startup with its cause. This proves the installed host Git operations exercised by the check, not every repository's compatibility, future executable immutability, account/API availability or Git execution inside an AI sandbox. Operators still provision tools and pinned skills. Recovery of a previously assigned or terminal accepted action can run before new-work preflight, using GitHub and any needed local Git or audit tools without starting a worker; new work still requires the normal checks. Install-only, usage and locked one-pass shortcuts do not gain this Git requirement.
By default, Minion runs as a persistent service on Linux, macOS, and Windows, with one selected local executor child. Add -oneshot for one cron-friendly polling pass. -mode=orchestrate selects the same service path explicitly:
./bin/minion -config ./config.local.jsonThe bundled config.json includes an optional admin block with an admin account whose password_hash is the inert PASTE_GENERATED_HASH_HERE placeholder. The default service starts a loopback listener whose home page shows sign-in disabled until you replace the placeholder with a generated hash and restart Minion. Until then, /admin/login returns HTTP 403. Remove the block and restart Minion to disable the listener. Usernames are case-sensitive, at most 100 characters, and may contain only ASCII letters, digits, ., _, and -; . and .. alone and names differing only by case are rejected. For example:
"admin": {
"listen": "127.0.0.1:8377",
"administrators": [{"username": "admin", "password_hash": "PASTE_GENERATED_HASH_HERE"}]
}Generate the Argon2id PHC hash with Minion on Linux, macOS, or Windows. It prompts once without echo and needs no config or GitHub access:
./bin/minion -mode=hashIn Windows PowerShell, run .\bin\minion.exe -mode=hash.
Do not put the password in the command line; the hash command rejects positional arguments. Admin passwords must contain 1 to 64 Unicode code points.
Paste the complete output into password_hash and keep the config file private. Minion logs a clickable home URL when it can infer one; the home page links to the configured sign-in methods. Success opens /; sign-out closes the session and sends a redirect to /. The live dashboard shows the current action from preparation through publication, worker readiness and phase, the last received worker status and the worker-reported time since execution progress at that status. It checks for changes every two seconds while connected. Repository, requester and action filters apply only to the current action and live in the URL, not the session. The tracked accepted-action total counts recorded acceptances; it does not infer missing actions from usage records or terminal decisions. A worker status alone does not prove readiness or reset the configured inactivity limit.
The signed-in admin navigation includes Status, Users, Repositories, Skills, and AI policy. Status shows the bot account and selected AI roles; Test GitHub auth asks GitHub to verify the token currently held by Minion. Users controls per-repository GitHub requester allowlists, with numeric account IDs pinned from GitHub. Repositories verifies bot access and saves repository and owner IDs before adding a monitored repository. Repositories added through the admin UI admit nobody until a requester is added or Allow any writer is selected; existing repository commands can be edited. Skills manages configured skill sources; a new local skill is installed before it is enabled. Admin sign-in accounts, OAuth clients, listener settings, credentials, and AI provider/model selection remain JSON settings that require a restart. The UI saves live changes to the configuration file and activates them between polling iterations after any accepted action ends. A local worker is replaced when its selected skills or commands change. A remote worker's skills are read-only in the UI, and its command selection can only be narrowed or restored to commands selected at startup. To expand that selection, edit the Minion configuration file and restart Minion and the remote worker. For skill changes, update both configurations, install skills on the worker and, for security audits, on Minion's host too, then restart both.
AI policy sets the required number of clean, independent full critic reviews before a fix branch and reply are published. The repository override takes priority, followed by the pinned requester's default, then the global value. Unset values follow their parent; explicit zero disables this additional gate. With a critic configured, the global default is one clean review. Without a critic, the gate is inactive. critic_stopping.max_rounds still limits proposal revisions; extra clean reviews inspect the unchanged candidate. The global AI instructions editor replaces Minion's built-in working guidance for new actions, and Reset to built-in default restores it. Fixed action boundaries and result formats remain enforced by Minion.
To enable user sign-in, register OAuth applications with the providers and add either or both of these entries inside admin. Use the public address of this listener in each redirect_url and register the same callback with the provider:
"github_oauth": {
"client_id": "GITHUB_CLIENT_ID",
"client_secret": "GITHUB_CLIENT_SECRET",
"redirect_url": "https://minion.example/auth/github/callback"
},
"gitlab_oauth": {
"client_id": "GITLAB_CLIENT_ID",
"client_secret": "GITLAB_CLIENT_SECRET",
"redirect_url": "https://minion.example/auth/gitlab/callback",
"base_url": "https://gitlab.com"
}gitlab_oauth.base_url defaults to https://gitlab.com; it can name an HTTPS GitLab instance that supports OpenID Connect. For local development without admin TLS, use the listener's literal loopback IP and port in both HTTP callbacks. Open Minion's logged URL so the browser sends its session cookie to the callback.
Open /auth/login to sign in with either provider and link the other account. Each linked account can contain at most one GitHub identity and one GitLab identity, even across GitLab hosts. Links are saved in <state_path>.link; back up this owner-only file with the state file. GitHub identity uses its numeric account ID; GitLab uses the OpenID Connect subject. GitHub requests read:user; GitLab requests openid email. Names and email addresses are labels, never ownership proof. Sign-in alone grants no repository access or GitLab action discovery. A linked account stays one account regardless of which provider is used next. Provider sign-in does not grant admin access; the local password account controls the admin dashboard.
An OAuth redirect temporarily keeps its JaWS session alive without a connected page; the provider callback must begin within ten minutes, then its provider calls have a separate 15-second deadline. New provider starts return 503 once the total JaWS session count reaches 480, leaving at least 32 of 512 slots for admin access. New visitors to / and /auth/login get 503 at 479 sessions; /admin/login remains available when configured. Repeated starts can temporarily deny other user sign-ins. Rate-limit /, /auth/login, /admin/login, and provider starts at the trusted edge. If linking fails after the session ends, sign in again and retry if the link button remains available.
The authenticated /admin/usage page filters usage by UTC start date (from inclusive, to exclusive), repository name or ID, requester login or ID, and service. /admin/usage.json exports the same filtered records and totals. With a service filter, totals.total_tokens and selected_usage.total_tokens reflect that service; each attempt's tokens and total_tokens retain usage from all services in the attempt. Both use the minion usage accounting query; incomplete and unallocated usage remain labeled, and billed credits are unknown.
Signed-in users can open /user/usage for token totals tied to their linked GitHub account ID. Repository totals appear only when Minion can verify current access to the same repository ID; losing access does not erase the overall total. Claude CLI cost estimates are labeled as estimates with their coverage. Billed cost and credits remain unknown.
During a live selected run, Force stop this attempt requests cancellation of the displayed job and attempt. Saving the request fences new assignment admission, model-call admission and publication. A call admitted just before that save can start while cancellation reaches the worker. The latest request stays visible after the action clears: requested means worker termination is still being checked, confirmed means the attempt stopped cleanly, and unverified means the worker outcome or cleanup could not be established. Inspect worker readiness before reuse when termination is unverified; unreachable or unclean workers remain unavailable. A write already issued before the stop may still need reconciliation.
Open the current action's detail link for saved private discussion, reports, diagnostics, the actual critic verdict and stopping reason, original deadline, remaining runtime and publication checkpoint. A bookmarked /admin/actions/<attempt-id> remains available while its terminal decision is retained, even after the current action clears. Private evidence may expire after 30 days; missing, incomplete and display-limited records are labeled. A terminal decision without a saved original deadline displays it as unavailable. The detail is a snapshot; reload to see newly received records.
JaWS keeps sessions in memory, so they end on restart or ordinarily after one minute without an attached JaWS page. The /admin/login page keeps its session live while its JaWS browser connection is attached. Successful password sign-in requires a current live JaWS session. Open or reload /admin/login if you have none. Sessions are bound to the client address seen by Minion, so changing networks can require signing in again. The listener caps sessions at 512 total. Each guest session can hold four outstanding public JaWS page requests. Failed logins are limited per configured username and in one shared bucket for unknown usernames. These separate limits can reveal configured usernames after repeated failures; usernames should not be treated as secrets. At most two password hashes run concurrently.
An empty listen uses 127.0.0.1:8377. The admin server starts with the default persistent run; -oneshot skips it. Without TLS, listen must use a literal loopback IP. Other addresses require both tls_cert_file and tls_key_file; relative file paths resolve against the config directory. OAuth callback URLs must use one host so both providers receive the session cookie. Minion advertises that callback origin when its TLS certificate covers the host, or the HTTP callback origin on loopback. The certificate must be trusted by the browser and cover the address used; when no certificate-valid URL can be inferred, Minion prints the listener address instead. -check verifies configured admin TLS files without binding the listener; normal service startup verifies them before binding. A -oneshot run neither loads unused admin TLS files nor starts the listener. An admin listener failure stops the service with exit status 1. The admin listener is separate from the worker HTTPS endpoint and does not use its token.
To offer model preferences, configure admin.user_model_choices.main, admin.user_model_choices.critic, or both; critic choices require adversarial. Each role accepts up to 16 approved {"model":"...","effort":"..."} pairs. Claude choices need concrete model IDs. The service default remains available. Configure GitHub OAuth to match command authors by numeric ID; a GitLab sign-in needs a linked GitHub identity to affect GitHub commands. Local -check validates approved choices. Workers preflight offered pairs and recheck an assigned pair before a model call.
Users choose approved pairs or service defaults at /auth/preferences. A withdrawn choice falls back to the default for new actions; accepted actions retain their frozen settings across restarts. Claude roles require an explicit effort in claude.effort or the role settings. When accounts link, the initiating account keeps its saved preferences, or inherits the other account's if it has none. Preferences live with account links in <state_path>.link; they cannot change providers, skills, permissions, runtime, or repository access.
Signed-in users can view their own action history at /auth/actions. Actions follow the original GitHub account ID across login renames, including when that account is linked to a GitLab sign-in. Private repository entries and details require current access to the original repository; inaccessible entries are omitted. The page shows progress, retry status, frozen model settings and retained diagnostics.
An active action's detail page offers Abort this action. The stop is terminal. A requested status awaits verification; confirmed means termination completed; unverified means an operator must check executor readiness before reuse.
To use one remote executor, add remote_executor to the orchestrator config with executor_id, listen (host:port), tls_cert_file, tls_key_file, and token. The certificate must be trusted by the worker host and cover the name in its HTTPS URL. Use the same 32-random-byte unpadded base64url token in a separate worker config:
{"base_url":"https://minion.example:8443","executor_id":"worker-1","token":"REPLACE_WITH_RANDOM_TOKEN","work_root":"/path/to/worker/work","claude":{"allow_all_unix_sockets":false}}On the worker host, create the configured work_root before starting Minion (for the example above, mkdir -p /path/to/worker/work on Unix) and give the worker account access using the host's normal permissions. Create worker-skills.json with bot_login, repositories, and the same commands and always_on_skills as the orchestrator config. Omit credentials and remote_executor, then install the skills:
./bin/minion -config=worker-skills.json -install-skillsSave the worker config outside the source checkout, for example at /path/to/private/remote-worker.json, and protect it with the worker host's normal account permissions. The worker's claude.allow_all_unix_sockets controls its own Linux sandbox; set true only after accepting the socket risk and Bubblewrap requirement below. Remote Claude roles need a concrete model ID, not default or another alias; the worker verifies the selection before any model call. Start the orchestrator, then run ./bin/minion -mode=remote-execute -config=/path/to/private/remote-worker.json early enough for the worker to finish preflight and open its first pull within the orchestrator's two-minute startup wait. Remote model preflight has a 90-second total limit, including at most 30 seconds for its HTTPS fetch. If the orchestrator restarts with an undelivered accepted action, it waits for a worker until that action's original deadline instead. The worker checks its Git and work root before pulling, then holds one authenticated pull. Each assignment supplies model settings, skill sources, and bounded provider credentials; required-skill and provider-login failures are reported through the existing outcome. For a persistent remote deployment, run the default orchestrator service and supervise the worker. A one-pass run closes the HTTPS endpoint when its pass ends; start a new worker for each scheduled pass.
If an idle worker disconnects, even a one-pass orchestrator run waits for a replacement pull before looking for another action; invitations and pending publication still progress while it waits. A disconnect during an assigned action can quarantine capacity and close the endpoint. Remote -check validates orchestrator Git and GitHub access and configuration, but cannot prove worker CLI readiness. The operator controls firewall rules, TLS trust, host permissions, and network egress. No other registration or acknowledgement service is required.
-check and -install-skills remain setup commands and do not start an executor. minion usage remains a separate reporting command; see Usage reports. -mode=execute is the internal child entry point: it reads trusted selected startup data from standard input and cannot be combined with -config, -oneshot, -check, or -install-skills. Do not launch it manually.
For scheduled one-pass runs, save this as an executable /path/to/minion/run-once.sh, adjusting the checkout and CLI paths:
#!/bin/sh
set -eu
export PATH=/usr/local/bin:/usr/bin:/bin
cd /path/to/minion
exec ./bin/minion -config ./config.local.json -oneshotAdd */5 * * * * /path/to/minion/run-once.sh to that user's crontab; use the same credential environment as the persistent service.
The gh token needs access to GitHub notifications and the whitelisted repositories. GitHub's notifications endpoint requires a classic personal access token or OAuth token; fine-grained tokens and GitHub App tokens do not work there. The bot needs repository write access so it can query collaborator permission and push fix branches to its fork. Fixes also need access to the base repository's private fork PR policy and the fork's Actions settings; an unreadable policy causes refusal. GitHub requires the classic token's repo scope for those settings; its fine-grained equivalent is repository Administration (read), though fine-grained tokens cannot poll notifications. When push rulesets exist, the token needs ruleset write access for GitHub to include their bypass lists. -check verifies notification and repository access, including fork and push-rule safety when fix is enabled. GitHub credentials are read from GH_TOKEN/GITHUB_TOKEN or gh auth token. AI provider API keys can be set in codex.api_key and claude.api_key in the ignored config.local.json; see authentication below. Keep real keys out of the checked-in sample and set chmod 600 config.local.json.
Edit config.local.json to change the repositories monitored for commands or their enabled actions. Each repositories entry needs an owner/repo name and its GitHub id and owner_id. actions lists the command names allowed in that repository, drawn from the top-level commands mapping. Omit allowed_actors or set it to [] to allow any account with the required GitHub permission. Set deny_all_actors to true to allow no requesters. To restrict requests to named accounts, list each login in allowed_actors and its numeric ID in allowed_actor_ids. invitation_owners lists accounts whose current and future repositories may invite the bot; each listed login needs its numeric ID in invitation_owner_ids. Invitations with write, maintain, or admin access automatically add the repository and its IDs to config.local.json with the configured command names. With a local worker, a command with a missing required skill stays unavailable until it is installed. A remote worker checks skills after assignment; a missing skill fails that action. The service waits for a restart and a new worker selection before monitoring an invited repository, preserving the repository's original notification cursor meanwhile. Existing repository actions and actor restrictions are preserved. start_at may be set to an RFC3339 timestamp before the state file is first created to process older notifications. Opening state saves that cursor even if later startup checks fail. Without it, a new state file starts at the current time.
When a configured command uses the fix action, a repository added from an invitation enables it. Provision its bot-owned fork with Actions disabled before fixes can publish; -check reports a missing or unsafe fork.
Confirm that each repository and account is still the one you intended to trust, then look up its immutable GitHub ID with authenticated gh commands, for example:
gh api repos/OWNER/REPO --jq '{id, owner_id: .owner.id}'
gh api users/OWNER --jq .id
gh api users/ACTOR --jq .idCopy those values into id, owner_id, invitation_owner_ids, and allowed_actor_ids as applicable. The sample config.json contains placeholder IDs. A missing, extra, or nonpositive pin stops startup; Minion does not infer trust from a login that another account may have claimed. Pending retries without IDs cannot run; they are discarded when their notification is seen or their retry becomes due.
The top-level commands object maps each command name to an application action and one or more skill sources in skills. Supported actions are review, fix, comment, and security-audit. The application action, not the skill, determines whether Minion posts a review or thread comment, pushes a fix branch, or writes a validated security report. Omit commands or set it to null to use the four mappings in config.json; set it to {} to disable all commands. Every repository's actions list must name configured commands. With a local worker, a mapped command runs only when all its required skills are available to the main provider. A remote worker checks provider support before discovery and required skills after assignment. Downloaded skills need matching Minion install metadata, while the embedded builtin://anti-prompt-injection skill needs no installation. Custom GitHub sources use https://github.com/OWNER/REPO/tree/REF/skills/NAME. REF is a single path segment; the path after skills/ may contain nested components, with its final component used as the installed skill name. The embedded URI is the only supported builtin:// source.
The always_on_skills list selects optional skills for every run. Omit it or set it to null for the default embedded anti-prompt-injection skill and caveman GitHub skill, in that order; set it to [] to use none. An external always-on skill is used when installed and skipped otherwise. The main model receives its command skills and installed always-on skills. The critic receives only installed always-on skills, so a command skill cannot turn its read-only critique into another execution of the task.
-install-skills copies configured embedded skills and downloads configured GitHub skills into $HOME/.agents/skills/ for Codex and $HOME/.claude/skills/ for Claude, then exits. The embedded anti-prompt-injection skill works in Minion jobs without this switch; copying it also makes it available to interactive Codex and Claude. The installer resolves each GitHub ref to its current commit, installs its complete skill directory, and records the fetched source and commit. The default main URLs therefore get their latest versions each time. GitHub skill repositories must be publicly readable because the installer fetches them without credentials. It replaces existing same-name skill directories in both locations, including manually installed copies; keep local customizations under other names. Run it explicitly when adding or updating skills; ordinary Minion runs never download them. Review each external source and fetched commit: skill instructions guide the models, and Minion runs the security-audit skill's validator scripts under the service account.
Stop Minion before editing its configuration. Install skills and run -check after editing it.
main selects "codex" (the default) or "claude". adversarial is optional; omit it or use "" to disable the critic. Codex can serve both roles; a Claude main and Claude critic pair remains gated until its CLI capabilities are verified. -check reports that gate before starting a worker. The codex object accepts model, reasoning_effort, and api_key; claude accepts model, effort, api_key, and allow_all_unix_sockets in local mode (remote workers set it in their worker config). The socket option applies only on Linux and is ignored elsewhere. Optional main_settings and critic_settings objects each accept model and effort and override that provider's defaults for one role. Local mode resolves omitted models from the provider's catalogue and requires an explicit effort for each selected Claude role. Remote mode requires an explicit effective model and effort for each role. This example uses a local worker:
{
"main": "codex",
"adversarial": "claude",
"critic_stopping": {
"max_rounds": 5,
"nonblocking_after_round": 0,
"consecutive_no_blocker_rounds": null
},
"codex": {
"api_key": ""
},
"claude": {
"effort": "high",
"api_key": "",
"allow_all_unix_sockets": false
}
}Add these fields to the service config. Swap the two provider names to use Claude as main and Codex as critic. For a local worker, install each selected CLI on the service host, configure API keys or saved logins, then run -check. For a remote worker, install the selected CLIs on the worker host and set explicit models and efforts; orchestrator -check cannot verify those CLIs. An unused CLI is not required.
A nonempty api_key takes precedence over environment credentials and saved CLI login for validation and job execution. Use an OpenAI API key for Codex and an Anthropic API key for Claude. With keys empty or omitted, Codex uses CODEX_API_KEY, CODEX_ACCESS_TOKEN or saved login; Claude uses its saved login. Configured keys are not placed in prompts or command arguments, and do not replace the operator's saved login. Minion preserves them when automatically registering a repository in the deployment config. The execution host requires a resolvable native user home for CLI authentication and session files.
In selected local-worker mode, the worker uses a transient copy of selected credentials for startup preflight, drops those references before its first pull, and receives credentials separately with each private assignment. It clears the Runner's credential fields and closes CLI sessions before uploading the outcome. It retains assigned values through outcome and watch handling to redact final errors, then drops those references before pulling another job. Saved CLI login remains with the worker's service account; Minion does not copy authentication directories. Operators control network egress outside Minion. Clearing Go references and temporary files does not guarantee erasure from process memory.
Claude support requires an installed CLI that passes authentication, model-catalogue and fresh/resume invocation probes using the requested sandbox and tool options; Minion imposes no Claude release-number minimum. Claude's Linux sandbox uses bubblewrap and socat; the CLI checks whether it can start. On Linux and macOS, the OS temp directory (TMPDIR, otherwise /tmp on either system) must have a canonical path of at most 56 bytes for Claude actions. The macOS default can be exactly 56 bytes. Choose a temp root outside source, report and Claude's default writable /tmp/claude, ~/.npm/_logs and ~/.claude/debug. Use storage that permits execution and has enough disk space; Claude builds and runs test binaries there, and its cache and temp files can consume substantial RAM on tmpfs. The sample service uses /var/tmp; check its filesystem on your host. macOS uses Claude's native shell sandbox. Its restricted execution tool set contains only Bash; the security-audit action is disabled with Claude as main because that workflow requires independent subagents. review, fix, and comment (default audit command) actions support either main provider on Linux or macOS.
Claude runs in safe mode with automatic instruction discovery, hooks, plugins, MCP, and connected tools disabled. Only configured skills are named in its prompt. Minion requests Claude's native Bash sandbox with filesystem and network restrictions and no unsandboxed fallback. By default, Minion keeps Claude's Unix-socket filter, including on hosts with kernel.apparmor_restrict_unprivileged_userns=1; a custom AppArmor profile may allow the normal sandbox there. If the normal sandbox fails on an affected Linux executor, claude.allow_all_unix_sockets: true disables Claude's Unix-socket filter and permits AF_VSOCK connections to VM host services. Minion checks for non-setuid Bubblewrap with --disable-userns before using this opt-in. When /run/user/<uid> exists as the policy is built, Minion denies sandboxed Bash access to it, including the user D-Bus/systemd manager; a directory created later is not covered. Keep work_root, the OS temp root, read-only evidence, and installed skills outside that blocked directory so Bash can access them. Other host sockets, including Docker, SSH agents, and Claude's session-messaging socket, can remain reachable. Isolate the executor from host service sockets, including unneeded vsock services in a VM. Source and report directories are added for writes only when needed. Claude enforces that policy; Minion filters credentials from the model-command environment. Claude's own API client retains authentication and network access. Minion disables the CLI's print-mode background wait ceiling (CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0), so the action deadline controls background work. Both roles are instructed to await delegated work and background checks before returning a final response. Sandboxed Bash is pre-approved with dontAsk and an explicit Bash allowance; this does not use bypass-permissions mode or depend on an approval classifier.
For a configured Claude key, Minion supplies a temporary credential helper and private authentication directory. The key is absent from Claude's model-command environment. Action-role authentication files remain for its continuing conversation and are removed during action cleanup; preflight removes its files after validation. Persistent cleanup failure, a crash or hard kill can leave a .minion-claude-auth-* directory under work_root, or an mc-* action or preflight directory under the OS temp root, with owner-only access on Unix. An orphan under /var/tmp can survive a reboot. Remove an orphan only after confirming no corresponding native call is running. Without a configured key, use claude auth login; Minion rejects nonempty ANTHROPIC_API_KEY and CLAUDE_CODE_OAUTH_TOKEN environment variables because the CLI supervisor may retain them. Authenticated proxy URLs are also rejected; ordinary proxy URLs without credentials remain supported for Claude's API client.
Claude preflight sends only initialization control requests, without inference or a sandboxed command. The fresh probe disables session persistence; the resume probe looks up a new nonexistent session ID, proving parsing and lookup rather than successful continuation. The CLI reports runtime sandbox failures when invoked. Minion treats failed CLI calls as job errors; its metadata check does not prove runtime isolation.
Each action keeps stable source, context, report and role-runtime paths until completion. Main revisions resume their existing native conversation; scoped finding verification resumes the latest critic conversation. Each full review starts a new independent conversation at the same paths, after clearing that role's writable scratch. Role sessions never cross actions or share main/critic history. Every invocation reapplies its selected schema and CLI tool policy; conversation reuse does not add automatic quota retries or service-restart recovery. Claude stored-login transcripts follow normal CLI retention. Minion removes verified action-owned Codex rollouts from CODEX_HOME after the final call's usage and diagnostics are collected, including api_key runs. Preexisting and unverified rollouts and saved login files remain. Role cleanup retries once; persistent failures report generic cleanup_failed and put the recovery path in a private cleanup_recovery diagnostic. They can leave rollouts, runtime files or materialized-skill directories for operator removal. An ongoing conversation retains its natural history. The explicit cross-round refutation list below contains only refutations; its durable ledger also stores call receipts and verdict flags, without answer or critic-feedback text. Earlier findings or approvals are not precedent preventing changed code from being examined again.
With a critic configured, the main model returns a public answer and private refutations. The first critic reviews the complete candidate against the original task and baseline. After a revision, scoped verification checks earlier findings against the changed files and private counterevidence; a valid no-blocker verification triggers a separate fresh full review. The final permitted proposal also receives that full review after completed blocking verification, without discarding its blockers. Verification alone never permits publication or counts toward the no-blocker streak. Full reviews receive no previous findings, answers or refutations. Normal publication requires a fresh full review that meets either the approval condition or the configured nonblocking stopping condition, with required verification findings still subject to the stopping policy. At the hard round limit, a completed full review may instead permit caveated publication when the clean-review gate is inactive. The next revision retains rejected feedback from both phases. The main model must implement valid findings at every severity, including nits and simplifications, or supply concrete counterevidence or a task constraint. These are model instructions, not mechanically verified guarantees.
The cross-round refutation list appends only revised main proposals' refutations field (round 2 onward), with its original round and private text after NUL removal and configured-key redaction, rather than answer or critic-feedback text. The first proposal must leave refutations empty; preemptive first-round text is discarded. The model is instructed to exclude implemented-finding summaries and approvals from refutations. Each revision and scoped verification also receives the immediately previous answer and critic feedback. They reuse applicable counterevidence; changed code, changed task constraints or material new evidence can invalidate an earlier refutation. Changed code remains open to review. Main-model refutations are claims, not inferred critic acceptance.
With a critic configured, the service atomically checkpoints call receipts (including verdict flags) and the refutation list under work_root/reviews/<attempt-id>.json before another call or publication, including failed actions. Review checkpoints and final Results remain executor claims: the parent validates identity, structure, the call prefix and decision consistency, but cannot prove what a critic saw. An initial metadata checkpoint verifies storage after work-root admission. Minion creates the record with mode 0600 and its directory with mode 0700 on Unix; the operator controls inherited host ACLs on Windows. A stable action-scoped refutation file is refreshed between calls, supplied to revisions and scoped verification, and removed when the action ends. The journal logs call phases, verdict and stop facts, input/cached-input/output token counters and accounting completeness, plus identity, location and refutation count, without private text. Retention failure stops model work and publication; identical final evidence does not rewrite the checkpoint. Records do not mean publication succeeded or resume interrupted actions. Complete discussion logging remains separate #98/#99 work.
Fix critics inspect a frozen candidate; Minion asks the selected CLI to keep the supplied source read-only during actions that share it. Audit critics inspect frozen reports with configured API keys redacted. Verification and full review of one proposal share immutable inputs and the original baseline, not the previous candidate tree. Minion rejects changes to the copied fix candidate and audit report files and their regular/executable modes and exports the frozen bytes; later live workspace changes cannot alter that export. Empty directories and directory modes carry no transferred source state and are not attested. Before result export, audit metadata is projected to supported fields: portable target/companion paths and ownership entries for known artifacts; unknown extensions are dropped. The parent validates uploaded metadata again; extra fields do not select its filesystem or GitHub targets. Raw audit artifact content is changed only by NUL removal and configured API-key redaction before critic review and export; JSON artifacts may be re-encoded when decoded values or keys change. Prompts request repository-relative paths, but a model can still quote executor-local paths; Minion does not mechanically remove them. Missing artifacts and empty non-JSON companion files can receive feedback. An empty REPORT.md, invalid or ambiguous JSON, or any audit artifact still exposing a configured key stops before review; unsafe, unreadable or oversized inputs do too. Model agreement does not replace validation or publication controls.
Fix changes containing either configured provider key as literal text, a JSON string or a single-pass HTML entity stop before critic review or publication. With a critic configured, proposed answers, private refutations and critic feedback containing a key in those forms stop before crossing to the other model. A separate accidental-copy guard rejects an exact whole-refutation reply of any length, or complete-sentence copies of at least 24 Unicode characters in public replies or newly disclosed Fix content, after case, space, Unicode, HTML-entity and surrounding *, _ or backtick normalization. It also checks raw audit finding titles and fingerprints before quoting them in the public summary. Exact short matches are limited to whole replies and audit titles; a fingerprint or Fix file can legitimately contain the same generic short value as a refutation. A quote already present in the parent-held head or base source, or in the parent-prepared GitHub title, description or requester notes, does not block a public reply or unrelated edit. Its public copy search and source-quote search each have a cumulative 1 GiB work budget; publication is refused when either search cannot finish. Already proven source quotations remain allowed. Exact whole-reply/title and configured-provider-key checks remain in force. Private audit artifacts are outside this public-output guard. Short partial copies, paraphrases, nested escapes and discussion omitted by a hostile executor remain outside it. The provider-key check does not recognize arbitrary encodings such as base64.
The critic_stopping object groups the stopping policy. max_rounds counts main proposals, including revisions: default 5 when omitted or 0; bounds are listed under Action boundary. Without a clean-review gate, N rounds allow at most 3N−1 main/critic calls; a gated fix can add fresh full reviews of unchanged candidates. nonblocking_after_round controls the absolute-round threshold. Omitted or null defaults to maximum minus one (4 by default; 0 for a one-round maximum, 1 for a two-round maximum). Explicit 0 requires every finding cleared for normal acceptance; it does not disable caveated publication at the hard limit when the clean-review gate is inactive. Top-level critic_max_rounds and critic_nonblocking_rounds keys are rejected, including when combined with the nested settings.
consecutive_no_blocker_rounds defaults to 2 when omitted or null. At or after a positive absolute threshold, nonblocking acceptance also requires this many consecutive proposal rounds whose fresh full reviews explicitly report no blockers. Explicit 0 disables only the streak requirement. A value above max_rounds prevents acceptance through the streak before the cap. A full review with blockers resets the streak; verification, missing, contradictory, incomplete or failed verdicts never count. When the clean-review gate is inactive, fresh full approval permits immediate publication once required verification findings are cleared. In strict mode, a new full approval cannot discard unresolved verification nits. Otherwise, remaining nonblocking findings can be accepted only when both threshold and streak conditions hold. At the hard limit, a completed, valid fresh full review can instead stop with round_cap when the gate is inactive, preserving any final verification blockers even if the full review approves. This outcome does not satisfy normal acceptance.
At round_cap, the public result states the original maximum, that normal critic acceptance was not reached, the reported full-critic approval and when a final-candidate review phase reported blockers. This application-generated caveat precedes the worker-supplied body or the parent-rendered audit summary; it never includes private critic discussion or refutations. Audit artifacts retain their original validated bytes. Oversized projected output is refused without truncation. Missing, invalid, incomplete or failed reviews, deadlines, authorization failures and source, artifact and publication checks still stop the action. With the gate disabled, a capped fix with changes is pushed for human review and is never automatically merged. Publication success, critic approval and review completion remain distinct.
Blockers are concrete correctness, security or task failures; stylistic nits and simplifications without that impact are nonblocking. Until the configured nonblocking stopping conditions are met, normal publication requires explicit approval with every finding implemented or evidence-refuted. Every review round is instructed to investigate its whole assigned scope comprehensively, collect findings at all severities in one pass, await delegated work and checks, and make a final coverage pass. Finding a few issues or no blockers is not a stopping point. Reviews must disclose unchecked scope and any runtime, context, quota or dependency limit that prevents completion. A critic is instructed to classify unfinished assigned review scope as blocking. The critic must explicitly attest review_complete for its assigned phase. A valid incomplete verdict reports blockers and retains its call evidence, then terminates the action without a revision, streak increment or publication. Missing or contradictory coverage evidence is invalid. This is a model attestation, not mechanical proof of inspected files or delegated work; native CLI completion and awaited-helper checks remain prerequisites.
The executor-reported critic approval and blocker flags stay separate from the application's stopping decision. Per-call review_complete describes assigned review coverage independently of complete, which describes token-accounting coverage. A completed review can have incomplete usage accounting; complete accounting cannot make an unfinished review publishable. Per-call has_blockers/no_blockers evidence distinguishes blocking and nonblocking verdicts from an invalid verdict or provider failure (both false). Failures after a native call, such as snapshot verification or usage persistence, retain its parsed verdict. Journal policy_allows_full and no_blocker_verdict_streak replay eligibility and the streak from reported verdicts; neither proves that integrity checks passed or execution advanced. policy_met is true only for an actual normal acceptance decision (approved or nonblocking) after call integrity checks. Journal stop_reason exposes the separately recorded critic_stop_reason; round_cap sets capped=true with the original critic_max_rounds and final-candidate cap_has_blockers, while policy_met remains false. Without an actual decision the reason is empty. Fix reply attempts also log the saved cap facts when recovering publication. Retained verdict flags can show approval even when integrity checks aborted that call before acceptance. The private review record retains the actual decision separately from those flags. A recorded stopping decision does not prove publication succeeded. See the failure policy for exhausted budgets.
max_runtime_minutes defaults to 240 (allowed 1–1440) and caps the original execution, including result acceptance and publication retries, from the first action acceptance time. An entered durable receipt write may finish syncing after the deadline, but that result cannot authorize publication after expiry. Executors receive the remaining duration at dispatch and account for transfer time using their own elapsed clock, without comparing host wall clocks. inactivity_timeout_minutes defaults to 10 (allowed 1–240). Executors enforce it from real execution progress, not Status contact. A silent model or tool turn may reach the default 10-minute inactivity limit even while it is working. Codex exec --json can delay root command output and model messages until their items complete. When native rollout persistence emits live exec_command_output chunks, Minion counts nonempty root chunks as progress if they can be attributed to the current call. On resume, a chunk without a turn ID cannot be distinguished from late old output and does not count; a silent command or one without attributable chunks can still reach the inactivity limit. Neither limit can be disabled; 0 or omission selects its default. An inactivity limit at least as large as max runtime is allowed but ineffective. The first accepted time and deadline are saved with failed-job state and reused across retries. The service also saves the request and reservation before reaction and restores an unassigned attempt after process restart. Releasing an unused reservation does not renew the job deadline. The first terminal decision is persisted and survives restart; late results cannot replace it. publication_grace_seconds defaults to 120 (0 or omission selects the default; explicit values 1–3600) and sets the periodic read-only reconciliation window for an already-issued task comment, review, fix branch push, or fixed failure reply. The write, fixed deadline and authenticated bot account ID are saved before the POST or Git push; the deadline is never renewed on restart. Each background reconciliation pass has a 10-second GitHub lookup budget and rotates pending writes across restarts. A write's first check after expiry makes one final read-only marker lookup, or an exact branch SHA lookup for a fix push, before recording an uncertain remote effect. An unconfirmed effect cannot authorize another POST, Git push, or AI work. Admission, cleanup and fixed refusal/failure-reply budgets can run outside the action runtime. The service saves acceptance and starts the deadline before reacting. During an active action, the service pauses new notification polling and services due invitation checks and existing terminal failure replies when they do not delay the selected publication. While an accepted fix waits for an issued push, it defers invitations and older publication scans. While a saved task reply is being prepared, posted or reconciled, the service defers invitations, older publication scans and sibling terminal replies. During a retry wait after a transient publication failure, it may service due invitations and sibling terminal replies when enough time remains before the original deadline. During the fix push wait and selected fix execution, it starts sibling terminal thread checks only while more than three minutes remain before its final 30-second control reserve. Checks already in a thread may continue until the reserve. Queued reply reads inherit the deadline at the start of that reserve; a reply POST starts only with its full 10-second budget available and then uses that bounded budget. Selected fix execution stops invitation checks before the same reserve.
With a critic, both CLIs receive schemas for main proposals and critic verdicts; Minion validates their contents. Decoded main answer/refutations share 58 KiB, raw proposal JSON allows 6×58 KiB+128 bytes for escaping, and critic JSON allows 64 KiB. NULs are stripped from main answers; with a critic, every proposal must contain a nonblank answer. A changed fix can discard an oversized unused summary after review, or a blank summary without a critic. A no-change fix needs a publishable answer before critic review. Audit publication uses a parent-rendered summary of validated structured records; the worker answer is ignored, while REPORT.md is validated and retained privately with a raw 58 KiB limit. Audit metadata such as run_status, execution_policy and shared_file_owners consists of executor declarations checked for consistency, not independent proof of completion, isolation or authorship. Without a critic, the main model keeps its ordinary response format. Every task states the rendered publication budget. Oversized published output is refused.
Codex uses a private temporary project root per action/role beside the supplied source snapshot and reads source through its absolute path. Source AGENTS.md files are not loaded as project instructions, and source .agents/skills are not offered as skills. The automatic skills catalogue is hidden; only skills selected by Minion's config are named in its prompt. The source includes a sanitized local .git directory with complete object history. Minion verifies the supplied Git metadata after model calls and refuses unexpected changes. Local-worker startup checks instruction and skill discovery with inert files using the installed Codex CLI, without an inference turn, and stops if those files enter an isolated job prompt. Repository source text remains untrusted context that can influence model output; inspect generated fixes and reports before relying on them.
With a local worker and Codex selected for either role, orchestrator startup or -check verifies its instruction boundary, queries the installed Codex CLI's model catalogue, and validates the model and its supported reasoning efforts. A startup recovering an already accepted action may finish that recovery first; it runs these checks before continuing into new work. Omitted values are resolved from that catalogue and pinned for the life of the service. Invalid model settings, an incompatible Codex protocol, a failed instruction check, or a failed catalogue lookup stop new notification polling. A first start may already have created the state file and saved its cursor (see start_at above). The log includes the rejected value and supported choices when available. For a local worker, missing required skills disable their mapped commands; missing always-on skills are skipped. Model discovery has a 30-second timeout and does not start an inference turn. The systemd service retries failed startups every 30 seconds, so a removed model or effort requires correcting config.local.json or the Codex installation before work resumes. Restart the local service after upgrading Codex or installing skills to revalidate. Catalogue validation checks compatibility, not future API availability or account quota.
With a remote worker, orchestrator -check validates TLS files, Git and GitHub access. Before its first pull, the worker checks Git and preflights every approved provider/model/effort pair against its installed CLI within a bounded startup window; a failed login or unsupported pair stops startup. It revalidates the selected CLI after each assignment; missing worker skills or later CLI support changes fail that action.
Minion waits for its direct external commands, including the AI CLIs, Git and audit validators. On cancellation it interrupts the direct command and bounds the wait. AI CLIs own cleanup of their model-tool processes. Minion does not inspect or stop descendant processes. It does not provide hard process, memory or disk quotas; use a VM or container when those guarantees are needed. CLI sandboxing still controls model-tool filesystem and network access.
For continuous operation, copy deploy/minion.service to ~/.config/systemd/user/minion.service. Set its absolute checkout path in WorkingDirectory and ExecStart, and add the installed gh and selected AI CLI directories to PATH if needed. With Claude's Unix-socket opt-in, Minion hides this user's systemd manager from sandboxed commands; isolate the service from other host sockets as needed. Then run systemctl --user daemon-reload and systemctl --user enable --now minion.service. Enable user lingering if it must continue after logout. After rebuilding the binary, restart it:
systemctl --user restart minion.service
systemctl --user status minion.service
journalctl --user -u minion.service -n 50 --no-pagerState, job clones and source snapshots live under var/. Action scratch uses the host-controlled work root, except CLI preflight scratch and Claude's native runtime and Go build cache, which use private directories under the OS temp root. Startup, -check and each job require a safe existing work-root directory; Minion checks path type and anchored access, while the operator controls placement and host permissions. On macOS, the service account needs read and search access to work_root and each ancestor because anchored path reads open those directories. Retained private critic-review records, work_root/attempt-records/*.jsonl (up to 64 MiB per attempt), work_root/worker-diagnostics/*.json, attempt-bound atomic-write leftovers in the review and diagnostic directories, attempt-owned work_root/audits/complete-*, .complete-* and incomplete-* directories, and attempt-bound scratch in the orchestrator work root are removed on a later poll 30 days after their saved terminal decision. An accepted attempt or pending publication write keeps its records until recovery finishes. Missing records after expiry do not prove that no discussion occurred; a saved capture_limit stop marker means capture was truncated, and a lost terminal decision means the worker outcome is unknown. An interrupted complete-report write can leave a work_root/audits/.complete-* staging directory with private report bytes; the next retry removes its own stale stage. Untagged incomplete-* audits, .minion-audit-validation-* scratch, and .record-* files cannot be matched to an attempt; remove stale ones after Minion stops. A remote worker's audit, report, and review scratch is outside the orchestrator work root and its retention pass. A hard-killed Minion process can leave source, checkout, context, index, and CLI workspaces or Minion mc-<digits> runtime directories. Inspect them individually; remove only Minion-owned directories after no corresponding CLI call is running. Worker diagnostic records contain private executor text. Partial audits are retained in incomplete-* directories with an INCOMPLETE.txt label. Completed audit files and directories are synced where the platform supports it; a complete-* name denotes validated audit status, not critic approval. Capped completed audits retain the original report artifacts; the application caveat appears in the public reply and private review record. Result-transfer bounds and output prechecks do not cap model-created disk usage or concurrent I/O. Retained CLI transcripts (preexisting or unverified CODEX_HOME rollouts and Claude stored-login sessions), Minion state/usage and journal logs need separate retention and capacity management.
A selected action's pending result is stored beside state_path at state_path.result. It can contain the repository source, model text and changes, so keep it private with the state file. Minion removes it after successful publication, discard or a terminal publication failure; preserve it with the state file for operator inspection if recovery stops. When the monitor runs, it removes a sidecar with no accepted entry in the state file. Copy the sidecar before editing or restoring the state file if the result needs operator inspection. A process or host crash during an atomic write can leave a .record+<destination>+* temporary file in the state directory (.record-* for long destination names); remove stale files after the service stops.
To stop a stuck publication replay after inspection, stop the service, back up the state file and sidecar, remove state_path.result, then restart. The assigned action becomes terminal and Minion attempts its fixed failure reply if the original command and requester remain eligible. This discards the saved result, so first check whether the publication failure can be repaired.
A file lock prevents overlapping runs. A second one-pass invocation skips Git and AI preflight when another run holds the lock. The process persists GitHub's minimum X-Poll-Interval, so an early one-pass invocation skips notification polling; startup checks and any due invitation check still run. The GitHub client and both AI CLIs need outbound network access to their APIs. Model-generated shell commands run under the selected CLI's native sandbox. The CLI applies its own filesystem and network restrictions. The Go service owns authenticated GitHub writes: it reacts to accepted commands before the models run and publishes allowed results afterward. Invitations use their own pinned authorization policy within the same sequential monitor.
Invitation checks become due every 15 minutes. The service checks them during idle waits and active actions. It checks pending repository invitations from the authenticated bot account's invitation feed. It accepts nonexpired invitations with a matching or omitted invitee.login only when the repository owner matches a pinned invitation_owners ID or the repository matches a monitored name and pinned repository and owner IDs. The owner in GitHub's repository record must match its full name. Invitations to monitored repositories still need write, maintain, or admin access; invitations to other allowed-owner repositories may grant any standard role. Read and triage invitations are accepted without enabling commands. Invitations from unlisted owners are declined unless their numeric owner or repository identity is pinned. Malformed or expired invitations, nonempty mismatching invitee logins, and pinned identity mismatches stay untouched. Each check fetches at most ten pages with one 30-second listing budget. All accept/decline API actions share a second 30-second budget. Fetched pages are handled after a later page fails unless GitHub returns HTTP 403 or 429; those responses defer further invitation actions until the next check. New writable repositories are saved atomically to the config before acceptance, so a failed config write leaves the invitation pending for retry. An acceptance failure can leave the entry in the config while acceptance retries. The config file and its directory must be writable; stop the service before manual JSON edits to avoid concurrent updates. This automates the invited account's acceptance step; a repository owner must still send the invitation.
The AI CLIs enforce model-command sandboxing. Minion requests Codex's native read-only or workspace-write mode and Claude's native Bash sandbox, validates supplied inputs and returned results, and controls action lifecycle. The orchestrator prepares source and controls GitHub writes. The local Runner consumes a bounded, credential-free Job and returns changes, reports, usage and diagnostics in Result.
The orchestrator now calls the typed Executor interface, with synchronous identity/source-bound checkpoints before each paid call, after returned usage (including failed or canceled calls), and for review prefixes and the separate stopping decision. Missing or failed acknowledgements prevent continuation or publication; a final Result cannot replace them. The same Runner pipeline serves this interface and direct local Run; optional usage still skips only accounting, while critic orchestration requires private evidence.
Minion's versioned worker protocol rejects mismatched versions and requires remote model preflight before the first pull. Jobs, checkpoints, statuses, outcomes and results bind the action and source; required acknowledgements gate model calls and publication. Status separates message, progress and log counters, so heartbeats do not reset inactivity. A disconnected pull can retry before assignment delivery; a delivered assignment is never sent again. A known terminal remote attempt can replay its Outcome for 30 days to retain late usage without reopening the action.
The internal RunHTTPLoop lets a selected worker pull another Job only after a valid final outcome ACK, including after a failed Job, or after an HTTP 425 retirement acknowledgement for an undelivered pull. It exits cleanly when canceled before a pull or after an acknowledged idle shutdown. Uncertain delivery and failed required control exchanges stop the loop with an error. The service uses this local worker path by default; -mode=orchestrate selects it explicitly.
ListenLocalExecution serves that handler over HTTPS on an ephemeral IPv4 loopback port. Each direct worker receives its own server certificate and client token through bounded startup stdin, outside arguments and the environment. NewLocalExecutionClient trusts only that certificate, dials the fixed loopback address without a proxy, refuses other origins and redirects, and sends the token only after TLS succeeds. The endpoint checks the token before decoding any protocol body. A local process without those credentials cannot authenticate as the worker or impersonate the parent; another process can still interrupt service. On shutdown the parent drains accepted callbacks while HTTPS remains available, lets the direct child receive idle shutdown until it exits or the stop deadline arrives, then closes HTTPS and joins the child. The first HTTP stop deadline is reused for direct-child termination and HTTPS shutdown; capacity stays retired while the child or accepted callbacks remain unresolved. No socket path or runtime directory is created.
HTTP bodies require uncompressed JSON, one value, known fields and valid UTF-8. Encoded caps are 256 MiB for the assignment, 64 MiB for the outcome, 8 MiB for review and 128 KiB for control/ACK bodies. The assignment credential bundle has an 8 KiB aggregate raw-byte cap and is excluded from Job, Status and Result. A conservative assignment estimate counts worst-case escaped text and credentials, per-file base64 padding and envelopes before encoding; it can refuse an otherwise valid decoded Job. Before typed decoding, 200,000 total JSON tokens and nesting depth 64 bound container expansion; maximum canonical Job arrays fit that token budget. Field limits preserve raw failed usage receipts without accepting them as a valid complete Result.
The bounds allow six encoded bytes per UTF-8 text byte and 4 * ceil(n/3) base64 bytes for each separate byte slice, plus object keys, scalars and envelopes:
| Body | Conservative size basis |
|---|---|
| Control/ACK, 128 KiB | Pinned identity, checkpoint/status metadata and at most one bounded Status log chunk; the largest finite failure adds an 8 KiB diagnostic. |
| Review, 8 MiB | At most 19 refutations of 58 KiB each expand to about 6.46 MiB. The raw shape also permits up to 439 calls with six 128-byte text fields each; worst-case escaping can exceed 8 MiB, so encoding may reject a shape-admissible review. |
| Outcome, 64 MiB | The finite raw shape permits seven 5 MiB artifacts, about 46.67 MiB in base64, plus 1 MiB aggregate changes, about 1.34 MiB in base64. Escaped refutations, call metadata, a 58 KiB answer, 100 bounded diagnostics, change/artifact paths and failure/envelope overhead keep the combined bound below 63 MiB. This uses all seven artifact maxima even though a valid complete REPORT.md has a smaller 58 KiB raw limit. |
| Assignment, 256 MiB | Deliberately narrower than decoded Job validity: separate 64 MiB bundle and file-content budgets, a 1 MiB diff, 64 MiB task text, role/skill settings and four path lists can exceed a GiB after encoding. The pre-encoding estimate counts actual admitted lengths with worst-case expansion, individual base64 padding, container overhead and an assignment reserve, then rejects estimates above 256 MiB. Even a 64 MiB ASCII description can be conservatively refused although its actual encoding would fit. |
The accepted hard expiry includes result validation, encoding, waiting and transfer. Publishing-host audit validators run during publication from the saved result, outside the outcome ACK path. Parent call admission and outcome sealing check the deadline directly, without waiting for its cancellation callback. Assignment pulls may wait for work without a transport deadline; once headers arrive, reading their response body uses a size-based deadline of at least 30 seconds. The parent's assignment write also respects the action stop deadline. Positive replies require HTTP 200. A held cancellation watch is admitted before execution. The caller supplies one bounded stop budget (30 seconds by default). The parent starts it once on cancellation, expiry, shutdown, or required control failure; the worker starts it on its own expiry, cancellation, or failed required control exchange. An ordinary Runner completion leaves the Result upload under its size-based request deadline; the parent still rejects outcomes after the original action expiry. Serial control requests share the worker deadline while returning available paid receipts. The same budget accompanies the assignment, and an earlier parent stop sends its remaining time to narrow worker control. A delayed watch reply can leave the worker's relative timer later than the parent deadline; the parent rejects late controls and refuses late outcomes for publication. Admitted transaction network reads and writes contract to the stop deadline. Remote /outcome requires a binding header for the current assignment so its upload uses that deadline independently of an older late upload; the decoded body still decides admission. A headerless late upload uses the size-based deadline before its binding is known. At most two 64 MiB Outcome bodies are processed at once, and endpoint shutdown still has a 30-second bound. Pre-admission /next and /cancel bodies retain 30-second request limits. Accepted context-free saves and bounded JSON encoding/validation may finish after grace; the parent fences late outcomes before publication. A stalled filesystem save can keep Execute waiting past grace. A parent-observed failure to deliver an assignment or required checkpoint ACK blocks publication and quarantines capacity even if a later bound outcome arrives. A bound received outcome survives loss of its receipt ACK; a detected receipt-ACK write failure quarantines capacity. A successful final receipt ACK can resolve independent malformed-watch transport uncertainty during final upload. It does not clear Runner or required-checkpoint failures, actual parent cancellation/deadline, parsed parent replies or external caller causes. Known pre-model request transport failures observed by the parent before assignment delivery retain infrastructure retry classification. Once delivery is acknowledged, a failed assignment is terminal even without a model call. Worker-reported transport or output-limit failures do not establish that classification; a bound failed outcome without parent-proved uncertainty consumes an attempt. An unknown execution outcome consumes the attempt and is never automatically repeated, including after cancellation or network failure. Receipt ACKs do not replace checkpoint saves or authorize publication. HTTP completion does not prove direct worker exit; the caller waits for that process separately. Call Close while the server still serves control requests. Supplied clients must permit the held watch, one optional Status request and a required control request concurrently; their configured timeout is preserved and redirects are refused.
The HTTP worker sends best-effort Status observations on phase changes, coalesced progress updates, and every 15 seconds after cancellation-watch admission. The parent keeps the latest accepted Status and its own receipt time in memory; contact does not reset progress age. Status may carry one redacted attempt-record fragment at a time, bounded by the 128 KiB control body. The worker redacts configured provider credentials from each complete logical event before splitting it; the parent redacts again and rejects a continuation that would reconstruct a configured credential. The parent appends and syncs each accepted record before acknowledging its log sequence; lost ACKs can be retried against the saved highwater. A terminal empty-text coverage marker means all events supplied by the current producers were delivered. The worker attempts it only after those events are delivered; the required Result proceeds concurrently, so a missing marker leaves completeness unproved.
At a confirmed full-store cap, the parent saves a fixed stop marker with capture_limit, stops AI, and retains the fixed capture_limit Result diagnostic. The rejected record is dropped, so coverage stays incomplete. An uncertain marker write or lost rejection does not authorize dropping the pending record; a missing final Outcome remains outcome-unknown.
Runner records completed direct main/critic CLI messages, tool events, Codex reasoning summaries and successful final answers, bound to native root or helper IDs, call ordinals, selected provider/model and worker-observed UTC time. Claude thinking blocks are excluded because its stream format also uses them for progress updates. Private attempt records retain each prepared call's stdin, argv, working directory and schema, with configured credentials and inherited Codex API/access tokens redacted; a prepared record does not prove that the CLI consumed it. Unsafe or oversized input is labeled unavailable and leaves coverage unproved. These records can contain task text and private critic context and need the same operator cleanup described above.
On a failed native call, Minion attempts to retain the bounded, redacted returned diagnostic or an explicit unavailable marker; the required Outcome still carries the failure if optional Status delivery is lost. The bounded queue keeps Status latency out of the CLI stdout reader; a lost event or identity leaves coverage unproved. The worker splits complete logical events for transport. Codex root discussion comes from CLI stdout; eligible helper discussion, including helper prompts, comes from bounded live rollout reads. Paginated response items and completed items have distinct source labels, since both may describe one act. Event messages, completed tool output and plans, and plaintext inter-agent messages are retained; encrypted content gets an unavailable marker. Verified pre-call prefixes and identified old turns are excluded. Uncertain inherited history is labeled possibly duplicate and leaves coverage unproved. Fresh attributable native execution events, including root command output, helper inputs and output, and lifecycle events, advance action progress when stdout is quiet; metadata, replayed, empty and unproved entries do not. A Status does not authorize publication or prove worker cleanup.
Key contract and execution limits:
| Data | Limit |
|---|---|
| Published answer | 58 KiB raw UTF-8 bytes and 58 KiB after line normalization, > prefixes, control/formatting-character escapes and 12 fence bytes; no NUL. The parent-rendered audit summary uses this bound. |
Private audit REPORT.md |
58 KiB raw UTF-8 bytes both before and after redaction, with non-ignorable text and no NUL in a completed Result. Local collection strips NUL before validation. |
| Title; combined title/description/notes | 1 KiB; 64 MiB |
| Identity segments / diagnostic codes | 1–100 ASCII characters from [A-Za-z0-9._-], except . and .. |
| Requester login | Same base spelling, optionally followed by one [bot] suffix; numeric identity and repository authorization remain required |
| Model/effort; skills | Model and effort: 128 UTF-8 bytes each, no space, tab, CR, LF or NUL. Skills: 64 required+always-on sources per role, 4096 bytes per source |
| Job runtime; critic budget | Greater than zero, at most 24 hours (configuration: 1–1440 minutes); 1–20 proposals with a critic, nonblocking threshold 0 through critic_stopping.max_rounds, no-blocker streak 0–20 |
| Diagnostics | 100 per Result; 8 KiB per UTF-8 message, no NUL |
| Status log | One 16 KiB text fragment per 128 KiB Status body; 4 MiB per logical event across fragments; 64 MiB per-attempt store |
| Complete audit | Seven named regular files; 5 MiB per non-summary file; REPORT.md has a 58 KiB raw UTF-8 limit |
| Transferred head / merge-base regular files | 10,000 per list; 64 MiB of contents across both |
| Changed paths; omissions | 10,000 changed paths; 10,000 total distinct omissions across head and merge base |
| Comparison diff; complete Git repository bundle | 1 MiB; separate 64 MiB and 100,000 references excluding HEAD. Oversized bundles are refused, never truncated. |
| Complete default/head/merge-base tree | 10,000 blob/gitlink paths, 64 MiB declared blob bytes, 100,000 metadata entries |
| Materialized Git metadata | 128 MiB total, 64 MiB per file, 100,000 entries counting directories and the .git root itself |
| Local complete-tree listing | 64 MiB per tree, before parsing metadata or reading blobs |
| Fix result; binary patch | 100 changes, 1 MiB of full changed-file contents including mode-only changes; separate 1 MiB patch limit includes removed/deleted contents and encoding overhead |
| Fix control files / source scans | 64 MiB per .gitignore/.gitattributes; 1 MiB total of new or changed control-file contents before Git reads them. Unchanged supplied controls are exempt. Scans allow 100,000 combined file and distinct ancestor-directory entries per admitted layout, and 100,000 visited entries in fix/frozen-input walks. |
| Provider stream | 64 MiB, 750,000 records; 4 MiB per raw JSONL line before LF, including a preceding CR |
| Claude resumed progress history | 16 MiB of retained progress keys, 100,000 entries per conversation; exceeding either limit stops the call with an explicit error |
| Codex model catalogue | 4 MiB per line after removing CR/LF; exact-cap LF, CRLF and EOF accepted |
| Codex rollout accounting | 128 MiB per rollout per pass; separate 512 MiB budgets for current collection, unchanged-history proof and the baseline pass on resume; 100,000 retained turn IDs and response IDs each per rollout |
| Codex live progress identities | 100,000 root item identities and 100,000 distinct hashed rollout progress records per CLI call; reaching either cap stops the call with an output-limit error. Positive ordinals use one high-water mark per rollout for output chunks, messages and reasoning. Eligible helper responses, completed items, tool begin/end and task/turn lifecycle events use the hash cap regardless of ordinal; repeating an unchanged hashed event does not renew inactivity. |
| Codex rollout discovery / completion | 1,024 candidate rollout headers (known plus recent); 100,000 entries; 64 KiB header excluding CR/LF; 4 MiB per raw line before LF, including a preceding CR; 256 helpers |
PR context always contains changed paths and merge-base versions of changed/deleted files, alongside pinned head source and separate base-tip identity. Complete Git history includes the base tip; supplied comparison file excerpts use the merge base. Diffs ignore repository attributes; an oversized diff alone is omitted and marked incomplete. Complete-tree limits include omitted links and apply to the entire merge base, even when only selected files transfer; shrinking an over-limit or unsupported base does not bypass them. Preparation uses ordinary complete clones and fetches, without shallow history, partial objects, filters or lazy fetching. Clone/fetch has no object/byte cap beyond the action deadline. Shallow/incomplete sources and oversized bundles are refused; Minion never substitutes truncated history.
Symlinks/gitlinks are omitted and declared in source.json; its single omitted_scope applies to the whole snapshot (head for issues or head_and_base for pull requests). head_omitted separately identifies current link paths for audit coverage; links cannot provide line citations. A base-only omission may have regular descendants in head. Fixes cannot modify omissions/descendants or replace their ancestor directories with files. Exact Git spelling is preserved, including case and backslashes; unrepresentable source is refused. Git source admission rejects NTFS/HFS aliases of .git. Fixes additionally require backslash-free regular paths and a final layout without Unicode SimpleFold collisions, including omissions. Security audits reject current regular and omitted-link paths that the installed report validators cannot cite before model work; base-only paths are comparison context. Components allow 255 UTF-8 bytes and relative paths fewer than 4096 bytes; native Windows rejects paths that cannot be safely represented there. Host filesystem and absolute-root limits still apply, so executor acceptance does not guarantee trusted import or cross-host compatibility.
Models see tracked .gitignore files and complete local history. The supplied repository is rebuilt from objects and refs, without copying source config, hooks, alternates or credentials. Implicit bare-repository discovery is refused; trusted operations that need a bare repository name its Git directory explicitly. Minion verifies supplied Git metadata after calls; fix critics receive an independent copy of the original repository alongside the candidate files. Omitted head links are marked skip-worktree so their absence does not appear as a deletion. A private offline index outside the supplied source snapshot collects fixes without remotes, credentials, hooks, filters or source Git config. Tracked edits count; ignored new build outputs do not. A case-only rename to an ignored spelling is refused instead of exported as a deletion. Case-insensitive .git components stay excluded. Wholly untracked nested repositories must be removed or ignored. Traversed ignore/attributes controls must be regular files; an ignored untracked .gitignore is refused if Git loads it but would not export it. Unused controls in ignored directories with tracked files are allowed. Skipped ignored subtrees consume no control-file scan entries beyond their visited parent; case-spelling checks may still read names beside tracked files. Case-spelling checks have a cumulative 100,000-entry scan limit; a larger ignored cache beside a tracked file may cause the fix to be refused. Orchestration rechecks the fork and validates imported data before its commit/push; executor Git metadata never transfers.
Codex uses its native read-only mode when no output directory is needed. Fixes use workspace-write for source edits; audit reports use workspace-write for their separate report directory, leaving the source outside writable roots. Minion supplies source, context and selected skill paths, disables unselected integrations, and checks exec and exec resume options, runtime config keys, the model catalogue and instruction isolation without requiring a Codex release number. The CLI selects and enforces its sandbox backend; Minion does not verify host-level read isolation. Select an instruction-free CODEX_HOME and authenticate there; Minion refuses homes containing global AGENTS.md or AGENTS.override.md. Every Codex model call disables cross-session memories. Explicit native provider-directory overrides are resolved and pinned for each action role; a mid-action change is refused. Codex uses that directory for rollout recovery. Codex calls require a resolvable native user home or CODEX_HOME; rollout accounting and helper verification read that directory.
Native completion is separate from accounting. An oversized provider event cannot be recovered by a later answer. Known Codex helpers require proof that their latest own turn completed. In measured Codex 0.160.0 runs, resuming the role conversation did not restore its prior helper roster; earlier helper reports remain in the conversation. On resume, a wait/close-only reference to an unchanged, already completed helper needs no new turn. If close appends records, the changed log follows the ordinary attribution and completion checks. Sending input, spawning, unfinished baselines and changed logs retain the completion checks; intentional close/interrupt of active work, missing or unreadable logs, inherited-only entries and capped tails that prevent proof reject completion. A completed attributable helper lifecycle with missing counters only leaves usage incomplete. On resume, discovered or previously known owned helper rollouts without current activity require an exact unchanged byte baseline; new, changed, ambiguous or unverifiable logs reject completion. A missing or changed known identity keeps later resumes of that role session rejected until its evidence is restored or a new conversation starts. Baseline-entry, per-rollout byte and separate 512 MiB proof limits also constrain this proof. Broader header/entry discovery caps and unreadable unrelated directories leave usage incomplete. Unusable historical content alone does not invalidate a fully read byte baseline. Minion waits for the direct CLI process; the CLI owns its model-tool processes. The two-second pipe-drain bound still rejects a call whose inherited output pipes stay open after the CLI exits.
Claude checks bounded stderr for wait-ceiling stops while retaining its last 8 KiB; managed settings may override the launch environment. Non-ambient stopped tasks require a fresh root response and result, and a result with a non-completed terminal reason cannot complete a call; a later completed result may. An unusable quota rejection or permanent native memory-pressure stop gets one non-renewable second to flush counters/result before cancellation; an earlier action deadline still wins. Later allowed events cannot erase interrupted work; usable overage and near-limit warnings do not fail a successful call. Reset times are informational.
The journal records role, round, phase, model settings, accounting completeness, diagnostic counts and private attempt-record paths. Worker diagnostic codes and messages stay in those private records. Follow-ups: admission/capacity #95, isolated local/remote executors #96, inactivity/max runtime #97, discussion capture and limit diagnostics #98, restricted records #99, usage/billing exports #100, dashboard #101, under plan #91. Independent role selection #102 is a separate future follow-up.
MINION_LIVE_CODEX_BOUNDARY=1 go test ./internal/codexcli -run '^TestInstalledCodexInstructionBoundary$' -count=1 opts into an installed-Codex check that renders local inert prompts without inference and skips if the CLI is absent.
For paid authenticated CLI review and fix checks, set MINION_LIVE_NATIVE_ACTIONS=1 and set MINION_LIVE_CODEX_MODEL, MINION_LIVE_CODEX_EFFORT, MINION_LIVE_CLAUDE_MODEL and MINION_LIVE_CLAUDE_EFFORT to resolved installed choices. Set MINION_LIVE_CODEX_API_KEY and MINION_LIVE_CLAUDE_API_KEY so native transcripts stay in test-owned scratch. Set MINION_LIVE_CLAUDE_ALLOW_ALL_UNIX_SOCKETS=1 to test the Linux workaround. Run go test ./internal/action -run '^TestInstalledNativeActions$' -count=1 -timeout 30m.
Create the ignored live-tests.local.json in the repository root to enable the GitHub live tests. Without it, they skip:
{
"owner_login": "example-owner",
"bot_login": "example-bot",
"repository_url": "https://github.com/example-owner/example-repo"
}Replace the sample values with two accounts authenticated in gh and a repository owned by owner_login that both can read. issue_number selects an owner-authored issue with an owner comment; pull_request_number selects an open owner-authored draft pull request with an owner comment. Each fixture owner comment must have an update time more than one second after thread creation. These numbers are optional for basic read-only checks. The write lifecycle and fix action need issue_number; live reviews need pull_request_number; the security-audit action needs both. The linkdata/miniontesting pull request fixture also needs a bot-authored inline review comment and submitted review with a non-empty body. Optional bot_ssh_key is an absolute path to the bot's SSH private key or a path relative to the repository root; it enables SSH identity and repository read checks. SSH checks also require a trusted github.com host key already present in the default SSH known-host files. Put no token or key contents in this file.
Run go test -race ./internal/github ./internal/gitwork -run '^(TestLiveSmoke|TestLiveClientReadOnly|TestLiveBotGitAccess|TestLiveCloneAndPullRequest)$' -count=1. Keep -count=1 so Go does not reuse a cached result. These tests read GitHub through REST, read-only GraphQL queries, SSH authentication, and Git fetches without changing GitHub state. They verify both accounts' gh credentials and, when configured, the bot's SSH key. The Git checkout test skips unless repository_url is exactly https://github.com/linkdata/miniontesting (no trailing slash) and pull_request_number is set.
Stop any running minion service before a write run so it cannot accept the test invitation or act on test events. The write run uses both accounts' gh credentials and the dedicated linkdata/miniontesting repository. For the dedicated write and action fixtures below, set owner_login to linkdata, bot_login to linkdataminion, and repository_url to exactly https://github.com/linkdata/miniontesting without a trailing slash:
MINION_LIVE_WRITE=1 go test -race ./internal/github -run '^TestLiveBotWriteLifecycle$' -count=1The write test reuses linkdataminion's write access, accepts a suitable pending invitation, or invites it when neither exists. It keeps the resulting access for later runs. The test posts and edits a bot issue comment, reacts to a temporary owner comment, and, when bot_ssh_key is set, pushes a temporary branch and opens a draft pull request. Set MINION_LIVE_REVIEW=1 as well to post a comment review on the configured fixture pull request. Cleanup deletes both comments, the reaction, and the temporary branch and closes the temporary pull request. GitHub retains submitted reviews and closed pull request records. Cancel an expired bot invitation in Collaborators settings before another write run.
After bot write access is established, create linkdataminion/miniontesting as a fork of linkdata/miniontesting and disable GitHub Actions on the fork. Exercise the production Git publishing path with MINION_LIVE_WRITE=1 go test -race ./internal/gitwork -run '^TestLiveCommitAndPush$' -count=1. It creates one branch on the bot fork, verifies its commit and bot identity, and deletes the branch after checking its exact SHA.
Exercise the production actions with stub Codex results; the security-audit test also requires node on PATH:
MINION_LIVE_WRITE=1 go test -race ./internal/action -run '^(TestLiveFixPublishesBranch|TestLiveSecurityAuditAction)$' -count=1The fix test publishes a temporary branch to linkdataminion/miniontesting and a reply on the base issue, checks a repeat execution, then removes both after identity checks. The security-audit test checks both issue and draft pull request heads, validates its report, checks a repeat execution, and removes its replies. Replies can leave GitHub notifications after cleanup.
To exercise the production review action, run MINION_LIVE_WRITE=1 MINION_LIVE_REVIEW=1 go test -race ./internal/action -run '^TestLiveReviewAction$' -count=1. It reviews the configured owner-authored draft pull request at its exact head, submits a comment review, and checks that a repeat execution does not submit another. GitHub retains the submitted review.
If a write test is interrupted before cleanup, inspect linkdata/miniontesting for temporary minion/live-* branches and bot replies carrying its job marker. Inspect linkdataminion/miniontesting for temporary minion/live-gitwork-* and minion/fix-* branches. Remove only artifacts created by that run. Notification delivery and the monitor's invitation handling depend on GitHub account settings and are covered by offline integration tests; the live suite checks the repository notifications API.
The invitation and revocation lifecycle has a separate opt-in test. Run it only when explicitly requested, with MINION_LIVE_WRITE unset and linkdataminion lacking collaborator access or a pending invitation; it removes the access it grants:
MINION_LIVE_COLLABORATION=1 go test -race ./internal/github -run '^TestLiveBotCollaborationLifecycle$' -count=1The service starts recording usage after command validation, duplicate-reply checks and attempt-ID creation, before action preparation. Later failures and retries are recorded; earlier failures can return without a usage record. Reports are local and need no GitHub or AI authentication or network access. They can run while the service is running:
# Group by repository, invoking user, and action (the default).
./bin/minion usage -config ./config.local.json
# October usage for one repository and invoking user.
./bin/minion usage -repo example-owner/example-repo -user example-user -from 2026-10-01 -to 2026-11-01
# Daily totals by repository.
./bin/minion usage -group-by day,repo
./bin/minion usage -group-by service,requester_id
./bin/minion usage -service codex -requester-id 123 -json
# Keep ID-bearing usage together across repository renames or reused names.
# The default repo,user,action grouping still splits rows by historical names.
./bin/minion usage -repository-id 456 -group-by repository_id -json
# Export attempts with model, effort, outcome and source URL.
# Executor and source commit are present when a model call was recorded.
./bin/minion usage -group-by none -json > usage.json
# Read records directly if configuration is unavailable or invalid.
# This relative default path assumes the config file is in the current directory.
./bin/minion usage -dir ./var/usage -group-by month,user -jsonBoth the service and the usage command default to config.local.json when -config is omitted. -repo and -user match case-insensitively; -repository-id and -requester-id select positive numeric identities. Use -repository-id to select ID-bearing usage across renames or distinguish a reused name, and add -group-by repository_id to combine renamed records in one row; either ID filter excludes records without its numeric ID. -service selects codex, claude, or unknown usage. -from is inclusive and -to is exclusive; both filter the attempt's start time. Dates use midnight UTC, and RFC3339 timestamps are also accepted. When both are supplied, -from must be strictly earlier than -to. -group-by accepts a comma-separated combination of repo, repository_id, user, requester_id, service, action, day, and month; day and month groups use UTC. Use none for one row per attempt. JSON includes the applied filters, grouped summaries or individual attempts, and overall totals.
Reports combine both providers and all main/critic rounds, track succeeded, failed and running attempts, and count incomplete usage. Per-attempt JSON records (-group-by none -json) include numeric repository_id and requester_id, main/critic models and efforts, and ordered calls once a pre-call checkpoint is saved. That checkpoint may precede acknowledgment. Records without numeric IDs or calls retain unknown identity or call detail. A call's 1-based ordinal together with its enclosing job_id and attempt id is its stable identity. Each call has its own start time, optional return time, provider, model, effort, role, round, review phase, token categories and coverage.
Service summaries allocate call receipts by each call's provider. Attempt totals without call rows appear under service unknown; their tokens are unallocated, while saved call counters retain their provider labels. A missing or zero requester ID means the identity is unknown. With a service filter, JSON detail records retain the full original attempt and add selected_usage; table token columns show the selected service, and named-service call columns show its calls. usage_recorded_actions counts distinct job IDs in the usage files, separately from running and terminal attempt counts. It is not an exact count of all accepted actions: action_coverage is unknown because actions without usage files cannot be counted. A mixed-provider attempt can repeat attempt, status and incomplete-usage counts across service groups. Retries can repeat one job's distinct-action count across service, date or label groups. Use overall totals, which count each recorded job and attempt once. Usage files remain after private discussion and report artifacts are pruned.
A call without finished_at has no saved return receipt: it may have incurred a charge, but the pending checkpoint does not prove one. An acknowledged call attempt remains recorded even if the native CLI fails before process start. When that error has no tokens and no usage report, the returned entry has unknown, partial coverage; zero reported tokens do not prove a free call.
An omitted reasoning_effort means the CLI selected its default and Minion cannot verify the effective level. The enclosing attempt record stores the parent-pinned executor and source commit; usage_source=executor_reported marks a saved returned usage claim. Returned-call token counts and completion flags are executor-reported claims, not verified billing.
Claude call receipts can include estimated_cost_usd and estimated_cost_complete. Cost coverage is independent of token coverage; a missing amount or completion flag leaves the estimate incomplete. These are CLI estimates, not billed charges.
Codex recovers root/helper session counters; Claude uses the CLI's native per-model modelUsage totals. When Claude's result scope is recognized, each resumed call records only its new invocation's usage; this still includes prior context used in new requests, including cache reads. An unrecognized result scope leaves coverage incomplete. Claude tokens.cache_creation_input_tokens counts cache creation separately from tokens.cached_input_tokens cache reads; both are included in tokens.input_tokens, so total tokens are input+output. Provider call receipts carry token scope (invocation, root_only, known_subset or unknown); attempt records use usage_coverage.scope=attempt and retain combined gap flags for missing or malformed final reports, unverified helpers, partial collection and unattributed cumulative output. Missing usage_coverage means coverage is unknown.
Missing, damaged or oversized provider accounting logs/records can leave incomplete saved counts; an incomplete attempt showing zero tokens was not necessarily free. A damaged local usage JSON file makes minion usage fail and name the file. Oversized Codex rollout lines are skipped to recover later counters. After worker expiry, Codex uses the existing fixed stop-grace budget to collect usage and verify owned rollouts before the final Result; it cannot restart model work. Direct Codex sessions without that budget may retain only stream counters when their action context is canceled. Application validation failure does not invalidate an already completed call's accounting.
Records contain metadata/counts, not prompts/replies. A running attempt can remain after a crash, hard kill, panic or failed final save. Graceful cancellation records failed. A pre-call checkpoint records dispatch intent; cancellation there can leave an incomplete call count without proving a charged request. Retries are separate attempts.
Records are stored in a usage/ directory beside state_path (var/usage/ relative to the config file's directory by default), independently of notification state. Keep this directory to retain usage history. On Unix, usage saves require directory fsync support for usage/ and its parent. On Windows, Minion syncs the record before replacement but cannot sync directory metadata; crash durability remains best effort. -config loads and validates the local configuration but does not run service preflight; -dir bypasses configuration entirely and cannot be combined with -config. A missing usage directory produces an empty report.
GitHub does not offer a webhook for a user account's personal mentions. The notifications API is the simplest route for a dedicated machine user: a paginated poll for each whitelisted repository, no public server, and no repository webhook administration. This service requests read and unread participating notifications since a durable cursor for each repository, obeys X-Poll-Interval, then inspects content in whitelisted threads. Notification reason is thread-level, so the service validates the actual command in each new body/comment and deduplicates by source item plus command, not by notification ID.
Initial issue and pull request descriptions and their comments, pull request reviews and line comments are scanned. Edits to an old issue or pull request description are not treated as new commands, because REST updated_at also changes for unrelated thread activity. GitHub notification delivery settings still control what reaches this account. Discussions are unsupported because their notification subjects can have no URL, leaving no reliable thread ID. If you later need guaranteed coverage of every edit, discussion support, or lower latency, install repository or GitHub App webhooks for the relevant events. A GitHub App posts as its own app bot identity, rather than the configured bot account.
The service stores no access token on disk. var/state.json tracks the account and repository cursors, next poll and invitation-check times, completed command IDs, first terminal decisions, failed job attempts, pending failed or capacity-deferred threads, pending fix commit records, issued task comments/reviews, fixed failure replies and fix pushes, and at most one selected worker's accepted action. That record binds the request and worker identity to the original runtime deadline; assignment is saved before worker delivery. State replacement syncs the new file before rename. On Linux and macOS, selected-worker startup syncs the state path ancestors, and a directory-sync error while saving acceptance or assignment stops the service; host power-loss durability still depends on the filesystem. Windows does not sync directory metadata, so the supported recovery guarantee there is process restart, not host power loss. Each pending fix record contains its SHA and, when capped, the original round maximum, final-candidate blocker warning and actual full-critic approval. The record is saved before push and supplies those facts to the publication reply. A saved assignment is treated as possibly delivered after restart and is never redispatched; a pull proven undelivered in the running process can release that marker. The accepted action keeps its original deadline. After a model-call acknowledgement, Minion never repeats the model work. A selected action with a committed complete result can resume publication after restart; an assigned action without such a result remains terminal. If the original command remains unedited and eligible, Minion attempts the fixed failure reply for terminal failures that did not already issue a task comment, review or fix push. An issued write suppresses that extra reply while its remote effect is reconciled. Edits or later authorization changes can also suppress a reply; retained records may need manual inspection of any pushed branch. The account cursor seeds a repository cursor when that repository is newly whitelisted. If a repository notification poll fails, only its cursor stays in place; the other repositories and pending retries continue. A repository with more than 2,000 notifications in one poll fails closed at 20 pages, preserving its cursor for intervention. A failed thread is retried from its original scan time with backoff up to 64 minutes while the cursor continues past other notifications. A permanently inaccessible thread remains in the pending list and is logged with its URL; an operator can remove that entry after checking it for valid commands. Generated code changes are pushed to branches for requesters to turn into pull requests.