This commit is contained in:
thePR0M3TH3AN committed 2026-02-20 12:51:17 -05:00
1 parent 03b6725cfc
commit 2b8a0c3d0d
25 files changed
+1126 -5

No files matched your search

+12
View File
@@ -158,12 +158,20 @@ Configuration options:
- `nostrLock.relays` (array of relay URLs)
- `nostrLock.ttlSeconds` (lock duration in seconds)
- `nostrLock.queryTimeoutMs` (timeout for relay queries)
- `nostrLock.publishTimeoutMs` (per-relay publish timeout in ms)
- `nostrLock.minSuccessfulRelayPublishes` (minimum successful publishes before lock continues, default: `1`)
- `nostrLock.relayFallbacks` (optional fallback relay URLs used when primary relays fail quorum)
- `nostrLock.minActiveRelayPool` (minimum relays kept active even when others are quarantined, default: `1`)
- `nostrLock.dailyRoster` / `nostrLock.weeklyRoster` (explicit rosters)
- `dashboard.defaultCadenceView` (`daily`, `weekly`, or `all`)
- `dashboard.defaultStatusView` (`active` or `all`)
- `dashboard.hashtag` (custom hashtag for lock events)
- `scheduler.firstPromptByCadence.daily` / `.weekly` (first prompt to run)
- `scheduler.handoffCommandByCadence.daily` / `.weekly` (shell command run after lock acquisition; must use `SCHEDULER_AGENT`, `SCHEDULER_CADENCE`, `SCHEDULER_PROMPT_PATH` env vars)
- `scheduler.paused.daily` / `.weekly` (array of agent names to pause locally)
- `scheduler.strict_lock` (lock backend policy; when `false`, defers backend-unavailable failures before failing, default: `true`)
- `scheduler.degraded_lock_retry_window` (ms window for non-strict deferral of backend failures)
- `scheduler.max_deferrals` (max non-strict lock deferrals before recording a hard failure)
### Dashboard
@@ -201,6 +209,10 @@ You can override configuration using environment variables:
- `NOSTR_LOCK_RELAYS` (comma-separated)
- `NOSTR_LOCK_TTL`
- `NOSTR_LOCK_QUERY_TIMEOUT_MS`
- `NOSTR_LOCK_PUBLISH_TIMEOUT_MS`
- `NOSTR_LOCK_MIN_SUCCESSFUL_PUBLISHES`
- `NOSTR_LOCK_RELAY_FALLBACKS` (comma-separated fallback relay URLs)
- `NOSTR_LOCK_MIN_ACTIVE_RELAY_POOL`
- `NOSTR_LOCK_DAILY_ROSTER` (comma-separated list of agents)
- `NOSTR_LOCK_WEEKLY_ROSTER` (comma-separated list of agents)
- `TORCH_CONFIG_PATH` (path to config file)
+3 -3
View File
@@ -7,7 +7,7 @@ This document identifies areas of the TORCH repository that lack specific agent
| Gap | Evidence | Proposed Agent |
| :--- | :--- | :--- |
| **Database Migrations** | `migrations/` directory and SQL files present. | `migration-agent` |
| **E2E Testing** | `playwright` dependency and `test/` integration files. | `e2e-agent` |
| **E2E Testing** | `test/` integration/e2e files (Node.js `--test`). | `e2e-agent` |
| **Memory Subsystem** | `scripts/memory/`, `.scheduler-memory/` exist. | `memory-health-agent` |
| **Relay Stability** | `src/relay-health.mjs` and known relay flakiness. | `relay-health-agent` |
| **Skills Management** | `skills/` directory and `torch-basics.md`. | `skills-agent` |
@@ -24,8 +24,8 @@ This document identifies areas of the TORCH repository that lack specific agent
- **Priority**: Medium
### 2. `e2e-agent`
- **Gap**: `playwright` is used for dashboard and integration testing, but no agent is dedicated to scenario maintenance.
- **Mission**: Maintain Playwright E2E scenarios, fix flaky tests, and ensure the dashboard remains functional across protocol changes.
- **Gap**: E2E/integration tests exist (e.g., `test/scheduler-preflight-lock.e2e.test.mjs` via Node.js `--test`), but no agent is dedicated to scenario maintenance.
- **Mission**: Maintain E2E scenarios, fix flaky tests, and ensure the dashboard and scheduler flow remain functional across protocol changes.
- **Priority**: High (given the "Scenario-First" mandate in `AGENTS.md`)
### 3. `memory-health-agent`
@@ -0,0 +1,11 @@
# Docs Agent: README/TORCH.md Synchronization Pattern
- **Context:** docs-agent run 2026-02-20. Audited README.md, CONTRIBUTING.md, AGENTS.md against package.json, src/, and TORCH.md.
- **Observation:** `TORCH.md` is the authoritative configuration and env-var reference. `README.md` had drifted and was missing 4 env vars and 8 `torch-config.json` keys present in TORCH.md and verified in `src/torch-config.mjs`.
- **Action taken:** Added missing items to README.md's "Environment variables" and "Configuration" sections. No changes to CONTRIBUTING.md or AGENTS.md — both were accurate.
- **Validation performed:** Grep confirmed all 4 missing env vars are used in `src/torch-config.mjs`. All referenced file paths verified to exist via Glob.
- **Recommendation for next agents:**
1. When updating env vars or torch-config keys in TORCH.md or `src/torch-config.mjs`, also update README.md's matching sections.
2. CONTRIBUTING.md is minimal and accurate — only update if new scripts are added to package.json.
3. AGENTS.md is a policy-only file — do not edit it for accuracy; open an issue if policy appears wrong.
4. The 4 env vars most often missed in README: `NOSTR_LOCK_PUBLISH_TIMEOUT_MS`, `NOSTR_LOCK_MIN_SUCCESSFUL_PUBLISHES`, `NOSTR_LOCK_RELAY_FALLBACKS`, `NOSTR_LOCK_MIN_ACTIVE_RELAY_POOL`.
@@ -0,0 +1,15 @@
# Docs Alignment Agent: Claims Tracing Pattern
- **Context:** docs-alignment-agent run 2026-02-20. Daily pass auditing docs/lib-overview.md, docs/lock-ops-overview.md, docs/memory-index-overview.md, docs/removal.md, docs/architecture/system-overview.md, docs/AGENT_COVERAGE_GAPS.md, src/constants.mjs USAGE_TEXT.
- **Observation:** Four confirmed doc/code divergences found and fixed:
1. `docs/lib-overview.md` claimed RACE_CHECK_DELAY_MS = 2000ms; actual is 1500ms (src/constants.mjs:2).
2. `src/constants.mjs` USAGE_TEXT said `NOSTR_LOCK_QUERY_TIMEOUT_MS` default is 15000; actual default is 30000 (`DEFAULT_QUERY_TIMEOUT_MS`). The 15000 value matches `DEFAULT_PUBLISH_TIMEOUT_MS` — likely copy-paste confusion between the two timeouts.
3. `docs/removal.md` env var cleanup list was missing 4 vars: `NOSTR_LOCK_PUBLISH_TIMEOUT_MS`, `NOSTR_LOCK_MIN_SUCCESSFUL_PUBLISHES`, `NOSTR_LOCK_RELAY_FALLBACKS`, `NOSTR_LOCK_MIN_ACTIVE_RELAY_POOL`.
4. `docs/AGENT_COVERAGE_GAPS.md` claimed "playwright dependency" — Playwright is not in package.json; E2E tests use Node.js built-in `--test` runner.
- **Action taken:** Fixed all four issues with minimal, targeted edits.
- **Validation performed:** Grep confirmed exact corrected values in all edited files post-edit.
- **Recommendation for next agents:**
1. When auditing docs, always trace numeric constants (timeouts, delays, counts) to `src/constants.mjs` — these drift most often.
2. The USAGE_TEXT in `src/constants.mjs` is the CLI `--help` string — verify its defaults match the exported constants above it.
3. `docs/removal.md` env var list must stay in sync with `src/torch-config.mjs` — the four new relay-reliability vars are the most commonly missed.
4. When AGENT_COVERAGE_GAPS.md mentions technology dependencies, verify against `package.json` before trusting the claim.
@@ -0,0 +1,11 @@
# Learning: Scheduler Cycle File Documentation Patterns
- **Context:** docs-code-investigator run 2026-02-20. Documented `scripts/agent/run-scheduler-cycle.mjs` (926 lines) with JSDoc, inline comments, and `docs/scheduler-cycle-overview.md`.
- **Observation:** The file's logic precisely mirrors the 19 numbered steps in `src/prompts/scheduler-flow.md`. The top-of-file comment already referenced scheduler-flow.md but didn't enumerate the steps — adding them makes the code immediately navigable.
- **Action taken:** Added module-level JSDoc with 19-step flow, JSDoc to 15 internal functions, targeted inline comments, and a full overview doc.
- **Validation performed:** Static read of full 926-line file. Call graph traced to scheduler-utils.mjs, scheduler-lock.mjs, src/utils.mjs, roster.json, and test files.
- **Recommendation for next agents:**
1. When documenting scheduler-related files, always cross-reference `src/prompts/scheduler-flow.md` — it is the spec; the code implements it.
2. The most non-obvious invariant: lock:complete is called ONLY after all validation gates pass. Document this explicitly when touching the scheduler.
3. Three test files cover scheduler behavior: run-scheduler-cycle-memory-policy.test.mjs, scheduler-time-window.test.mjs, scheduler-preflight-lock.e2e.test.mjs. Run all three when changing scheduler logic.
4. `task-logs/<cadence>/.scheduler-run-state.json` is day-scoped (resets at midnight UTC). Do not treat it as persistent cross-day storage.
+1 -1
View File
@@ -40,7 +40,7 @@ Displays a CLI-friendly table of active locks.
- Content: JSON payload with metadata.
4. **Publish**: Send to all configured relays.
5. **Race Check**:
- Wait `RACE_CHECK_DELAY_MS` (default 2000ms).
- Wait `RACE_CHECK_DELAY_MS` (default 1500ms).
- Query relays again.
- If another valid event exists with an *earlier* `created_at`: **Abort** (Race lost).
- Otherwise: **Success**.
+4
View File
@@ -106,6 +106,10 @@ If you configured TORCH environment variables in your shell profile, CI, or `.en
- `NOSTR_LOCK_RELAYS`
- `NOSTR_LOCK_TTL`
- `NOSTR_LOCK_QUERY_TIMEOUT_MS`
- `NOSTR_LOCK_PUBLISH_TIMEOUT_MS`
- `NOSTR_LOCK_MIN_SUCCESSFUL_PUBLISHES`
- `NOSTR_LOCK_RELAY_FALLBACKS`
- `NOSTR_LOCK_MIN_ACTIVE_RELAY_POOL`
- `NOSTR_LOCK_DAILY_ROSTER`
- `NOSTR_LOCK_WEEKLY_ROSTER`
- `TORCH_CONFIG_PATH`
+262
View File
@@ -0,0 +1,262 @@
# Scheduler Cycle Overview (`scripts/agent/run-scheduler-cycle.mjs`)
## What this module does
`run-scheduler-cycle.mjs` is the main scheduler entry point. Given a cadence
(`daily` or `weekly`), it selects the next eligible agent from the roster,
acquires a distributed Nostr lock to prevent duplicate execution across agents,
runs the selected agent's prompt via a configured handoff command, verifies that
required run artifacts and memory evidence were produced, then publishes task
completion.
This script owns the full agent lifecycle for one slot per invocation:
lock acquisition → prompt execution → artifact/memory verification → lock:complete.
Spawned agents must **not** call `lock:complete` themselves — the scheduler does it
after all validation passes.
## Where it fits
```
npm run scheduler:daily
└─ scripts/agent/run-scheduler-cycle.mjs daily
├─ src/prompts/roster.json (agent list)
├─ torch-config.json (handoff command, memory policy, etc.)
├─ scripts/agent/scheduler-utils.mjs
├─ scripts/agent/scheduler-lock.mjs
├─ npm run lock:check:daily (relay query)
├─ npm run lock:lock (relay publish)
├─ <handoffCommand> (spawns the agent)
├─ scripts/agent/verify-run-artifacts.mjs
└─ npm run lock:complete (relay publish, permanent)
```
## Typical call flow
```
1. Parse args → cadence='daily', platform='codex', model=null
2. Load roster from src/prompts/roster.json → ['audit-agent', 'docs-agent', ...]
3. Read AGENTS.md → print to stdout (best-effort; non-fatal if missing)
4. [LOOP] Read run-state → { run_date: '2026-02-20', lock_deferral: null }
5. [LOOP] Lock health preflight (if enabled):
npm run lock:health -- --cadence daily
→ non-zero: write _deferred.md or _failed.md, exit
6. [LOOP] npm run lock:check:daily -- --json --quiet
→ exclusion set: { locked: ['ci-health-agent'], paused: [], completed: [] }
7. [LOOP] Time-window guard: exclude agents that ran within 24h window
8. [LOOP] Round-robin select:
roster = ['audit-agent', 'ci-health-agent', 'docs-agent', ...]
previous = 'ci-health-agent' (from latest log)
excluded = { 'ci-health-agent' }
→ selected = 'docs-agent'
9. [LOOP] npm run lock:lock -- --agent docs-agent --cadence daily
→ exit 0: lock acquired (continue)
→ exit 3: race lost (restart loop at step 4)
→ exit 2: backend error → defer (non-strict) or fail (strict)
10. [LOOP] Validate src/prompts/daily/docs-agent.md
→ not readable → _failed.md (prompt_parse_error), exit 1
→ bad first line → _failed.md (prompt_schema_error), exit 1
11. [LOOP] Clear deferral state. Run memory retrieve command (if configured).
12. [LOOP] Run handoff command (e.g. `claude --prompt "$SCHEDULER_PROMPT_PATH"`)
→ non-zero → _failed.md (execution_error), exit non-zero
→ missing → _failed.md, exit 1
13. [LOOP] Run memory store command (if configured).
14. [LOOP] Verify memory evidence (markers/artifacts):
→ mode='required' + missing → _failed.md, exit 1
→ mode='optional' + missing → console.warn, continue
15. [LOOP] node scripts/agent/verify-run-artifacts.mjs --since <runStart>
→ non-zero → _failed.md (prompt_schema_error), exit non-zero
16. [LOOP] npm run lint (or configured validationCommands)
→ non-zero → _failed.md (execution_error), exit non-zero
17. [LOOP] npm run lock:complete -- --agent docs-agent --cadence daily
→ non-zero → _failed.md, exit non-zero
18. [LOOP] Write _completed.md task log. Print run summary. Exit 0.
```
## Public API summary
This is a CLI script — it has no exported functions. The entry point is:
```bash
node scripts/agent/run-scheduler-cycle.mjs <daily|weekly> [--platform <p>] [--model <m>]
# or via npm:
npm run scheduler:daily
npm run scheduler:weekly
```
**Environment variables:**
| Variable | Source | Purpose |
|----------|--------|---------|
| `AGENT_PLATFORM` | env | Platform identifier (codex, claude, linux) |
| `AGENT_MODEL` | env | Model identifier forwarded to lock events |
| `SCHEDULER_LOCK_MAX_RETRIES` | env | Override lock:lock retry count (default: 2) |
| `SCHEDULER_LOCK_BACKOFF_MS` | env | Override base backoff ms (default: 250) |
| `SCHEDULER_LOCK_JITTER_MS` | env | Override jitter ms (default: 75) |
| `SCHEDULER_LOCK_HEALTH_PREFLIGHT` | env | Enable/disable relay health preflight |
| `SCHEDULER_SKIP_LOCK_HEALTH_PREFLIGHT` | env | Skip preflight even if enabled |
| `SCHEDULER_STRICT_LOCK` | env | Override strict_lock policy |
| `SCHEDULER_DEGRADED_LOCK_RETRY_WINDOW_MS` | env | Override deferral window ms |
| `SCHEDULER_MAX_DEFERRALS` | env | Override max deferral count |
**Variables injected into child processes:**
| Variable | Value | Purpose |
|----------|-------|---------|
| `SCHEDULER_AGENT` | selected agent name | Used by handoff/memory commands |
| `SCHEDULER_CADENCE` | `daily`\|`weekly` | Used by handoff/memory commands |
| `SCHEDULER_PROMPT_PATH` | absolute path | Path to prompt file |
| `SCHEDULER_MEMORY_FILE` | absolute path | Agent writes learnings here |
## Key internal functions
| Function | Purpose |
|----------|---------|
| `parseArgs(argv)` | Parses cadence, platform, model from CLI args |
| `getSchedulerConfig(cadence, opts)` | Merges torch-config.json + env overrides |
| `readRunState(cadence)` / `writeRunState(path, state)` | Deferral state persistence (day-scoped) |
| `getLatestFile(logDir)` | Finds most recent valid task log for previous-agent detection |
| `selectNextAgent({roster, excludedSet, previousAgent, firstPrompt})` | Round-robin with exclusion |
| `validatePromptFile(promptPath)` | Reads and validates prompt format |
| `verifyMemoryStep({name, markers, artifacts, outputText, sinceMs})` | Evidence check |
| `writeLog({cadence, agent, status, ...})` | Writes YAML-frontmatter task log file |
| `exitWithSummary(code, data)` | Prints summary + calls process.exit |
## Key invariants
1. **lock:complete gate**: `npm run lock:complete` is called only after memory
verification (step 14), artifact verification (step 15), and all validation
commands (step 16) pass. If any gate fails, the run ends without publishing
completion.
2. **Completed log gate**: `_completed.md` task log is written only after
lock:complete exits 0 (step 17). Earlier failures always write `_failed.md`
or `_deferred.md`.
3. **Race resolution**: Lock exit code 3 (race lost) causes the scheduler to
restart the loop at step 4, re-checking what agents are available. This is
the normal race resolution path — not an error.
4. **Time-window guard**: Agents that appear in a recent local log (within 24h
for daily, 7d for weekly) are added to the exclusion set regardless of relay
state. This handles the cross-midnight edge case where the relay date-scope
has rolled over.
5. **Cycle saturation**: When all roster agents are in the time-window exclusion
set, the scheduler exits 0 with a "cycle complete" reason — not treated as
an error by cron/CI.
6. **Deferral state is day-scoped**: `task-logs/<cadence>/.scheduler-run-state.json`
is reset whenever `run_date` changes, so deferral budgets do not carry across days.
## Edge cases & error paths
| Scenario | Behavior |
|----------|----------|
| No roster entries | `process.exit(1)` before loop |
| Roster fully excluded (not cycle-saturated) | `_failed.md` + exit 1 |
| All relays unhealthy (preflight) | `_deferred.md` + exit 0 |
| Lock backend error (strict mode) | `_failed.md` + exit 2 |
| Lock backend error (non-strict, within window/budget) | `_deferred.md` + exit 0; deferral state persisted |
| Lock backend error (non-strict, budget exhausted) | `_failed.md` + exit 2 |
| Prompt file missing or unreadable | `_failed.md (prompt_parse_error)` + exit 1 |
| Prompt first line not `#` or `>` | `_failed.md (prompt_schema_error)` + exit 1 |
| handoffCommand missing (non-interactive mode) | `_failed.md (execution_error)` + exit 1 |
| handoffCommand exits non-zero | `_failed.md (execution_error)` + exit non-zero |
| Memory evidence missing (required mode) | `_failed.md (prompt_schema_error)` + exit 1 |
| Memory evidence missing (optional mode) | console.warn, continue |
| Artifact verification fails | `_failed.md (prompt_schema_error)` + exit non-zero |
| Validation command exits non-zero | `_failed.md (execution_error)` + exit non-zero |
| lock:complete exits non-zero | `_failed.md` + exit non-zero |
## Security considerations
- ⚠️ Environment variables injected into child processes (`SCHEDULER_AGENT`,
`SCHEDULER_CADENCE`, `SCHEDULER_PROMPT_PATH`, `SCHEDULER_MEMORY_FILE`) are derived
from the roster and config — not from user input. No sanitization issue here, but
ensure `torch-config.json` is not writable by untrusted parties.
- The handoff command is executed via `bash -lc` with a login shell. The command
string comes from `torch-config.json`. Do not allow untrusted config sources.
- No cryptographic or signing logic in this file.
## Performance & concurrency
- The loop is single-threaded and sequential; no parallel lock attempts.
- Each iteration makes several network calls (lock:check, lock:lock, lock:complete)
that may take seconds each. Total wall-clock time per cycle depends heavily on
relay latency and handoff command duration.
- `getLatestFile()` reads all log filenames and file contents synchronously in
memory — acceptable for typical log counts (< 1000 files) but worth monitoring
if logs accumulate without rotation.
## Related files and call graph
| File | Relationship |
|------|-------------|
| `scripts/agent/scheduler-utils.mjs` | Utility functions (runCommand, readJson, parsers, etc.) |
| `scripts/agent/scheduler-lock.mjs` | Lock acquisition, error classification, preflight |
| `scripts/agent/verify-run-artifacts.mjs` | Run artifact validation (called as subprocess) |
| `scripts/agent/scheduler-utils.mjs:buildRecentlyRunExclusionSet` | Time-window guard |
| `src/utils.mjs:detectPlatform` | Auto-detects agent platform |
| `src/prompts/roster.json` | Agent roster (read at startup) |
| `src/prompts/scheduler-flow.md` | Authoritative numbered steps (implemented by this script) |
| `torch-config.json` | Handoff command, memory policy, lock policy |
| `task-logs/<cadence>/` | Task logs written and read here |
| `task-logs/<cadence>/.scheduler-run-state.json` | Deferral state (day-scoped) |
| `memory-updates/` | Memory update files (written path injected as env var) |
## Why it works this way
- **Infinite loop with restart on race loss**: Distributed lock systems inherently
have races; the loop lets the scheduler immediately try the next available agent
without requiring the caller to re-invoke the script.
- **Day-scoped run-state**: Deferral tracking needs to persist across multiple rapid
scheduler invocations on the same day (e.g., cron every 5 minutes) but should
reset daily so a failed relay doesn't block the next day's run.
- **lock:complete gated behind all validation**: Ensures a completion event on the
relay (which prevents future agents from re-running the same slot) is only
published after the run is genuinely finished and verified. This prevents
"green-but-worthless" completions.
- **Subprocess model for handoff**: Agents run in their own process with
environment variables injected by the scheduler. This isolates agent crashes
and allows the scheduler to check exit codes cleanly.
## When to change
- **New validation gate**: Add a step between artifact check and lock:complete.
Maintain the gate ordering: memory evidence → artifact check → validation → lock:complete.
- **New failure category**: Add to the `FAILURE_CATEGORY` constant and document
in `src/prompts/scheduler-flow.md`.
- **New config option**: Add parsing in `getSchedulerConfig()` with an env var
override following the existing pattern.
- **Log format change**: Update `writeLog()` — also update `verify-run-artifacts.mjs`
and `scheduler-utils.mjs:parseFrontmatterAgent` to match.
## Tests that validate behavior
- `test/run-scheduler-cycle-memory-policy.test.mjs` — memory policy enforcement
- `test/scheduler-time-window.test.mjs` — time-window guard behavior
- `test/scheduler-preflight-lock.e2e.test.mjs` — preflight + lock flow
```bash
# Run all scheduler tests:
node --test test/run-scheduler-cycle-memory-policy.test.mjs test/scheduler-time-window.test.mjs
# Or full suite (may be slow in sandbox):
npm test
```
@@ -0,0 +1,18 @@
# Memory Update — docs-agent — 2026-02-20
## Key findings
- `TORCH.md` is the authoritative env-var and torch-config.json reference; README.md is consumer-facing and drifts from TORCH.md when new config keys are added.
- `CONTRIBUTING.md` is accurate; all commands verified against `package.json` (no `npm run format` script exists in this project — only `npm run lint`).
- `AGENTS.md` is a policy document only; do not edit it for accuracy.
## Patterns / reusable knowledge
- When auditing docs: diff README.md "Environment variables" and "Configuration" sections against TORCH.md as the ground truth; also grep `src/torch-config.mjs` to confirm env var usage.
- Run artifact YAML frontmatter must include `agent:`, `cadence:`, and `run-start:` — missing any of these causes `verify-run-artifacts.mjs` to fail.
- Glob is the right tool for path existence checks; Grep confirms env var/config key usage in source.
## Warnings / gotchas
- Claude Code sandbox blocks outbound WebSockets — cannot run lock commands or relay health checks; document this limitation in test logs.
- All 4 missing env vars that were added to README.md: `NOSTR_LOCK_PUBLISH_TIMEOUT_MS`, `NOSTR_LOCK_MIN_SUCCESSFUL_PUBLISHES`, `NOSTR_LOCK_RELAY_FALLBACKS`, `NOSTR_LOCK_MIN_ACTIVE_RELAY_POOL`.
@@ -0,0 +1,18 @@
# Memory Update — docs-alignment-agent — 2026-02-20
## Key findings
- `src/constants.mjs` is the single source of truth for numeric defaults (RACE_CHECK_DELAY_MS=1500, DEFAULT_QUERY_TIMEOUT_MS=30000, DEFAULT_PUBLISH_TIMEOUT_MS=15000). Docs frequently show stale values — always re-verify these numbers.
- USAGE_TEXT in `src/constants.mjs` (displayed by `torch-lock --help`) had a wrong default for `NOSTR_LOCK_QUERY_TIMEOUT_MS`: said 15000 but constant is 30000. This was likely copy-paste of the publish timeout value.
- `docs/AGENT_COVERAGE_GAPS.md` tech claims can be stale — verify against `package.json` (Playwright was not present despite being claimed as a dependency).
## Patterns / reusable knowledge
- **Claims tracing sequence:** Read doc → extract numeric/behavioral claim → Grep `src/constants.mjs` for actual constant → compare. Repeat for every timeout/delay/count.
- **Four env vars most likely to be missing from lists:** `NOSTR_LOCK_PUBLISH_TIMEOUT_MS`, `NOSTR_LOCK_MIN_SUCCESSFUL_PUBLISHES`, `NOSTR_LOCK_RELAY_FALLBACKS`, `NOSTR_LOCK_MIN_ACTIVE_RELAY_POOL`. Check `docs/removal.md`, README.md, and any other env-var reference lists.
- Architecture file paths in `docs/architecture/system-overview.md` are accurate and do not need re-verification unless new files are added.
## Warnings / gotchas
- Claude Code sandbox: outbound WebSocket/EPERM restrictions prevent `npm run lint` and `npm test` — document this in test_logs but do not treat it as a failure for docs-only changes.
- The 2000ms RACE_CHECK_DELAY_MS value may appear in other agents' memory or notes — it is wrong; actual value is 1500ms.
@@ -0,0 +1,18 @@
# Memory Update — docs-code-investigator — 2026-02-20
## Key findings
- `scripts/agent/run-scheduler-cycle.mjs` (926 lines) is now documented with a 19-step module JSDoc + JSDoc on all 15 internal functions + `docs/scheduler-cycle-overview.md`. Do not re-document this file on next run.
- The scheduler's key invariant: lock:complete is called ONLY after memory evidence check, artifact check, and all validation commands pass. This ordering is critical and must be preserved in any scheduler modification.
- `src/prompts/scheduler-flow.md` is the spec; `run-scheduler-cycle.mjs` implements it. Step numbers in scheduler-flow.md map directly to code sections.
## Patterns / reusable knowledge
- **File selection heuristic:** run `git ls-files '*.js' '*.mjs' | xargs -n1 wc -l | sort -rn | head -20`, then subtract files already covered by `docs/*-overview.md`. Next best undocumented large files: `src/ops.mjs` (795 lines), `scripts/agent/scheduler-utils.mjs` (321 lines).
- **JSDoc insertion:** Use Edit tool with targeted old_string/new_string to insert JSDoc above each function. Do not use Write to rewrite the whole file — too risky for a 926-line file.
- **Docs style:** Match existing overview docs (`docs/lib-overview.md`, `docs/lock-ops-overview.md`) — sections: What it does / Where it fits / Typical call flow / Public API / Key invariants / Edge cases / Security / Related files / Why / When to change / Tests.
## Warnings / gotchas
- `npm run lint` and `npm test` not runnable in Claude Code sandbox. For documentation-only changes this is acceptable — document it in TEST_LOG but don't treat as blocking.
- `CONTEXT_20260220T041302Z.md` already exists in src/context/ (owned by prompt-maintenance-agent). Use a different timestamp for docs-code-investigator artifacts.
+207
View File
@@ -1,4 +1,43 @@
#!/usr/bin/env node
/**
* run-scheduler-cycle.mjs — TORCH Agent Scheduler
*
* Orchestrates one full scheduling cycle for a given cadence (daily|weekly).
* Selects the next eligible agent from the roster, acquires a distributed Nostr
* lock, executes the agent's prompt via a configured handoff command, verifies
* required artifacts and memory evidence, then publishes completion.
*
* This script owns the full lifecycle for a single agent slot per invocation:
* lock acquisition → prompt execution → artifact verification → lock:complete.
* Spawned agents MUST NOT call lock:complete themselves.
*
* Main flow (mirrors src/prompts/scheduler-flow.md numbered MUST steps):
* 1. Parse cadence (daily|weekly), platform, and model from CLI args.
* 2. Load roster from src/prompts/roster.json.
* 3. Read AGENTS.md policy file (best-effort, non-fatal if missing).
* 4. [loop] Read cadence run-state (deferral tracking for the current UTC day).
* 5. [loop] Optional lock health preflight — fail/defer if all relays unhealthy.
* 6. [loop] Run lock:check to build the exclusion set (locked + paused + completed).
* 7. [loop] Apply local time-window guard (24 h daily / 7 d weekly) to exclusion set.
* 8. [loop] Round-robin select next eligible agent from roster minus exclusion set.
* 9. [loop] If no agent eligible → exit (cycle-saturated or all-excluded).
* 10. [loop] Acquire lock with retry. exit 3 → back to step 4; exit 2 → defer or fail.
* 11. [loop] Validate prompt file (readable, correct format).
* 12. [loop] Clear deferral state. Run memory retrieve command (if configured).
* 13. [loop] Run handoff command — hard fail if missing or exits non-zero.
* 14. [loop] Run memory store command (if configured).
* 15. [loop] Verify memory evidence (markers/artifact files).
* 16. [loop] Verify run artifacts via verify-run-artifacts.mjs.
* 17. [loop] Run validation commands (default: npm run lint).
* 18. [loop] Publish lock:complete — hard fail if relay error.
* 19. [loop] Write completed task log and print run summary. Exit 0.
*
* Key invariants:
* - lock:complete is called ONLY after all validation gates pass (step 18 is last gate).
* - Completed task log is written ONLY after lock:complete succeeds.
* - Lock exit code 3 (race lost) causes a loop restart to pick the next agent.
* - Memory mode 'required' turns missing evidence into a hard failure.
*/
// Source of truth: numbered MUST steps 2 and 4-16 in src/prompts/scheduler-flow.md are
// implemented by this script; step 3 (policy-file read) is best-effort and non-fatal.
import fs from 'node:fs/promises';
@@ -43,6 +82,14 @@ const FAILURE_CATEGORY = {
EXECUTION: 'execution_error',
};
/**
* Parses CLI arguments into a structured options object.
* Supports both positional cadence (`daily`|`weekly`) and named flags.
* Falls back to AGENT_PLATFORM env var or auto-detected platform for platform.
*
* @param {string[]} argv - process.argv slice (args after the script name)
* @returns {{ cadence: string|null, platform: string, model: string|null }}
*/
function parseArgs(argv) {
const args = { cadence: null, platform: process.env.AGENT_PLATFORM || detectPlatform() || 'unknown', model: process.env.AGENT_MODEL || null };
for (let i = 0; i < argv.length; i += 1) {
@@ -68,10 +115,25 @@ function parseArgs(argv) {
return args;
}
/**
* Returns an ISO 8601 timestamp string safe for use as a filename component.
* Colons are replaced with dashes and milliseconds are stripped.
* Example output: `2026-02-20T07-00-00Z`
*
* @returns {string}
*/
function ts() {
return new Date().toISOString().replace(/\.\d{3}Z$/, 'Z').replace(/:/g, '-');
}
/**
* Builds a metadata object that includes a standardized `failure_category` field.
* Used by writeLog() to attach structured failure classification to task logs.
*
* @param {string} failureCategory - One of FAILURE_CATEGORY values (e.g. 'lock_backend_error').
* @param {Object} [extraMetadata={}] - Additional key/value pairs to merge.
* @returns {Object}
*/
function categorizeFailureMetadata(failureCategory, extraMetadata = {}) {
return {
failure_category: failureCategory,
@@ -79,6 +141,16 @@ function categorizeFailureMetadata(failureCategory, extraMetadata = {}) {
};
}
/**
* Validates that a prompt file is readable and contains a valid markdown heading
* or blockquote as its first non-empty line.
*
* This is a lightweight schema check — it does not parse frontmatter or validate
* the full prompt contract (that is handled by validate-prompt-contract.mjs).
*
* @param {string} promptPath - Absolute path to the prompt markdown file.
* @returns {Promise<{ok: true}|{ok: false, category: string, reason: string, detail: string}>}
*/
async function validatePromptFile(promptPath) {
let content;
try {
@@ -106,6 +178,14 @@ async function validatePromptFile(promptPath) {
return { ok: true };
}
/**
* Checks whether a file exists and was modified at or after `sinceMs`.
* Used to verify that memory evidence artifacts were produced during the current run.
*
* @param {string|null} filePath - Relative or absolute path to check.
* @param {number} sinceMs - Epoch milliseconds threshold (run start time).
* @returns {Promise<boolean>}
*/
async function artifactExistsSince(filePath, sinceMs) {
if (!filePath) return false;
const resolved = path.isAbsolute(filePath)
@@ -119,6 +199,19 @@ async function artifactExistsSince(filePath, sinceMs) {
}
}
/**
* Verifies that a memory pipeline step (retrieve or store) produced observable evidence.
* Evidence can be either a deterministic output marker string OR a qualifying artifact file.
* A step is considered complete when either form of evidence is present.
*
* @param {Object} params
* @param {string} params.name - Step name ('retrieve'|'store') for logging.
* @param {string[]} params.markers - Substrings to search for in combined command output.
* @param {string[]} params.artifacts - Relative paths of artifact files to check.
* @param {string} params.outputText - Combined stdout+stderr from all commands this run.
* @param {number} params.sinceMs - Run start epoch ms; artifact files must be newer.
* @returns {Promise<{name: string, markerMatched: boolean, artifactMatched: boolean, complete: boolean}>}
*/
async function verifyMemoryStep({ name, markers, artifacts, outputText, sinceMs }) {
const markerMatched = markers.some((marker) => outputText.includes(marker));
const artifactResults = await Promise.all(artifacts.map((artifact) => artifactExistsSince(artifact, sinceMs)));
@@ -133,6 +226,17 @@ async function verifyMemoryStep({ name, markers, artifacts, outputText, sinceMs
}
/**
* Finds the most recent valid scheduler task log file in `logDir`.
* "Valid" means the filename passes isStrictSchedulerLogFilename() AND the file
* contains a parseable timestamp (from frontmatter created_at or the filename itself).
*
* Files with unparseable timestamps are skipped with a console warning.
* The effective timestamp for sorting is derived from frontmatter first, then filename.
*
* @param {string} logDir - Absolute path to the cadence log directory (e.g. task-logs/daily/).
* @returns {Promise<string|null>} Filename (not path) of the latest log, or null if none.
*/
async function getLatestFile(logDir) {
await fs.mkdir(logDir, { recursive: true });
const entries = await fs.readdir(logDir, { withFileTypes: true });
@@ -170,6 +274,22 @@ async function getLatestFile(logDir) {
return latest?.filename || null;
}
/**
* Selects the next eligible agent from the roster using round-robin scheduling.
*
* Selection algorithm:
* - If `previousAgent` is in the roster, start scanning from the next index (wrap-around).
* - Otherwise, start from `firstPrompt` index (if configured) or index 0.
* - Returns the first candidate not present in `excludedSet`.
* - Returns null if every roster agent is excluded.
*
* @param {Object} params
* @param {string[]} params.roster - Ordered array of agent names for this cadence.
* @param {Set<string>} params.excludedSet - Agents to skip (locked, paused, completed, time-excluded).
* @param {string|null} params.previousAgent - Agent that ran most recently (from latest log).
* @param {string|null} params.firstPrompt - Configured first-run starting agent name.
* @returns {string|null} Selected agent name, or null if roster is fully excluded.
*/
function selectNextAgent({ roster, excludedSet, previousAgent, firstPrompt }) {
const previousIndex = roster.indexOf(previousAgent);
const firstIndex = roster.indexOf(firstPrompt);
@@ -186,6 +306,27 @@ function selectNextAgent({ roster, excludedSet, previousAgent, firstPrompt }) {
return null;
}
/**
* Loads and normalizes scheduler configuration for the given cadence.
* Merges torch-config.json settings with environment variable overrides.
*
* Configuration covers:
* - handoffCommand: shell command to execute the selected agent's prompt.
* - validationCommands: commands that must pass before lock:complete (default: npm run lint).
* - lockRetry: maxRetries, backoffMs, jitterMs for lock acquisition retries.
* - lockHealthPreflight: whether to probe relay health before lock acquisition.
* - lockFailurePolicy: strictLock, degradedLockRetryWindowMs, maxDeferrals.
* - memoryPolicy: mode (required|optional), retrieve/store commands, markers, artifacts.
*
* Environment variable overrides (all optional):
* SCHEDULER_LOCK_MAX_RETRIES, SCHEDULER_LOCK_BACKOFF_MS, SCHEDULER_LOCK_JITTER_MS,
* SCHEDULER_LOCK_HEALTH_PREFLIGHT, SCHEDULER_SKIP_LOCK_HEALTH_PREFLIGHT,
* SCHEDULER_STRICT_LOCK, SCHEDULER_DEGRADED_LOCK_RETRY_WINDOW_MS, SCHEDULER_MAX_DEFERRALS
*
* @param {string} cadence - 'daily'|'weekly'
* @param {{ isInteractive: boolean }} options
* @returns {Promise<Object>} Normalized scheduler config object.
*/
async function getSchedulerConfig(cadence, { isInteractive }) {
const cfg = await readJson(path.resolve(process.cwd(), 'torch-config.json'), {});
const scheduler = cfg.scheduler || {};
@@ -262,6 +403,17 @@ async function getSchedulerConfig(cadence, { isInteractive }) {
};
}
/**
* Reads persisted scheduler run-state for the current UTC day.
* Run-state is used to track lock deferral metadata across multiple invocations
* within the same day (e.g., retry window tracking for non-strict lock failures).
*
* If the state file is missing, unreadable, or belongs to a previous day, returns
* a fresh default state. State is keyed by `run_date` (YYYY-MM-DD UTC).
*
* @param {string} cadence - 'daily'|'weekly'
* @returns {Promise<{ statePath: string, state: { run_date: string, lock_deferral: Object|null } }>}
*/
async function readRunState(cadence) {
const statePath = path.resolve(process.cwd(), 'task-logs', cadence, '.scheduler-run-state.json');
const fallback = { run_date: getRunDateKey(), lock_deferral: null };
@@ -283,15 +435,45 @@ async function readRunState(cadence) {
};
}
/**
* Persists scheduler run-state to disk (JSON file).
* Creates parent directories if they do not exist.
*
* @param {string} statePath - Absolute path to the state file.
* @param {Object} state - State object to serialize.
* @returns {Promise<void>}
*/
async function writeRunState(statePath, state) {
await fs.mkdir(path.dirname(statePath), { recursive: true });
await fs.writeFile(statePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
}
/**
* Generates a unique idempotency key for a deferred lock attempt.
* The key is stable across retries for the same agent/cadence/date combination
* by being stored in run-state and reused on subsequent invocations.
*
* @param {{ cadence: string, selectedAgent: string, runDate: string }} params
* @returns {string}
*/
function createIdempotencyKey({ cadence, selectedAgent, runDate }) {
return `${cadence}:${selectedAgent}:${runDate}:${randomUUID()}`;
}
/**
* Writes a task log file to `task-logs/<cadence>/` with YAML frontmatter.
* File naming: `<ts>__<agent>__<status>.md`
*
* The frontmatter includes cadence, agent, status, reason, created_at, platform,
* and any additional metadata fields. The file body repeats key fields as bullets
* for human readability.
*
* These files are read by getLatestFile(), cmdCheck(), and the dashboard.
*
* @param {{ cadence: string, agent: string, status: string, reason: string,
* detail?: string, platform?: string, metadata?: Object }} params
* @returns {Promise<string>} The filename (not full path) of the written log.
*/
async function writeLog({ cadence, agent, status, reason, detail, platform, metadata = {} }) {
const logDir = path.resolve(process.cwd(), 'task-logs', cadence);
await fs.mkdir(logDir, { recursive: true });
@@ -328,6 +510,15 @@ async function writeLog({ cadence, agent, status, reason, detail, platform, meta
return file;
}
/**
* Prints a human-readable run summary to stdout.
* Includes status, agent, prompt path, platform, reason, and the contents of
* the memory update file (up to 2000 characters, truncated if longer).
*
* @param {{ status: string, agent: string, promptPath: string|null, reason: string,
* detail?: string, memoryFile?: string, platform: string }} params
* @returns {Promise<void>}
*/
async function printRunSummary({ status, agent, promptPath, reason, detail, memoryFile, platform }) {
let learnings = 'No learnings recorded.';
if (memoryFile) {
@@ -360,6 +551,15 @@ async function printRunSummary({ status, agent, promptPath, reason, detail, memo
process.stdout.write('================================================================================\n\n');
}
/**
* Prints the run summary then terminates the process with the given exit code.
* All normal and failure exit paths in main() go through this function to ensure
* a summary is always printed before exit.
*
* @param {number} code - Exit code (0 = success, 1 = general failure, 2 = backend error).
* @param {Object|null} summaryData - Data passed to printRunSummary, or null to skip.
* @returns {Promise<never>}
*/
async function exitWithSummary(code, summaryData) {
if (summaryData) {
await printRunSummary(summaryData);
@@ -433,6 +633,8 @@ async function main() {
}
}
// Step 6: Build exclusion set from relay state. Prefer the top-level `excluded`
// field (computed by cmdCheck), falling back to the union of locked/paused/completed.
const checkResult = await runCommand('npm', ['run', `lock:check:${cadence}`, '--', '--json', '--quiet']);
const checkPayload = parseJsonFromOutput(`${checkResult.stdout}\n${checkResult.stderr}`) || {};
const excluded = Array.isArray(checkPayload.excluded)
@@ -523,6 +725,8 @@ async function main() {
});
const lockResult = lockAttempt.result;
// Lock exit 3: race lost — another agent claimed this slot between our check
// and our publish. Restart the loop to pick the next available agent.
if (lockResult.code === 3) {
continue;
}
@@ -664,6 +868,8 @@ async function main() {
const runStartMs = Date.parse(runArtifactSince);
const outputChunks = [];
// Pre-create the memory update file path and inject it as SCHEDULER_MEMORY_FILE
// so the spawned agent can write learnings without computing the path itself.
const memoryDir = path.resolve(process.cwd(), 'memory-updates');
await fs.mkdir(memoryDir, { recursive: true });
const memoryFile = path.join(memoryDir, `${ts()}__${selectedAgent}.md`);
@@ -795,6 +1001,7 @@ async function main() {
sinceMs: runStartMs,
});
// mode='required' → missing evidence is a hard failure; mode='optional' → warning only.
const missingSteps = [retrieveCheck, storeCheck].filter((step) => !step.complete).map((step) => step.name);
if (missingSteps.length && schedulerConfig.memoryPolicy.mode === 'required') {
await writeLog({
+1 -1
View File
@@ -69,7 +69,7 @@ Environment:
NOSTR_LOCK_NAMESPACE Namespace prefix for lock tags (default: torch)
NOSTR_LOCK_RELAYS Comma-separated relay WSS URLs
NOSTR_LOCK_TTL Lock TTL in seconds (default: 7200)
NOSTR_LOCK_QUERY_TIMEOUT_MS Relay query timeout in milliseconds (default: 15000)
NOSTR_LOCK_QUERY_TIMEOUT_MS Relay query timeout in milliseconds (default: 30000)
NOSTR_LOCK_DAILY_ROSTER Comma-separated daily roster (optional)
NOSTR_LOCK_WEEKLY_ROSTER Comma-separated weekly roster (optional)
TORCH_CONFIG_PATH Optional path to torch-config.json (default: ./torch-config.json)
@@ -0,0 +1,38 @@
---
agent: docs-agent
cadence: daily
run-start: "2026-02-20T05:00:00Z"
prompt: src/prompts/daily/docs-agent.md
---
# Context — docs-agent — 2026-02-20
## Goal
Audit key documentation files (`README.md`, `AGENTS.md`) for accuracy relative to the codebase and fix any inaccuracies found.
## Scope
- Baseline reads: `AGENTS.md`, `CLAUDE.md`, `KNOWN_ISSUES.md`, `docs/agent-handoffs/README.md`
- Audit targets: `README.md`, `AGENTS.md`, `CONTRIBUTING.md`
- Verify all scripts/commands against `package.json`
- Fix P1 inaccuracies (missing env vars, missing config keys)
- Write run artifacts in canonical paths
## Constraints
- Do not invent commands or APIs not verified in code
- Minimal edits — prefer targeted fixes over wholesale rewrites
- Do not change test behavior or policy intent in AGENTS.md/CLAUDE.md
## Open questions
- None. All referenced files found and verified.
## Findings
1. `README.md` environment variables section is missing 4 env vars present in `TORCH.md` and used in `src/torch-config.mjs`: `NOSTR_LOCK_PUBLISH_TIMEOUT_MS`, `NOSTR_LOCK_MIN_SUCCESSFUL_PUBLISHES`, `NOSTR_LOCK_RELAY_FALLBACKS`, `NOSTR_LOCK_MIN_ACTIVE_RELAY_POOL`.
2. `README.md` `torch-config.json` section is missing 8 config keys fully documented in `TORCH.md`: `publishTimeoutMs`, `minSuccessfulRelayPublishes`, `relayFallbacks`, `minActiveRelayPool`, `handoffCommandByCadence`, `strict_lock`, `degraded_lock_retry_window`, `max_deferrals`.
3. `CONTRIBUTING.md` — accurate; all commands verified against `package.json`. No issues found.
4. `AGENTS.md` — accurate; no broken paths or incorrect commands.
5. `.env.example`, `torch-config.example.json`, `docs/lib-overview.md`, `docs/lock-ops-overview.md`, `skills/`, `dashboard/` — all exist ✓
@@ -0,0 +1,29 @@
---
agent: docs-alignment-agent
cadence: daily
run-start: "2026-02-20T06:00:00Z"
prompt: src/prompts/daily/docs-alignment-agent.md
---
# Context — docs-alignment-agent — 2026-02-20
## Goal
Verify, correct, and validate that development documentation matches codebase reality (daily diff-driven pass). Trace concrete doc claims to code and fix confirmed mismatches.
## Scope
- Audit targets: `docs/lib-overview.md`, `docs/lock-ops-overview.md`, `docs/memory-index-overview.md`, `docs/removal.md`, `docs/architecture/system-overview.md`, `docs/AGENT_COVERAGE_GAPS.md`, `src/constants.mjs` (USAGE_TEXT)
- Verify each claim by tracing to `src/` code
- Fix P1 (incorrect) and P2 (incomplete) issues in targeted docs
- Write claims map, diagnosis, and validation notes
## Constraints
- No inventing behavior — trace every claim to code
- Minimal edits only
- Constants USAGE_TEXT fix is a doc string in source code — small, safe, and a clear correctness fix
## Open questions
- None. All relevant source files found and inspected.
@@ -0,0 +1,37 @@
---
agent: docs-code-investigator
cadence: daily
run-start: "2026-02-20T07:00:00Z"
prompt: src/prompts/daily/docs-code-investigator.md
---
# Context — docs-code-investigator — 2026-02-20
## Selected target file
- **File:** `scripts/agent/run-scheduler-cycle.mjs`
- **Line count:** 926 lines
- **Reason for selection:**
- Largest undocumented source file in the repo (existing overview docs cover only `src/lib.mjs`, `src/lock-ops.mjs`, `src/services/memory/index.js`).
- Central orchestrator for all agent scheduling — highest maintenance risk.
- Recent active commits with no corresponding documentation.
- No `TODO/FIXME/XXX` but function-level complexity warrants JSDoc and a flow overview.
- **Last significant commits:**
- `a5f08ff` feat(scheduler): add local time-window guard (recent)
- `9278dad` Print final run summary in scheduler cycle
- `ae2e382` Fix: Correctly detect agent platform for locks
## Plan
1. Add top-of-file flow comment (16-step outline per `src/prompts/scheduler-flow.md`).
2. Add JSDoc to all internal helper functions.
3. Add targeted inline comments where intent is non-obvious.
4. Produce `docs/scheduler-cycle-overview.md`.
5. Write run artifacts.
## Constraints
- Documentation only — no behavioral changes.
- No crypto code in this file; no security issue to open.
- Single file scope: only `scripts/agent/run-scheduler-cycle.mjs` modified.
- `npm run lint` and `npm test` not runnable in Claude Code sandbox (KNOWN-ISSUE-sandbox-eprem-tests).
@@ -0,0 +1,50 @@
---
agent: docs-agent
cadence: daily
run-start: "2026-02-20T05:00:00Z"
prompt: src/prompts/daily/docs-agent.md
---
# Decisions — docs-agent — 2026-02-20
## Decision 1: Add missing env vars to README.md
**Decision:** Add the 4 missing environment variables to the README.md "Environment variables" section.
**Alternatives considered:**
- Open an issue instead of fixing — rejected; the change is small, safe, and well-supported by TORCH.md as authoritative source.
- Add to TORCH.md only — rejected; README.md is the consumer-facing entry point and should be complete.
**Rationale:** `TORCH.md` lists all env vars and is synchronized with `src/torch-config.mjs`. README.md was missing `NOSTR_LOCK_PUBLISH_TIMEOUT_MS`, `NOSTR_LOCK_MIN_SUCCESSFUL_PUBLISHES`, `NOSTR_LOCK_RELAY_FALLBACKS`, and `NOSTR_LOCK_MIN_ACTIVE_RELAY_POOL`. These affect production lock reliability and users configuring these env vars would not find them in README.md.
**Consequences/follow-ups:** Keep README.md env var list synchronized with TORCH.md on future changes.
---
## Decision 2: Add missing torch-config.json options to README.md
**Decision:** Add 8 missing configuration keys to README.md's `torch-config.json` section.
**Alternatives considered:**
- Open an issue instead of fixing — rejected; all keys are verified in TORCH.md and code.
- Only add the most user-facing ones — rejected; all 8 keys are production-relevant and documented in TORCH.md.
**Rationale:** `TORCH.md` is the authoritative configuration reference. The README.md configuration section was a subset, omitting scheduler behavior keys (`handoffCommandByCadence`, `strict_lock`, `degraded_lock_retry_window`, `max_deferrals`) and lock reliability keys (`publishTimeoutMs`, `minSuccessfulRelayPublishes`, `relayFallbacks`, `minActiveRelayPool`) that users configuring production deployments need.
**Consequences/follow-ups:** Keep README.md config section synchronized with TORCH.md on future changes.
---
## Decision 3: No changes to CONTRIBUTING.md
**Decision:** No changes made to CONTRIBUTING.md.
**Rationale:** All commands verified against package.json. Node.js >=22 requirement, npm install, npm run build, npm test, npm run lint, npm run test:unit:lock-backend, npm run test:extended-main, npm run validate:scheduler — all exist. Referenced docs (docs/lib-overview.md, docs/lock-ops-overview.md) exist.
---
## Decision 4: No changes to AGENTS.md
**Decision:** No changes made to AGENTS.md.
**Rationale:** AGENTS.md is the test integrity policy document. It is accurate and consistent with CLAUDE.md. No broken paths or incorrect commands found.
@@ -0,0 +1,50 @@
---
agent: docs-alignment-agent
cadence: daily
run-start: "2026-02-20T06:00:00Z"
prompt: src/prompts/daily/docs-alignment-agent.md
---
# Decisions — docs-alignment-agent — 2026-02-20
## Decision 1: Fix RACE_CHECK_DELAY_MS in docs/lib-overview.md (2000→1500ms)
**Decision:** Update the docs to reflect the actual value.
**Evidence:** `src/constants.mjs` line 2: `export const RACE_CHECK_DELAY_MS = 1500;`
**Rationale:** docs/lib-overview.md stated "Wait `RACE_CHECK_DELAY_MS` (default 2000ms)" — that value is wrong. The constant is 1500. This is a P1 incorrect doc claim.
**Alternatives considered:** Open an issue instead — rejected; the fix is a single-word change with unambiguous ground truth in code.
---
## Decision 2: Fix USAGE_TEXT query timeout default in src/constants.mjs (15000→30000)
**Decision:** Update the `--help` display string to reflect the actual default.
**Evidence:** `DEFAULT_QUERY_TIMEOUT_MS = 30_000` in `src/constants.mjs` line 10. The USAGE_TEXT on line 72 said `(default: 15000)` which matches `DEFAULT_PUBLISH_TIMEOUT_MS` (15_000), not the query timeout.
**Rationale:** The USAGE_TEXT is a documentation string displayed in CLI `--help` output. Showing an incorrect default misleads users configuring `NOSTR_LOCK_QUERY_TIMEOUT_MS`. The fix is a one-character change with clear code evidence. Permitted as a small, safe correctness fix.
**Alternatives considered:** Open an issue only — rejected; code change is trivial and unambiguous.
---
## Decision 3: Add 4 missing env vars to docs/removal.md cleanup list
**Decision:** Add `NOSTR_LOCK_PUBLISH_TIMEOUT_MS`, `NOSTR_LOCK_MIN_SUCCESSFUL_PUBLISHES`, `NOSTR_LOCK_RELAY_FALLBACKS`, `NOSTR_LOCK_MIN_ACTIVE_RELAY_POOL` to the manual cleanup list.
**Evidence:** These vars are used in `src/torch-config.mjs` and were already added to README.md in the prior docs-agent run. The removal guide's env var list was incomplete.
**Rationale:** Users following the manual removal guide would leave these env vars set in their shell profile / CI / .env files. Completing the list is a P2 completeness fix.
---
## Decision 4: Fix Playwright claim in docs/AGENT_COVERAGE_GAPS.md
**Decision:** Replace "playwright dependency and test/ integration files" with "test/ integration/e2e files (Node.js --test)".
**Evidence:** `package.json` has no playwright in dependencies or devDependencies. The `test/scheduler-preflight-lock.e2e.test.mjs` file uses the Node.js built-in `--test` runner, not Playwright.
**Rationale:** The Playwright claim is factually wrong. Leaving it misleads any agent/developer who reads this doc to look for Playwright configuration that does not exist.
@@ -0,0 +1,77 @@
---
agent: docs-code-investigator
cadence: daily
run-start: "2026-02-20T07:00:00Z"
prompt: src/prompts/daily/docs-code-investigator.md
---
# Decisions — docs-code-investigator — 2026-02-20
## Decision 1: Target file = scripts/agent/run-scheduler-cycle.mjs
**Decision:** Document `scripts/agent/run-scheduler-cycle.mjs` (926 lines).
**Rationale:**
- Largest undocumented source file in the repo.
- Central orchestrator for all agent scheduling — highest maintenance risk and value.
- `src/lib.mjs`, `src/lock-ops.mjs`, `src/services/memory/index.js` already have overview docs.
- Recent active commits (time-window guard, summary printing, platform detection) without documentation.
**Alternatives considered:**
- `src/ops.mjs` (795 lines) — also undocumented, but less critical than the scheduler.
- `dashboard/app.js` (822 lines) — frontend UI, lower maintenance priority.
---
## Decision 2: Add top-of-file module JSDoc block with 19-step flow comment
**Decision:** Add a multi-line JSDoc comment before the imports that describes the
module's purpose, role, and numbered main flow steps.
**Rationale:** The file has a comment stub ("Source of truth: numbered MUST steps…")
but no description of what the module does or the complete execution flow. The
19-step flow directly mirrors `src/prompts/scheduler-flow.md` and is the clearest
way to help future maintainers understand the script.
**Constraints:** Documentation only — no behavioral changes.
---
## Decision 3: Add JSDoc to all internal helper functions
**Decision:** Add JSDoc (description, @param, @returns, @throws where applicable) to:
parseArgs, ts, categorizeFailureMetadata, validatePromptFile, artifactExistsSince,
verifyMemoryStep, getLatestFile, selectNextAgent, getSchedulerConfig, readRunState,
writeRunState, createIdempotencyKey, writeLog, printRunSummary, exitWithSummary.
**Rationale:** These functions are the building blocks of the scheduler loop.
Without docs, their contracts, side effects, and error modes are unclear to
contributors who did not write them.
**Constraints:** Documentation only — no changes to function signatures or behavior.
---
## Decision 4: Add targeted inline comments for non-obvious logic
**Decision:** Add inline comments at:
- Lock exit 3 handling (loop restart vs. error)
- Exclusion set fallback construction (why the union of locked/paused/completed)
- Memory file path pre-creation (why SCHEDULER_MEMORY_FILE is injected)
- Memory mode check (required vs. optional semantics)
**Rationale:** These are the spots most likely to confuse contributors doing
bug investigation or adding new validation gates.
---
## Decision 5: Create docs/scheduler-cycle-overview.md
**Decision:** Create a full overview document mirroring the style of docs/lib-overview.md.
**Rationale:** The scheduler cycle is the most complex operational entry point in TORCH.
An overview doc provides a call-flow diagram, API reference, invariant documentation,
and error path table — material that does not fit cleanly in inline comments.
**No issues opened:** No correctness problems, security issues, or design concerns found
that require maintainer decision.
@@ -0,0 +1,74 @@
---
agent: docs-agent
cadence: daily
run-start: "2026-02-20T05:00:00Z"
prompt: src/prompts/daily/docs-agent.md
---
# Test Log — docs-agent — 2026-02-20
## Verification: package.json scripts
**Method:** Read `package.json` and compared against commands in `README.md` and `CONTRIBUTING.md`.
**Result:** PASS
Verified scripts exist:
- `npm test` ✓
- `npm run build` ✓
- `npm run lint` ✓
- `npm run test:unit:lock-backend` ✓
- `npm run test:extended-main` ✓
- `npm run validate:scheduler` ✓
- `npm run lock:check:daily` ✓
- `npm run lock:check:weekly` ✓
- `npm run lock:list` ✓
- `npm run lock:health` ✓
- `npm run lock:complete` ✓
- `npm run scheduler:daily` ✓
- `npm run scheduler:weekly` ✓
- `npm run report:lock-reliability` ✓
- `npm run dashboard:serve` ✓
No `npm run format` script exists — CONTRIBUTING.md does not reference it (only `npm run lint`). No discrepancy.
## Verification: referenced paths
**Method:** Used Glob to check file existence for all paths referenced in README.md and CONTRIBUTING.md.
**Result:** PASS
- `.env.example` ✓
- `torch-config.example.json` ✓
- `TORCH.md` ✓
- `docs/prompt-versioning.md` ✓
- `docs/removal.md` ✓
- `docs/lib-overview.md` ✓
- `docs/lock-ops-overview.md` ✓
- `src/lib.mjs` (referenced as main export in package.json) ✓
- `src/prompts/` ✓
- `skills/` ✓
- `dashboard/` ✓
- `src/prompts/roster.json` ✓
- `bin/torch-lock.mjs` (referenced as CLI bin) ✓
## Verification: env vars in codebase
**Method:** Used Grep on `src/torch-config.mjs` to verify the 4 env vars missing from README.md.
**Result:** PASS — all 4 confirmed used in `src/torch-config.mjs`:
- `NOSTR_LOCK_PUBLISH_TIMEOUT_MS` ✓
- `NOSTR_LOCK_MIN_SUCCESSFUL_PUBLISHES` ✓
- `NOSTR_LOCK_RELAY_FALLBACKS` ✓
- `NOSTR_LOCK_MIN_ACTIVE_RELAY_POOL` ✓
## Verification: README.md changes
**Method:** Inspected README.md after edits to confirm additions are accurate.
**Result:** PASS — all added content matches TORCH.md and src/torch-config.mjs.
## Environmental limitations
- `npm run lint` not executed (outbound WebSocket restricted in Claude Code environment — see KNOWN_ISSUES.md). Documentation-only changes; lint would verify JS syntax only, not markdown content.
- `npm test` not executed (sandbox restrictions — see KNOWN_ISSUES.md: `KNOWN-ISSUE-sandbox-eprem-tests`). No test changes made this run.
@@ -0,0 +1,41 @@
---
agent: docs-alignment-agent
cadence: daily
run-start: "2026-02-20T06:00:00Z"
prompt: src/prompts/daily/docs-alignment-agent.md
---
# Test Log — docs-alignment-agent — 2026-02-20
## Claims Map
| Claim | Doc Location | Code Location | Status |
|-------|-------------|---------------|--------|
| `cmdCheck`, `cmdLock`, `cmdComplete`, `cmdList` exist | docs/lib-overview.md | src/lib.mjs:66,167,344,431 | ✅ matches |
| `cmdLock` returns `{ status: 'ok', eventId }` | docs/lib-overview.md | src/lib.mjs:333 | ✅ matches |
| RACE_CHECK_DELAY_MS default 2000ms | docs/lib-overview.md | src/constants.mjs:2 | ⚠️ WRONG — was 2000, actual 1500 |
| RelayHealthManager, LockPublisher architecture | docs/lock-ops-overview.md | src/relay-health-manager.mjs, src/lock-publisher.mjs | ✅ matches |
| `publishLock`, `queryLocks`, `parseLockEvent` API | docs/lock-ops-overview.md | src/lock-ops.mjs | ✅ matches |
| Memory import at src/lib.mjs:28 | docs/memory-index-overview.md | src/lib.mjs:28-35 | ✅ matches (imports start at 28) |
| scripts/memory/retrieve.mjs:1, store.mjs:1 | docs/memory-index-overview.md | scripts/memory/ | ✅ both files exist |
| Memory public API (ingestEvents, getRelevantMemories, etc.) | docs/memory-index-overview.md | src/services/memory/index.js | ✅ matches |
| USAGE_TEXT NOSTR_LOCK_QUERY_TIMEOUT_MS default 15000 | src/constants.mjs:72 | src/constants.mjs:10 | ⚠️ WRONG — was 15000, actual DEFAULT_QUERY_TIMEOUT_MS = 30000 |
| proposal stored in src/proposals/<ts>_<agent>/ | docs/prompt-versioning.md | src/cmd-proposal.mjs | ✅ matches |
| TORCH_HOST_SCRIPTS list matches docs/removal.md | docs/removal.md | src/constants.mjs:30-37 | ✅ matches |
| docs/removal.md env var list complete | docs/removal.md | src/torch-config.mjs | ⚠️ INCOMPLETE — missing 4 vars |
| playwright dependency | docs/AGENT_COVERAGE_GAPS.md | package.json | ⚠️ WRONG — not in package.json |
| landing/ and migrations/ directories exist | docs/AGENT_COVERAGE_GAPS.md | filesystem | ✅ both exist |
| system-overview.md file paths (bin/, src/, dashboard/, etc.) | docs/architecture/system-overview.md | filesystem | ✅ all exist |
| DEFAULT_DASHBOARD_PORT = 4173 | (inferred from dashboard command examples) | src/constants.mjs:1 | ✅ matches |
## Changes verified post-edit
- `docs/lib-overview.md:43` — "1500ms" confirmed ✅
- `src/constants.mjs:72` — "(default: 30000)" confirmed ✅
- `docs/removal.md:109-112` — 4 new env vars present ✅
- `docs/AGENT_COVERAGE_GAPS.md` — "playwright" no longer present ✅
## Environmental limitations
- `npm run lint` not run (Claude Code sandbox — WebSocket/EPERM restrictions per KNOWN_ISSUES.md). Changed files: one doc string in constants.mjs + pure markdown. ESLint not expected to flag these.
- `npm test` not run (same sandbox restrictions).
@@ -0,0 +1,56 @@
---
agent: docs-code-investigator
cadence: daily
run-start: "2026-02-20T07:00:00Z"
prompt: src/prompts/daily/docs-code-investigator.md
---
# Test Log — docs-code-investigator — 2026-02-20
## Static analysis verification
### All 926 lines read
Read the complete file across multiple passes. No missed sections.
### Call graph verified
- `scripts/agent/scheduler-utils.mjs` — all imported symbols confirmed used (runCommand, parseJsonFromOutput, readJson, normalizeStringList, parseNonNegativeInt, parseBooleanFlag, excerptText, getRunDateKey, toYamlScalar, buildRecentlyRunExclusionSet, parseDateValue, parseFrontmatterCreatedAt, parseFrontmatterAgent, parseTimestampFromFilename, isStrictSchedulerLogFilename, parseAgentFromFilename).
- `scripts/agent/scheduler-lock.mjs` — all 5 imported symbols confirmed used: classifyLockBackendError, buildLockBackendRemediation, runLockHealthPreflight, acquireLockWithRetry, summarizeLockFailureReasons.
- `src/utils.mjs:detectPlatform` — confirmed used at line 35.
- `src/prompts/roster.json` — confirmed read at main() startup.
### Related test files confirmed
- `test/run-scheduler-cycle-memory-policy.test.mjs` — 613 lines, tests memory policy enforcement.
- `test/scheduler-time-window.test.mjs` — 310 lines, tests time-window guard.
- `test/scheduler-preflight-lock.e2e.test.mjs` — present in test/.
### No crypto or signing logic found
The file contains no cryptographic operations. No `requires-security-review` issue needed.
### No TODO/FIXME/XXX found
Confirmed by git grep (only 2 hits in repo, both in non-target files).
### JSDoc additions verified
Read the modified file to confirm all 15 helper functions now have JSDoc blocks.
No behavioral changes made — only documentation insertions.
## Commands not run (environmental limitations)
- `npm run lint` — not run (Claude Code sandbox: outbound WebSocket/EPERM restrictions per KNOWN_ISSUES.md: KNOWN-ISSUE-sandbox-eprem-tests).
- `npm test` — not run (same sandbox restrictions).
**Assessment:** Changes are documentation-only (JSDoc blocks, inline comments).
ESLint will not flag JSDoc content — only syntax. No code logic was altered.
Risk of lint failure from these changes: negligible.
## Git diff summary
```
scripts/agent/run-scheduler-cycle.mjs | +~130 lines (JSDoc + flow comment + inline comments)
docs/scheduler-cycle-overview.md | +254 lines (new overview document)
```
+31
View File
@@ -0,0 +1,31 @@
---
agent: docs-agent
cadence: daily
run-start: "2026-02-20T05:00:00Z"
prompt: src/prompts/daily/docs-agent.md
---
# Todo — docs-agent — 2026-02-20
## Pending tasks
(none remaining)
## Completed tasks
- [x] Read AGENTS.md, CLAUDE.md, KNOWN_ISSUES.md, docs/agent-handoffs/README.md (baseline reads)
- [x] Read latest memory files (.scheduler-memory/latest/daily/memories.md)
- [x] Audit README.md for accuracy against package.json and src/
- [x] Audit CONTRIBUTING.md for accuracy
- [x] Audit AGENTS.md for accuracy
- [x] Verify all referenced paths and files exist
- [x] Verify env vars used in src/torch-config.mjs
- [x] Update README.md — add missing env vars and torch-config.json keys
- [x] Write context artifact
- [x] Write decisions artifact
- [x] Write test_log artifact
- [x] Write learning note in docs/agent-handoffs/learnings/
## Blocked tasks
(none)
+33
View File
@@ -0,0 +1,33 @@
---
agent: docs-alignment-agent
cadence: daily
run-start: "2026-02-20T06:00:00Z"
prompt: src/prompts/daily/docs-alignment-agent.md
---
# Todo — docs-alignment-agent — 2026-02-20
## Completed tasks
- [x] Baseline reads: AGENTS.md, CLAUDE.md, KNOWN_ISSUES.md, docs/agent-handoffs/README.md
- [x] Read memory files (.scheduler-memory/latest/daily/memories.md)
- [x] Audit docs/lib-overview.md — RACE_CHECK_DELAY_MS value
- [x] Audit docs/lock-ops-overview.md — architecture claims
- [x] Audit docs/memory-index-overview.md — line references, API surface
- [x] Audit docs/removal.md — env var list completeness
- [x] Audit docs/AGENT_COVERAGE_GAPS.md — technology claims
- [x] Audit src/constants.mjs USAGE_TEXT — query timeout default
- [x] Audit docs/architecture/system-overview.md — file paths, architecture
- [x] Trace RACE_CHECK_DELAY_MS to src/constants.mjs → confirmed 1500ms
- [x] Trace DEFAULT_QUERY_TIMEOUT_MS to src/constants.mjs → confirmed 30000
- [x] Trace TORCH_HOST_SCRIPTS to confirm removal.md script list accuracy
- [x] Fix docs/lib-overview.md: 2000ms → 1500ms
- [x] Fix src/constants.mjs USAGE_TEXT: default 15000 → 30000
- [x] Fix docs/removal.md: add 4 missing env vars
- [x] Fix docs/AGENT_COVERAGE_GAPS.md: remove false Playwright claim
- [x] Write context, todo, decisions, test_logs artifacts
- [x] Write learning note
## Pending
(none)
+29
View File
@@ -0,0 +1,29 @@
---
agent: docs-code-investigator
cadence: daily
run-start: "2026-02-20T07:00:00Z"
prompt: src/prompts/daily/docs-code-investigator.md
---
# Todo — docs-code-investigator — 2026-02-20
## Completed tasks
- [x] Baseline reads: AGENTS.md, CLAUDE.md, KNOWN_ISSUES.md, docs/agent-handoffs/README.md
- [x] Reviewed memory files
- [x] Ran file discovery: git ls-files | xargs wc -l | sort -rn; git grep TODO/FIXME/XXX
- [x] Selected target: scripts/agent/run-scheduler-cycle.mjs (926 lines, no existing docs)
- [x] Read entire file (all 926 lines across multiple reads)
- [x] Read scheduler-lock.mjs (173 lines) for call graph
- [x] Checked existing docs coverage (lib-overview, lock-ops-overview, memory-index-overview)
- [x] Wrote CONTEXT artifact
- [x] Added top-of-file JSDoc + 19-step flow comment
- [x] Added JSDoc to all 10 internal helper functions
- [x] Added targeted inline comments for non-obvious logic
- [x] Created docs/scheduler-cycle-overview.md (full overview)
- [x] Wrote TODO, DECISIONS, TEST_LOG artifacts
- [x] Wrote learning note
## Pending
(none)