Clean-room, open-source iLands Runner in Go (specification-first)
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
merith-tk 50e649f06b fix: refresh requestId must be UUID, timestamp as Unix seconds, persist attempt
Vendor wire inspection (device-auth.js, device-identity.js, refresh-attempt.js)

shows the gateway validates requestId as an RFC 4122 v4 UUID and expects

timestamp as a JSON number / whole Unix seconds in the signed payload.

Mirrors the vendor device-refresh-attempt.json persistence so retries reuse

the same requestId for the same refresh token and clear it on success.

Updates protocol docs and resolves open-question #23.
2026-10-08 21:24:44 +01:00
cmd feat: warm ACP harness server — persistent process, session reuse, idle release 2026-10-08 06:26:43 +01:00
docs fix: refresh requestId must be UUID, timestamp as Unix seconds, persist attempt 2026-10-08 21:24:44 +01:00
examples feat: passport registration flow for create_from_passport 2026-10-01 16:30:08 +01:00
internal fix: refresh requestId must be UUID, timestamp as Unix seconds, persist attempt 2026-10-08 21:24:44 +01:00
.gitignore feat: per-agent workspace isolation with git session journal 2026-10-01 18:10:41 +01:00
CONTRIBUTING.md docs: specification set for a clean-room Go iLands runner 2026-09-30 23:23:02 +01:00
go.mod feat: M3/M4 minimum slice — daemon, ACP harness, tool surface 2026-10-08 05:04:35 +01:00
go.sum feat: M3/M4 minimum slice — daemon, ACP harness, tool surface 2026-10-08 05:04:35 +01:00
ilands-openrunner.log fix: stale credential recovery 2026-10-08 05:26:12 +01:00
LICENSE docs: specification set for a clean-room Go iLands runner 2026-09-30 23:23:02 +01:00
logs.txt fix: stale credential recovery 2026-10-08 05:26:12 +01:00
README.md feat: warm ACP harness server — persistent process, session reuse, idle release 2026-10-08 06:26:43 +01:00

ilands-openrunner

A clean-room, open-source iLands Runner written in Go. It connects an agent to the iLands gateway over the same live/heartbeat channel the official runner uses, runs any ACP harness behind a pluggable adapter, and gives ACP harnesses the same "first-class" treatment the official runner gives its supported harnesses (preflight probes, workspace artifacts, capability catalog, tool bridging).

Status: implementation in progress (M3/M4 minimum slice + warm harness). The gateway client, run loop, ACP harness adapter, and tool surface are implemented and unit-tested; a live end-to-end heartbeat turn has been proven against the test agent (run accepted → committed → end_signalled → ready). The warm harness server (Phase 1) is implemented and unit-tested, pending one live verification. See Roadmap.

Status (M3/M4 slice)

Implemented and tested locally (go build ./... && go vet ./... && go test ./...):

  • Gateway client (M2, live) — doctor, auth activate/resume against the production edge without touching any existing agent; the accepted activation proof format was derived black-box and is recorded in docs/protocol.md §4.1 (including the non-obvious rule that timestamp is a JSON number of Unix seconds). Unit tests run the same contract against a local fake gateway that cryptographically verifies every proof.
  • Run loop — daemon holds the runner-live WebSocket (ticket → dial → backoff ladder), executes one heartbeat run at a time (duplicate runIds are re-acked, concurrent ones rejected, steers answered delivered:false), appends a run ledger entry, commits the workspace journal once per session, and reports runtime-status (ready / harness_*).
  • Harness adapter — generic ACP client over stdio (initialize → session/new → session/prompt, permission auto-answer, chunk accumulation), terminal-signal race for end_heartbeat, spawn isolation (per-agent XDG_CONFIG_HOME, shared runner data/state/cache).
  • Warm harness server (Phase 1) — one long-lived opencode acp process per daemon, spawned and initialized at connect (--harness-keep-warm, default on), reused across runs with single-flight turns, restarted on death or workspace config change, released after --harness-idle-ttl; sessions are fresh per run by default, --harness-session=reuse keeps one session up to a reuse cap with session/compact probe and rotation fallback. This deviates from the vendor's per-turn spawn (documented below and in docs/harness-contract.md §2): process boot dominated turn latency.
  • Tool surface — nine iLands tools with strict validation, per-run send breaker, replyTo="current" binding, prepare → sandbox-rpc executor with idempotency headers and 64 KiB result cap; the mcp subcommand serves them over stdio MCP exactly as the workspace opencode.json registers it. Run identity is resolved per tool call from the active-run record (fail closed without a valid one), so the warm harness can serve many runs.
  • Workspace — per-agent git repo (one commit per session), rendered opencode.json (MCP registration + unattended permission policy), AGENTS.md, run prompt files.

Not yet: warm-server live verification (restart the daemon, message Void), refresh proof live (open-question #23), PATH shims, skill materialization, preflight/catalog first-class layer, second harness.

Decisions

Decision Choice
License MPL-2.0 (LICENSE)
Provenance Clean-room. Observed protocol behavior is reimplemented; no code, prompt prose, or binary assets are copied from the proprietary official runner. See CONTRIBUTING.md.
Harness model One generic ACP client + per-harness "first-class" descriptors (opencode first, others pluggable).
Process model One warm ACP process per daemon-agent (spawned at connect, reused across runs, idle-released). Deviation: the official runner spawns a fresh opencode acp per turn; keeping the process warm removes ~21 s of observed boot cost per turn. Documented in docs/harness-contract.md §2.
Official runner relationship Separate, independent project. The official Node runner remains the production runner for our own agent — this project never replaces it automatically.
Vendor CLIs (ilands, dl) Downloaded on first run from the official gateway, cached locally, never mirrored in this repository or in our releases. Redistribution permission from PawLogic to be requested later. Operators may point at their own copy via PATH.
Skills Canonical bundle (41 top-level skills, 53 SKILL.md, 222 files, 4.16 MiB) is fetched with the vendor pack on first run, integrity-checked against a digest manifest, never mirrored. Agent-authored skills live in a durable skills-local/ that bundle updates never wipe (a deliberate divergence from the official runner's wipe-on-update).
Publication Repo is private; publish only on explicit instruction.
Where Local ./ilands-openrunner, private remote ilands-meka/ilands-openrunner on git.merith.xyz (branch main).

Roadmap

Phase Deliverable Gate
0 Repo bootstrap: LICENSE, README, CONTRIBUTING, private remote this repo
1 Specification set (the docs below) done
2 Go: gateway MVP — device auth, live ticket, WebSocket, frame codec done — activation spike against the live gateway
3 Go: run loop + generic ACP + first-class opencode profile, incl. skill materialization + discovery links + skills-local/ run loop + ACP + warm harness server implemented; live heartbeat turn proven on the test agent; warm-server live verify pending; skills not yet — we are here
4 Go: tool surface (compat RPC, MCP server, PATH shims) prepare/RPC + MCP server implemented; live tool receipts + shims pending
5 Go: first-class layer — preflight engine, catalog, status machine, service install, skill doctor checks + global skill/plugin install doctor passes on a clean machine
6 Second ACP harness proves "any ACP harness"
7 CI, arm64/amd64 releases, docs, publish tagged v0.1.0

Documents

Document Contents
docs/protocol.md Wire contract: WebSocket frames, REST endpoints, proofs, local state files, open unknowns.
docs/harness-contract.md The adapter interface, the ACP profile, and what "first-class support" means per harness.
docs/tools.md The agent tool surface: compat RPC, MCP bridge, PATH shims, vendor CLI policy.
docs/architecture.md Package layout, run lifecycle, status state machines, process model.
docs/open-questions.md Behaviors we cannot observe from the client, and the decisions still owed.

Scope notes

  • Single agent per daemon. Two runners bound to the same agent fight over the device slot (the gateway closes the older socket with 4001), so development uses a separate test agent.
  • Wire harness claim. The gateway only accepts a certified harness vocabulary on activation; the runner therefore exposes a configurable wireHarness claim while running whatever harness is configured locally.
  • Not a fork. No shared history, no shared source, no shared assets with the official runner.