This repository has been archived on 2026-07-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
2026-07-13 18:50:38 +02:00
2026-07-13 18:50:38 +02:00
2026-07-13 18:50:38 +02:00
2026-07-13 18:50:38 +02:00
2026-07-13 18:50:38 +02:00
2026-07-13 18:50:38 +02:00
2026-07-13 18:50:38 +02:00
2026-07-13 18:50:38 +02:00

Olixero Agent Server

This repository contains a stateful, webhook-driven Gitea agent server. It plans issues, reviews plans, implements accepted plans, independently reviews diffs, and publishes pull requests without using Gitea Actions or a Gitea runner.

Architecture

The deployment has two privilege-separated services:

  • controller receives signed webhooks and owns the Gitea write token. It claims requests, publishes comments and labels, validates results, pushes branches, and creates pull requests. It does not contain the OpenCode executable or OAuth data.
  • executor owns the Gitea read token and OpenCode OAuth data. It checks out trusted revisions and runs agents. It never receives the Gitea write token or webhook secret.

Both services share a local SQLite database and disposable workspaces. SQLite runs in WAL mode, so the shared state directory must be on a local filesystem rather than NFS or another network filesystem. Execution concurrency is one because OpenAI OAuth refresh tokens can rotate.

OpenCode conversations are durable:

  • One planner conversation is retained per issue.
  • One implementer conversation is retained per issue and accepted-plan digest.
  • Every review iteration uses a fresh reviewer session.
  • Current Gitea state, repository contents, and stored digests remain authoritative over conversation memory.

Triggers

Existing labels remain supported:

  • agent:plan
  • agent:implement

The controller consumes a trigger label after durably admitting its request. Gitea 1.26 label webhooks expose the resulting label set rather than the exact label that changed, so the controller reconciles current issue state and tracks each claim in SQLite.

New issue comments can use these commands:

/agent plan [optional instruction]
/agent implement [optional instruction]
/agent continue [optional instruction]
/agent retry [optional instruction]
/agent cancel
/agent status

Commands must begin the comment and are processed only on comment creation. Pull-request comments, command edits, bot comments, unauthorized users, and non-command comments are ignored. Ordinary human comments remain part of the issue digest and invalidate stale accepted plans. Agent command comments are control messages and are excluded from that digest.

Security Model

  • Webhooks require X-Gitea-Signature, verified as HMAC-SHA256 over the exact request bytes.
  • The webhook body is limited to 1 MiB and must be JSON.
  • The configured repository ID and full name must match every payload.
  • Actor authorization is fail-closed. Numeric Gitea user IDs are preferred over logins.
  • Webhook deliveries and logical requests are independently deduplicated.
  • OpenCode receives an explicitly limited tool policy and only the read token.
  • External Exa and grep.app MCPs are disabled by default to avoid private-repository data leakage.
  • Repository code is never executed by the credential-bearing services.
  • Git hooks, signing, global configuration, text conversion, symlinks, unsafe paths, and protected automation paths are blocked or independently validated.
  • The publisher revalidates the issue digest, accepted plan, default branch, Git metadata, changed files, file types, and remote branch state before writing.

Prerequisites

  • Gitea 1.26 or newer.
  • Docker Engine with Compose v2, or compatible Podman tooling.
  • A dedicated bot account with a repository-scoped write token.
  • A separate read-only Gitea token for the executor and Gitea MCP.
  • An OpenAI account usable by OpenCode.
  • A reverse proxy providing HTTPS to the controller.

The images pin OpenCode CLI and SDK 1.17.18 and Gitea MCP 1.3.0.

Configuration

Create the environment file and state directories:

cp deploy/.env.example deploy/.env
sudo install -d -o 10001 -g 10001 -m 0700 \
  /srv/olixero-agent/state \
  /srv/olixero-agent/workspaces \
  /srv/olixero-agent/opencode \
  /srv/olixero-agent/cache

Set GITEA_SERVER_URL, GITEA_REPOSITORY, and CI_AGENT_BOT_LOGIN in .env. Configure at least one actor allowlist:

CI_AGENT_ALLOWED_ACTOR_IDS=10,11
CI_AGENT_ALLOWED_ACTORS=

Create local secret files:

install -d -m 0700 deploy/secrets
install -m 0600 /dev/null deploy/secrets/gitea-write-token
install -m 0600 /dev/null deploy/secrets/gitea-read-token
install -m 0600 /dev/null deploy/secrets/gitea-webhook-secret

Populate them as follows:

  • gitea-write-token: bot PAT able to read issues and write comments, labels, branches, and pull requests.
  • gitea-read-token: separate PAT restricted to repository, issue, comment, and pull-request reads.
  • gitea-webhook-secret: a randomly generated webhook secret, for example openssl rand -hex 32.

Local Docker and Podman Compose implementations bind-mount file-backed secrets and may ignore the Compose uid, gid, and mode fields. After populating the files, make them readable only by the container identity:

sudo chown 10001:10001 deploy/secrets/gitea-write-token deploy/secrets/gitea-read-token deploy/secrets/gitea-webhook-secret
sudo chmod 0400 deploy/secrets/gitea-write-token deploy/secrets/gitea-read-token deploy/secrets/gitea-webhook-secret

Do not place tokens directly in .env.

OpenCode Authentication

Build the executor and run the interactive login with the persistent OAuth volume:

docker compose --env-file deploy/.env -f deploy/compose.yaml build executor
docker compose --env-file deploy/.env -f deploy/compose.yaml run --rm --no-deps --entrypoint opencode executor \
  auth login --provider openai --method "ChatGPT Pro/Plus (headless)"

Verify authentication:

docker compose --env-file deploy/.env -f deploy/compose.yaml run --rm --no-deps --entrypoint opencode executor auth list
docker compose --env-file deploy/.env -f deploy/compose.yaml run --rm --no-deps --entrypoint opencode executor models openai
docker compose --env-file deploy/.env -f deploy/compose.yaml run --rm --no-deps --entrypoint opencode executor \
  run --model openai/gpt-5.6-sol "Reply with exactly: OPENCODE_AGENT_AUTH_OK"

OAuth credentials and OpenCode's conversation database remain under ${OPENCODE_DATA_DIR} and are never mounted into the controller.

Gitea Webhook

Create a repository webhook with:

  • Target URL: https://agent.example.com/webhooks/gitea
  • Content type: application/json
  • Secret: the exact content of gitea-webhook-secret
  • Events: issue label changes and issue comments
  • Active: enabled

Gitea's test-delivery button sends a push event rather than an issue event. The server accepts only the configured issue event types, so validate the installation by creating a test issue and adding agent:plan or posting /agent status.

The webhook endpoint commits each verified delivery before returning 204. Gitea 1.26 does not automatically retry failed HTTP deliveries, but manual replay is safe because delivery and business keys are deduplicated separately.

Start

docker compose --env-file deploy/.env -f deploy/compose.yaml build --pull
docker compose --env-file deploy/.env -f deploy/compose.yaml up -d
docker compose --env-file deploy/.env -f deploy/compose.yaml logs -f controller executor

Health endpoints:

GET /healthz
GET /readyz

By default Compose binds the controller to 127.0.0.1:8080; expose it through a reverse proxy. Do not expose the executor.

Operation

Planning creates or resumes the issue's planner conversation. An independent reviewer can request up to three revisions. The accepted plan is published in a protocol-v1 marked comment so plans created by the previous runner remain discoverable.

Keep CI_AGENT_BOT_LOGIN set to the account that authored existing protocol-v1 plan comments if those plans must remain implementable. Changing bot accounts requires replanning outstanding issues.

Implementation requires an accepted plan whose issue digest and base SHA are current. The executor uses a deterministic branch named agent/issue-<number>-p<digest>. The controller publishes only after independently validating the reviewed workspace and remote branch state.

/agent cancel sets a durable cancellation flag. The executor checks it while heartbeating and propagates cancellation into Gitea requests, Git subprocesses, and OpenCode prompts. Interrupted running jobs return to the queue on service restart; interrupted publications remain in the outbox and are retried.

Backup And Recovery

Back up both application state and OpenCode data. For a simple consistent offline backup:

docker compose --env-file deploy/.env -f deploy/compose.yaml stop
sudo cp -a /srv/olixero-agent/state /backup/olixero-agent-state
sudo cp -a /srv/olixero-agent/opencode /backup/olixero-agent-opencode
docker compose --env-file deploy/.env -f deploy/compose.yaml start

Workspaces are disposable and do not need backup. If an OpenCode session is unavailable after restore, the executor creates a replacement conversation while retaining durable job and Gitea artifacts.

Development

Biome provides formatting, import organization, and static analysis. It enforces four-space indentation; lint:fix applies safe fixes, while lint:fix:unsafe is available only for explicitly reviewed semantic fixes. The check command also enforces the repository limits of three files and four folders per directory and 250 lines per non-test source file.

npm --prefix app ci
npm --prefix app run lint:fix
npm --prefix app run check
npm --prefix app test
docker compose --env-file deploy/.env -f deploy/compose.yaml config
docker compose --env-file deploy/.env -f deploy/compose.yaml build

Tests cover webhook signatures and command parsing, canonical issue snapshots, delivery deduplication, label claims, job leases, outbox transitions, and restart-safe SQLite state.

Cutover From The Runner

  1. Build and deploy the controller in a non-executing environment and verify health.
  2. Disable the old Gitea Actions issue workflow and stop the registered agent runner.
  3. Configure the repository webhook.
  4. Start the controller and executor.
  5. Trigger one planning request and verify its status, accepted-plan marker, and durable conversation.
  6. Trigger implementation and verify branch and pull-request reuse.
  7. Remove the old runner registration after the rollback window.

Do not run the old workflow and this server against the same trigger labels simultaneously.

S
Description
a web hook base gitea agentic runner
Readme
334 KiB
Languages
TypeScript 95.9%
JavaScript 3.2%
Dockerfile 0.8%