150 lines
7.5 KiB
Markdown
150 lines
7.5 KiB
Markdown
# Agent CI
|
|
|
|
Agent CI is a private Gitea webhook host that turns issue and pull-request comments into resumable
|
|
OpenCode planning and implementation workflows. Docker Compose runs the webhook worker and a
|
|
private OpenCode server on the same Docker network as Gitea.
|
|
|
|
## Commands
|
|
|
|
| Location | Command | Behavior |
|
|
| --- | --- | --- |
|
|
| Issue | `/agent plan [message]` | Create and independently review a new canonical plan. |
|
|
| Issue | `/agent discuss <message>` | Resume the newest planner and post its response. |
|
|
| Issue | `/agent implement [message]` | Create, review, and open a PR. A prior plan is optional. |
|
|
| Issue | `/agent iterate [message]` | Revise and review the plan once, unless an agent PR is open or merged. |
|
|
| PR | `/agent iterate [message]` | Resume an agent implementation and its reviewer once. |
|
|
| PR | `/agent fix [message]` | Start a fresh one-shot fix session and push one commit. |
|
|
|
|
The requester must have Gitea `write`, `admin`, or `owner` permission on the repository. Each
|
|
accepted command gets separate queued and started comments. Final plans, PR results, failures, and
|
|
remaining review findings are posted separately.
|
|
|
|
## Deploy
|
|
|
|
1. Create a Gitea bot user with repository read/write access and an API token.
|
|
2. Copy `.env.example` to `.env` and set the external network, Gitea URL, and provider-qualified
|
|
OpenCode models.
|
|
3. Create `secrets/gitea_token`, `secrets/webhook_secret`, and
|
|
`secrets/opencode_server_password`. Use high-entropy values for both secret/password files.
|
|
4. Build the image:
|
|
|
|
```sh
|
|
docker compose build
|
|
```
|
|
|
|
5. Authenticate the configured OpenCode providers before starting the persistent server:
|
|
|
|
```sh
|
|
docker compose run --rm opencode opencode auth login
|
|
docker compose run --rm opencode opencode auth list
|
|
```
|
|
|
|
6. Start the services:
|
|
|
|
```sh
|
|
docker compose up --no-build -d
|
|
```
|
|
|
|
7. In Gitea, create a JSON webhook targeting `http://agentci:8080/webhooks/gitea`. Set the same
|
|
webhook secret and subscribe to issue comments, PR timeline comments, and PR review comments.
|
|
|
|
OpenCode caches provider state. After adding or changing authentication on an already running
|
|
deployment, run the one-off `auth login` command above and then `docker compose restart opencode`.
|
|
|
|
`/health/live` reports process health. `/health/ready` returns 503 until the OpenCode server is
|
|
healthy and every configured model exists, supports tool calls, accepts its configured variant, and
|
|
has a connected provider. The worker leaves jobs queued while the runtime is unavailable.
|
|
|
|
## OpenCode
|
|
|
|
Models use OpenCode's `provider/model` format. Planning, implementation, and research can use
|
|
different providers. Optional `AGENTCI_PLAN_VARIANT` and `AGENTCI_IMPLEMENT_VARIANT` values are
|
|
passed directly to OpenCode for providers that support variants. `AGENTCI_RESEARCH_VARIANT`
|
|
configures the research subagent and defaults to `high`. `AGENTCI_EXPLORE_MODEL` and
|
|
`AGENTCI_EXPLORE_VARIANT` configure OpenCode's explore agent and default to
|
|
`openai/gpt-5.6-luna` with `low`.
|
|
|
|
`AGENTCI_OPENCODE_VERSION` controls the npm version or range installed into the image and defaults
|
|
to `^1`. The build verifies that the resolved version is still OpenCode 1.x and prints it. Compose
|
|
requests a no-cache build so the configured range is resolved again on each build. Runtime
|
|
auto-update is disabled so an image cannot cross into OpenCode 2.x after it is built.
|
|
|
|
The trusted configuration is `opencode/opencode.json`. It grants `permission: "allow"` globally
|
|
and to every built-in or custom agent that Agent CI can invoke. Repository-local OpenCode config
|
|
and external plugins are disabled so a clone cannot replace the service policy. OpenCode's default
|
|
plugins remain enabled for provider authentication. Its scanned home and global configuration paths
|
|
are root-owned and read-only, and external skill discovery is disabled, so an agent cannot persist
|
|
instructions for later repositories. CodeGraph, Context7, and `gh_grep` are configured as MCP
|
|
servers. Set `AGENTCI_CONTEXT7_API_KEY` to raise Context7 rate limits.
|
|
|
|
The `research` subagent can only use Exa web search, Context7, and the `gh_grep` public-code search
|
|
MCP. All filesystem, shell, editing, task, and other tools are denied for that agent. Exa is enabled
|
|
with `OPENCODE_ENABLE_EXA=1`. Agent CI initializes or refreshes CodeGraph before every parent turn
|
|
and locally excludes `.codegraph/` from Git.
|
|
|
|
### Security boundary
|
|
|
|
OpenCode does not provide an OS sandbox. It can execute shell commands, edit Git metadata and
|
|
environment files, access external directories, use loopback and network services, bind ports, and
|
|
invoke subagents without approval. It can access every shared workspace, development tool, runtime
|
|
credential, and mounted file readable by the non-root container user. Environment filtering is
|
|
only accidental-exposure hygiene and cannot protect readable files from shell commands.
|
|
|
|
Docker remains the OS boundary. The services run as non-root without added capabilities,
|
|
privileged mode, an unconfined seccomp/AppArmor profile, or a nested `bubblewrap` sandbox. The only
|
|
host bind mount is the read-only installer directory; there are no
|
|
writable host filesystem mounts. The OpenCode HTTP server is not published to the host, is password
|
|
protected, and is reachable by Agent CI over an internal Compose network.
|
|
|
|
## Development environments
|
|
|
|
`AGENTCI_INSTALL_SCRIPTS` is a comma-delimited ordered list of development environment installers.
|
|
The supplied `python` and `dotnet` scripts install only their runtimes; they can be replaced or
|
|
removed. Configure them with `AGENTCI_PYTHON_VERSION` and `AGENTCI_DOTNET_CHANNEL`:
|
|
|
|
```env
|
|
AGENTCI_INSTALL_SCRIPTS=python,dotnet,company-tools
|
|
AGENTCI_PYTHON_VERSION=3.13
|
|
AGENTCI_DOTNET_CHANNEL=10.0
|
|
```
|
|
|
|
Every name resolves to a file in `install-scripts/`, mounted read-only at
|
|
`/etc/agentci/install-scripts`. Names cannot contain paths and duplicates are rejected. Installers
|
|
run after each implementation clone or branch sync and fail the job on an unknown script, timeout,
|
|
or non-zero exit. They receive no Agent CI or Gitea secret values in their environment, but remain
|
|
trusted operator code. Tools persist under `/var/lib/agentci/dev-tools`, and OpenCode can read or
|
|
modify them through its unrestricted shell. See `install-scripts/README.md` for the script contract.
|
|
|
|
Agent CI continues to create branches, validate diffs, commit, and push after OpenCode returns. This
|
|
keeps workflow behavior deterministic, but unrestricted OpenCode is not prevented from running Git
|
|
commands itself.
|
|
|
|
## State and recovery
|
|
|
|
The `agentci_data` volume contains SQLite, workflow clones, and installed development runtimes.
|
|
The `opencode_home` volume contains provider authentication, OpenCode's database, and resumable
|
|
sessions. Tea's Gitea token configuration is regenerated in an ephemeral tmpfs and is not copied to
|
|
`opencode_home`. Back up both persistent volumes together.
|
|
|
|
The OpenCode migration tags existing workflows as Codex-owned and preserves their session IDs for
|
|
rollback, but OpenCode refuses to resume them. Follow-up commands against those workflows ask for a
|
|
new plan or implementation. Queued jobs survive restart. An in-progress job is aborted in OpenCode
|
|
and marked failed instead of being replayed because a partial model turn may already have changed
|
|
files. Git pushes are never forced.
|
|
|
|
## Development
|
|
|
|
Install and validate with:
|
|
|
|
```sh
|
|
uv sync
|
|
uv run ruff check .
|
|
uv run pyright
|
|
uv run pytest
|
|
docker compose config
|
|
docker compose build
|
|
```
|
|
|
|
The tests fail if any tracked Python file exceeds 250 lines. Prompts and JSON schemas live outside
|
|
Python so orchestration modules remain small and readable.
|