You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: skills/harness/SKILL.md
+53-10Lines changed: 53 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -26,6 +26,15 @@ Executable protocol enabling any agent task to run continuously across multiple
26
26
/harness add "task description" # Add a task to the list
27
27
```
28
28
29
+
## Activation Marker
30
+
31
+
Hooks only take effect when `.harness-active` marker file exists in the harness root (same directory as `harness-tasks.json`).
32
+
Hook 注册配置在 `hooks/hooks.json`。
33
+
34
+
-`/harness init` and `/harness run` MUST create this marker: `touch <project-path>/.harness-active`
35
+
- When all tasks complete (no pending/in_progress/retryable left), remove it: `rm <project-path>/.harness-active`
36
+
- Without this marker, all hooks are no-ops — they exit 0 immediately
37
+
29
38
## Progress Persistence (Dual-File System)
30
39
31
40
Maintain two files in the project working directory:
@@ -54,6 +63,7 @@ Free-text log of all agent actions across sessions. Never truncate.
54
63
"version": 2,
55
64
"created": "2025-07-01T10:00:00Z",
56
65
"session_config": {
66
+
"concurrency_mode": "exclusive",
57
67
"max_tasks_per_session": 20,
58
68
"max_sessions": 50
59
69
},
@@ -126,6 +136,8 @@ Free-text log of all agent actions across sessions. Never truncate.
126
136
127
137
Task statuses: `pending` → `in_progress` (transient, set only during active execution) → `completed` or `failed`. A task found as `in_progress` at session start means the previous session was interrupted — handle via Context Window Recovery Protocol.
128
138
139
+
In concurrent mode (see Concurrency Control), tasks may also carry claim metadata: `claimed_by` and `lease_expires_at` (ISO timestamp).
140
+
129
141
**Session boundary**: A session starts when the agent begins executing the Session Start protocol and ends when a Stopping Condition is met or the context window resets. Each session gets a unique `SESSION-N` identifier (N = `session_count` after increment).
130
142
131
143
## Concurrency Control
@@ -134,7 +146,23 @@ Before modifying `harness-tasks.json`, acquire an exclusive lock using portable
134
146
135
147
```bash
136
148
# Acquire lock (fail fast if another agent is running)
The lock is held for the entire session. The `trap EXIT` handler releases it automatically on normal exit, errors, or signals. Never release the lock between tasks within a session.
189
+
Modes:
190
+
191
+
-**Exclusive (default)**: hold the lock for the entire session (the `trap EXIT` handler releases it automatically). Any second session in the same state root fails fast.
192
+
-**Concurrent (opt-in via `session_config.concurrency_mode: "concurrent"`)**: treat this as a **state transaction lock**. Hold it only while reading/modifying/writing `harness-tasks.json` (including `.bak`/`.tmp`) and appending to `harness-progress.txt`. Release it immediately before doing real work.
193
+
194
+
Concurrent mode invariants:
195
+
196
+
- All workers MUST point at the same state root (the directory that contains `harness-tasks.json`). If you are using separate worktrees/clones, pin it explicitly (e.g., `HARNESS_STATE_ROOT=/abs/path/to/state-root`).
197
+
- Task selection is advisory; the real gate is **atomic claim** under the lock: set `status="in_progress"`, set `claimed_by` (stable worker id, e.g., `HARNESS_WORKER_ID`), set `lease_expires_at`. If claim fails (already `in_progress` with a valid lease), pick another eligible task and retry.
198
+
- Never run two workers in the same git working directory. Use separate worktrees/clones. Otherwise rollback (`git reset --hard` / `git clean -fd`) will destroy other workers.
162
199
163
200
## Infinite Loop Protocol
164
201
165
202
### Session Start (Execute Every Time)
166
203
167
204
1.**Read state**: Read last 200 lines of `harness-progress.txt` + full `harness-tasks.json`. If JSON is unparseable, see JSON corruption recovery in Error Handling.
168
205
2.**Read git**: Run `git log --oneline -20` and `git diff --stat` to detect uncommitted work
169
-
3.**Acquire lock**: Fail if another session is active
206
+
3.**Acquire lock** (mode-dependent): Exclusive mode fails if another session is active. Concurrent mode uses the lock only for state transactions.
170
207
4.**Recover interrupted tasks** (see Context Window Recovery below)
171
208
5.**Health check**: Run `harness-init.sh` if it exists
172
209
6.**Track session**: Increment `session_count` in JSON. Check `session_count` against `max_sessions` — if reached, log STATS and STOP. Initialize per-session task counter to 0.
@@ -189,13 +226,13 @@ Then pick the next task in this priority order:
189
226
190
227
For each task, execute this exact sequence:
191
228
192
-
1.**Claim**: Record `started_at_commit` = current HEAD hash. Set status to `in_progress`, log `Starting [<task-id>] <title> (base=<hash>)`
229
+
1.**Claim** (atomic, under lock): Record `started_at_commit` = current HEAD hash. Set status to `in_progress`, set `claimed_by`, set `lease_expires_at`, log `Starting [<task-id>] <title> (base=<hash>)`. If the task is already claimed (`in_progress` with a valid lease), pick another eligible task and retry.
193
230
2.**Execute with checkpoints**: Perform the work. After each significant step, log:
194
231
```
195
232
[timestamp] [SESSION-N] CHECKPOINT [task-id] step=M/N "description of what was done"
196
233
```
197
-
Also append to the task's `checkpoints` array: `{ "step": M, "total": N, "description": "...", "timestamp": "ISO" }`
198
-
3.**Validate**: Run the task's `validation.command`wrapped with `timeout`: `timeout <timeout_seconds> <command>`. If no validation command, skip. Before running, verify the command exists (e.g., `command -v <binary>`) — if missing, treat as `ENV_SETUP` error.
234
+
Also append to the task's `checkpoints` array: `{ "step": M, "total": N, "description": "...", "timestamp": "ISO" }`. In concurrent mode, renew the lease at each checkpoint (push `lease_expires_at` forward).
235
+
3.**Validate**: Run the task's `validation.command` with a timeout wrapper (prefer `timeout`; on macOS use `gtimeout` from coreutils). If `validation.command` is empty/null, log `ERROR [<task-id>] [CONFIG] Missing validation.command` and STOP — do not declare completion without an objective check. Before running, verify the command exists (e.g., `command -v <binary>`) — if missing, treat as `ENV_SETUP` error.
199
236
- Command exits 0 → PASS
200
237
- Command exits non-zero → FAIL
201
238
- Command exceeds timeout → TIMEOUT
@@ -217,6 +254,9 @@ For each task, execute this exact sequence:
217
254
218
255
When a new session starts and finds a task with `status: "in_progress"`:
219
256
257
+
- Exclusive mode: treat this as an interrupted previous session and run the Recovery Protocol below.
258
+
- Concurrent mode: only recover a task if either (a) `claimed_by` matches this worker, or (b) `lease_expires_at` is in the past (stale lease). Otherwise, treat it as owned by another worker and do not modify it.
259
+
220
260
1.**Check git state**:
221
261
```bash
222
262
git diff --stat # Uncommitted changes?
@@ -243,6 +283,7 @@ Each error category has a default recovery strategy:
243
283
| Category | Default Recovery | Agent Action |
244
284
|----------|-----------------|--------------|
245
285
|`ENV_SETUP`| Re-run init, then STOP if still failing | Run `harness-init.sh` again immediately. If fails twice, log and stop — environment is broken |
286
+
|`CONFIG`| STOP (requires human fix) | Log the config error precisely (file + field), then STOP. Do not guess or auto-mutate task metadata |
246
287
|`TASK_EXEC`| Rollback via `git reset --hard <started_at_commit>`, retry | Verify `started_at_commit` exists (`git cat-file -t <hash>`). If missing, mark failed at max_attempts. Otherwise reset, run `on_failure.cleanup` if defined, retry if attempts < max_attempts |
247
288
|`TEST_FAIL`| Rollback via `git reset --hard <started_at_commit>`, retry | Reset to `started_at_commit`, analyze test output to identify fix, retry with targeted changes |
248
289
|`TIMEOUT`| Kill process, execute cleanup, retry | Wrap validation with `timeout <seconds> <command>`. On timeout, run `on_failure.cleanup`, retry (consider splitting task if repeated) |
@@ -251,7 +292,7 @@ Each error category has a default recovery strategy:
251
292
252
293
**JSON corruption**: If `harness-tasks.json` cannot be parsed, check for `harness-tasks.json.bak` (written before each modification). If backup exists and is valid, restore from it. If no valid backup, log `ERROR [ENV_SETUP] harness-tasks.json corrupted and unrecoverable` and STOP — task metadata (validation commands, dependencies, cleanup) cannot be reconstructed from logs alone.
253
294
254
-
**Backup protocol**: Before every write to `harness-tasks.json`, copy the current file to `harness-tasks.json.bak`.
295
+
**Backup protocol**: Before every write to `harness-tasks.json`, copy the current file to `harness-tasks.json.bak`. Write updates atomically: write JSON to `harness-tasks.json.tmp` then `mv` it into place (readers should never see a partial file).
255
296
256
297
## Environment Initialization
257
298
@@ -279,7 +320,7 @@ All log entries use grep-friendly format on a single line:
At session end, update `harness-tasks.json`: increment `session_count`, set `last_session` to current timestamp. Then append:
337
+
At session end, update `harness-tasks.json`: set `last_session` to current timestamp. (Do NOT increment `session_count` here — it is incremented at Session Start.) Then append:
@@ -321,9 +362,11 @@ Does NOT acquire the lock (read-only operation).
321
362
322
363
## Add Command (`/harness add`)
323
364
324
-
Append a new task to `harness-tasks.json` with auto-incremented id (`task-NNN`), status `pending`, default `max_attempts: 3`, empty `depends_on`, and no validation command. Prompt user for optional fields: `priority`, `depends_on`, `validation.command`, `timeout_seconds`. Requires lock acquisition (modifies JSON).
365
+
Append a new task to `harness-tasks.json` with auto-incremented id (`task-NNN`), status `pending`, default `max_attempts: 3`, empty `depends_on`, and no validation command (required before the task can be completed). Prompt user for optional fields: `priority`, `depends_on`, `validation.command`, `timeout_seconds`. Requires lock acquisition (modifies JSON).
325
366
326
367
## Tool Dependencies
327
368
328
369
Requires: Bash, file read/write, git. All harness operations must be executed from the project root directory.
329
370
Does NOT require: specific MCP servers, programming languages, or test frameworks.
371
+
372
+
Concurrent mode requires isolated working directories (`git worktree` or separate clones). Do not run concurrent workers in the same working tree.
0 commit comments