
Security News
PHP Foundation Says Chainguard Broke Security Embargo Ahead of Emergency Patches
The PHP Foundation says Chainguard broke a security embargo ahead of critical RCE patches, as leaks disrupted the coordinated release.
oh-my-harness-loop
Advanced tools
Autonomous harness loop OpenCode plugin — gate-driven development workflow
Autonomous gate-driven development workflow plugin for OpenCode.
The harness loop plugin automates your development workflow by driving your project through configured gates (build, test, lint, etc.) until all pass or a hard-stop condition triggers. It hooks into OpenCode's session.idle event to continuously re-check gates and inject continuation prompts when fixes are needed.
Fastest path: ask your AI agent in OpenCode:
setup harness-on
The agent reads docs/SETUP_INSTRUCTIONS_FOR_AGENT.md and walks through the setup for you.
Manual path (5 minutes):
# 1. Install
npm install oh-my-harness-loop@latest
# 2. Register in opencode.json
mkdir -p .opencode
echo '{"plugin":["oh-my-harness-loop@latest"]}' > .opencode/opencode.json
# 3. Copy templates (config + runner stub + gate docs + .gitignore)
cp node_modules/oh-my-harness-loop/templates/init/.opencode/harness.config.json .opencode/
mkdir -p scripts && cp node_modules/oh-my-harness-loop/templates/init/scripts/harness-check.sh scripts/
chmod +x scripts/harness-check.sh
mkdir -p docs/harness/gates && cp node_modules/oh-my-harness-loop/templates/init/docs/harness/gates/*.md docs/harness/gates/
cat node_modules/oh-my-harness-loop/templates/init/gitignore.template >> .gitignore
# 4. Sanity check
./scripts/harness-check.sh pre-work --json
# 5. Restart OpenCode, then run:
# /harness-on
Full walkthrough with troubleshooting: docs/GETTING_STARTED.md
The templates ship with a no-op stub runner (every gate returns PASS). Edit scripts/harness-check.sh and docs/harness/gates/*.md to wire your real checks (tsc, vitest, lint, etc.).
When you npm install oh-my-harness-loop in a project, a postinstall script automatically creates the OpenCode slash-command shims:
.opencode/commands/harness-on.md.opencode/commands/harness-off.mdThese shims make /harness-on and /harness-off appear in OpenCode's autocomplete. The plugin intercepts the commands at runtime via the command.execute.before hook.
The postinstall:
OH_MY_HARNESS_LOOP_SKIP_POSTINSTALL=1 before installing.If you skip the postinstall (or it failed), create the shims manually:
mkdir -p .opencode/commands
cat > .opencode/commands/harness-on.md <<'EOF'
---
description: Start the harness gate loop for the current feature
---
$ARGUMENTS
EOF
cat > .opencode/commands/harness-off.md <<'EOF'
---
description: Cancel the active harness gate loop
---
EOF
Then restart OpenCode for the commands to appear in autocomplete.
| Field | Type | Default | Description |
|---|---|---|---|
runner_path | string | required | Path to runner script (relative to project root) |
gates | string[] | required | Ordered list of gate names |
fail_policy | "auto" | "hybrid" | "ask" | "hybrid" | How to handle failures |
rule_id_format | string | "{id}" | Format string for rule IDs (e.g., "R{id}", "FP #{id}") |
max_total_iterations | number | 100 | Hard cap on total iterations across all gates |
max_iterations_per_gate | number | 10 | Max attempts per gate before escalating |
auto_fix_attempts | number | 3 | In hybrid mode, auto-fix N times before asking user |
cache_ttl_minutes | number | 30 | How long to cache PASS results |
runner_timeout_seconds | number | 300 | Runner subprocess timeout |
completion_promise | string | "HARNESS-COMPLETE" | Promise tag the agent emits to signal completion |
ultrawork_verify_gates | string[] | [] | Gates that require Oracle verification after PASS |
state_file_path | string | ".opencode/harness-loop.local.json" | Where to store loop state |
gate_instructions | object | {} | Per-gate doc paths and skill lists |
Point each gate to project-specific documentation and skills:
{
"gate_instructions": {
"pre-merge": {
"doc": "docs/harness/gates/pre-merge.md",
"skills": ["review-work"]
},
"smoke-e2e": {
"doc": "docs/harness/gates/smoke-e2e.md",
"skills": ["playwright"]
}
}
}
If doc is omitted, the plugin tries docs/harness/gates/<gate>.md by convention.
For gates that depend on external systems (CI, deploys, npm publish):
{
"gate_instructions": {
"post-merge-npm-release": {
"doc": "docs/harness/gates/post-merge-npm-release.md",
"async": true,
"async_max_wait_seconds": 1800,
"async_poll_interval_seconds": 60
}
}
}
Your runner must:
<gate-name> [--feature=<id>] [--force] [--json] as arguments{
"gate": "pre-work",
"status": "PASS | FAIL | SKIP | WAITING | BLOCKED | ERROR",
"checks": [
{ "id": "1.1", "name": "Issue exists", "status": "PASS", "rule_id": "R89" }
],
"next_gate": "in-progress",
"instructions_for_agent": "Required when status is FAIL or BLOCKED",
"wait_seconds": 60,
"rule_ids_violated": ["R29", "R31"]
}
| Command | Description |
|---|---|
/harness-on | Start the harness loop. If a loop is already active, emits an error telling you to use --resume or --restart. |
/harness-on --resume | Rebind an existing loop into the current session. Re-runs the current gate from iteration 0 (gates are idempotent). |
/harness-on --restart | Wipe existing state and start fresh from the first gate. |
/harness-on --force | Start fresh, ignoring cached gate results. |
/harness-on --epic [path] | Start epic mode. See Epic Mode. |
/harness-on --epic --resume | Resume preserved epic at the current story. |
/harness-off | Stop the active loop. Preserves epic state for --resume. |
/harness-off --clean | Stop and wipe all state, including epic. |
Copy the plugin directory into your project:
cp -r .opencode/plugin/harness-loop <your-project>/.opencode/plugin/
Install and build:
cd <your-project>/.opencode/plugin/harness-loop
npm install && npm run build
cd ../../..
Create .opencode/harness.config.json (minimal example):
{
"runner_path": "./scripts/harness-check.sh",
"gates": ["pre-work", "in-progress", "pre-merge"],
"fail_policy": "hybrid"
}
Write or adapt a runner script that accepts <gate> [--json] and outputs the runner contract JSON.
Start the loop:
/harness-on
Two mechanisms let you pause the loop when automatic fixing isn't possible:
Agent token — add this anywhere in the agent's reply:
[HARNESS-OVERRIDE]: <reason for human intervention>
The loop pauses and waits for user approval before continuing.
File override — create .opencode/harness.override.json before running /harness-on:
{
"max_iterations_per_gate": 20,
"skip_gates": ["e2e"]
}
The config is merged once at loop start and the file is auto-deleted when the loop ends.
Drive every story in a multi-story backlog through the gate cycle with a single /harness-on --epic invocation. The plugin advances stories automatically, pauses on failure for operator input (ask policy), and persists progress for --resume.
.opencode/harness.epic.json:
{
"epic_id": "EPIC-24",
"title": "Postgres migration",
"stories": [
{
"id": "STORY-24-0",
"title": "PG governance setup",
"feature_id": "feat/24-0-pg-governance",
"issue_number": 240,
"story": "Set up governance for the Postgres migration ...",
"depends_on": []
},
{
"id": "STORY-24-1",
"title": "PG Alembic baseline",
"feature_id": "feat/24-1-pg-alembic-baseline",
"depends_on": ["STORY-24-0"]
}
]
}
Required fields: id, title. Optional: feature_id, issue_number, story, depends_on (defaults to []).
Add to harness.config.json:
{
"epic": {
"backlog_source": "file",
"backlog_file": ".opencode/harness.epic.json",
"failure_policy": "ask",
"max_iterations_per_epic": 500
}
}
The epic block is optional. Without it, single-story mode works exactly as v305.
| Command | Behavior |
|---|---|
/harness-on | Single-story mode (unchanged) |
/harness-on --epic | Epic mode, default backlog from config |
/harness-on --epic=<path> | Epic mode, custom backlog file |
/harness-on --epic --resume | Resume from preserved epic state |
/harness-off | Preserve epic state (resume-able) |
/harness-off --clean | Full wipe (legacy v305 behavior) |
depends_on is an array of story IDs that must complete before this story is eligible. The plugin topo-sorts at start; cycles and missing references fail loud.
Phase 1 supports only failure_policy: "ask" — when any story exhausts max_iterations_per_gate, the epic pauses and waits for /harness-on --epic --resume.
Phase 2 will add "skip" and "abort".
Per-story toasts:
🚀 Epic "EPIC-24" started: 5 stories. First: "STORY-24-0".
✅ Story "STORY-24-2" done → next "STORY-24-3" (3/5)
⏸️ Story "STORY-24-2" PAUSED at gate "pre-merge". Use /harness-on --epic --resume after fix.
🏆 Epic "EPIC-24" complete! 5/5 stories done.
Full audit trail in state.loop.epic.story_progress array (state file).
The gate_instructions field maps each gate name to a doc path and optional skill list. The plugin reads the doc at loop start and prepends it to the agent's context for that gate.
{
"gate_instructions": {
"pre-merge": {
"doc": "docs/harness/gates/pre-merge.md",
"skills": ["review-work", "code-review"]
},
"smoke-e2e": {
"doc": "docs/harness/gates/smoke-e2e.md"
},
"post-merge": {
"skills": ["git-master"]
}
}
}
doc + skills — explicit doc injected, plus named skills loaded.doc only — doc injected; no extra skills.skills only (no doc) — skills loaded; plugin falls back to docs/harness/gates/<gate>.md by convention. A warning is logged if that file doesn't exist.When doc is omitted entirely, the convention path is tried silently.
Gate instruction docs tell the agent exactly what to do for each gate. Use this structure:
## Hard Rules
- <rule that must never be violated>
## Step-by-Step Procedure
1. <first action>
2. <second action>
## Evidence Requirements
- Paste output of: <command>
## FAIL Conditions
- <condition that causes an automatic FAIL>
Example (docs/harness/gates/smoke-e2e.md):
## Hard Rules
- Never claim smoke:e2e passes without showing actual curl output.
## Step-by-Step Procedure
1. Build: `go build -o ./bin/nano-brain ./cmd/nano-brain/`
2. Start server on port 3199
3. Wait for GET /health → `{"ready":true}`
4. Exercise changed endpoints with curl
5. Kill the server
## Evidence Requirements
- Paste all curl commands and responses in the PR description.
## FAIL Conditions
- Server fails to start
- Any curl returns non-2xx
- Response JSON is missing required fields
Set strict_instructions: true in harness.config.json to require every gate to have an instruction doc before the loop starts:
{
"strict_instructions": true
}
With strict_instructions: true, /harness-on refuses to start and prints a list of gates missing docs. Fix by adding a doc path to gate_instructions or creating the convention path file.
The default (false) warns about missing docs but continues. Useful when adopting the plugin incrementally.
Use async: true for gates that depend on external systems where the result isn't available immediately (CI pipelines, npm publish, deploy health checks).
{
"gate_instructions": {
"post-merge-release": {
"doc": "docs/harness/gates/post-merge-release.md",
"async": true,
"async_max_wait_seconds": 1800,
"async_poll_interval_seconds": 60,
"async_subagent_type": "explore"
}
}
}
async_max_wait_seconds — size this at 2-3x your expected CI or deploy time. A release workflow that normally takes 8 minutes should get at least 1800 seconds (30 min) to account for queue delays.
async_subagent_type — controls which subagent polls the external system. Use "explore" for read-only checks (gh CLI, curl). Use "hephaestus" when the polling subagent may need to take corrective action.
Heartbeat toasts — while waiting, the plugin emits a toast every async_poll_interval_seconds: "⏳ Waiting for post-merge-release (elapsed: 2m30s / max: 30m)". These are visible in the OpenCode status bar.
When async_max_wait_seconds expires without a terminal result, the gate emits FAIL with instructions_for_agent set to the timeout message.
For async gates, your runner returns WAITING when the external system hasn't settled yet. The plugin re-polls after wait_seconds.
#!/bin/bash
# scripts/harness-check.sh post-merge-release --json
status=$(gh run list --workflow=release.yml --limit 1 --json status --jq '.[0].status')
if [[ "$status" == "in_progress" || "$status" == "queued" ]]; then
# Not done yet — tell the plugin to wait 60 seconds and re-poll
echo '{"gate":"post-merge-release","status":"WAITING","checks":[],"wait_seconds":60}'
exit 3
fi
conclusion=$(gh run list --workflow=release.yml --limit 1 --json conclusion --jq '.[0].conclusion')
if [[ "$conclusion" == "success" ]]; then
echo '{"gate":"post-merge-release","status":"PASS","checks":[],"next_gate":null}'
exit 0
else
echo "{\"gate\":\"post-merge-release\",\"status\":\"FAIL\",\"checks\":[],\"instructions_for_agent\":\"Release workflow failed with conclusion: $conclusion. Check the workflow logs.\"}"
exit 1
fi
Key points:
3 signals WAITING to the plugin.wait_seconds in the JSON body overrides async_poll_interval_seconds for that specific poll cycle.PASS, FAIL, SKIP, BLOCKED, ERROR) use exit codes 0, 1, 2, 4, 5 respectively.MIT
FAQs
Autonomous harness loop OpenCode plugin — gate-driven development workflow
The npm package oh-my-harness-loop receives a total of 5 weekly downloads. As such, oh-my-harness-loop popularity was classified as not popular.
We found that oh-my-harness-loop demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
The PHP Foundation says Chainguard broke a security embargo ahead of critical RCE patches, as leaks disrupted the coordinated release.

Security News
PHP releases emergency patches for two critical RCE vulnerabilities as agencies urge organizations to take unpatched sites offline.

Threat Research
/Security News
A supply chain attack compromised LiteLLM 1.107.0 on PyPI and Docker, spreading malware that steals cloud credentials, developer secrets, and AI provider keys.