Arche is a CLI-first TypeScript control plane for running self-hosted development workflows from Jira tickets.
It includes:
- an
archeCLI - a Fastify server for webhooks and machine-facing APIs
- a separate Node worker for long-running jobs
- SQLite for local state, logs, and locks
- local Docker sandboxes for isolated runs
The recommended deployment model today is VM/VPS host mode with CLI + server + worker.
docker-compose is still available, but only as a secondary packaging or demo option.
At minimum you need:
- Node.js 22+
- npm
gitdocker- Jira access if you want to process real tickets
- GitLab access if you want to publish real merge requests
- an OpenRouter account and API key (
USER_OPENROUTER_API_KEY); Arche uses the OpenRouter TypeScript SDK and calls the OpenRouterchat/completionsAPI
For end users, the recommended path is to install the arche binary into the PATH.
From the repository:
npm install
npm run build
npm install -g .
arche initThe init wizard:
- creates or updates
.arche/environment(project install layout; shipped defaults live ininstall/env.defaultinside the package) - creates or updates
orchestrator.ymlwith the branch prefix and optional default repository fallback - initializes SQLite automatically in
${ARCHE_RUNTIME_ROOT}/arche.db - creates runtime directories
- can configure
USER_OPENROUTER_API_KEYfor the default OpenRouter-backed executor profile
For a non-interactive bootstrap:
arche init --yes --forceFor local development without a system-wide global install:
npm install
npm run build
npm link
arche initOnce the package is published, the intended install flow is:
npm install -g arche
arche init.arche/environment: runtime configuration and secrets for this workspace (written byarche init).env(optional): still loaded first if present; use for ad hoc overrides or legacy setupsorchestrator.yml: business rules and sandbox configurationruntime/arche.db: SQLite databaseruntime/repos: local Git mirrorsruntime/runs: worktrees and run artifactsruntime/logs: runtime logs
Example:
DATABASE_URL=./runtime/arche.db
ARCHE_CONFIG_PATH=./orchestrator.yml
ARCHE_RUNTIME_ROOT=./runtime
ARCHE_SERVER_HOST=127.0.0.1
ARCHE_SERVER_AUTH_TOKEN=change-me
ARCHE_LOG_LEVEL=info
USER_OPENROUTER_API_KEY=change-me
USER_GIT_AUTHOR_NAME=arche-bot
USER_GIT_AUTHOR_EMAIL=arche-bot@example.invalid
USER_JIRA_BASE_URL=https://example.atlassian.net
USER_JIRA_EMAIL=agent-dev@example.com
USER_JIRA_API_TOKEN=change-me
USER_GITLAB_BASE_URL=https://gitlab.example.com
USER_GITLAB_TOKEN=change-meNotes:
DATABASE_URLnormally does not need to be edited manually.ARCHE_SERVER_HOSTdefaults to127.0.0.1, so the machine API stays local-only unless you opt in to remote exposure.ARCHE_SERVER_AUTH_TOKENprotects the machine API viaAuthorization: Bearer <token>orx-arche-api-token: <token>.USER_GIT_AUTHOR_NAMEandUSER_GIT_AUTHOR_EMAIL: Git identity for automated commits (defaults suit a fresh install)./health,/ready, and/webhooks/jirastay reachable without the server auth token.- if you bind Arche on a non-loopback host such as
0.0.0.0,ARCHE_SERVER_AUTH_TOKENis required and startup will fail without it. ARCHE_LOG_LEVELonly controls process log verbosity forserver,worker, andcli. Keepinfoby default and switch todebugtemporarily when troubleshooting.USER_*variables (OpenRouter key, Git identity, Jira, GitLab): values you supply. Arche reservesARCHE_*andDATABASE_URLfor its own runtime and paths.USER_OPENROUTER_API_KEY: OpenRouter API key from openrouter.ai/keys. The defaultorchestrator.ymlprofile setsapi_key_envto this variable name.- If you add extra executor profiles, point each profile’s
api_key_envat the env var that holds that profile’s OpenRouter key (or reuseUSER_OPENROUTER_API_KEYwhen one key is enough). USER_JIRA_API_TOKENis created from your Atlassian account:https://id.atlassian.com/manage-profile/security/api-tokens- Atlassian currently sets new API tokens to expire after one year by default, and the token value must be copied when it is created.
runs manualuses Jira to fetch the ticket. Without Jira configured, that command will fail.runs manualenforces the same eligibility policy and active-run guard as the Jira webhook unless you pass an explicit force override.- Model traffic goes through OpenRouter; set each profile
base_urltohttps://openrouter.ai/api/v1(the shipped default).
The orchestrator.yml file controls:
- runtime directories
- Jira eligibility policy
- repository fallback routing
- git branch naming
- the Docker sandbox image
- allowed commands
- project validation commands
- optional bootstrap entries
Minimal example:
runtime:
root_dir: ./runtime
repos_dir: ./runtime/repos
runs_dir: ./runtime/runs
logs_dir: ./runtime/logs
db_retention_days: 30
artifact_retention_days: 14
failed_worktree_retention_days: 3
sandbox:
image: node:22-bookworm
network: bridge
shell: /bin/bash
read_only_rootfs: true
tmpfs_paths:
- /tmp
cap_drop:
- ALL
no_new_privileges: true
pids_limit: 256
memory_limit_mb: 2048
cpus: "2"
env_allowlist: []
user: ""
defaults:
allowed_commands:
- pwd
- ls
- find
- cat
- grep
- git status
- git diff
- pnpm install
- pnpm lint
- pnpm test
- pnpm typecheck
- pnpm build
validation_commands:
- pnpm lint
- pnpm test
- pnpm typecheck
routing:
default_repository: my-service
git:
branch_prefix: jira/
workflow:
mode: plan_execute_review
max_review_cycles: 3
require_plan_approval: true
require_publish_approval: true
executors:
defaults:
planner: planner
executor: executor
reviewer: reviewer
profiles:
planner:
driver: openai_compatible_api
base_url: https://openrouter.ai/api/v1
model: openai/gpt-5.4
api_key_env: USER_OPENROUTER_API_KEY
timeout_seconds: 120
max_actions: 8
temperature: 0.1
executor:
driver: openai_compatible_api
base_url: https://openrouter.ai/api/v1
model: openai/gpt-5.4-mini
api_key_env: USER_OPENROUTER_API_KEY
timeout_seconds: 120
max_actions: 8
temperature: 0.1
reviewer:
driver: openai_compatible_api
base_url: https://openrouter.ai/api/v1
model: openai/gpt-5.4-mini
api_key_env: USER_OPENROUTER_API_KEY
timeout_seconds: 120
max_actions: 8
temperature: 0.1Important:
- the default
sandbox.image: arche-app:localvalue is only a placeholder - replace it with a real image available on your machine or VPS
- the image must contain what your repositories need: shell, git, runtime, package manager, build tools
read_only_rootfs,tmpfs_paths,cap_drop,no_new_privileges,pids_limit,memory_limit_mb, andcpusharden Docker isolation without changing the VPS deployment modelenv_allowlistcontrols the only environment variables injected into the run containerallowed_commandsandvalidation_commandsare now exact tokenized commands- Arche no longer executes them through an implicit shell
pnpm testtherefore does not authorizepnpm test --watchorpnpm test && ...routing.default_repositoryis an optional fallback used only when norepo-rulematches the ticketgit.branch_prefixcontrols generated branch names; withjira/,PROJ-123becomesjira/PROJ-123-...- execution is now API-only and always uses the OpenRouter TypeScript SDK against
POST /chat/completionsfor the selected profile on each role plannerandreviewerare direct structured calls;executorruns through a bounded patch loop controlled by Arche
arche doctorThis command currently checks:
gitdocker- SQLite connectivity
- local availability of the sandbox Docker image
- warnings for containerized setups combining
docker.sockwith relative paths
arche repositories add \
--name my-service \
--remote-url git@gitlab.example.com:team/my-service.git \
--local-mirror-path ./runtime/repos/my-service \
--default-branch main \
--gitlab-project-id team%2Fmy-serviceRecommendations:
local-mirror-pathshould point to a stable local pathgitlab-project-idis strongly recommended for merge request creation- for GitLab, this is usually the URL-encoded project identifier, for example
team%2Fmy-service
If you configured a default profile:
arche profiles showArche resolves the target repository through repo-rules.
A rule can filter by:
- Jira project
- label
- issue type
If no rule matches and routing.default_repository is set, Arche falls back to that repository.
Example:
arche repo-rules add \
--name proj-bugs \
--repository my-service \
--jira-project-key PROJ \
--label agent-ready \
--issue-type Bug \
--priority 100arche repositories list
arche profiles show
arche repo-rules list
arche runs listIn one terminal:
arche serve --port 8787arche serve now binds on 127.0.0.1 by default.
For a remote API, set ARCHE_SERVER_HOST=0.0.0.0 or pass --host 0.0.0.0, and configure ARCHE_SERVER_AUTH_TOKEN.
In a second terminal:
arche workerIn a third terminal, if you want the live operator view:
arche dashboardcurl http://127.0.0.1:8787/health
curl http://127.0.0.1:8787/ready
curl -H "Authorization: Bearer ${ARCHE_SERVER_AUTH_TOKEN}" http://127.0.0.1:8787/runs
curl -H "Authorization: Bearer ${ARCHE_SERVER_AUTH_TOKEN}" http://127.0.0.1:8787/runs/<run-id>/logs
curl -H "Authorization: Bearer ${ARCHE_SERVER_AUTH_TOKEN}" http://127.0.0.1:8787/runs/<run-id>/events
curl -H "Authorization: Bearer ${ARCHE_SERVER_AUTH_TOKEN}" http://127.0.0.1:8787/runs/<run-id>/commands
curl -H "Authorization: Bearer ${ARCHE_SERVER_AUTH_TOKEN}" http://127.0.0.1:8787/repositories
curl -H "Authorization: Bearer ${ARCHE_SERVER_AUTH_TOKEN}" http://127.0.0.1:8787/profiles
curl -H "Authorization: Bearer ${ARCHE_SERVER_AUTH_TOKEN}" http://127.0.0.1:8787/repo-rulesarche runs manual PROJ-123Force override for an operator:
arche runs manual PROJ-123 --forcePreconditions:
- Jira configured
- ticket exists
- ticket is eligible under the configured policy
- no active run already exists for the ticket
- the selected execution profiles are configured
- at least one
repo-rulethat resolves the repository
--force bypasses the eligibility policy and active-run guard, but it still requires Jira to be configured, the ticket to exist, the execution profiles to be configured, and a repository rule to resolve the ticket.
arche runs inspect <run-id>
arche runs logs <run-id> --kind allarche dashboardThe dashboard is a read-only terminal UI. It shows:
- all registered workers at the top
- the selected worker's current state and current ticket
- recent runs previously handled by that worker
- a read-only transcript, events, commands, and recent logs for the current run
runs inspect returns run details, retained debug paths (worktree_path, artifacts_path), and the most recent useful logs and events.
By default, the ticket must satisfy the policy from orchestrator.yml:
- assigned to
agent-dev - status
In Progress - label
agent-ready - allowed issue type
- sufficiently long description
The server exposes:
POST /webhooks/jira
Example:
curl -X POST http://127.0.0.1:8787/webhooks/jira \
-H 'content-type: application/json' \
-d '{"issue":{"key":"PROJ-123"}}'There is no webhook signing or Referer check: anyone who can reach this URL can post. Keep the server on 127.0.0.1 for local use, or protect the route at your edge before exposing it publicly.
arche runs logs <run-id>
arche runs logs <run-id> --kind logs
arche runs logs <run-id> --kind events --json
arche runs logs <run-id> --kind commands --json
arche runs logs <run-id> --followNotes:
logsexposes the human-readablesystem|stdout|stderrstreameventsexposes the structured machine audit trail for the runcommandsexposes command history with excerpts and artifact paths- full command output is written to
runtime/logs/runs/<run-id>/commands/
Recommended and supported beta mode.
npm install
npm run build
arche init --yes --force
arche doctor
export ARCHE_SERVER_HOST=0.0.0.0
export ARCHE_SERVER_AUTH_TOKEN=change-me
arche serve --host 0.0.0.0 --port 8787
arche workerThe repository already includes docker-compose.yml and Dockerfile, but this mode is secondary.
Before docker compose up, run arche init (or create .arche/environment yourself) with absolute paths:
ARCHE_RUNTIME_ROOT=/srv/arche/runtime
ARCHE_CONFIG_PATH=/srv/arche/orchestrator.yml
DATABASE_URL=/srv/arche/runtime/arche.db
ARCHE_SERVER_HOST=0.0.0.0
ARCHE_SERVER_AUTH_TOKEN=change-medocker compose up --buildNotes:
- the
serverservice now binds on0.0.0.0explicitly throughARCHE_SERVER_HOST ARCHE_SERVER_AUTH_TOKENis required for any non-loopback bind, including Docker Compose- the
workerservice mounts/var/run/docker.sock - this is required to create per-run Docker sandboxes
orchestrator.ymlandruntime/must be mounted at the same absolute path on both host and container- the image declared in
sandbox.imagemust still be available for the run sandboxes themselves - if you want the most stable path today, use
server+workerdirectly on the host
arche init
arche init --yes --force
arche doctor
arche repositories list
arche repositories add --name repo --remote-url git@gitlab.example.com:group/repo.git --local-mirror-path ./runtime/repos/repo --gitlab-project-id group%2Frepo
arche profiles show
arche repo-rules list
arche repo-rules add --name proj-bugs --repository my-service --jira-project-key PROJ --label agent-ready --issue-type Bug
arche runs list
arche runs manual PROJ-123
arche runs manual PROJ-123 --force
arche runs logs <run-id>
arche runs logs <run-id> --kind all --follow
arche runs logs <run-id> --kind commands --json
arche runs retry <run-id>
arche runs cancel <run-id>
arche serve --port 8787
arche workerGET /healthGET /readyGET /runsGET /runs/{id}GET /runs/{id}/logsGET /runs/{id}/eventsGET /runs/{id}/commandsPOST /runs/manualPOST /runs/{id}/retryPOST /runs/{id}/cancelGET /repo-rulesPOST /repo-rulesGET /repositoriesPOST /repositoriesGET /profilesPOST /webhooks/jira
POST /runs/manual accepts:
{
"ticketKey": "PROJ-123",
"force": false
}npm run lint
npm run typecheck
npm test
npm run build
npm run check