An AI collaborator for your GitHub repository, powered by OpenCode, running entirely on GitHub Actions.
It reads your code, reviews your pull requests, audits merge readiness, answers questions in your issues, and writes fixes when you ask, with genuine judgment, in its own voice, on infrastructure you already have.
The Ask Mirrobot badge in the row above is this project's own.
- What you get
- How it works
- Quick start (~10 minutes)
- Trigger words & identity
- Configuration, every secret and variable
- Pause switches
- Cross-repo guest mode
- The workflows
- Security
- Development
- Documentation
- FAQ
| The agent will... | |
|---|---|
| 🔍 Analyze every new issue | Assess the problem, hunt duplicates, trace the root cause through your code, suggest a fix and labels |
| 🧠 Review every pull request | Severity-graded findings (🔴 🟠 🟡 🔵), inline on the exact lines, with a justified verdict in plain words |
| ✅ Audit merge readiness | On /mirrobot-check: documentation, consistency, good practices, gating the merge via a real status check |
| 💬 Answer anywhere | @mirrobot-agent in any issue or PR: questions answered, code investigated, other PRs reviewed on demand |
| 🌡️ Judge without ceremony | "Check this PR, is it good?" gets a calibrated opinion — a verdict-first comment, findings anchored inline where they earn it, an honest scope note — never a heavyweight review object unless you actually ask for a review |
| 🔧 Contribute code | "Fix this" → it branches, implements, self-reviews, and opens a PR, never touching your workflows |
| 🛡️ Refuse to be exploited | Injection-hardened, adversarially tested, scrubbed workspaces, least-privilege tokens |
Closer to a colleague than a linter: it reads the whole thread, remembers its own previous reviews, admits uncertainty, argues when you're wrong, and stays quiet when it has nothing to add.
Verdict: changes requested: the tracing hook has two blocking problems; the rest is solid and I'd approve once they're addressed.
logger.py:42🔴 Critical:redact_keysskipsAuthorizationandx-api-key, so the "redacted" trace file contains raw credentialslogger.py:88🟠 Major: the flush happens after the response returns, so a crashed request loses its own traceREADME.md🟡 Minor: newTRACE_FILEenv var is undocumentedtests/🔵 Info: no coverage for the new hook; the existing fixtures would take a case easilyThis review was generated by an AI assistant.
Every finding carries its severity. The verdict always says why. Push a fix and the follow-up review sees only the delta, and checks whether its previous feedback was actually addressed.
flowchart TB
subgraph events["GitHub events"]
C[comment created]
I[issue opened]
P[PR activity]
end
C --> ROUTER["<b>Agent Router</b><br/>one comment → one run<br/>(dispatches by comment id)"]
I --> ISSUE["<b>Issue Analysis</b>"]
P --> STUB["<b>PR Review Trigger</b><br/>zero-secret stub: decide +<br/>pending merge-blocker status"]
P --> GATE["<b>Compliance Gate</b><br/>redundant pending-status poster"]
STUB -->|review wanted only| REVIEW
subgraph agent["Agent workflows, privileged, run from the default branch"]
REVIEW["<b>PR Review</b><br/>severity + verdicts"]
BOT["<b>Bot Reply</b><br/>strategies + review kit"]
COMPL["<b>Compliance Check</b><br/>merge audit"]
ISSUE
end
ROUTER --> BOT & REVIEW & COMPL
subgraph engine["Every agent run"]
S1["1 · Identity: account PAT or App token<br/>(scope-gated, fail-fast)"]
S2["2 · Trusted artifacts secured<br/>before any PR checkout"]
S3["3 · Workspace scrub + taint alarm<br/>(removed files quarantined, readable as data)"]
S4["4 · Context: 3-block review memory,<br/>diffs, trusted roster, requester trust line"]
S5["5 · Prompt assembled from parts<br/>(34 parts → 13 mode manifests)"]
S6["6 · OpenCode session<br/>the agent does the work"]
S7["7 · Verification: footer/SHA checks,<br/>repair, reactions"]
S1 --> S2 --> S3 --> S4 --> S5 --> S6 --> S7
end
REVIEW & BOT & COMPL & ISSUE --> engine
The core security principle: PR content is only ever data. Privileged workflows always execute the default branch's copy of themselves; a malicious PR cannot redefine the pipeline that reviews it. Untrusted text never touches a shell except through environment variables. Everything the agent auto-loads from a checkout (instruction files, agent configs, skills) survives only when its bytes match a state a trusted branch shipped; otherwise it's removed, logged, and quarantined for the agent to read as data. The details are in security; read them before you open this to strangers.
And the platform checks itself: a fixture suite and prompt structural checks run in CI on every change to .github/, so drift shows up as a red build instead of a silent change. CI enforces machine contracts only (assembly integrity, placeholder completeness, marker couplings, security behavior) — prompt prose is never pinned, so forks can reword anything.
Copy .github/ from this repo into yours, on the default branch. That's the whole platform.
Two optional companions, each enabling one feature:
| Also copy | Enables |
|---|---|
decrypt_share_link.py |
Decrypting the agent's encrypted session share links locally (without it, links are captured and masked but not recoverable) |
tools/mention-worker/ |
Cross-repo guest mode, the agent answering mentions in repos it's not installed in |
Option A: bot account (recommended): a dedicated user account with a classic PAT, public_repo scope only (no workflow scope; the platform rejects it), invited to the repo as a collaborator with Write.
Option B, GitHub App: create an App with Contents: read · Issues: read/write · Pull requests: read/write, install it on the repo.
Both work identically downstream; presence of the account token selects account mode automatically.
Settings → Secrets and variables → Actions → Secrets, the minimal set:
| Secret | What |
|---|---|
OPENCODE_MODEL |
Main model, provider/model format, e.g. anthropic/claude-sonnet-4 |
OPENCODE_CONFIG_JSON |
Your complete OpenCode config, minified to one line: permission profile, providers, API keys, MCP servers, plugins. Start from the committed template: .github/actions/bot-setup/permissions.example.json |
| + identity | ACCOUNT_GH_TOKEN (account mode), or BOT_APP_ID + BOT_PRIVATE_KEY (App mode) |
# minify the template (or your own config) to one line first:
python minify_json_secret.py .github/actions/bot-setup/permissions.example.json > config.min.json
gh secret set OPENCODE_MODEL -R <owner>/<repo> --body "anthropic/claude-sonnet-4"
gh secret set OPENCODE_CONFIG_JSON -R <owner>/<repo> < config.min.json # bash/macOS/Linux
gh secret set ACCOUNT_GH_TOKEN -R <owner>/<repo> # paste the PAT when promptedGet-Content config.min.json | gh secret set OPENCODE_CONFIG_JSON -R <owner>/<repo> # PowerShellFor orientation, a minimal config looks like:
{
"provider": { "anthropic": { "options": { "apiKey": "sk-ant-..." } } },
"permission": { "...": "the block from permissions.example.json" }
}Then run Agent Bootstrap once (Actions → Agent Bootstrap → Run workflow, needs repo write access): it creates every tuning variable with its safe default (never overwrites) and prints the full secrets checklist into the run summary. Full reference with where-to-get-everything: Configuration.
Settings → Branches → protect your default branch → require the compliance-check status. Now nothing merges until the agent's audit passes.
One trap to avoid: do not add a "Restrict updates" rule with admin-only bypass; it deadlocks bot merges entirely.
Open an issue, open a PR, or comment @mirrobot-agent hello; within about a minute you should see a 👀 reaction, then a reply. The router's run in the Actions tab is the audit trail of every dispatch decision.
Two systems with two different jobs:
Triggers, what summons the agent. The default words are @mirrobot, @mirrobot-agent, /mirrobot-review, and /mirrobot-check. They all come from one variable. BOT_TRIGGERS holds plain names, and each name expands into a mention plus two commands. Concretely, the default value:
BOT_TRIGGERS = "mirrobot, mirrobot-agent"
makes the agent answer to @mirrobot and @mirrobot-agent, run a review for /mirrobot-review and /mirrobot-agent-review, and run compliance for /mirrobot-check and /mirrobot-agent-check. Renaming works the same way: set BOT_TRIGGERS="acme" and the bot answers to @acme, /acme-review, /acme-check, and the mirrobot words stop routing (setting the variable replaces the defaults). When the variable is unset, the names come from the bot's own identity, so an account-mode install answers to its account name.
Identity, who the agent is. When the agent asks "did I write this review?" (loop guards, review attribution, footer verification), it compares against its logins: whatever you put in the BOT_IDENTITIES variable, plus (in account mode) the account name it detects live from its token. If neither exists, the stock names apply. "mirrobot" is the agent's name, not an identity: a user who happens to be named mirrobot is never treated as the agent itself. Bootstrap seeds both variables.
Details: configuration · renaming.
Full reference with worked examples lives in docs/configuration.md. Summary:
Secrets (Settings → … → Secrets): the three above are the minimal set. Optional: OPENCODE_API_KEY (provider key when it's not inside your config), OPENCODE_FAST_MODEL (global small_model, session-title generation), SHARE_LINK_PUBKEY (encrypted share links; python decrypt_share_link.py setup does everything).
Variables (Settings → … → Variables):
| Variable | Default | What it tunes |
|---|---|---|
AGENT_PAUSED |
false |
Global kill switch, see pause switches |
OPEN_TRIGGERING |
true |
false = on-demand summons limited to collaborators + trusted roster (auto paths stay open) |
AGENT_PAUSED_PARTS_JSON |
all false |
Per-part pause, see pause switches |
AGENT_MODELS_JSON |
(empty template) | Per-agent models, see docs |
BOT_IDENTITIES |
account login / stock names | Who the agent is (self-detection set) |
BOT_TRIGGERS |
mirrobot, mirrobot-agent |
What summons the agent (stems; commands derive) |
CONTEXT_LIMITS_JSON |
(full budget template) | Context budget: how many comments/reviews/threads the agent reads, lower = smaller prompts. Details |
CONTEXT_IGNORE_AUTHORS |
(empty) | Logins whose posts never enter agent context |
CONTEXT_FILTER_PATTERNS_JSON |
baked AI-noise defaults | Regex patterns dropping matching posts; replaces the defaults |
TRUSTED_AGENT_USERS |
(empty) | Extra friendly users beyond collaborators (collaborators already count) |
PREVIOUS_BOT_REVIEWS_COUNT |
1 |
How many of the agent's own newest reviews are elevated (unfiltered memory). Older ones still appear, filtered, in the history block |
OPENCODE_PLUGINS_JSON (+_1.._5) |
(empty) | Plugin files without committing them, see docs |
FOREIGN_MENTIONS_ENABLED / FOREIGN_MENTIONS_USERS |
false / (empty) |
Cross-repo guest mode, see below |
Notes:
- Model strings are
provider/model; the provider can be a built-in (whenOPENCODE_API_KEYsupplies its key) or an entry in yourOPENCODE_CONFIG_JSON. - The runtime protects the config secret: every credential leaf inside it is masked, and the file (plus materialized plugins) is deleted right after opencode reads it. Details.
- Thread context is filtered before it's allocated: hidden (minimized) content never reaches the agent (its own posts included), and the built-in defaults drop known AI-reviewer noise (rate-limit notices, skip posts) while keeping those tools' substantive reviews. Output from other AI reviewers is verified like anyone else's; none of it is treated as a verdict.
AGENT_PAUSED=true pauses everything: the router dispatches nothing, the mention poller stays silent, all agent workflows skip with a visible gray "skipped" (manual dispatches included). The two base-branch marker workflows keep running, so open PRs keep their pending merge-blocker status; pausing never makes a PR mergeable.
AGENT_PAUSED_PARTS_JSON pauses one part at a time:
{ "pr-review": true, "bot-reply": false, "compliance-check": false, "issue-analysis": false }Paused parts skip visibly; the router stops dispatching them; missing keys (or the whole variable) mean not paused; malformed JSON fails the run with a visible error. Typical use: quiet just the issue triage during a label migration, or just the reviewer while you rebase a big stack.
The agent can answer wherever its account is mentioned: any public repo, even ones it's not installed in. Mentions of a bot account become account notifications; an external Cloudflare worker relays them to the mention-poller workflow, which re-verifies everything (allowlist, genuine-mention token, reason) before spawning a guest session under strict guest rules, read-only by default, authority pinned to the allowlist.
Enable (account mode only): add notifications scope to the PAT, set FOREIGN_MENTIONS_ENABLED=true, optionally FOREIGN_MENTIONS_USERS (who may summon it abroad; collaborators already can), and deploy the worker (~10 minutes, free tier). End-to-end latency is about a minute and a half (the home-repo path in the quick start answers in under a minute, the guest path adds the worker relay). Full story: docs/workflows/mention-poller.md.
| Workflow | Trigger | Runs from | What it does |
|---|---|---|---|
| Agent Router | any comment | main | Parses once, dispatches exactly one target by comment id, one visible run per comment, no fan-out |
| PR Review Trigger | PR events | PR's base branch | Zero-secret stub (no checkout, no secrets): decides if a review is wanted, posts the pending merge-blocker status, dispatches PR Review, declined events dispatch nothing |
| PR Review | dispatch only | main | The reviewer: FIRST/FOLLOW-UP protocols, severity-graded findings, verdicts, footer verification and repair |
| Issue Analysis | issue opened | main | Duplicate hunt, root cause, labels, suggested fix |
| Compliance Check | /mirrobot-check |
main | End-of-life merge audit; posts the compliance status + report |
| Compliance Gate | PR events | PR's base branch | Redundant poster of the pending status; goes red rather than letting a transient error make a PR look mergeable |
| Bot Reply on Mention | dispatch only | main | The general agent: conversations, investigations, on-demand reviews, contributions |
| Agent Bootstrap | dispatch (write access) | main | One-time setup: seeds every variable, prints the secrets checklist; state-silent (logs never reveal state) |
| Mention Poller | dispatch only | main | Cross-repo guest mode entry (see the worker) |
| Scrub Fixture Suite | .github/ changes |
the branch under test | The batteries: security fixtures + prompt structural checks + strict YAML validation (run against the pushed branch's own copy) |
| Excerpts Refresh | Weekly schedule + manual dispatch | Default branch | Rebuilds the landing page's card pool from the agent's real posts and deploys docs/ to Pages as an artifact |
The "Runs from" column is the platform's spine: everything that thinks (agents, prompts, scrub, routing) always executes from main, on every PR (the fixture suite is the one deliberate exception: it's CI, and it tests whatever copy it runs on). Only the two zero-secret marker/dispatcher workflows execute from the PR's base branch (a GitHub rule for pull_request[_target] triggers), and their downstream dispatches always target main. Platform updates land on main; your integration branch needs no per-batch sync. The one exception to that: auto-load content (AGENTS.md and friends) evolves on the integration branch with the work it describes. The full sync doctrine lives in design.md.
Per-workflow docs, triggers, knobs, failure meanings, test recipes: docs/workflows/.
- Opened → stub dispatches PR Review → first review: full diff, acknowledgment within its first action, severity-graded findings, a justified verdict
- New commits → (with the
Agent Monitoredlabel) → follow-up: incremental diff only, previous feedback re-verified - Ready to merge →
/mirrobot-check→ compliance audit → status goes 🟢 (clean, or warnings, read the description) or 🔴 (blocking) - Merge: ask the agent and it will merge after its own safety review and after reading the compliance description, not merely because the status is green
The review-request button works too: on repos with the platform, requesting a review from the agent's identity triggers an instant review (same path as /mirrobot-review).
Mention the agent and it picks its own approach, loading the matching instruction set on demand: conversationalist, investigator (evidence-grade codebase exploration), code reviewer (full review flow for any PR, @mirrobot-agent review #42 works from any thread), code contributor (branch → implement → self-review → PR), repository manager (labels, issues, housekeeping).
Built against real adversarial testing (disguised injection PRs, trojan documentation, malicious agent-config files, symlink escapes, evil merges) and re-verified by CI on every change.
- No untrusted interpolation: comment bodies, PR titles, file contents reach shells only as environment variables; a pinned audit proves it
- Privileged execution from the default branch only: the only base-branch workflows are zero-secret, no-checkout markers; a tampered copy of them is powerless by construction
- Workspace scrub, split trust: auto-loaded files (instruction files, skills, configs) survive only when their bytes match a state a trusted branch shipped at-or-after the fork point;
.githubwiring anchors to main alone; everything else is removed and quarantined, readable as data - Least-privilege identity + dying secrets: scope-gated tokens, credential leaves masked, config and plugins deleted right after boot
The full threat model, defense by defense, with the known residuals: docs/security.md.
.github/
├── actions/
│ ├── bot-setup/ # dual-identity token + config passthrough
│ │ └── permissions.example.json # → your OPENCODE_CONFIG_JSON secret
│ └── requester-context/ # trust-line context (association + roster)
├── prompts/
│ ├── security-brief.md # read first in every agent session
│ ├── parts/ # 34 instruction parts (the prose)
│ └── manifests/ # 13 mode manifests (the assembly order)
├── scripts/ # 14 scripts: assembler, scrub, identity
│ # config, discussion fetch, roster, review
│ # kit, mention gauntlet, router, reactions,
│ # share filter, boot cleanup,
│ # + the two CI batteries
└── workflows/ # the 10 workflows above
minify_json_secret.py # JSON → single-line secret string
Changing behavior: find the part (grep -r .github/prompts/parts/), edit it, re-pin if the wording is load-bearing, verify locally (Windows: run these from Git Bash or WSL):
bash .github/scripts/assemble-prompt.sh --verify # manifests resolve
bash .github/scripts/assemble-prompt.sh --list # see every mode
bash .github/scripts/assemble-prompt.sh pr-review-first # read a full prompt
bash .github/scripts/prompt-rule-fixtures.sh # prompt structure green
bash .github/scripts/scrub-fixtures.sh # security fixtures green (add --quick/--only/--parallel for local speed)Contributing: fork → branch → change → batteries green → PR. Your PR will get the full treatment, automated review and compliance check before merge.
- Getting started: the install guide in full detail (identity choice, secrets, branch protection, first hello)
- Design: the execution map, the life of a PR, the trust model, the update doctrine
- Configuration: every variable and secret, with examples and interaction rules
- Customization: prompts, the compliance watch-list, permission profile, renaming the agent
- Security: the threat model, defense by defense
- Workflows: one page per workflow
- ARCHITECTURE.md / STRUCTURE.md: the code-state mirrors (layers, entry points, file inventory); the docs above carry the principles and guides
Repositories running a Mirrobot deployment can carry the Ask Mirrobot badge, linking visitors to the informational page. Two knobs on the badge link:
?bot=— your deployment's bot name, so visitors know who to mention back home; comma-separate several if it answers to more than one (?bot=MyBot,MyBot[bot])&repo=— repoints the page's live-activity strip at the linking repository (&repo=OWNER/REPO)
The page ships in docs/ and publishes through the Excerpts Refresh workflow as a Pages artifact; its "field notes" cards are dealt at random from a weekly-harvested pool of the agent's real posts (harvest knobs).
Cost? Public repos: Actions minutes are free; you pay only LLM usage (typically a few dollars/month for an active project). Private repos: 2,000 free minutes/month. Each run prints its own token usage in the step summary; CONTEXT_LIMITS_JSON and AGENT_MODELS_JSON are the cost knobs.
Providers? Anything OpenCode speaks: OpenAI, Anthropic, self-hosted (Ollama, vLLM), proxies, regional providers, configured in OPENCODE_CONFIG_JSON.
Can it commit code? Yes, when asked (contributor strategy: branch, implement, self-review, open a PR). It never modifies .github/workflows (hard-denied at the permission layer). It merges only after its own safety review + a green compliance check.
Fork PRs? Fully supported, including reviews; fork heads are fetched explicitly and pinned to the API-recorded SHA.
What if it's wrong? It's an AI: review its output critically, correct it in-thread (it reads follow-ups), and improve the parts. The batteries keep your edits from breaking the system silently.
Multiple bots? Different App or account per repo, different secrets, or different prompt parts per fork. The platform is the same.
Built on OpenCode · GitHub Actions · GitHub Apps. MIT License, see LICENSE.
Made for the open-source community: Deploy in ten minutes; you keep your infrastructure and your keys.