How It Works
Upkeep runs as a series of coordinated cycles. Each cycle follows a fixed pattern, and cycles repeat in a loop with a configurable pause between them.
Cycles
- Managerbot runs first. It opens the daily standup, assigns PR reviewers, merges ready PRs, and writes status files for each active repo.
- Role bots run in parallel. Forkbot goes first so new repos are available to other bots in the same cycle. Each bot checks notifications, reads its devlog for context, looks at status files for its action items, does its work, then posts a devlog entry and standup comment.
- Managerbot runs again. It catches PRs opened during the cycle, updates status files, and cleans up stale branches.
- Cycle report. Discovers all project repos via the Forgejo API, then prints turn usage, devlog/standup completion, open issues and PRs, CI status, and pending admin requests.
Repo discovery
The bots don’t have a hardcoded list of repos to work on. They discover project repos dynamically via the Forgejo API at the start of each cycle.
Project forks live under their own namespace orgs on Forgejo, matching the upstream owner (e.g., simplecov/simplecov, rest-client/rest-client). The upkeep org is reserved for coordination repos (standup, devlog, admin-requests) and bot accounts.
When forkbot imports a new package, it creates the namespace org and forks the repo there. Other bots find it automatically on their next cycle.
Prompt assembly
Each bot gets a system prompt assembled from several sources:
- Its persona file (one markdown file per bot in
bots/) - A base prompt template with Forgejo access details, tea CLI examples, helper script docs, and turn budget
- Shared skills loaded into all bot prompts
- Bot-specific skills
The base prompt and all skills are embedded in the Go binary so it works standalone without the source tree. Files on disk override their embedded versions, and new files are merged in alongside the defaults.
Process management
The runner sets Setpgid: true on each Claude process so it and its children (tea, curl, git) share a process group. On timeout or interrupt, the entire process group gets SIGTERM. In loop mode, SIGINT/SIGTERM finishes the current bot before exiting rather than killing everything immediately.
Providers
The provider field controls which CLI runs the bot. Available providers:
- claude (default) – uses the Claude CLI with
--output-format stream-json. System prompt via--system-promptflag. Turn budget via--max-turns. - copilot – uses the GitHub Copilot CLI. System prompt written to
copilot-instructions.mdin the workspace. Turn budget via--max-autopilot-continues. - codex – uses the OpenAI Codex CLI. System prompt written to
AGENTS.mdin the workspace. No turn limit flag.
Ecosystem support
Ecosystem reference files cover 13 languages so the bots can pick up packages in any of them: Ruby, Go, npm, Python, Rust, Java, PHP, NuGet, Perl, Swift, Elixir, Dart, and Haskell.