npx skills add https://github.com/cexll/myclaude --skill harness一种可执行协议,使任何智能体任务能够在多个会话中持续运行,具备自动进度恢复、任务依赖关系解析、失败回滚和标准化错误处理功能。
/harness init <project-path> # 在项目中初始化 harness 文件
/harness run # 启动/恢复无限循环
/harness status # 显示当前进度和统计数据
/harness add "task description" # 向列表添加任务
仅当 .harness-active 标记文件存在于 harness 根目录(与 harness-tasks.json 相同的目录)时,钩子才会生效。
/harness init 和 必须创建此标记:广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
/harness runtouch <project-path>/.harness-activerm <project-path>/.harness-active在项目工作目录中维护两个文件:
跨会话的所有智能体操作的自由文本日志。永不截断。
[2025-07-01T10:00:00Z] [SESSION-1] INIT Harness initialized for project /path/to/project
[2025-07-01T10:00:05Z] [SESSION-1] INIT Environment health check: PASS
[2025-07-01T10:00:10Z] [SESSION-1] LOCK acquired (pid=12345)
[2025-07-01T10:00:11Z] [SESSION-1] Starting [task-001] Implement user authentication (base=def5678)
[2025-07-01T10:05:00Z] [SESSION-1] CHECKPOINT [task-001] step=2/4 "auth routes created, tests pending"
[2025-07-01T10:15:30Z] [SESSION-1] Completed [task-001] (commit abc1234)
[2025-07-01T10:15:31Z] [SESSION-1] Starting [task-002] Add rate limiting (base=abc1234)
[2025-07-01T10:20:00Z] [SESSION-1] ERROR [task-002] [TASK_EXEC] Redis connection refused
[2025-07-01T10:20:01Z] [SESSION-1] ROLLBACK [task-002] git reset --hard abc1234
[2025-07-01T10:20:02Z] [SESSION-1] STATS tasks_total=5 completed=1 failed=1 pending=3 blocked=0 attempts_total=2 checkpoints=1
{
"version": 2,
"created": "2025-07-01T10:00:00Z",
"session_config": {
"concurrency_mode": "exclusive",
"max_tasks_per_session": 20,
"max_sessions": 50
},
"tasks": [
{
"id": "task-001",
"title": "Implement user authentication",
"status": "completed",
"priority": "P0",
"depends_on": [],
"attempts": 1,
"max_attempts": 3,
"started_at_commit": "def5678",
"validation": {
"command": "npm test -- --testPathPattern=auth",
"timeout_seconds": 300
},
"on_failure": {
"cleanup": null
},
"error_log": [],
"checkpoints": [],
"completed_at": "2025-07-01T10:15:30Z"
},
{
"id": "task-002",
"title": "Add rate limiting",
"status": "failed",
"priority": "P1",
"depends_on": [],
"attempts": 1,
"max_attempts": 3,
"started_at_commit": "abc1234",
"validation": {
"command": "npm test -- --testPathPattern=rate-limit",
"timeout_seconds": 120
},
"on_failure": {
"cleanup": "docker compose down redis"
},
"error_log": ["[TASK_EXEC] Redis connection refused"],
"checkpoints": [],
"completed_at": null
},
{
"id": "task-003",
"title": "Add OAuth providers",
"status": "pending",
"priority": "P1",
"depends_on": ["task-001"],
"attempts": 0,
"max_attempts": 3,
"started_at_commit": null,
"validation": {
"command": "npm test -- --testPathPattern=oauth",
"timeout_seconds": 180
},
"on_failure": {
"cleanup": null
},
"error_log": [],
"checkpoints": [],
"completed_at": null
}
],
"session_count": 1,
"last_session": "2025-07-01T10:20:02Z"
}
任务状态:pending → in_progress(瞬态,仅在活动执行期间设置)→ completed 或 failed。在会话开始时发现状态为 in_progress 的任务意味着前一个会话被中断 — 通过上下文窗口恢复协议处理。
在并发模式下(参见并发控制),任务可能还携带声明元数据:claimed_by 和 lease_expires_at(ISO 时间戳)。
会话边界:当智能体开始执行会话启动协议时,会话开始;当满足停止条件或上下文窗口重置时,会话结束。每个会话获得一个唯一的 SESSION-N 标识符(N = 递增后的 session_count)。
在修改 harness-tasks.json 之前,使用可移植的 mkdir 获取独占锁(在所有 POSIX 系统上原子操作,适用于 macOS 和 Linux):
# 获取锁(如果另一个智能体正在运行,则快速失败)
# 即使从子目录调用,锁键也必须稳定。
ROOT="$PWD"
SEARCH="$PWD"
while [ "$SEARCH" != "/" ] && [ ! -f "$SEARCH/harness-tasks.json" ]; do
SEARCH="$(dirname "$SEARCH")"
done
if [ -f "$SEARCH/harness-tasks.json" ]; then
ROOT="$SEARCH"
fi
PWD_HASH="$(
printf '%s' "$ROOT" |
(shasum -a 256 2>/dev/null || sha256sum 2>/dev/null) |
awk '{print $1}' |
cut -c1-16
)"
LOCKDIR="/tmp/harness-${PWD_HASH:-unknown}.lock"
if ! mkdir "$LOCKDIR" 2>/dev/null; then
# 检查锁持有者是否仍然存活
LOCK_PID=$(cat "$LOCKDIR/pid" 2>/dev/null)
if [ -n "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2>/dev/null; then
echo "ERROR: Another harness session is active (pid=$LOCK_PID)"; exit 1
fi
# 陈旧锁 — 通过 mv 原子回收以避免 TOCTOU 竞争
STALE="$LOCKDIR.stale.$$"
if mv "$LOCKDIR" "$STALE" 2>/dev/null; then
rm -rf "$STALE"
mkdir "$LOCKDIR" || { echo "ERROR: Lock contention"; exit 1; }
echo "WARN: Removed stale lock${LOCK_PID:+ from pid=$LOCK_PID}"
else
echo "ERROR: Another agent reclaimed the lock"; exit 1
fi
fi
echo "$$" > "$LOCKDIR/pid"
trap 'rm -rf "$LOCKDIR"' EXIT
记录锁获取:[timestamp] [SESSION-N] LOCK acquired (pid=<PID>)
记录锁释放:[timestamp] [SESSION-N] LOCK released
模式:
trap EXIT 处理程序会自动释放它)。同一状态根目录中的任何第二个会话都会快速失败。session_config.concurrency_mode: "concurrent" 选择加入):将其视为状态事务锁。仅在读取/修改/写入 harness-tasks.json(包括 .bak/.tmp)和追加到 harness-progress.txt 时持有它。在执行实际工作之前立即释放它。并发模式不变量:
harness-tasks.json 的目录)。如果使用单独的工作树/克隆,请显式固定它(例如,HARNESS_STATE_ROOT=/abs/path/to/state-root)。status="in_progress",设置 claimed_by(稳定的工作进程 ID,例如 HARNESS_WORKER_ID),设置 lease_expires_at。如果声明失败(已经 in_progress 且具有有效的租约),则选择另一个符合条件的任务并重试。git reset --hard / git clean -fd)将破坏其他工作进程。harness-progress.txt 的最后 200 行 + 完整的 harness-tasks.json。如果 JSON 无法解析,请参阅错误处理中的 JSON 损坏恢复。git log --oneline -20 和 git diff --stat 以检测未提交的工作harness-init.sh,则运行它session_count。检查 session_count 是否达到 max_sessions — 如果达到,记录 STATS 并停止。初始化每会话任务计数器为 0。在选择之前,运行依赖关系验证:
depends_on 传递闭包。如果任何任务出现在其自身的链中,则将其标记为 failed,并附带 [DEPENDENCY] Circular dependency detected: task-A -> task-B -> task-A。自引用(depends_on 包含自身 ID)也是循环。depends_on 包含一个已 failed 且永远不会重试的任务(attempts >= max_attempts 或其 error_log 包含 [DEPENDENCY] 条目),则将阻塞的任务标记为 failed,并附带 [DEPENDENCY] Blocked by failed task-XXX。重复此操作,直到没有更多任务可以传播。然后按此优先级顺序选择下一个任务:
"pending" 且所有 depends_on 任务均为 completed 的任务 — 按 priority(P0 > P1 > P2)排序,然后按 id(最低优先)排序"failed" 且 attempts < max_attempts 且所有 depends_on 均为 completed 的任务 — 按优先级排序,然后按失败时间最早排序对于每个任务,执行此确切序列:
声明(原子操作,在锁下):记录 started_at_commit = 当前 HEAD 哈希。将状态设置为 in_progress,设置 claimed_by,设置 lease_expires_at,记录 Starting [<task-id>] <title> (base=<hash>)。如果任务已被声明(in_progress 且具有有效的租约),则选择另一个符合条件的任务并重试。
带检查点执行:执行工作。在每个重要步骤之后,记录:
[timestamp] [SESSION-N] CHECKPOINT [task-id] step=M/N "description of what was done"
同时追加到任务的 checkpoints 数组:{ "step": M, "total": N, "description": "...", "timestamp": "ISO" }。在并发模式下,在每个检查点续租(将 lease_expires_at 向前推)。
3. 验证:使用超时包装器运行任务的 validation.command(首选 timeout;在 macOS 上使用来自 coreutils 的 gtimeout)。如果 validation.command 为空/null,则记录 ERROR [<task-id>] [CONFIG] Missing validation.command 并停止 — 在没有客观检查的情况下不要宣告完成。运行前,验证命令是否存在(例如,command -v <binary>)— 如果缺失,则视为 ENV_SETUP 错误。
* 命令退出码 0 → 通过
* 命令退出码非零 → 失败
* 命令超时 → 超时
4. 记录结果:
* 成功:状态=completed,设置 completed_at,记录 Completed [<task-id>] (commit <hash>),git commit
* 失败:递增 attempts,将错误追加到 error_log。通过 git cat-file -t <hash> 验证 started_at_commit 是否存在 — 如果缺失,则在 max_attempts 时标记为失败。否则执行 git reset --hard <started_at_commit> 和 git clean -fd 以回滚所有提交并删除未跟踪的文件。如果定义了 on_failure.cleanup,则执行它。记录 ERROR [<task-id>] [<category>] <message>。设置状态=failed(当 attempts < max_attempts 时,任务选择算法第 2 步处理重试)
5. 跟踪:递增每会话任务计数器。如果达到 max_tasks_per_session,则记录 STATS 并停止。
6. 继续:立即选择下一个任务(零空闲时间)
completedfailed 或被失败依赖关系阻塞session_config.max_tasks_per_sessionsession_config.max_sessions当新会话启动并发现状态为 "in_progress" 的任务时:
claimed_by 与此工作进程匹配,或 (b) lease_expires_at 已过去(陈旧租约)时,才恢复任务。否则,将其视为由另一个工作进程拥有,不进行修改。检查 git 状态:
git diff --stat # 未提交的更改?
git log --oneline -5 # 自任务开始以来的最近提交?
git stash list # 任何暂存的工作?
检查检查点:读取任务的 checkpoints 数组以确定最后完成的步骤
决策矩阵(通过检查提交消息中是否包含任务 ID 来验证最近的提交是否属于此任务):
| 未提交? | 最近的任务提交? | 检查点? | 操作 |
|---|---|---|---|
| 否 | 否 | 无 | 标记为 failed,附带 [SESSION_TIMEOUT] No progress detected,递增 attempts |
| 否 | 否 | 有 | 验证文件状态是否与检查点声明匹配。如果文件反映了检查点进度,则从最后一步恢复。否则,标记为 failed — 工作丢失 |
| 否 | 是 | 有 | 运行 validation.command。如果通过 → 标记为 completed。如果失败 → git reset --hard <started_at_commit>,标记为 failed |
| 是 | 否 | 有 | 在存在未提交更改的情况下运行验证。如果通过 → 提交,标记为 completed。如果失败 → git reset --hard <started_at_commit> + git clean -fd,标记为 failed |
| 是 | 是 | 有 | 提交未提交的更改,运行 validation.command。如果通过 → 标记为 completed。如果失败 → git reset --hard <started_at_commit> + git clean -fd,标记为 failed |
[timestamp] [SESSION-N] RECOVERY [task-id] action="<action taken>" reason="<reason>"每个错误类别都有默认的恢复策略:
| 类别 | 默认恢复 | 智能体操作 |
|---|---|---|
ENV_SETUP | 重新运行 init,如果仍然失败则停止 | 立即再次运行 harness-init.sh。如果失败两次,则记录并停止 — 环境已损坏 |
CONFIG | 停止(需要人工修复) | 精确记录配置错误(文件 + 字段),然后停止。不要猜测或自动更改任务元数据 |
TASK_EXEC | 通过 git reset --hard <started_at_commit> 回滚,重试 | 验证 started_at_commit 是否存在(git cat-file -t <hash>)。如果缺失,则在 max_attempts 时标记为失败。否则重置,如果定义了 on_failure.cleanup 则运行它,如果 attempts < max_attempts 则重试 |
TEST_FAIL | 通过 git reset --hard <started_at_commit> 回滚,重试 | 重置到 started_at_commit,分析测试输出以确定修复方法,重试并进行针对性更改 |
TIMEOUT | 终止进程,执行清理,重试 | 使用 timeout <seconds> <command> 包装验证。超时时,运行 on_failure.cleanup,重试(如果重复,考虑拆分任务) |
DEPENDENCY | 跳过任务,标记为阻塞 | 记录哪个依赖关系失败,将任务标记为 failed 并附带依赖关系原因 |
SESSION_TIMEOUT | 使用上下文窗口恢复协议 | 新会话通过恢复协议评估部分进度 — 根据验证结果可能完成或失败 |
JSON 损坏:如果 harness-tasks.json 无法解析,请检查是否存在 harness-tasks.json.bak(每次修改前写入)。如果备份存在且有效,则从中恢复。如果没有有效备份,则记录 ERROR [ENV_SETUP] harness-tasks.json corrupted and unrecoverable 并停止 — 任务元数据(验证命令、依赖关系、清理)无法仅从日志中重建。
备份协议:在每次写入 harness-tasks.json 之前,将当前文件复制到 harness-tasks.json.bak。原子地写入更新:将 JSON 写入 harness-tasks.json.tmp,然后 mv 到位(读取器永远不应看到部分文件)。
如果 harness-init.sh 存在于项目根目录中,则在每次会话启动时运行它。脚本必须是幂等的。
示例 harness-init.sh:
#!/bin/bash
set -e
npm install 2>/dev/null || pip install -r requirements.txt 2>/dev/null || true
curl -sf http://localhost:5432 >/dev/null 2>&1 || echo "WARN: DB not reachable"
npm test -- --bail --silent 2>/dev/null || echo "WARN: Smoke test failed"
echo "Environment health check complete"
所有日志条目都使用 grep 友好的单行格式:
[ISO-timestamp] [SESSION-N] <TYPE> [task-id]? [category]? message
[task-id] 和 [category] 在适用时包含(任务范围内的条目)。会话级别的条目(INIT、LOCK、STATS)省略它们。
类型:INIT、Starting、Completed、ERROR、CHECKPOINT、ROLLBACK、RECOVERY、STATS、LOCK、WARN
错误类别:ENV_SETUP、CONFIG、TASK_EXEC、TEST_FAIL、TIMEOUT、DEPENDENCY、SESSION_TIMEOUT
过滤:
grep "ERROR" harness-progress.txt # 所有错误
grep "ERROR" harness-progress.txt | grep "TASK_EXEC" # 仅执行错误
grep "SESSION-3" harness-progress.txt # 所有会话 3 的活动
grep "STATS" harness-progress.txt # 所有会话摘要
grep "CHECKPOINT" harness-progress.txt # 所有检查点
grep "RECOVERY" harness-progress.txt # 所有恢复操作
在会话结束时,更新 harness-tasks.json:将 last_session 设置为当前时间戳。(不要在此处递增 session_count — 它在会话启动时递增。)然后追加:
[timestamp] [SESSION-N] STATS tasks_total=10 completed=7 failed=1 pending=2 blocked=0 attempts_total=12 checkpoints=23
blocked 是在统计时计算的:其 depends_on 包含永久失败任务的待处理任务计数。它不是存储的状态值。
/harness init)harness-progress.txtsession_config 的 harness-tasks.jsonharness-init.sh 模板(chmod +x).gitignore?/harness status)读取 harness-tasks.json 和 harness-progress.txt,然后显示:
blocked = 其 depends_on 包含永久失败任务的待处理任务(计算得出,非存储状态)。[status] task-id: title (attempts/max_attempts)harness-progress.txt 的最后 5 行不获取锁(只读操作)。
/harness add)将新任务追加到 harness-tasks.json,使用自动递增的 ID(task-NNN),状态为 pending,默认 max_attempts: 3,空的 depends_on,且无验证命令(在任务可以完成之前需要)。提示用户输入可选字段:priority、depends_on、validation.command、timeout_seconds。需要获取锁(修改 JSON)。
需要:Bash、文件读/写、git。所有 harness 操作必须从项目根目录执行。不需要:特定的 MCP 服务器、编程语言或测试框架。
并发模式需要隔离的工作目录(git worktree 或单独的克隆)。不要在同一工作树中运行并发工作进程。
每周安装次数
45
仓库
GitHub 星标数
2.4K
首次出现
2026年2月18日
安全审计
安装于
claude-code43
opencode13
github-copilot13
codex13
kimi-cli13
gemini-cli13
Executable protocol enabling any agent task to run continuously across multiple sessions with automatic progress recovery, task dependency resolution, failure rollback, and standardized error handling.
/harness init <project-path> # Initialize harness files in project
/harness run # Start/resume the infinite loop
/harness status # Show current progress and stats
/harness add "task description" # Add a task to the list
Hooks only take effect when .harness-active marker file exists in the harness root (same directory as harness-tasks.json).
/harness init and /harness run MUST create this marker: touch <project-path>/.harness-activerm <project-path>/.harness-activeMaintain two files in the project working directory:
Free-text log of all agent actions across sessions. Never truncate.
[2025-07-01T10:00:00Z] [SESSION-1] INIT Harness initialized for project /path/to/project
[2025-07-01T10:00:05Z] [SESSION-1] INIT Environment health check: PASS
[2025-07-01T10:00:10Z] [SESSION-1] LOCK acquired (pid=12345)
[2025-07-01T10:00:11Z] [SESSION-1] Starting [task-001] Implement user authentication (base=def5678)
[2025-07-01T10:05:00Z] [SESSION-1] CHECKPOINT [task-001] step=2/4 "auth routes created, tests pending"
[2025-07-01T10:15:30Z] [SESSION-1] Completed [task-001] (commit abc1234)
[2025-07-01T10:15:31Z] [SESSION-1] Starting [task-002] Add rate limiting (base=abc1234)
[2025-07-01T10:20:00Z] [SESSION-1] ERROR [task-002] [TASK_EXEC] Redis connection refused
[2025-07-01T10:20:01Z] [SESSION-1] ROLLBACK [task-002] git reset --hard abc1234
[2025-07-01T10:20:02Z] [SESSION-1] STATS tasks_total=5 completed=1 failed=1 pending=3 blocked=0 attempts_total=2 checkpoints=1
{
"version": 2,
"created": "2025-07-01T10:00:00Z",
"session_config": {
"concurrency_mode": "exclusive",
"max_tasks_per_session": 20,
"max_sessions": 50
},
"tasks": [
{
"id": "task-001",
"title": "Implement user authentication",
"status": "completed",
"priority": "P0",
"depends_on": [],
"attempts": 1,
"max_attempts": 3,
"started_at_commit": "def5678",
"validation": {
"command": "npm test -- --testPathPattern=auth",
"timeout_seconds": 300
},
"on_failure": {
"cleanup": null
},
"error_log": [],
"checkpoints": [],
"completed_at": "2025-07-01T10:15:30Z"
},
{
"id": "task-002",
"title": "Add rate limiting",
"status": "failed",
"priority": "P1",
"depends_on": [],
"attempts": 1,
"max_attempts": 3,
"started_at_commit": "abc1234",
"validation": {
"command": "npm test -- --testPathPattern=rate-limit",
"timeout_seconds": 120
},
"on_failure": {
"cleanup": "docker compose down redis"
},
"error_log": ["[TASK_EXEC] Redis connection refused"],
"checkpoints": [],
"completed_at": null
},
{
"id": "task-003",
"title": "Add OAuth providers",
"status": "pending",
"priority": "P1",
"depends_on": ["task-001"],
"attempts": 0,
"max_attempts": 3,
"started_at_commit": null,
"validation": {
"command": "npm test -- --testPathPattern=oauth",
"timeout_seconds": 180
},
"on_failure": {
"cleanup": null
},
"error_log": [],
"checkpoints": [],
"completed_at": null
}
],
"session_count": 1,
"last_session": "2025-07-01T10:20:02Z"
}
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.
In concurrent mode (see Concurrency Control), tasks may also carry claim metadata: claimed_by and lease_expires_at (ISO timestamp).
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).
Before modifying harness-tasks.json, acquire an exclusive lock using portable mkdir (atomic on all POSIX systems, works on both macOS and Linux):
# Acquire lock (fail fast if another agent is running)
# Lock key must be stable even if invoked from a subdirectory.
ROOT="$PWD"
SEARCH="$PWD"
while [ "$SEARCH" != "/" ] && [ ! -f "$SEARCH/harness-tasks.json" ]; do
SEARCH="$(dirname "$SEARCH")"
done
if [ -f "$SEARCH/harness-tasks.json" ]; then
ROOT="$SEARCH"
fi
PWD_HASH="$(
printf '%s' "$ROOT" |
(shasum -a 256 2>/dev/null || sha256sum 2>/dev/null) |
awk '{print $1}' |
cut -c1-16
)"
LOCKDIR="/tmp/harness-${PWD_HASH:-unknown}.lock"
if ! mkdir "$LOCKDIR" 2>/dev/null; then
# Check if lock holder is still alive
LOCK_PID=$(cat "$LOCKDIR/pid" 2>/dev/null)
if [ -n "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2>/dev/null; then
echo "ERROR: Another harness session is active (pid=$LOCK_PID)"; exit 1
fi
# Stale lock — atomically reclaim via mv to avoid TOCTOU race
STALE="$LOCKDIR.stale.$$"
if mv "$LOCKDIR" "$STALE" 2>/dev/null; then
rm -rf "$STALE"
mkdir "$LOCKDIR" || { echo "ERROR: Lock contention"; exit 1; }
echo "WARN: Removed stale lock${LOCK_PID:+ from pid=$LOCK_PID}"
else
echo "ERROR: Another agent reclaimed the lock"; exit 1
fi
fi
echo "$$" > "$LOCKDIR/pid"
trap 'rm -rf "$LOCKDIR"' EXIT
Log lock acquisition: [timestamp] [SESSION-N] LOCK acquired (pid=<PID>) Log lock release: [timestamp] [SESSION-N] LOCK released
Modes:
trap EXIT handler releases it automatically). Any second session in the same state root fails fast.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.Concurrent mode invariants:
harness-tasks.json). If you are using separate worktrees/clones, pin it explicitly (e.g., HARNESS_STATE_ROOT=/abs/path/to/state-root).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.git reset --hard / git clean -fd) will destroy other workers.harness-progress.txt + full harness-tasks.json. If JSON is unparseable, see JSON corruption recovery in Error Handling.git log --oneline -20 and git diff --stat to detect uncommitted workharness-init.sh if it existssession_count in JSON. Check session_count against max_sessions — if reached, log STATS and STOP. Initialize per-session task counter to 0.Before selecting, run dependency validation:
depends_on transitively. If any task appears in its own chain, mark it failed with [DEPENDENCY] Circular dependency detected: task-A -> task-B -> task-A. Self-references (depends_on includes own id) are also cycles.depends_on includes a task that is failed and will never be retried (either attempts >= max_attempts OR its error_log contains a [DEPENDENCY] entry), mark the blocked task as failed with . Repeat until no more tasks can be propagated.Then pick the next task in this priority order:
status: "pending" where ALL depends_on tasks are completed — sorted by priority (P0 > P1 > P2), then by id (lowest first)status: "failed" where attempts < max_attempts and ALL depends_on are completed — sorted by priority, then oldest failure firstFor each task, execute this exact sequence:
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.
Execute with checkpoints : Perform the work. After each significant step, log:
[timestamp] [SESSION-N] CHECKPOINT [task-id] step=M/N "description of what was done"
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).
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.
* Command exits 0 → PASS
* Command exits non-zero → FAIL
* Command exceeds timeout → TIMEOUT
4. :
* : status=, set , log , git commit
* : increment , append error to . Verify exists via — if missing, mark failed at max_attempts. Otherwise execute and to rollback ALL commits and remove untracked files. Execute if defined. Log . Set status= (Task Selection Algorithm pass 2 handles retries when attempts < max_attempts)
5. : Increment per-session task counter. If reached, log STATS and STOP.
6. : Immediately pick next task (zero idle time)
completedfailed at max_attempts or blocked by failed dependenciessession_config.max_tasks_per_session reached for this sessionsession_config.max_sessions reached across all sessionsWhen a new session starts and finds a task with status: "in_progress":
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.Check git state :
git diff --stat # Uncommitted changes?
git log --oneline -5 # Recent commits since task started?
git stash list # Any stashed work?
Check checkpoints : Read the task's checkpoints array to determine last completed step
Decision matrix (verify recent commits belong to this task by checking commit messages for the task-id):
| Uncommitted? | Recent task commits? | Checkpoints? | Action |
|---|---|---|---|
| No | No | None | Mark failed with [SESSION_TIMEOUT] No progress detected, increment attempts |
| No | No | Some | Verify file state matches checkpoint claims. If files reflect checkpoint progress, resume from last step. If not, mark failed — work was lost |
| No | Yes | Any | Run validation.command. If passes → mark completed. If fails → , mark |
[timestamp] [SESSION-N] RECOVERY [task-id] action="<action taken>" reason="<reason>"Each error category has a default recovery strategy:
| Category | Default Recovery | Agent Action |
|---|---|---|
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 |
CONFIG | STOP (requires human fix) | Log the config error precisely (file + field), then STOP. Do not guess or auto-mutate task metadata |
TASK_EXEC | Rollback via git reset --hard <started_at_commit>, retry | Verify started_at_commit exists (). If missing, mark failed at max_attempts. Otherwise reset, run if defined, retry if attempts < max_attempts |
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.
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).
If harness-init.sh exists in the project root, run it at every session start. The script must be idempotent.
Example harness-init.sh:
#!/bin/bash
set -e
npm install 2>/dev/null || pip install -r requirements.txt 2>/dev/null || true
curl -sf http://localhost:5432 >/dev/null 2>&1 || echo "WARN: DB not reachable"
npm test -- --bail --silent 2>/dev/null || echo "WARN: Smoke test failed"
echo "Environment health check complete"
All log entries use grep-friendly format on a single line:
[ISO-timestamp] [SESSION-N] <TYPE> [task-id]? [category]? message
[task-id] and [category] are included when applicable (task-scoped entries). Session-level entries (INIT, LOCK, STATS) omit them.
Types: INIT, Starting, Completed, ERROR, CHECKPOINT, ROLLBACK, RECOVERY, STATS, LOCK, WARN
Error categories: ENV_SETUP, CONFIG, TASK_EXEC, TEST_FAIL, TIMEOUT, DEPENDENCY, SESSION_TIMEOUT
Filtering:
grep "ERROR" harness-progress.txt # All errors
grep "ERROR" harness-progress.txt | grep "TASK_EXEC" # Execution errors only
grep "SESSION-3" harness-progress.txt # All session 3 activity
grep "STATS" harness-progress.txt # All session summaries
grep "CHECKPOINT" harness-progress.txt # All checkpoints
grep "RECOVERY" harness-progress.txt # All recovery actions
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:
[timestamp] [SESSION-N] STATS tasks_total=10 completed=7 failed=1 pending=2 blocked=0 attempts_total=12 checkpoints=23
blocked is computed at stats time: count of pending tasks whose depends_on includes a permanently failed task. It is not a stored status value.
/harness init)harness-progress.txt with initialization entryharness-tasks.json with empty task list and default session_configharness-init.sh template (chmod +x).gitignore?/harness status)Read harness-tasks.json and harness-progress.txt, then display:
blocked = pending tasks whose depends_on includes a permanently failed task (computed, not a stored status).[status] task-id: title (attempts/max_attempts)harness-progress.txtDoes NOT acquire the lock (read-only operation).
/harness add)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).
Requires: Bash, file read/write, git. All harness operations must be executed from the project root directory. Does NOT require: specific MCP servers, programming languages, or test frameworks.
Concurrent mode requires isolated working directories (git worktree or separate clones). Do not run concurrent workers in the same working tree.
Weekly Installs
45
Repository
GitHub Stars
2.4K
First Seen
Feb 18, 2026
Security Audits
Gen Agent Trust HubWarnSocketFailSnykPass
Installed on
claude-code43
opencode13
github-copilot13
codex13
kimi-cli13
gemini-cli13
Azure RBAC 权限管理工具:查找最小角色、创建自定义角色与自动化分配
142,000 周安装
通过Rube MCP实现Make自动化:集成Composio工具包管理场景与操作
72 周安装
Microsoft Teams自动化指南:通过Rube MCP实现频道消息、聊天与会议管理
72 周安装
Electrobun 最佳实践:TypeScript + Bun 跨平台桌面应用开发指南
72 周安装
ATXP Memory:AI代理记忆管理工具 - 云端备份与本地向量搜索
72 周安装
Brave Search Spellcheck API:智能拼写检查与查询纠正,提升搜索准确性
72 周安装
Amazon竞品分析器 - 自动化抓取ASIN数据,深度分析竞争对手定价、规格与评论
72 周安装
[DEPENDENCY] Blocked by failed task-XXXcompletedcompleted_atCompleted [<task-id>] (commit <hash>)attemptserror_logstarted_at_commitgit cat-file -t <hash>git reset --hard <started_at_commit>git clean -fdon_failure.cleanupERROR [<task-id>] [<category>] <message>failedmax_tasks_per_sessiongit reset --hard <started_at_commit>failed| Yes | No | Any | Run validation WITH uncommitted changes present. If passes → commit, mark completed. If fails → git reset --hard <started_at_commit> + git clean -fd, mark failed |
| Yes | Yes | Any | Commit uncommitted changes, run validation.command. If passes → mark completed. If fails → git reset --hard <started_at_commit> + git clean -fd, mark failed |
git cat-file -t <hash>on_failure.cleanupTEST_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 |
TIMEOUT | Kill process, execute cleanup, retry | Wrap validation with timeout <seconds> <command>. On timeout, run on_failure.cleanup, retry (consider splitting task if repeated) |
DEPENDENCY | Skip task, mark blocked | Log which dependency failed, mark task as failed with dependency reason |
SESSION_TIMEOUT | Use Context Window Recovery Protocol | New session assesses partial progress via Recovery Protocol — may result in completion or failure depending on validation |