This controller is for autonomous open-source maintenance. It is intentionally paced like a careful maintainer:
- Discover open issues and the account's existing PRs.
- Score issues using labels, maintainer activity, discussion context, focus, assignment state, and issue age.
- Follow existing PRs before starting new work.
- Apply a global active-PR budget, per-repository limits, daily quotas, and adaptive cooldowns so contribution pace follows repository health.
- Prepare an isolated worktree and ask Codex to reproduce, implement, and test one focused issue.
- Reject dirty worktrees, oversized diffs, missing validation commands, and changes that produce no evidence.
- Only then push a branch, open a PR, and leave a link on the source issue.
- Revisit one existing PR at a time when checks fail, a maintainer requests changes, or the branch has a conflict.
- Merge only when checks, approval, clean state, current SHA, and repository ownership all pass the merge gate.
The issue body is untrusted input. It cannot change the controller protocol, request credentials, broaden the file scope, or authorize external writes.
Create a local configuration from the public template. The working configuration is intentionally ignored by Git because it contains local paths, repository selection, and account-specific settings:
cp config.toml.example config.toml
$EDITOR config.tomlconfig.toml is deliberately dry-run. The first scan is read-only and can
use GitHub-wide discovery without publishing anything:
python3 maintainer_agent.py --config config.toml --json scanPrepare a reviewable task manifest without writing to GitHub:
python3 maintainer_agent.py --config config.toml prepare \
--repository OWNER/REPOSITORY --issue 1Check the complete live boundary without making any GitHub write:
python3 maintainer_agent.py --config config.toml doctorThe command deliberately does not fall back to the human gh keyring token.
When GH_TOKEN is present, it performs one read-only request against the first
allowlisted repository and checks the local checkout, clean worktree, upstream
fetch remote, fork push remote, and validation commands. It exits with status 2
until live mode is fully ready.
Live execution is an explicit, separate operation. Start from the reviewed template, set the bot commit email, and run the doctor before any execute call:
cp config.live.toml.example config.live.toml
$EDITOR config.live.toml
python3 maintainer_agent.py --config config.live.toml doctorSet both identity.email and identity.login in the live config. The login is
the GitHub App bot login, normally ending in [bot]; it lets the controller
track both existing human-authored PRs and PRs created by the App.
Only after the doctor reports Live readiness: READY may live writes occur. A
direct execute call is available for an explicit one-off run:
MAINTAINER_LIVE_CONFIRMATION=I_UNDERSTAND_EXTERNAL_PR \
GH_TOKEN="$SHORT_LIVED_GITHUB_APP_TOKEN" \
python3 maintainer_agent.py --config config.live.toml execute \
--repository OWNER/REPOSITORY --issue 1The live token should be a short-lived GitHub App installation token. Do not
put a human PAT, private key, or token in config.toml, state JSON, Obsidian,
PR bodies, or logs. live_cycle.py mints an installation token for one cycle,
passes it only to the controller process, and never writes it to disk.
config.toml remains available for manual dry-run analysis. It is a local,
git-ignored file created from config.toml.example. The installed
github-maintainer-live.service obtains a fresh installation token from
live.env for each cycle, and github-maintainer-live.timer invokes it every
30 minutes; the old dry-run timer is disabled to avoid competing state writers.
After the GitHub App has been created and installed, prepare the local secret
boundary. Keep the private key outside this repository with mode 0600:
cp live.env.example live.env
$EDITOR live.env
cp config.live.toml.example config.live.toml
$EDITOR config.live.toml
mkdir -p ~/.config/systemd/user
cp systemd/github-maintainer-console.service ~/.config/systemd/user/
cp systemd/github-maintainer-live.service.example ~/.config/systemd/user/github-maintainer-live.service
cp systemd/github-maintainer-live@.service.example ~/.config/systemd/user/github-maintainer-live@.service
cp systemd/github-maintainer-live.timer.example ~/.config/systemd/user/github-maintainer-live.timer
systemctl --user daemon-reload
systemctl --user enable --now github-maintainer-live.timer
systemctl --user enable --now github-maintainer-console.serviceThe timer first runs doctor, then starts one live cycle. Each cycle searches
GitHub-wide for eligible public Issues, refreshes all open PRs authored by the
account, handles one existing PR follow-up before new work, applies the durable
human-paced quota, and evaluates the strict merge gate last. The Console queues
single-Issue work through github-maintainer-live@<workflow-id>.service; it
does not run a second private worker or bypass the shared lock.
- It does not auto-tag releases, delete branches, or force-close issues.
- Automatic follow-up is limited to one existing PR per cycle and only handles failed checks, requested changes, or a dirty/behind branch. It requires an App-owned head branch, an unchanged head SHA, an isolated worktree, a bounded diff, and all configured validation commands passing before push.
- Automatic merge requires a non-draft PR with at least one completed check
suite, no failed or pending checks, maintainer approval, no outstanding
review request, a clean merge state, and an exact head SHA. The base
repository must also be owned by the installed App account. External upstream
PRs therefore stop at
no_merge_candidateuntil an upstream maintainer merges them. - It refuses new work when the global active-PR budget, per-repository budget, daily quota, or negative-health cooldown is exhausted.
- It fetches the details of existing PRs and classifies failed checks, review requests, conflicts, pending checks, and stale maintenance state before considering new work.
- Each scan also records read-only repository health: open PR volume, Renovate/ Dependabot-style dependency PRs, latest default-branch workflow state, and recent releases. Current workflow health uses the latest run per workflow; historical failures remain available as audit evidence.
- It excludes security issues and assigned issues from automatic claiming.
- It keeps worktrees after a PR opens so follow-up review can continue from the same state.
- Automatic PR creation is disabled in
config.toml; it is enabled only by the reviewed live template and remains blocked until the full live preflight passes. - The coding worker receives no short-lived GitHub App token; only the controller performs push, PR comments, and merge requests.
- A failed follow-up leaves its worktree and manifest for explicit inspection; the next timer does not blindly retry the same failed write.
The controller is a local worker, not a GitHub Action. This keeps the coding agent, local test dependencies, and Obsidian memory loop in one auditable place.
Public files use placeholders and relative or systemd-specifier paths. Do not
commit config.toml, config.live.toml, live.env, private keys, state,
logs, local user identifiers, or repository-specific private context. Before
publishing a change, inspect both the working tree and the complete Git
history. Treat credentials, user content, local identifiers, and unnecessary
account metadata as private by default.
Start the local control plane against the live configuration:
python3 console/console_server.py --config config.live.tomlOpen http://127.0.0.1:8787. The console reads the latest cycle, shows the PR
and issue queue including GitHub-wide candidates, displays repository health,
and exposes the same multi-agent stages used by the Live worker. Starting an
Issue queues a durable systemd workflow; Scout and Diagnose run in
parallel, Implement and Verify work in an isolated checkout, and Writer
commits, pushes, creates the PR, and links the source Issue after all gates
pass. The panel exposes output, evidence, pause/resume/cancel/retry controls,
and the current worker state.
The Backend execution section reads state/execution.json, which is written
by the live controller and manual scan controller with a cross-process lock. It
shows the current cycle phase, safe command labels, target repository or issue,
and recent results while a cycle is running. Tokens, private keys, prompts, and
raw command output are never written to this trace.
The Console is a control and observation plane, not a credential holder. The
systemd worker owns the external write path, mints a short-lived App token for
the process, and enforces the same live preflight and ownership gates for both
timer cycles and browser-queued workflows. Pointing the Console at
config.toml remains an explicit dry-run choice.
The Console's 启动统一工作流 action and the systemd live cycle both call
workflow_engine.py. They share the same durable state machine and Writer:
- Scout and Diagnose inspect the repository in parallel with read-only Codex workers.
- Arbiter records an evidence-based go/no-go decision.
- Implement edits only an isolated worktree and cannot commit or push.
- Verify runs every configured validation command plus a read-only diff review.
- Communicate drafts the PR body from the verified evidence.
- Writer is the only stage allowed to commit, push, create a PR, or comment, and only after the live preflight and ownership gates pass.
Each run is recorded in state/workflows.json with per-Agent status, safe
command labels, streamed command/Agent output, artifact paths, stage events,
structured implementation and verification previews, and blocking reasons.
The live systemd path enables Writer only with config.live.toml, a fresh App
installation token, and the explicit live confirmation. Writer checkpoints
commit, push, PR creation, and Issue linking so a worker restart resumes
idempotently instead of duplicating external writes.
The unified workflow panel is also an operation console. 暂停, 继续, and
取消 persist durable controls consumed by the systemd worker; the browser
does not need to own the worker process. Stopped runs can be retried; the retry
keeps a retry_of link to the prior evidence. The browser subscribes to
/api/workflows/<run-id>/events using SSE and falls back to polling if the
stream is unavailable. GET /api/workflows/<run-id>/artifacts/<name> exposes
only sanitized state-directory artifact previews for local observation.