关于 start
start 是 autoloop 技能,用于在项目中生成循环契约并准备启动反复读取该契约的 `/loop`。它为长期自主任务设置契约、状态目录、会话绑定和唤醒配置,适用于需要持续执行的工作流。
主要功能
- 01生成并注册循环契约
- 02迁移旧版契约布局
- 03安装会话与心跳钩子
- 04配置 launchd 唤醒程序
- 05提供 `/loop` 启动指引
适用场景
- 01建立长期自主执行任务
- 02在同一项目中管理多个循环
- 03将现有循环契约迁移到新版布局
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ terrylica/ cc-skills --skill start安装后在 Claude Code 中输入/start确认已加载。
详细介绍
autoloop:启动
搭建 LOOP_CONTRACT.md,并启动一个每次触发时都会读取契约的自我修订 /loop。
自我演进技能:此技能会在使用过程中不断改进。如果指令有误、参数发生偏移,或需要采用变通方法,请立即修复此文件,不要拖延。仅针对真实且可复现的问题进行更新。
参数
- 位置参数(可选):契约文件路径。默认为
./LOOP_CONTRACT.md。
步骤 1:确保钩子已安装
如果 ~/.claude/settings.json 中尚未安装 autoloop 的两个钩子,请安装。此操作具有幂等性。
- PostToolUse →
heartbeat-tick.sh— 每次调用工具时更新心跳,并检测 cwd 是否发生偏移。 - SessionStart →
session-bind.sh— 从 stdin payload 权威地绑定owner_session_id(取代失效的$CLAUDE_SESSION_ID环境变量捕获方式;参见 anthropics/claude-code#47018)。 - PreToolUse(ScheduleWakeup) →
pacing-veto.sh— 拒绝伪装成节奏控制的唤醒器(延迟处于 300–1199s 缓存未命中区间,或延迟 >270s 且原因文本中包含 token-budget/cache-warm/self-pacing/cooldown/rest)。当不存在真正的外部阻塞因素时,强制使用 Tier 0(在当前轮次内)。
# Source the hook install library
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/autoloop}"
source "$PLUGIN_ROOT/scripts/hook-install-lib.sh"
# Wave 5 A4: strip macOS quarantine xattrs in case this is the first run
# after `claude plugin marketplace add`. No-op on Linux / clean trees.
strip_plugin_quarantine_xattrs "$PLUGIN_ROOT" >/dev/null 2>&1 || true
# Wave 5 A8: explicit "auto-setup-on-start". If neither hook is installed,
# announce that we're running first-time setup before the install_all_hooks
# call so the user understands the side effect. If both are already
# installed, stay quiet — there's nothing to surface.
SETTINGS_PATH="$HOME/.claude/settings.json"
if [ "$(is_hook_installed "$SETTINGS_PATH" 2>/dev/null)" != "yes" ] || \
[ "$(is_session_bind_installed "$SETTINGS_PATH" 2>/dev/null)" != "yes" ]; then
echo "Installing autoloop hooks into ~/.claude/settings.json (one-time setup)..."
fi
# Install all four autoloop hooks (idempotent). DO NOT swallow stderr —
# install_all_hooks emits actionable diagnostics on failure (malformed JSON,
# write errors, missing hook scripts) that the user needs to see. If install
# fails, abort the bootstrap rather than continuing into a half-bootstrapped
# state where the loop appears registered but session-bind / heartbeat /
# pacing-veto / empty-firing-detector never run.
if ! install_all_hooks; then
echo "ERROR: Failed to install autoloop hooks." >&2
echo " The bootstrap cannot continue safely — a registered loop without" >&2
echo " its hooks installed is what /autoloop:tinker calls F2_missing_hooks." >&2
echo " Inspect: jq . $SETTINGS_PATH ; ls $PLUGIN_ROOT/hooks/" >&2
exit 1
fi
# Wave 6 anti-fragility (v12.52.0+): runtime self-test the just-installed
# hooks. Without this, a hook can be registered in settings.json yet crash
# on real invocation due to: missing shebang, broken shellcheck disable,
# stale path to a sibling lib, syntax error from a botched edit, missing
# dependency. The self-test catches those by feeding each hook a synthetic
# Claude Code event payload in a sandbox HOME and asserting exit 0.
if ! run_hook_runtime_selftest "$PLUGIN_ROOT" 2>&1; then
echo "ERROR: Autoloop hook runtime self-test failed." >&2
echo " Hooks were registered in settings.json but at least one crashes" >&2
echo " on invocation. Inspect the FAIL line(s) above." >&2
echo " Recover: /autoloop:tinker will redo the install with diagnostics." >&2
exit 1
fi
结果:每个在已注册循环的契约目录中启动的 Claude 会话都会 (1) 在 SessionStart 时绑定到该循环,然后 (2) 在每次调用工具时更新心跳并检测 cwd 偏移。
步骤 2:预检(v2 布局 — .autoloop/<slug>--<hash>/CONTRACT.md)
Wave 3 引入了新的磁盘布局:契约存放在
<project_cwd>/.autoloop/<campaign-slug>--<short-hash>/CONTRACT.md 下,并配有同级的
state/ 目录。这样一来,同一 cwd 中的多个 campaign 可以共存,任何 AI Agent 也能通过目录名称识别正确的契约。
首次在包含旧版 <cwd>/LOOP_CONTRACT.md 的任何目录中运行 /autoloop:start 时,会自动迁移:
PROJECT_CWD="${1:-$(pwd -P)}"
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/autoloop}"
source "$PLUGIN_ROOT/scripts/registry-lib.sh"
source "$PLUGIN_ROOT/scripts/state-lib.sh"
# Returns "noop" if nothing to migrate, or two lines starting with
# "migrated_to_loop_id:" / "migrated_to_path:" on success.
MIGRATION_OUTPUT=$(migrate_legacy_contract "$PROJECT_CWD" "${CLAUDE_SESSION_ID:-$$-$(date +%s)}")
if [ "$MIGRATION_OUTPUT" != "noop" ]; then
echo "$MIGRATION_OUTPUT"
echo "Legacy LOOP_CONTRACT.md migrated to .autoloop/ subdir; resuming with new layout."
fi
然后检查此项目是否已存在任何 v2 契约:
existing=$(compgen -G "$PROJECT_CWD/.autoloop/*--[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]/CONTRACT.md" 2>/dev/null || true)
if [ -n "$existing" ]; then
echo "v2 contract(s) present:"
echo "$existing"
# Ask the user: resume an existing campaign, start a new one alongside, or stop?
fi
使用 AskUserQuestion 选择:恢复现有 campaign、并行启动新 campaign(创建第二个 .autoloop/<slug>--<hash>/),或停止。
步骤 3:收集契约输入
使用 AskUserQuestion 收集:
1. name — short slug for this loop (e.g. "odb-research", "flaky-ci-watcher")
2. scope — one-line Core Directive describing the long-horizon goal
3. cadence — typical wake-up cadence hint (continuous | event-driven | hourly | daily)
契约路径由 name 和稳定的 short_hash 确定性推导得出,因此用户无需选择路径:
SLUG=$(slugify "$NAME_INPUT")
SHORT_HASH=$(compute_short_hash "${CLAUDE_SESSION_ID:-$$-$(date +%s)}" "$(date -u +%s)")
CONTRACT_DIR="$PROJECT_CWD/.autoloop/${SLUG}--${SHORT_HASH}"
CONTRACT_PATH="$CONTRACT_DIR/CONTRACT.md"
mkdir -p "$CONTRACT_DIR/state/revision-log"
如果 $CONTRACT_DIR 已存在,请为 short_hash 添加数字后缀,或让用户选择不同的 name。
步骤 4:搭建契约
复制插件附带的模板并替换占位符。使用 $CLAUDE_PLUGIN_ROOT(Claude Code 为每个插件技能设置此变量)解析模板——不要使用 $0,当 SKILL.md 作为指令加载而非脚本执行时,该变量不会被设置。
# CLAUDE_PLUGIN_ROOT points at /…/plugins/<plugin-name> when invoked from
# a skill. Fall back to the marketplace cache path if unset (e.g., when a
# user invokes the skill's Bash manually outside the harness).
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/autoloop}"
TEMPLATE="$PLUGIN_ROOT/templates/LOOP_CONTRACT.template.md"
if [ ! -f "$TEMPLATE" ]; then
echo "ERROR: template missing at $TEMPLATE — reinstall the plugin" >&2
exit 1
fi
cp "$TEMPLATE" "$CONTRACT_PATH"
# Also copy the PROVENANCE.md template (Wave 3) — sits alongside CONTRACT.md
PROV_TEMPLATE="$PLUGIN_ROOT/templates/PROVENANCE.template.md"
if [ -f "$PROV_TEMPLATE" ]; then
cp "$PROV_TEMPLATE" "$CONTRACT_DIR/PROVENANCE.md"
fi
然后使用 sed 注入用户输入(或使用 Claude 的工具编辑文件):
<SHORT_DESCRIPTIVE_NAME>→ 用户提供的name<ISO_8601_UTC>→date -u +"%Y-%m-%dT%H:%M:%SZ"(始终使用-u;不带参数的date会返回本地时间,导致与契约中的 UTC 字段不一致且不会提示)<RELATIVE_PATH_TO_LOOP_CONTRACT_MD>→$CONTRACT_PATH(现在位于.autoloop/<slug>--<hash>/CONTRACT.md下)<CORE DIRECTIVE>/<PROJECT OR CAMPAIGN TITLE>→ 用户提供的scope
模板就位后,调用 init_contract_frontmatter_v2 写入所有 v2 创建记录字段(loop_id、campaign_slug、created_at_utc 等):
NEW_LOOP_ID=$(derive_loop_id "$CONTRACT_PATH")
init_contract_frontmatter_v2 "$CONTRACT_PATH" "$NEW_LOOP_ID" "$CONTRACT_DIR/state"
步骤 5:在机器注册表中注册循环
在步骤 2/3 中推导出循环 ID 后,将此循环注册到机器级注册表中。
此处将
owner_session_id设为pending-bind。 SessionStart 钩子(hooks/session-bind.sh)会在下次会话启动时以原子方式替换该值,因为技能 Bash 子进程中不会设置$CLAUDE_SESSION_ID环境变量(参见 anthropics/claude-code#47018)。在此处直接捕获只会存储空值或垃圾值——钩子才是唯一正确的绑定位置。
# Source the registry library
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/autoloop}"
source "$PLUGIN_ROOT/scripts/registry-lib.sh"
# Derive loop_id from contract path (if not already done)
loop_id=$(derive_loop_id "$CONTRACT_PATH")
# Create entry JSON with all required fields. owner_session_id="pending-bind"
# is the canonical placeholder; the SessionStart hook replaces it atomically
# on the next session start. bound_cwd starts empty; heartbeat-tick records
# it on the first tick and detects drift thereafter.
#
# state_dir resolution: ALWAYS go through `state_dir_path "$loop_id"
# "$CONTRACT_PATH"` instead of hand-rolling `<dir>/.loop-state/<id>/`.
# Wave 3 introduced the v2 layout (.autoloop/<slug>--<hash>/state/) and
# `state_dir_path` returns the correct path for both v1 and v2 contracts.
# Hand-rolling .loop-state would register a v1 path against a v2 contract
# and the heartbeat-tick hook would write to a directory the contract
# doesn't recognize.
entry=$(jq -n \
--arg loop_id "$loop_id" \
--arg contract_path "$(realpath "$CONTRACT_PATH")" \
--arg state_dir "$(state_dir_path "$loop_id" "$CONTRACT_PATH")" \
--arg owner_session_id "pending-bind" \
--arg bound_cwd "" \
--arg owner_pid "$$" \
--arg owner_start_time_us "$(date +%s%N | cut -c1-16)" \
--arg launchd_label "com.user.claude.loop.$loop_id" \
--arg started_at_us "$(date +%s%N | cut -c1-16)" \
--arg expected_cadence_seconds "$cadence_seconds" \
--arg generation "0" \
'{loop_id: $loop_id, contract_path: $contract_path, state_dir: $state_dir, owner_session_id: $owner_session_id, bound_cwd: $bound_cwd, owner_pid: $owner_pid, owner_start_time_us: $owner_start_time_us, launchd_label: $launchd_label, started_at_us: $started_at_us, expected_cadence_seconds: $expected_cadence_seconds, generation: $generation}')
# Register in machine registry (atomic, serialized write)
if ! register_loop "$entry"; then
echo "WARNING: Failed to register loop in machine registry (may already exist)" >&2
fi
步骤 6:生成并加载 launchd plist
注册循环后,生成 launchd plist 并加载:
# Source the launchd library
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/plugins/marketplaces/cc-skills/plugins/autoloop}"
source "$PLUGIN_ROOT/scripts/launchd-lib.sh"
source "$PLUGIN_ROOT/scripts/state-lib.sh"
# Derive loop_id and state_dir
loop_id=$(derive_loop_id "$CONTRACT_PATH")
state_dir=$(state_dir_path "$loop_id" "$CONTRACT_PATH")
# Stub waker script path (Phase 9 will ship the actual script)
waker_script="$PLUGIN_ROOT/scripts/waker.sh"
# Calculate polling interval (default 150 seconds = half of typical cadence)
interval_seconds="${cadence_seconds:-300}"
interval_seconds=$((interval_seconds / 2))
if [ "$interval_seconds" -lt 60 ]; then
interval_seconds=60
fi
# Generate plist. The Wave-6 anti-fragility wave hardened generate_plist to
# refuse a plist pointing at a missing waker_script (turned silent failure
# into loud failure).
if ! generate_plist "$loop_id" "$state_dir" "$waker_script" "$interval_seconds"; then
echo "ERROR: Failed to generate launchd plist" >&2
echo " The bootstrap cannot continue safely; recover with /autoloop:tinker" >&2
exit 1
fi
# Load plist (bootstraps on macOS; skipped gracefully on non-macOS).
# DO NOT swallow stderr — load_plist's diagnostic output (FDA permission,
# label collision, malformed plist, MDM block) is the only signal the user
# has when launchctl rejects the plist.
if ! load_plist "$loop_id" "$state_dir"; then
echo "ERROR: Failed to load launchd plist" >&2
echo " The loop is registered but will not fire; recover with /autoloop:tinker" >&2
exit 1
fi
# Wave-6 anti-fragility: post-bootstrap launchctl assertion.
# load_plist returning 0 only means the bootstrap call succeeded; verify
# the label actually appears in `launchctl list` so we never ship a
# half-loaded state. Skipped on non-macOS.
if [ "$(uname -s)" = "Darwin" ]; then
label="com.user.claude.loop.${loop_id}"
if ! launchctl list "$label" >/dev/null 2>&1; then
echo "ERROR: launchctl list does not show '$label' after bootstrap" >&2
echo " This is a half-loaded plist state. Recover with /autoloop:tinker" >&2
exit 1
fi
fi
步骤 7:输出指针触发词(从步骤 7 更新)
打印用户可以传给 /loop 的片段:
/loop
Read and execute the latest autonomous work contract at:
$CONTRACT_PATH
Follow its instructions verbatim. That file self-updates; this trigger stays fixed.
步骤 8:询问是否立即启动循环
使用 AskUserQuestion 提供两个选项:
Start now— 通过Skill(loop)调用原生/loop技能,并将指针触发词作为输入Not yet— 用户稍后会手动调用/loop(例如,在提交契约后)
如果选择 Start now,则将指针触发词片段作为 args 调用 Skill(loop)。否则打印 "契约已搭建完成,路径为 $CONTRACT_PATH。准备好后,使用上方的指针触发词运行 /loop。"
步骤 9:建议提交
打印建议的首次提交信息:
git add "$CONTRACT_PATH"
git commit -m "loop(bootstrap): scaffold LOOP_CONTRACT.md for $name"
步骤 10:打印绑定验证区块(Wave 6)
步骤 9 后,打印下方明确的验证区块。这是 /autoloop:start 退出前用户可见的最后一条信号。切勿跳过它——促使 Wave 6 推出的静默待绑定故障模式,部分原因是 /autoloop:start 在循环实际上并未自动运行时仍会正常退出。
🔧 Loop registered: $loop_id ($display_name)
project_root: $project_root # = .created_at_cwd; bind hooks match cwd against this
contract: $CONTRACT_PATH
📡 Autonomous-fire requirement
The launchd waker can only fire /loop on this loop AFTER the
SessionStart hook has bound owner_session_id. The hook fires when
you open Claude Code with cwd anywhere in the path hierarchy:
$project_root ⊆ cwd ⊆ $(dirname "$CONTRACT_PATH")
Until that bind happens, the registry shows owner_session_id =
"pending-bind" and the waker logs "dead+fresh anomaly" instead of
firing. Run /autoloop:muster to verify state.
✅ Verify within 30s of your next session start
/autoloop:muster
expect: STATUS=ACTIVE (not PENDING-BIND or PENDING-BIND-STUCK)
If PENDING-BIND-STUCK after 5min:
• cd "$project_root" && claude # opens a session that the hook can bind
• /autoloop:tinker $loop_id # patches owner_session_id from THIS session
• heal-self.sh runs hourly and will auto-bind to a live session
whose JSONL mtime is <5min — a third safety net
原样回显此区块,并展开其中的变量。不要改述或缩短——对于首次遇到待绑定问题的运维人员,故障排查提示路径至关重要。
反模式
- 未经用户明确确认,不得覆盖现有契约
- 不得将契约写入当前工作树之外的目录(绕过沙箱)
- 不得将完整的 Core Directive 作为提示词传给
/loop——始终使用简短的指针触发词模式
故障排查
| 症状 | 修复方法 |
|---|---|
| 未找到模板 | claude plugin reinstall autoloop@cc-skills |
/loop 提示 "unknown skill" | Claude Code 版本低于 2.1.101;升级以使用原生 /loop |
| 指针触发词重新读取了过时文件 | 确保 $CONTRACT_PATH 是绝对路径或相对于 CWD 稳定不变 |
执行后反思
- 确定自身位置。 — 在进行任何编辑前,确认此 SKILL.md 是规范文件。
- 出了什么问题? — 修复导致问题的指令。
- 发生了哪些偏移? — 如果模板与最新惯例不一致,则更新模板。
- 记录下来。 — 添加演进日志条目,记录触发原因、修复内容和证据。