OpenLoom: a lightweight agent task harness — schedule, monitor, and verify AI coding tasks.
Project description
OpenLoom
Don't trust the agent's word — trust the file system.
OpenLoom is a webhook-driven task harness and observer for OpenCode. Webhook handlers call POST /api/tasks to dispatch a goal; OpenLoom attaches the goal to an OpenCode session, watches the session for activity, and emits a stream of TASK_UPDATED events on the bus. Webhook consumers can take over a stuck session with POST /api/tasks/{id}/abort and follow up with a fresh prompt — even from a phone, without touching the host.
OpenLoom does not replace OpenCode. It fills the gaps in OpenCode's HTTP API: a JSON task API, a session monitor, a webhook notification fan-out, and a web dashboard for browsing live work.
What's in 0.12
0.12 is a YAGNI cut. The previous release also offered an openloom watch runner (single-spec file mode with manual checks / acceptance / step-acknowledgement), a file-inbox dispatch path, a stale-busy alert, file-based notification sinks, and an AI task planner. All of that is gone. The webhook API below is the single control surface.
What stayed:
- The 0.10 task lifecycle (create / start / update / complete / fail / archive / delete).
- The 0.11 webhook notification format with
task_name+timestamp+timestamp_iso+recent_activity(last 3 assistant messages, 1 000 chars each, with a tool-call summary). - The web dashboard (Dashboard / Activity / Tasks / Permissions / session drawer).
What's new in 0.12:
POST /api/tasks/{id}/abort— break a stuck agent loop on the task's session before sending a follow-up prompt.TaskSpecreduced to three fields (name,workspace,goal) — every webhook handler payload is now 4 lines.- Single CLI subcommand —
openloom serveis the only thing the CLI does.init,watch,status,logare removed.
Install
Requires Python 3.11+ and a running OpenCode server.
pip install openloom
# or
uv tool install openloom
Web dashboard (FastAPI + bundled Svelte UI):
pip install "openloom[ui]"
Optional extras — pick what you need; there is intentionally no [all]:
| Extra | Purpose |
|---|---|
ui |
Web dashboard (openloom serve) |
server |
Same as ui (alias) |
Quick start
1. Start OpenCode
OpenLoom talks to OpenCode over HTTP. In a separate terminal:
opencode serve
# or launch the OpenCode app / TUI (it also exposes the API on port 4096)
If OpenCode is not running, openloom serve starts the dashboard but session features stay offline until OpenCode is up.
2. Configure the OpenCode connection (optional)
Most local setups need no env vars — OpenLoom defaults to http://127.0.0.1:4096 with no HTTP auth (same as opencode serve without OPENCODE_SERVER_PASSWORD).
If your OpenCode server uses HTTP basic auth:
export OPENLOOM_OPENCODE_URL=http://127.0.0.1:4096
export OPENLOOM_OPENCODE_USERNAME=opencode
export OPENLOOM_OPENCODE_PASSWORD=your-password
Full env-var reference: docs/configuration.md.
3. Start OpenLoom
openloom serve
# or with overrides
openloom serve --host 0.0.0.0 --port 55413
Open http://127.0.0.1:55413 for:
- Dashboard — task list, per-session token usage, model breakdown.
- Activity — sessions by workspace, archived tasks.
- New Task — the same
POST /api/tasksbody as a webhook would send. - Permissions — pending tool approvals with Once / Always / Deny.
- Session drawer — full transcript, diff, metadata.
The UI static assets are pre-built inside the wheel — no Node.js required at install time.
4. Create a task via webhook
curl -X POST http://127.0.0.1:55413/api/tasks \
-H 'Content-Type: application/json' \
-d '{
"name": "Fix the type error",
"workspace": "/Users/you/project",
"goal": "There is a type error in src/foo.ts line 42. Fix it and run npm test."
}'
The response is {ok, taskId, status, name, workspace, sessionId}. The task is now pending; on the next harness tick (within 8 seconds) it transitions to running with an OpenCode session id bound to it.
If you have a webhook configured, the next events arrive on it:
{
"event": "TASK_STARTED",
"task_id": "task_abc123",
"task_name": "Fix the type error",
"timestamp": 1749999999.123,
"timestamp_iso": "2026-06-15T00:00:00Z",
"store_version": 1,
"data": {
"session_id": "ses_xyz789",
"summary": "Harness started and bootstrap prompt sent"
}
}
5. Take over a stuck session
# 1. Get the stuck task's id from the webhook or dashboard
TASK=task_abc123
# 2. Abort the in-flight agent loop on the task's session
curl -X POST http://127.0.0.1:55413/api/tasks/$TASK/abort
# 3. Archive the old task so it stops showing in the active list
curl -X POST http://127.0.0.1:55413/api/tasks/$TASK/archive
# 4. Send a follow-up prompt as a new task bound to the same session
curl -X POST http://127.0.0.1:55413/api/tasks \
-H 'Content-Type: application/json' \
-d '{
"name": "Take over: fix the type error",
"sessionId": "ses_xyz789",
"goal": "Forget what you were doing. Just look at src/foo.ts line 42 and fix the type error."
}'
The new task binds to the same OpenCode session; the old task is archived. The session itself keeps its full transcript — both the old and new task can be opened in the dashboard and read each other's messages.
API surface
POST /api/tasks create a task from {name, workspace, goal, sessionId?}
GET /api/tasks list tasks
GET /api/tasks/{id} task detail
POST /api/tasks/{id}/abort release any in-flight agent loop on the task's session
POST /api/tasks/{id}/pause pause a running task
POST /api/tasks/{id}/resume resume a paused task
POST /api/tasks/{id}/complete mark a task complete
POST /api/tasks/{id}/archive archive a task
DELETE /api/tasks/{id} delete an archived task
GET /api/sessions list sessions (monitor snapshot)
GET /api/sessions/{id}/messages full transcript
GET /api/sessions/{id}/diff accumulated diff
GET /api/permissions list pending permissions
POST /api/sessions/{id}/permissions/{pid} respond to a permission
POST /api/sessions/{id}/archive archive a session
POST /api/sessions/{id}/delete hard-delete a session
GET /api/state composite dashboard state
GET /api/events server-sent event stream
GET /api/recent-workspaces recently used workspaces
DELETE /api/recent-workspaces remove from recent
GET /api/browse directory listing for the path picker
POST /api/pick-folder native OS folder picker
Webhook payload format and event types: docs/notifications.md.
Architecture (short)
core/ Harness, store, event bus, protocols
runtime/ OpenCode HTTP client, prompts
levels/
notify/ WebhookSink + NotifyConfig
server/ SessionMonitor, WebSink, ConsoleSink, serve
server/ FastAPI app + routes + static UI ([ui] extra)
docs/ Detailed guides (configuration, notifications, architecture)
tests/ contracts/ holds the architecture-enforcement tests
State changes flow: store write → event emit → sinks. API routes only read the store; the event bus pushes notifications, not data. Details: docs/architecture.md.
Development
uv sync
uv run pytest
uv run ruff check src/ tests/
uv run mypy src/openloom
# Rebuild frontend into the package (before release)
cd frontend && npm install && npm run build
cd .. && uv build
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openloom-0.15.3.tar.gz.
File metadata
- Download URL: openloom-0.15.3.tar.gz
- Upload date:
- Size: 674.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1232c536b1c0704fdaad9cf6bd2f9a9ddcbd89b5b822f3e60c96ef35b7e3e26a
|
|
| MD5 |
ad025060267cb559183a7bec8e3639db
|
|
| BLAKE2b-256 |
7f7bf823a530f3ac6747f9bab6ca121313778dacb42773b26239898230bbff3d
|
Provenance
The following attestation bundles were made for openloom-0.15.3.tar.gz:
Publisher:
release.yml on atbeta/openloom
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openloom-0.15.3.tar.gz -
Subject digest:
1232c536b1c0704fdaad9cf6bd2f9a9ddcbd89b5b822f3e60c96ef35b7e3e26a - Sigstore transparency entry: 2019267598
- Sigstore integration time:
-
Permalink:
atbeta/openloom@de32e6e6156438b964778b7a3d5aaab9e1deccf2 -
Branch / Tag:
refs/tags/v0.15.3 - Owner: https://github.com/atbeta
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@de32e6e6156438b964778b7a3d5aaab9e1deccf2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file openloom-0.15.3-py3-none-any.whl.
File metadata
- Download URL: openloom-0.15.3-py3-none-any.whl
- Upload date:
- Size: 664.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
41a248613909d3838210c928a0222f0dc9242c3ab952ae6616f9fab76fe5a5db
|
|
| MD5 |
a06a49dcfb928c31bde73e2472f64b6f
|
|
| BLAKE2b-256 |
2558b0af17267ed6bb691219daee3b1f77c0d60929a2cc93a599158aaa868bea
|
Provenance
The following attestation bundles were made for openloom-0.15.3-py3-none-any.whl:
Publisher:
release.yml on atbeta/openloom
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openloom-0.15.3-py3-none-any.whl -
Subject digest:
41a248613909d3838210c928a0222f0dc9242c3ab952ae6616f9fab76fe5a5db - Sigstore transparency entry: 2019267745
- Sigstore integration time:
-
Permalink:
atbeta/openloom@de32e6e6156438b964778b7a3d5aaab9e1deccf2 -
Branch / Tag:
refs/tags/v0.15.3 - Owner: https://github.com/atbeta
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@de32e6e6156438b964778b7a3d5aaab9e1deccf2 -
Trigger Event:
push
-
Statement type: