feat: switch to opencode
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# Agent CI
|
||||
|
||||
Agent CI is a private Gitea webhook host that turns issue and pull-request
|
||||
comments into resumable Codex planning and implementation workflows. It runs as
|
||||
one persistent Docker Compose service on the same Docker network as Gitea.
|
||||
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
|
||||
|
||||
@@ -15,68 +15,89 @@ one persistent Docker Compose service on the same Docker network as Gitea.
|
||||
| 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. |
|
||||
|
||||
Anyone who can comment on an issue or pull request can enqueue commands. Each
|
||||
command gets separate queued and started comments. Final plans, PR results,
|
||||
failures, and remaining review findings are posted separately.
|
||||
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 and internal
|
||||
Gitea URL.
|
||||
3. Create `secrets/gitea_token` containing the bot token and
|
||||
`secrets/webhook_secret` containing a high-entropy webhook secret.
|
||||
4. Build and start the service:
|
||||
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 up --build -d
|
||||
./scripts/build.sh
|
||||
```
|
||||
|
||||
5. Authenticate Codex interactively in the persistent container:
|
||||
5. Authenticate the configured OpenCode providers before starting the persistent server:
|
||||
|
||||
```sh
|
||||
docker compose exec agentci codex login --device-auth
|
||||
docker compose exec agentci codex login status
|
||||
docker compose run --rm opencode opencode auth login
|
||||
docker compose run --rm opencode opencode auth list
|
||||
```
|
||||
|
||||
6. In Gitea, create a JSON webhook targeting
|
||||
`http://agentci:8080/webhooks/gitea`. Set the same secret and subscribe to
|
||||
issue comments, PR timeline comments, and PR review comments.
|
||||
6. Start the services:
|
||||
|
||||
`/health/live` reports process health. `/health/ready` returns 503 until Codex
|
||||
authentication is usable. The worker leaves jobs queued while authentication is
|
||||
missing.
|
||||
```sh
|
||||
docker compose up --no-build -d
|
||||
```
|
||||
|
||||
## Configuration
|
||||
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.
|
||||
|
||||
Model, reasoning effort, review-pass counts, bot identity, branch prefix, and
|
||||
turn timeout use `AGENTCI_` environment variables. Defaults are shown in
|
||||
`.env.example`. Gitea credentials and webhook secrets are intentionally
|
||||
file-based Compose secrets.
|
||||
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`.
|
||||
|
||||
Every planning and implementation session can delegate external research to a
|
||||
read-only `research` subagent. It defaults to `gpt-5.6-luna` with high reasoning
|
||||
and has public network access, live web search, Context7 documentation lookup,
|
||||
and `gh_grep` public GitHub code search. Configure its model and effort with
|
||||
`AGENTCI_RESEARCH_MODEL` and `AGENTCI_RESEARCH_REASONING`. Context7 works
|
||||
without authentication at lower rate limits; set the optional
|
||||
`AGENTCI_CONTEXT7_API_KEY` for authenticated usage. The key is passed only to
|
||||
Codex's Context7 MCP transport and is excluded from agent shell environments.
|
||||
`/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.
|
||||
|
||||
Planning, implementation, and all review sessions also receive the local
|
||||
CodeGraph MCP server for repository structure, symbol relationships, and change
|
||||
impact. Agent CI initializes or refreshes the index before every Codex turn and
|
||||
locally excludes `.codegraph/` from Git. The research subagent intentionally
|
||||
does not receive CodeGraph.
|
||||
## OpenCode
|
||||
|
||||
### Development environments
|
||||
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_INSTALL_SCRIPTS` is a comma-delimited ordered list of development
|
||||
environment installers. The supplied `python` and `dotnet` scripts install only
|
||||
their runtimes; they are ordinary scripts that can be replaced or removed.
|
||||
Implementation agents remain responsible for restoring project dependencies
|
||||
and selecting build/test commands. Configure the supplied scripts with
|
||||
`AGENTCI_PYTHON_VERSION` and `AGENTCI_DOTNET_CHANNEL`:
|
||||
`scripts/build.sh` supplies a unique required cache key on every invocation. The image then runs
|
||||
`npm install -g 'opencode-ai@^1'`, verifies the installed major version, and prints it. Compose also
|
||||
requests a no-cache build. A direct build without `OPENCODE_REFRESH` fails rather than silently
|
||||
reusing an old OpenCode installation layer. This is intentionally fresh rather than reproducible.
|
||||
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 plugins are disabled so a clone cannot replace the service policy. OpenCode's 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 has the same unrestricted permissions as every other agent. Its prompt
|
||||
asks it to focus on external evidence, but this is guidance rather than an isolation boundary.
|
||||
Agent CI initializes or refreshes CodeGraph before every 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
|
||||
@@ -84,44 +105,29 @@ AGENTCI_PYTHON_VERSION=3.13
|
||||
AGENTCI_DOTNET_CHANNEL=10.0
|
||||
```
|
||||
|
||||
Every name resolves to an executable file in `install-scripts/`, mounted
|
||||
read-only at `/etc/agentci/install-scripts`. Names cannot contain paths and
|
||||
duplicates are rejected. See `install-scripts/README.md` for the script contract.
|
||||
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.
|
||||
|
||||
Installers run in order after each implementation clone or branch sync and fail
|
||||
the job on an unknown script, timeout, or non-zero exit. They receive no AgentCI
|
||||
or Gitea secret values in their environment, but remain trusted operator code
|
||||
running as the service user. Tools persist under `/var/lib/agentci/dev-tools`;
|
||||
its `bin` directory is added to implementation agents' `PATH` with read-only
|
||||
sandbox access. The agents can use those tools for builds and validation, but
|
||||
Agent CI does not impose host-side build commands.
|
||||
|
||||
Planning/review commands can only read their workflow clone and have no shell
|
||||
network access. Implementation/fix commands can edit the clone but cannot
|
||||
modify `.git`; they can reach public internet destinations while private and
|
||||
loopback destinations remain blocked. Codex's interactive Git trust check is
|
||||
skipped because every turn runs non-interactively against a service-owned clone;
|
||||
the configured filesystem and network permissions still apply. Git credentials
|
||||
exist only in the service-owned clone/push subprocess and are not inherited by
|
||||
Codex turns. The image and Compose capability/security settings let the non-root
|
||||
service create Codex's nested `bwrap` sandbox. They follow Codex's secure
|
||||
devcontainer pattern instead of making the service container privileged. Do not
|
||||
remove Codex's configured filesystem and network restrictions. In particular,
|
||||
`systempaths=unconfined` removes Docker's outer masked `/proc` subpaths so the
|
||||
nested user/PID namespace can mount its own procfs. Runtimes such as CoreCLR
|
||||
require `/proc/self/maps`; Codex still controls visibility through the fresh
|
||||
procfs and its filesystem policy.
|
||||
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, persistent workflow clones, and
|
||||
installed development runtimes.
|
||||
`codex_home` contains login state and resumable Codex sessions. Both are kept
|
||||
indefinitely and should be backed up together.
|
||||
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.
|
||||
|
||||
Queued jobs survive restart. An in-progress job is marked failed after restart
|
||||
instead of being replayed, because replaying a partially completed model turn
|
||||
could duplicate changes. Git pushes are never forced.
|
||||
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
|
||||
|
||||
@@ -132,7 +138,9 @@ uv sync
|
||||
uv run ruff check .
|
||||
uv run pyright
|
||||
uv run pytest
|
||||
docker compose config
|
||||
./scripts/build.sh
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user