From 445262110b61c9945dd315464c321c656a2a4537 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 20:59:44 +0000 Subject: [PATCH] refactor(agents): simplify scheduler to Nostr-only locking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the git push and GitHub API fallback methods from the scheduler flow, leaving only the Nostr lock as the single locking mechanism. This significantly reduces the agent reasoning chain: - scheduler-flow.md: 10 steps → 8 steps, single code path - META_PROMPTS.md: 5 MUST blocks → 4, no jq pipeline or branching - AGENTS.md: 3-method protocol → 2 commands (check + lock) The agent no longer needs to parse jq output, decide between methods, handle cascading fallbacks, or perform manual race detection. It runs two scripts and checks exit codes. https://claude.ai/code/session_01BpDsVSAkVWtJLnfKxsB9ib --- AGENTS.md | 45 +++------- docs/agents/prompts/META_PROMPTS.md | 58 +++++-------- docs/agents/prompts/scheduler-flow.md | 115 +++++++------------------- 3 files changed, 61 insertions(+), 157 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ee495cb1..ce7d3cd2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -244,58 +244,35 @@ When you create a PR or start work, leave a clear signal: - **PR title prefix**: Use a descriptive prefix like `[nostr-core]`, `[playback]`, `[ui]`, `[ci]` so the scope is visible at a glance. - **PR description**: Include a "Files Modified" section listing the key files touched, so other agents can quickly detect conflicts. -### Task Claiming Protocol (Distributed Lock) +### Task Claiming Protocol (Nostr Distributed Lock) -To prevent multiple agents from working on the same task simultaneously, every agent must **check for existing locks and create a claim** before starting work. The lock is broadcast via Nostr relays (primary) or GitHub PRs (fallback), making it visible to all agents across all platforms (Claude Code, Codex, Jules). +To prevent multiple agents from working on the same task simultaneously, every agent must **check for existing locks and create a claim** before starting work. Locks are broadcast via Nostr relays using NIP-78 events with NIP-40 auto-expiration, making them visible to all agents across all platforms (Claude Code, Codex, Jules). No tokens or secrets are needed. **Before starting any task:** -1. **Check for existing claims.** Run both checks and merge the exclusion sets: - - **Nostr lock check (primary):** +1. **Check for existing claims:** ```bash node scripts/agent/nostr-lock.mjs check --cadence ``` - Returns JSON with `locked` (claimed agents) and `available` (free agents) arrays. + Returns JSON with `locked` (claimed agents) and `available` (free agents) arrays. If an agent appears in the `locked` list, **skip it**. - **GitHub PR check (supplemental):** - ``` - curl -s "https://api.github.com/repos/PR0M3TH3AN/bitvid/pulls?state=open&per_page=100" | jq '{count: length, titles: [.[].title]}' - ``` - If an agent appears in either the Nostr lock list or has an open PR, **skip that agent**. - -2. **Claim the task.** Use one of these methods (try in order): - - **Method A — Nostr lock (preferred, works everywhere Node.js is available):** +2. **Claim the task:** ```bash AGENT_PLATFORM= \ node scripts/agent/nostr-lock.mjs lock \ --agent \ --cadence ``` - Generates an ephemeral keypair, publishes a NIP-78 lock event with NIP-40 auto-expiration (2 hours) to public Nostr relays. No tokens or secrets needed — the key is used once and discarded. The script includes a built-in race check: exit code 0 = lock acquired, exit code 3 = lost race (another agent claimed first). + The script generates an ephemeral keypair, publishes a lock event to public Nostr relays, and performs a built-in race check. The key is used once and discarded. Locks auto-expire after 2 hours (configurable via `NOSTR_LOCK_TTL`). - **Method B — git push (fallback when relays are unreachable):** - a. Create your working branch. - b. Make a minimal initial commit (e.g., create a `context/CONTEXT_.md` with the task scope). - c. Create a `started` log file in the appropriate task-log directory. - d. Push the branch immediately to make the claim visible. - e. Only after the branch is pushed and the claim is visible, begin the actual work. + - **Exit 0** = lock acquired, begin work. + - **Exit 3** = race lost, another agent claimed first. Go back to step 1. + - **Exit 2** = relay error. Write a `_failed.md` log and stop. - **Method C — GitHub API (fallback when git push is also blocked):** +3. **View all active locks** (optional, for debugging): ```bash - bash scripts/agent/claim-task-api.sh \ - --agent \ - --cadence \ - --base unstable + node scripts/agent/nostr-lock.mjs list ``` - Creates the remote branch, `_started.md` log file, and draft PR via `curl` and the GitHub REST API. Requires `GITHUB_TOKEN` or `GH_TOKEN` in the environment. - - If **no method** works, do not proceed — write a `_failed.md` log and stop. - -3. **Handle race conditions.** - - If Method A (Nostr) was used, the race check is automatic (built into the script). - - If Method B or C was used, re-check for duplicates via the GitHub PR preflight. If you find another agent's claim that was created **before** yours, abandon your branch and skip the task. **Scheduler agents:** Daily and weekly scheduler agents must follow this protocol in addition to their directory-based rotation logic (`docs/agents/task-logs/daily/` and `docs/agents/task-logs/weekly/`). The lock check happens _after_ determining the next task but _before_ executing it. See the scheduler prompts for the specific implementation steps. diff --git a/docs/agents/prompts/META_PROMPTS.md b/docs/agents/prompts/META_PROMPTS.md index 7ac4d2fe..2e2f85ea 100644 --- a/docs/agents/prompts/META_PROMPTS.md +++ b/docs/agents/prompts/META_PROMPTS.md @@ -17,33 +17,24 @@ MUST 1: Set cadence config to: - branch_prefix = agents/daily/ - prompt_dir = docs/agents/prompts/daily/ -MUST 2: Run BOTH preflight commands to build the exclusion set: +MUST 2: Run preflight to get the exclusion set: -a) Nostr lock check (primary): node scripts/agent/nostr-lock.mjs check --cadence daily -b) GitHub PR check (supplemental): -curl -s "https://api.github.com/repos/PR0M3TH3AN/bitvid/pulls?state=open&per_page=100" | jq '{count: length, prs: [.[] | {number, created_at, draft, head: {ref: .head.ref}, agent: ((.head.ref | capture("^agents/daily/(?[^/]+)/")?.agent) // (.title | capture("(?[A-Za-z0-9-]+-agent)")?.agent // null))}] | sort_by(.created_at, .number)}' +Use the `locked` array from the JSON output as the exclusion set. -Exclusion set = union of Nostr locked agents + agents with open PRs. - -MUST 3: Run the exact command list in this order: +MUST 3: Run these commands in this order: 1) cat AGENTS.md CLAUDE.md 2) ls -1 docs/agents/task-logs/daily/ | sort | tail -n 1 -3) [claim] Use the Nostr lock (preferred, no tokens needed): +3) Select next roster agent not in exclusion set +4) Claim via Nostr lock: AGENT_PLATFORM=jules node scripts/agent/nostr-lock.mjs lock --agent --cadence daily - Exit 0 = lock acquired. Exit 3 = race lost, go back to step 2. - IF Nostr relays are unreachable, try git push fallback. - IF git push also fails, try: - bash scripts/agent/claim-task-api.sh --agent --cadence daily --base unstable - IF all methods fail, write `_failed.md` and stop. -4) execute selected prompt from docs/agents/prompts/daily/ -5) npm run lint -6) create `_completed.md` or `_failed.md`, commit, push (or API update) + Exit 0 = lock acquired, proceed. Exit 3 = race lost, go back to step 2. +5) Execute selected prompt from docs/agents/prompts/daily/ +6) npm run lint +7) Create `_completed.md` or `_failed.md`, commit, push -MUST 4: Race rule: the Nostr lock script handles race detection automatically (exit code 3 = lost). For git push/API fallbacks: rerun GitHub PR preflight after claiming. Earlier `created_at` wins; if tied, lower PR number wins. Print exactly one line: `RACE CHECK: won` or `RACE CHECK: lost`. - -MUST 5: If all daily agents are excluded, stop and write `_failed.md` with this exact reason: `All roster tasks currently claimed by other agents`. +MUST 4: If all daily agents are excluded, stop and write `_failed.md` with this exact reason: `All roster tasks currently claimed by other agents`. ``` ## Weekly Scheduler Meta Prompt @@ -59,31 +50,22 @@ MUST 1: Set cadence config to: - branch_prefix = agents/weekly/ - prompt_dir = docs/agents/prompts/weekly/ -MUST 2: Run BOTH preflight commands to build the exclusion set: +MUST 2: Run preflight to get the exclusion set: -a) Nostr lock check (primary): node scripts/agent/nostr-lock.mjs check --cadence weekly -b) GitHub PR check (supplemental): -curl -s "https://api.github.com/repos/PR0M3TH3AN/bitvid/pulls?state=open&per_page=100" | jq '{count: length, prs: [.[] | {number, created_at, draft, head: {ref: .head.ref}, agent: ((.head.ref | capture("^agents/weekly/(?[^/]+)/")?.agent) // (.title | capture("(?[A-Za-z0-9-]+-agent)")?.agent // null))}] | sort_by(.created_at, .number)}' +Use the `locked` array from the JSON output as the exclusion set. -Exclusion set = union of Nostr locked agents + agents with open PRs. - -MUST 3: Run the exact command list in this order: +MUST 3: Run these commands in this order: 1) cat AGENTS.md CLAUDE.md 2) ls -1 docs/agents/task-logs/weekly/ | sort | tail -n 1 -3) [claim] Use the Nostr lock (preferred, no tokens needed): +3) Select next roster agent not in exclusion set +4) Claim via Nostr lock: AGENT_PLATFORM=jules node scripts/agent/nostr-lock.mjs lock --agent --cadence weekly - Exit 0 = lock acquired. Exit 3 = race lost, go back to step 2. - IF Nostr relays are unreachable, try git push fallback. - IF git push also fails, try: - bash scripts/agent/claim-task-api.sh --agent --cadence weekly --base unstable - IF all methods fail, write `_failed.md` and stop. -4) execute selected prompt from docs/agents/prompts/weekly/ -5) npm run lint -6) create `_completed.md` or `_failed.md`, commit, push (or API update) + Exit 0 = lock acquired, proceed. Exit 3 = race lost, go back to step 2. +5) Execute selected prompt from docs/agents/prompts/weekly/ +6) npm run lint +7) Create `_completed.md` or `_failed.md`, commit, push -MUST 4: Race rule: the Nostr lock script handles race detection automatically (exit code 3 = lost). For git push/API fallbacks: rerun GitHub PR preflight after claiming. Earlier `created_at` wins; if tied, lower PR number wins. Print exactly one line: `RACE CHECK: won` or `RACE CHECK: lost`. - -MUST 5: If all weekly agents are excluded, stop and write `_failed.md` with this exact reason: `All roster tasks currently claimed by other agents`. +MUST 4: If all weekly agents are excluded, stop and write `_failed.md` with this exact reason: `All roster tasks currently claimed by other agents`. ``` diff --git a/docs/agents/prompts/scheduler-flow.md b/docs/agents/prompts/scheduler-flow.md index 67678261..96b7f4c0 100644 --- a/docs/agents/prompts/scheduler-flow.md +++ b/docs/agents/prompts/scheduler-flow.md @@ -27,96 +27,75 @@ During scheduler execution, the scheduler-specific instructions in this document - `branch_prefix` = `agents//` - `prompt_dir` = `docs/agents/prompts//` -2. **MUST** run the preflight check to build the exclusion set. Use the Nostr lock check (preferred) with GitHub PR check as supplemental: +2. **MUST** run the preflight check to build the exclusion set: - **Primary — Nostr lock check:** ```bash node scripts/agent/nostr-lock.mjs check --cadence ``` - This queries Nostr relays for active lock events and returns JSON with `locked` and `available` agent lists. - **Supplemental — GitHub PR check** (catches agents with open PRs that predate the Nostr system): - ```bash - curl -s "https://api.github.com/repos/PR0M3TH3AN/bitvid/pulls?state=open&per_page=100" | jq '{count: length, prs: [.[] | {number, created_at, draft, head: {ref: .head.ref}, agent: ((.head.ref | capture("^agents//(?[^/]+)/")?.agent) // (.title | capture("(?[A-Za-z0-9-]+-agent)")?.agent // null))}] | sort_by(.created_at, .number)}' - ``` + This queries Nostr relays for active lock events and returns JSON with `locked` and `available` agent lists. Use the `locked` array as the exclusion set. - The exclusion set is the **union** of agents locked via Nostr AND agents with open PRs. - -3. **MUST** derive excluded agents from both outputs: - - From Nostr check: use the `locked` array directly. - - From GitHub PR check: 1) parse `head.ref` as `agents///...`; 2) fallback to title regex; 3) if still unknown, stop with a global lock message. - -4. **MUST** run this command and read both policy files before selecting an agent: +3. **MUST** read both policy files before selecting an agent: ```bash cat AGENTS.md CLAUDE.md ``` -5. **MUST** run this command to identify the latest cadence log file, then choose the next roster agent not in the exclusion set: +4. **MUST** run this command to identify the latest cadence log file, then choose the next roster agent not in the exclusion set: ```bash ls -1 | sort | tail -n 1 ``` -6. **MUST** stop and write a `_failed.md` log with `All roster tasks currently claimed by other agents` when every roster agent is excluded. +5. **MUST** stop and write a `_failed.md` log with `All roster tasks currently claimed by other agents` when every roster agent is excluded. -7. **MUST** claim the selected agent using **one** of these methods (try in order): +6. **MUST** claim the selected agent: - **Method A — Nostr lock (preferred, works everywhere Node.js is available):** ```bash AGENT_PLATFORM= \ node scripts/agent/nostr-lock.mjs lock \ --agent \ --cadence ``` - This generates an ephemeral keypair, publishes a NIP-78 lock event to Nostr relays with NIP-40 auto-expiration (2 hours default), and performs a built-in race check. No tokens or secrets needed. Exit code 0 = won, exit code 3 = lost race. - **Method B — git push (when Nostr relays are unreachable):** - 1) create branch, - 2) create and commit `__started.md` in ``, - 3) push branch and commit. + The script generates an ephemeral keypair, publishes a NIP-78 lock event to Nostr relays with NIP-40 auto-expiration (2 hours), and performs a built-in race check. No tokens or secrets needed. - **Method C — GitHub API fallback (when git push is also blocked):** - ```bash - bash scripts/agent/claim-task-api.sh \ - --agent \ - --cadence \ - --base unstable - ``` - Requires `GITHUB_TOKEN` or `GH_TOKEN` in the environment. + - **Exit code 0** = lock acquired, proceed to Step 7. + - **Exit code 3** = race lost (another agent claimed first). Return to Step 2. + - **Exit code 2** = relay error. Write a `_failed.md` log with reason `Nostr relay error` and stop. - If **all three methods** fail, write a `_failed.md` log with reason `Cannot create remote claim: all locking methods failed` and stop. +7. **MUST** execute `/` end-to-end. -8. **MUST** verify the lock was acquired: - - If Method A was used: the script's built-in race check already ran. If exit code was 0, print `RACE CHECK: won`. If exit code 3, print `RACE CHECK: lost`, abandon, and return to Step 5. - - If Method B or C was used: re-run the GitHub PR preflight from Step 2. If an earlier claim exists for the same derived agent, print `RACE CHECK: lost (agent already claimed by PR #)`, abandon, and return to Step 5. +8. **MUST** create exactly one final status file (`_completed.md` or `_failed.md`), run `npm run lint`, then commit and push. -9. **MUST** execute `/` end-to-end. - -10. **MUST** create exactly one final status file (`_completed.md` or `_failed.md`), run `npm run lint`, then commit and push (or use the API to update the remote branch if push is blocked). - -## Canonical Example Run Output - -### Method A — Nostr lock (preferred) +## Canonical Example Run ```text -# Step 2: Preflight — check Nostr locks + GitHub PRs +# Step 1: Set cadence +cadence=daily + +# Step 2: Preflight — check Nostr locks $ node scripts/agent/nostr-lock.mjs check --cadence daily -{ "cadence": "daily", "date": "2026-02-14", "locked": ["audit-agent"], "available": [...], ... } +{ + "cadence": "daily", + "date": "2026-02-14", + "locked": ["audit-agent"], + "available": ["ci-health-agent", "const-refactor-agent", ...], + "lockCount": 1, + "locks": [...] +} -$ curl -s "https://api.github.com/repos/PR0M3TH3AN/bitvid/pulls?state=open&per_page=100" | jq '...' -{ "count": 2, "prs": [ ... ] } - -# Steps 3-5: Read policies, check logs, select agent +# Step 3: Read policies $ cat AGENTS.md CLAUDE.md ... +# Step 4: Check logs, select next agent $ ls -1 docs/agents/task-logs/daily/ | sort | tail -n 1 2026-02-13_18-40-00_load-test-agent_completed.md Selected agent: nip-research-agent -# Step 7: Claim via Nostr lock +# Step 6: Claim via Nostr lock $ AGENT_PLATFORM=jules node scripts/agent/nostr-lock.mjs lock --agent nip-research-agent --cadence daily Locking: agent=nip-research-agent, cadence=daily, date=2026-02-14 Step 1: Checking for existing locks... @@ -132,46 +111,12 @@ LOCK_AGENT=nip-research-agent LOCK_CADENCE=daily LOCK_EXPIRES_ISO=2026-02-14T02:00:00.000Z -# Step 9: Execute agent prompt +# Step 7: Execute agent prompt (agent runs nip-research-agent prompt...) -# Step 10: Finalize +# Step 8: Finalize $ npm run lint > lint passed Wrote: docs/agents/task-logs/daily/2026-02-14_00-12-00_nip-research-agent_completed.md ``` - -### Method B — git push (fallback if relays are unreachable) - -```text -$ node scripts/agent/nostr-lock.mjs check --cadence daily -nostr-lock failed: Relay query timed out -# Nostr relays unreachable — fall back to GitHub PR check only - -$ curl -s "https://api.github.com/repos/PR0M3TH3AN/bitvid/pulls?state=open&per_page=100" | jq '...' -{ "count": 12, "prs": [ ... ] } - -$ cat AGENTS.md CLAUDE.md -... - -Selected agent: nip-research-agent -Created: docs/agents/task-logs/daily/2026-02-14_00-00-00_nip-research-agent_started.md -Pushed branch: agents/daily/nip-research-agent/2026-02-14-run - -$ curl -s "https://api.github.com/repos/PR0M3TH3AN/bitvid/pulls?state=open&per_page=100" | jq '...' -RACE CHECK: won -``` - -### Method C — GitHub API (fallback if git push is also blocked) - -```text -# Both Nostr relays and git push failed -$ bash scripts/agent/claim-task-api.sh --agent nip-research-agent --cadence daily --base unstable -CLAIM_BRANCH=agents/daily/nip-research-agent/2026-02-14-run-1707868800 -CLAIM_PR_NUMBER= -CLAIM_PR_URL= - -$ curl -s "https://api.github.com/repos/PR0M3TH3AN/bitvid/pulls?state=open&per_page=100" | jq '...' -RACE CHECK: won -```