@matt82198/aesop 0.1.0-beta.4 → 0.1.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -5
- package/README.md +57 -1
- package/aesop.config.example.json +8 -1
- package/bin/CLAUDE.md +30 -2
- package/bin/cli.js +286 -73
- package/daemons/CLAUDE.md +4 -1
- package/daemons/run-watchdog.sh +5 -2
- package/docs/RELEASING.md +159 -0
- package/docs/archive/README.md +3 -0
- package/docs/{spikes → archive/spikes}/tiered-cognition/README.md +1 -3
- package/docs/case-study-portfolio.md +61 -0
- package/docs/self-stats-data.json +11 -0
- package/docs/templates/FLEET-OPS-ANALYSIS.example.md +27 -0
- package/docs/templates/FLEET-OPS-RECOMMENDATIONS.example.md +24 -0
- package/docs/templates/PROPOSALS-LOG.example.md +64 -0
- package/hooks/CLAUDE.md +23 -0
- package/mcp/CLAUDE.md +213 -0
- package/mcp/package.json +26 -0
- package/mcp/server.mjs +543 -0
- package/monitor/CHARTER.md +76 -110
- package/monitor/collect-signals.mjs +85 -0
- package/package.json +4 -1
- package/scan/fleet-scan.example.mjs +292 -0
- package/skills/healthcheck/SKILL.md +44 -0
- package/state_store/CLAUDE.md +39 -0
- package/state_store/__init__.py +24 -0
- package/state_store/__pycache__/__init__.cpython-314.pyc +0 -0
- package/state_store/__pycache__/api.cpython-314.pyc +0 -0
- package/state_store/__pycache__/export.cpython-314.pyc +0 -0
- package/state_store/__pycache__/ingest.cpython-314.pyc +0 -0
- package/state_store/__pycache__/projections.cpython-314.pyc +0 -0
- package/state_store/__pycache__/store.cpython-314.pyc +0 -0
- package/state_store/api.py +35 -0
- package/state_store/export.py +19 -0
- package/state_store/ingest.py +24 -0
- package/state_store/projections.py +52 -0
- package/state_store/store.py +102 -0
- package/tools/CLAUDE.md +30 -149
- package/tools/__pycache__/alert_bridge.cpython-314.pyc +0 -0
- package/tools/__pycache__/ci_merge_wait.cpython-314.pyc +0 -0
- package/tools/__pycache__/fleet_prompt_extractor.cpython-314.pyc +0 -0
- package/tools/__pycache__/healthcheck.cpython-314.pyc +0 -0
- package/tools/__pycache__/metrics_gate.cpython-314.pyc +0 -0
- package/tools/__pycache__/secret_scan.cpython-314.pyc +0 -0
- package/tools/__pycache__/self_stats.cpython-314.pyc +0 -0
- package/tools/__pycache__/session_usage_summary.cpython-314.pyc +0 -0
- package/tools/__pycache__/stall_check.cpython-314.pyc +0 -0
- package/tools/__pycache__/transcript_replay.cpython-314.pyc +0 -0
- package/tools/__pycache__/transcript_timeline.cpython-314.pyc +0 -0
- package/tools/__pycache__/verify_dash.cpython-314.pyc +0 -0
- package/tools/__pycache__/verify_submit_encoding.cpython-314.pyc +0 -0
- package/tools/alert_bridge.py +449 -0
- package/tools/ci_merge_wait.py +121 -8
- package/tools/fleet_prompt_extractor.py +134 -0
- package/tools/healthcheck.py +296 -0
- package/tools/secret_scan.py +8 -1
- package/tools/self_stats.py +509 -0
- package/tools/session_usage_summary.py +198 -0
- package/tools/svg_to_png.mjs +50 -0
- package/tools/transcript_replay.py +236 -0
- package/tools/transcript_timeline.py +184 -0
- package/tools/verify_dash.py +361 -542
- package/tools/verify_submit_encoding.py +12 -4
- package/ui/CLAUDE.md +57 -41
- package/ui/__pycache__/agents.cpython-314.pyc +0 -0
- package/ui/__pycache__/collectors.cpython-314.pyc +0 -0
- package/ui/__pycache__/config.cpython-314.pyc +0 -0
- package/ui/__pycache__/cost.cpython-314.pyc +0 -0
- package/ui/__pycache__/csrf.cpython-314.pyc +0 -0
- package/ui/__pycache__/handler.cpython-314.pyc +0 -0
- package/ui/__pycache__/render.cpython-314.pyc +0 -0
- package/ui/__pycache__/serve.cpython-314.pyc +0 -0
- package/ui/__pycache__/sse.cpython-314.pyc +0 -0
- package/ui/agents.py +9 -5
- package/ui/api/__pycache__/submit.cpython-314.pyc +0 -0
- package/ui/api/submit.py +44 -5
- package/ui/collectors.py +184 -35
- package/ui/config.py +9 -0
- package/ui/cost.py +260 -0
- package/ui/csrf.py +7 -3
- package/ui/handler.py +254 -4
- package/ui/render.py +26 -6
- package/ui/sse.py +16 -1
- package/ui/web/.gitattributes +13 -0
- package/ui/web/dist/assets/index-2LZDQirC.js +9 -0
- package/ui/web/dist/assets/index-D4M1qyOv.css +1 -0
- package/ui/web/dist/index.html +14 -0
- package/ui/web/index.html +13 -0
- package/ui/web/package-lock.json +2225 -0
- package/ui/web/package.json +26 -0
- package/ui/web/src/App.test.tsx +74 -0
- package/ui/web/src/App.tsx +142 -0
- package/ui/web/src/CONTRIBUTING-UI.md +49 -0
- package/ui/web/src/components/AgentRow.css +187 -0
- package/ui/web/src/components/AgentRow.test.tsx +209 -0
- package/ui/web/src/components/AgentRow.tsx +207 -0
- package/ui/web/src/components/AgentsPanel.css +108 -0
- package/ui/web/src/components/AgentsPanel.test.tsx +41 -0
- package/ui/web/src/components/AgentsPanel.tsx +58 -0
- package/ui/web/src/components/AlertsPanel.css +88 -0
- package/ui/web/src/components/AlertsPanel.test.tsx +51 -0
- package/ui/web/src/components/AlertsPanel.tsx +67 -0
- package/ui/web/src/components/BacklogPanel.test.tsx +126 -0
- package/ui/web/src/components/BacklogPanel.tsx +122 -0
- package/ui/web/src/components/CostChart.css +110 -0
- package/ui/web/src/components/CostChart.test.tsx +144 -0
- package/ui/web/src/components/CostChart.tsx +152 -0
- package/ui/web/src/components/CostTable.css +93 -0
- package/ui/web/src/components/CostTable.test.tsx +165 -0
- package/ui/web/src/components/CostTable.tsx +94 -0
- package/ui/web/src/components/EventsFeed.css +68 -0
- package/ui/web/src/components/EventsFeed.test.tsx +36 -0
- package/ui/web/src/components/EventsFeed.tsx +31 -0
- package/ui/web/src/components/HealthHeader.css +137 -0
- package/ui/web/src/components/HealthHeader.test.tsx +278 -0
- package/ui/web/src/components/HealthHeader.tsx +281 -0
- package/ui/web/src/components/InboxForm.css +135 -0
- package/ui/web/src/components/InboxForm.test.tsx +208 -0
- package/ui/web/src/components/InboxForm.tsx +116 -0
- package/ui/web/src/components/MessagesTail.module.css +144 -0
- package/ui/web/src/components/MessagesTail.test.tsx +176 -0
- package/ui/web/src/components/MessagesTail.tsx +94 -0
- package/ui/web/src/components/ReposPanel.css +90 -0
- package/ui/web/src/components/ReposPanel.test.tsx +45 -0
- package/ui/web/src/components/ReposPanel.tsx +67 -0
- package/ui/web/src/components/Scorecard.css +106 -0
- package/ui/web/src/components/Scorecard.test.tsx +117 -0
- package/ui/web/src/components/Scorecard.tsx +85 -0
- package/ui/web/src/components/Timeline.module.css +151 -0
- package/ui/web/src/components/Timeline.test.tsx +215 -0
- package/ui/web/src/components/Timeline.tsx +99 -0
- package/ui/web/src/components/TrackerBoard.test.tsx +121 -0
- package/ui/web/src/components/TrackerBoard.tsx +107 -0
- package/ui/web/src/components/TrackerCard.test.tsx +180 -0
- package/ui/web/src/components/TrackerCard.tsx +160 -0
- package/ui/web/src/components/TrackerForm.test.tsx +189 -0
- package/ui/web/src/components/TrackerForm.tsx +144 -0
- package/ui/web/src/lib/api.ts +218 -0
- package/ui/web/src/lib/format.test.ts +89 -0
- package/ui/web/src/lib/format.ts +103 -0
- package/ui/web/src/lib/sanitizeUrl.test.ts +84 -0
- package/ui/web/src/lib/sanitizeUrl.ts +38 -0
- package/ui/web/src/lib/types.ts +230 -0
- package/ui/web/src/lib/useHashRoute.test.ts +60 -0
- package/ui/web/src/lib/useHashRoute.ts +23 -0
- package/ui/web/src/lib/useSSE.ts +175 -0
- package/ui/web/src/main.tsx +10 -0
- package/ui/web/src/styles/global.css +179 -0
- package/ui/web/src/styles/theme.css +184 -0
- package/ui/web/src/styles/work.css +572 -0
- package/ui/web/src/test/fixtures.ts +385 -0
- package/ui/web/src/test/setup.ts +49 -0
- package/ui/web/src/views/Activity.module.css +43 -0
- package/ui/web/src/views/Activity.test.tsx +89 -0
- package/ui/web/src/views/Activity.tsx +31 -0
- package/ui/web/src/views/Cost.css +87 -0
- package/ui/web/src/views/Cost.test.tsx +142 -0
- package/ui/web/src/views/Cost.tsx +54 -0
- package/ui/web/src/views/Overview.css +51 -0
- package/ui/web/src/views/Overview.test.tsx +76 -0
- package/ui/web/src/views/Overview.tsx +46 -0
- package/ui/web/src/views/Work.test.tsx +82 -0
- package/ui/web/src/views/Work.tsx +79 -0
- package/ui/web/src/vite-env.d.ts +10 -0
- package/ui/web/tsconfig.json +22 -0
- package/ui/web/vite.config.ts +25 -0
- package/ui/web/vitest.config.ts +12 -0
- package/ui/templates/dashboard.html +0 -1202
- /package/docs/{spikes → archive/spikes}/tiered-cognition/ACTIVATION.md +0 -0
- /package/docs/{spikes → archive/spikes}/tiered-cognition/DESIGN.md +0 -0
- /package/docs/{spikes → archive/spikes}/tiered-cognition/FINDINGS.md +0 -0
- /package/docs/{spikes → archive/spikes}/tiered-cognition/aesop-cognition.example.md +0 -0
- /package/docs/{spikes → archive/spikes}/tiered-cognition/force-model-policy.merged.mjs +0 -0
- /package/docs/{spikes → archive/spikes}/tiered-cognition/strip-tools-hook.mjs +0 -0
package/monitor/CHARTER.md
CHANGED
|
@@ -1,137 +1,103 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Orchestration Refinement Monitor — Charter
|
|
2
2
|
|
|
3
|
-
A standing background monitor (Haiku
|
|
3
|
+
A standing background monitor (Haiku) that watches how the multi-agent system operates and
|
|
4
|
+
acts on points of refinement. It improves the *machinery*, never the *mission*.
|
|
4
5
|
|
|
5
6
|
## FIXED GOAL (never changes)
|
|
7
|
+
> Ship and maintain your projects correctly, cheaply, and autonomously under the cardinal rules
|
|
8
|
+
> (`~/.claude/CLAUDE.md`). The monitor's job is to make that machine run better — it must NOT
|
|
9
|
+
> redirect, re-scope, or invent new project goals. If it ever thinks the goal should change, it
|
|
10
|
+
> writes a note in `PROPOSALS.md` and stops there.
|
|
11
|
+
|
|
12
|
+
## What it watches (via `collect-signals.mjs`, refreshed each cycle)
|
|
13
|
+
1. **Junk-script sprawl** — one-off Python/JS scripts Claude Code writes to temp/scratch/cloud and
|
|
14
|
+
never reuses. Repeated throwaway `.py` to remote agents is a known cost/clutter sink.
|
|
15
|
+
2. **Memory gaps** — projects/decisions/gotchas with no durable memory file; stale or duplicate memories.
|
|
16
|
+
3. **Rule friction** — cardinal-rule violations in recent activity (non-Haiku subagents, main-thread
|
|
17
|
+
big reads, unpushed green work) and rules that are ambiguous, contradicted, or repeatedly ignored.
|
|
18
|
+
4. **Orchestration health** — hung/stale agents, drift, repeated failures, git state across repos.
|
|
6
19
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
20
|
+
## Action tiers
|
|
21
|
+
- **AUTO (apply immediately, then log to `ACTIONS.log`):**
|
|
22
|
+
- Persist/refresh project memory files (one fact each) + update `MEMORY.md` index.
|
|
23
|
+
- Quarantine confirmed-dead one-off scripts: move junk temp `.py`/`.mjs` (older than 24h, not
|
|
24
|
+
referenced by any repo, not the active session's scratchpad) into `orchestration-monitor/quarantine/`
|
|
25
|
+
with a manifest — never delete outright, never touch a live session scratchpad or repo `src/`.
|
|
26
|
+
- Clarifying, non-behavioral edits to a memory/log the monitor owns.
|
|
27
|
+
- **PROPOSE (write to `PROPOSALS.md`, do NOT apply — needs user OK):**
|
|
28
|
+
- Any change to `~/.claude/CLAUDE.md`, `~/.claude/skills/**`, or a project `CLAUDE.md`.
|
|
29
|
+
- Anything that alters agent behavior, model choice, push targets, or goal scope.
|
|
30
|
+
- Deleting anything outside the monitor's own quarantine.
|
|
11
31
|
|
|
12
|
-
##
|
|
32
|
+
## Hard guardrails (inviolable)
|
|
33
|
+
- Preserve the FIXED GOAL. Improve rules; never rewrite intent.
|
|
34
|
+
- Subagents are ALWAYS Haiku (this monitor included). Opus orchestrates on the main thread only.
|
|
35
|
+
- NEVER work around a classifier denial (cross-repo private-source push = exfil; reusing a
|
|
36
|
+
credential meant for another service = credential exploration). If seen, log + propose, don't act.
|
|
37
|
+
- NEVER print secret values. NEVER push to a remote or run a destructive command as an AUTO action.
|
|
38
|
+
- Idempotent + additive: safe to run repeatedly; prefer append/quarantine over overwrite/delete.
|
|
39
|
+
- Stay cheap: read the signal brief, not raw code/logs. One tight cycle, then sleep.
|
|
13
40
|
|
|
14
|
-
|
|
41
|
+
## End-of-day wipe-survival sweep
|
|
15
42
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
43
|
+
The monitor may run `python ~/scripts/eod_sweep.py` in its final cycle of the day.
|
|
44
|
+
This verifies all known git repos are safe (no data loss risk):
|
|
45
|
+
- working trees clean OR dirty-file list logged
|
|
46
|
+
- current branch pushed to origin (ahead-count 0)
|
|
47
|
+
- untracked files not silently .gitignore'd
|
|
48
|
+
- SECURITY-ALERTS.log has no unreviewed HIGH/MED items
|
|
49
|
+
- memory INBOX consumed (0 pending items)
|
|
50
|
+
- no heartbeat file claims a dead loop (>1h stale)
|
|
19
51
|
|
|
20
|
-
|
|
21
|
-
|
|
52
|
+
Output: single verdict line (`EOD-SWEEP: SAFE` or `EOD-SWEEP: AT-RISK — <n> findings`) + one finding per line;
|
|
53
|
+
appends to handoff log. Exit code 0 only if SAFE.
|
|
22
54
|
|
|
23
|
-
|
|
24
|
-
append summary to BRIEF.md ("N memories stale — keeper should re-verify").
|
|
55
|
+
If findings detected, the monitor stages an alert: `EOD-SWEEP AT-RISK — <n> findings` (each finding on bullet).
|
|
25
56
|
|
|
26
|
-
|
|
27
|
-
default 500 lines / 40 KB). If a log exceeds threshold, emit rotation signal (AUTO tier can
|
|
28
|
-
invoke rotate_logs.py if available); append rotation summary to BRIEF.md.
|
|
57
|
+
## Standing checks (trimmed for solo operation)
|
|
29
58
|
|
|
30
|
-
|
|
31
|
-
older than 24 hours, not in a live session directory (avoid false-positives during active work).
|
|
32
|
-
Count total, estimate quarantinable, list oldest. AUTO tier quarantines confirmed junk into
|
|
33
|
-
`monitor/quarantine/` with manifest.
|
|
59
|
+
**KEPT (durable value):**
|
|
34
60
|
|
|
35
|
-
|
|
36
|
-
|
|
61
|
+
1. **Scripts outside `~/scripts`** — flag `.py`/`.mjs`/`.sh` accumulating outside `~/scripts`
|
|
62
|
+
(scratchpads, temp, repo roots). Quarantine per existing tiers; when one looks genuinely reusable,
|
|
63
|
+
suggest promotion to `~/scripts` (indexed in `~/scripts/CLAUDE.md`).
|
|
37
64
|
|
|
38
|
-
|
|
39
|
-
|
|
65
|
+
2. **Remote Python execution = HIGH-tier finding** — any scheduled/cloud agent or workflow that would
|
|
66
|
+
execute Python remotely violates Rule 5 (local-only execution). Flag immediately; never create one.
|
|
40
67
|
|
|
41
|
-
|
|
42
|
-
prompt-signatures (first ~40 chars, lowercased); count occurrences in recent window (last 50 rows).
|
|
43
|
-
Flag any signature appearing >3 times as probable hung-agent loop → rule 6 cap breached.
|
|
44
|
-
Limitation: heuristic may have false positives for intentional similar-task fan-outs.
|
|
68
|
+
3. **Memory freshness** — scan `~/.claude/projects/*/memory/*.md` (excluding INBOX.md, MEMORY.md); emit `staleMemories` signal in SIGNALS.json listing any file >30d old; append to BRIEF.md: "N memories >30d — keeper should re-verify: <names>".
|
|
45
69
|
|
|
46
|
-
|
|
47
|
-
to
|
|
70
|
+
4. **SECURITY ALERT REVIEW LOOP** — on each /power cycle, arm a persistent Monitor tailing
|
|
71
|
+
SECURITY-ALERTS.log filtered to new 'HIGH'/'MED' lines (exclude SUPPRESSED-FP). Each new alert spawns a read-only fleet-auditor agent to classify REAL vs FALSE-POSITIVE (mask any secret: first 4 chars + ***), verdict appended to SECURITY-ALERTS.log as a REVIEW note and, if REAL, surfaced to the user immediately. Rationale: the append-only alert log is only useful if alerts are triaged; unreviewed HIGHs rot into ignored noise.
|
|
48
72
|
|
|
49
|
-
|
|
50
|
-
spawns since last review; emit count in SIGNALS.json + note in BRIEF.md.
|
|
73
|
+
5. **Fleet ledger harvest** — each cycle, run `python ~/scripts/fleet_ledger.py harvest` to scan session task outputs (JSONL format) and append missing agent outcomes to OUTCOMES-LEDGER.md. Tracks last-harvest state in .fleet-ledger-harvest.json sidecar. Deterministic, local-only, append-only; feeds cost analysis and agent respawn detection. Then invoke `python ~/scripts/fleet_ledger.py rotate` to archive old rows if ledger exceeds 200 lines.
|
|
51
74
|
|
|
52
|
-
|
|
75
|
+
6. **Log rotation** — after ledger operations, invoke `python ~/scripts/rotate_logs.py <file> --max-lines 500 --max-kb 40` over: hook-activity.log, SECURITY-ALERTS.log, FLEET-BACKUP.log (BUILDLOG.md at 1000-line threshold — historical record). Emit `logRotations` list in SIGNALS.json and summary line in BRIEF.md.
|
|
53
76
|
|
|
54
|
-
|
|
55
|
-
enabled via configuration:
|
|
77
|
+
7. **Heartbeat liveness check** — read state/.heartbeats/* (epoch on line 1) + legacy beat files (.monitor-heartbeat, .watchdog-heartbeat, etc.); flag any older than threshold (watchdog 300s, monitor 3600s, others 1800s default) as `staleLoops` signal in SIGNALS.json; append warning to BRIEF.md. Continuous watchdog complement to /power selftest.
|
|
56
78
|
|
|
57
|
-
|
|
58
|
-
- **Env override:** `AESOP_EXTENDED_SIGNALS` (string `'true'` or `'1'` to enable)
|
|
59
|
-
- **Precedence:** env var > config file > default (false)
|
|
79
|
+
8. **Respawn watch (Rule 6 machine-enforcement)** — parse OUTCOMES-LEDGER.md for agent descriptions; normalize each to prompt-signature (first ~40 chars, lowercased, trimmed); count occurrences per signature within recent window (last 50 rows OR since marker in .signal-state.json). Any signature appearing >3 times = probable hung-agent respawn loop → add to `respawnWatch` in SIGNALS.json + warning line in BRIEF.md. *Limitation:* heuristic may have false positives for intentional similar-task fan-outs (e.g., per-file processing with distinct file names in description); BRIEF includes note to manually review flagged signatures. Pure node parsing (no LLM).
|
|
60
80
|
|
|
61
|
-
|
|
62
|
-
as "extended (off)". AUTO junk-quarantine action only runs when the junk check is enabled.
|
|
63
|
-
PROPOSE-tier signals for extended checks (respawn-watch-breach, stray-repo-scripts) are only
|
|
64
|
-
emitted when extended_signals is ON.
|
|
81
|
+
9. **Semantic prompt review** — each cycle, collect-signals.mjs runs `fleet_prompt_extractor.py` to identify NEW unreviewed agent-spawn prompts (tracks seen-hash state in `.fleet-prompts-seen.json`); count is emitted as `unreviewedPrompts` in SIGNALS.json + noted in BRIEF.md. If count > 0, the monitor agent does semantic review (read each prompt, classify: OK / DRIFT / SUSPICIOUS) and runs `drift_escalator.py` to write verdicts to DRIFT-REVIEW.log + escalate any DRIFT/SUSPICIOUS findings to SECURITY-ALERTS.log + DRIFT-ESCALATIONS.md. This closes the gap: regex fleet-scan is NOT semantic review; LLM judgment gates malformed/off-goal/injection prompts. Completes before any flagged prompt's agent runs.
|
|
65
82
|
|
|
66
|
-
|
|
83
|
+
10. **Mirror refresh cadence (throttled)** — each monitor cycle mirrors scripts and docs to your fleet-ops directory and regenerates asset indexes. If either produces diffs: check `.last-mirror-pr-time` in state/. Only open a PR if >1 hour has passed since last PR; otherwise log silently as no-op to ACTIONS.log with timestamp. Ensures mirrors stay in sync and asset lists reflect tier-1 changes, but prevents PR spam. Pure Python automation; runs in monitor context only.
|
|
67
84
|
|
|
68
|
-
|
|
69
|
-
- Heartbeat freshness checks (read-only).
|
|
70
|
-
- Log rotation (invoke rotate_logs.py if available; fail-open if not).
|
|
71
|
-
- Persist heartbeat write (`.monitor-heartbeat` update after cycle).
|
|
72
|
-
- Quarantine confirmed-junk scripts: move old temp `.py`/`.mjs` (not live session) into
|
|
73
|
-
`monitor/quarantine/` with manifest — never delete outright (only when extended_signals is ON).
|
|
74
|
-
|
|
75
|
-
- **PROPOSE (write to `PROPOSALS.md`, do NOT apply — needs user approval):**
|
|
76
|
-
- Changes to cardinal rules or agent configuration.
|
|
77
|
-
- Alterations to agent behavior, model choice, or push targets.
|
|
78
|
-
- Deletions of anything outside monitor's own quarantine.
|
|
79
|
-
- Any change to orchestration policy.
|
|
85
|
+
11. **UI inbox drain (session-independent work queue)** — each cycle, run `python ~/scripts/inbox_drain.py pending` to surface queued submissions that arrived while no session was running. If items exist, write a one-line `QUEUED WORK PENDING` flag to BRIEF.md so an active monitor also surfaces it. This complements /power's inbox drain (step 5); the monitor ensures work doesn't rot in the queue even between sessions.
|
|
80
86
|
|
|
81
|
-
|
|
87
|
+
**CUT or DEMOTED (no durable value for solo operator):**
|
|
82
88
|
|
|
83
|
-
-
|
|
84
|
-
- **
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
- Robust to missing files: treat missing dirs/logs as empty, never crash.
|
|
89
|
+
- **Suppression audit** — (CUT) A solo operator watching the dashboard notices suppression count manually; meta-audits add ceremony without signal.
|
|
90
|
+
- **Cost cadence** — (DEMOTED; blocked) Ledger parsing incomplete; once cost meter is proven, restore. For now, flag as blocked in PROPOSALS.md if touched.
|
|
91
|
+
|
|
92
|
+
**Rationale & changes:**
|
|
93
|
+
|
|
94
|
+
The original 12-check charter was ceremony-heavy for a one-person system. Cuts preserve the critical signals (liveness, security triage, hung loops, prompt safety, cost data acquisition) and demote/cut only checks whose value is marginal or already visible to a solo operator. Check #10 was throttled instead of cut because the mirror sync is critical, but PR-per-cycle wastes bandwidth — now it only PRs when changes exist AND at least 1 hour has passed.
|
|
90
95
|
|
|
91
|
-
|
|
96
|
+
Total standing checks: **11** (reflecting genericization; some checks are environment-specific).
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
- `SIGNALS.json` —
|
|
98
|
+
## Outputs (all under orchestration-monitor/)
|
|
99
|
+
- `BRIEF.md` / `SIGNALS.json` — the deterministic snapshot (written by the collector).
|
|
95
100
|
- `ACTIONS.log` — append-only record of AUTO actions taken (timestamped).
|
|
96
|
-
- `PROPOSALS.md` — staged changes awaiting user approval.
|
|
97
|
-
- `quarantine/` — parked junk scripts + `MANIFEST.tsv
|
|
98
|
-
- `.monitor-heartbeat` —
|
|
99
|
-
- `.signal-state.json` — sidecar state (cycle count, seen prompts, etc.).
|
|
100
|
-
|
|
101
|
-
## Operating it
|
|
102
|
-
|
|
103
|
-
1. **Deploy the monitor** as a continuous background task (or invoke manually each cycle).
|
|
104
|
-
2. **Customize via config**: populate `aesop.config.json` with your repo paths, thresholds.
|
|
105
|
-
3. **Review PROPOSALS.md** periodically; approve changes or reject them.
|
|
106
|
-
4. **Check ACTIONS.log** to see what was automated.
|
|
107
|
-
5. **Monitor BRIEF.md** for drift signals; respond to findings.
|
|
108
|
-
|
|
109
|
-
## Customization
|
|
110
|
-
|
|
111
|
-
Edit `collect-signals.mjs` to:
|
|
112
|
-
- Add/remove checks based on your project needs.
|
|
113
|
-
- Configure paths via environment variables (AESOP_ROOT, BRAIN_ROOT, SCRIPTS_ROOT, TEMP_ROOT)
|
|
114
|
-
or load from aesop.config.json.
|
|
115
|
-
- Set heartbeat thresholds appropriate to your workflow.
|
|
116
|
-
- Integrate custom signal collectors (e.g., compliance checks, custom linters).
|
|
117
|
-
|
|
118
|
-
See comments in `collect-signals.mjs` for extension points. **Keep it CRLF-safe** (no line
|
|
119
|
-
continuations; maintain Windows+POSIX compatibility).
|
|
120
|
-
|
|
121
|
-
## Single-instance guard
|
|
122
|
-
|
|
123
|
-
Before running, check `.monitor-heartbeat` — if <300s old, skip cycle (another monitor is running).
|
|
124
|
-
After cycle completes, update `.monitor-heartbeat` with current epoch.
|
|
125
|
-
Override with `AESOP_MONITOR_FORCE=true` or `AESOP_MONITOR_FORCE=1` for manual runs or tests (explicit comparison, not truthiness: strings like `'0'` or `'false'` do NOT bypass the guard).
|
|
126
|
-
|
|
127
|
-
## Single-writer discipline with atomic operations
|
|
128
|
-
|
|
129
|
-
- Only the monitor edits `BRIEF.md`, `SIGNALS.json`, `ACTIONS.log`, `.monitor-heartbeat`, `.signal-state.json`.
|
|
130
|
-
- Writes are atomic (temp file + rename) to prevent mid-write corruption if a kill occurs.
|
|
131
|
-
- Rename is retried on EPERM/EBUSY (Windows readers holding file) with exponential backoff; .tmp is cleaned up on final failure; prior file is preserved (degrade gracefully).
|
|
132
|
-
- `PROPOSALS.md` uses atomic operations (mkdir-style lockfile) to prevent race conditions between concurrent appends (emitProposal) and full rewrites (accept/reject).
|
|
133
|
-
- **Both emitProposal and moveProposal (accept/reject) acquire the PROPOSALS.md.lock before read-check-append/write**.
|
|
134
|
-
- Lock contains pid+timestamp for staleness detection (>60s stale locks are reclaimed).
|
|
135
|
-
- Lock is acquired, held during read-modify-write, released after write completes.
|
|
136
|
-
- **Fail-closed (P0 wave-8 fix)**: If lock acquisition fails after timeout (default 30s, configurable via AESOP_LOCK_TIMEOUT_MS env var or config), the operation throws an error instead of proceeding. In emitProposal (monitor context), lock failures skip proposal emission for that cycle and retry next cycle. In proposals.mjs CLI (accept/reject), lock failures exit with error code 1 (caller should retry).
|
|
137
|
-
- Quarantine manifest is append-only; never edit entries, only add new ones.
|
|
101
|
+
- `PROPOSALS.md` — staged rule/behavior changes awaiting user approval.
|
|
102
|
+
- `quarantine/` — parked junk scripts + `MANIFEST.tsv`.
|
|
103
|
+
- `.monitor-heartbeat` — liveness for any dashboard.
|
|
@@ -455,6 +455,70 @@ function checkUnreviewedPrompts() {
|
|
|
455
455
|
return 0;
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
+
// 11) Isolation violation detection (rec #5: git-tracked dirs don't flag)
|
|
459
|
+
// Detects worktrees created INSIDE repo root (violation) vs sanctioned ../sibling-wt-* (OK)
|
|
460
|
+
// FP fix: git-tracked source directories (new modules) do NOT flag as violations
|
|
461
|
+
function detectIsolationViolations() {
|
|
462
|
+
if (!fs.existsSync(AESOP_ROOT)) return { violations: [], count: 0 };
|
|
463
|
+
|
|
464
|
+
const violations = [];
|
|
465
|
+
const trackedFiles = new Set();
|
|
466
|
+
|
|
467
|
+
// Get list of git-tracked files to exclude legitimate source dirs
|
|
468
|
+
try {
|
|
469
|
+
const tracked = sh('git ls-files', AESOP_ROOT);
|
|
470
|
+
tracked.split('\n').forEach(f => {
|
|
471
|
+
if (f) {
|
|
472
|
+
const dir = f.split('/')[0];
|
|
473
|
+
trackedFiles.add(dir);
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
} catch {
|
|
477
|
+
// If git ls-files fails, proceed without tracked set (less precise but fail-open)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Walk AESOP_ROOT (shallow) looking for dirs with nested .git
|
|
481
|
+
try {
|
|
482
|
+
const entries = fs.readdirSync(AESOP_ROOT, { withFileTypes: true });
|
|
483
|
+
for (const entry of entries) {
|
|
484
|
+
if (!entry.isDirectory()) continue;
|
|
485
|
+
if (entry.name.startsWith('.')) continue;
|
|
486
|
+
if (entry.name === 'node_modules') continue;
|
|
487
|
+
|
|
488
|
+
const dirPath = path.join(AESOP_ROOT, entry.name);
|
|
489
|
+
const gitPath = path.join(dirPath, '.git');
|
|
490
|
+
|
|
491
|
+
// Check if this directory has a .git file or .git dir (worktree indicator)
|
|
492
|
+
try {
|
|
493
|
+
const gitStat = fs.statSync(gitPath);
|
|
494
|
+
// It has .git — check if it's a worktree (contains "gitdir:" or is a symlink)
|
|
495
|
+
const isWorktree = gitStat.isFile() || gitStat.isSymbolicLink();
|
|
496
|
+
|
|
497
|
+
if (isWorktree) {
|
|
498
|
+
// This looks like a worktree. Check if it's git-tracked (FP fix)
|
|
499
|
+
const isTracked = trackedFiles.has(entry.name);
|
|
500
|
+
|
|
501
|
+
if (!isTracked) {
|
|
502
|
+
// Untracked worktree inside repo root = VIOLATION
|
|
503
|
+
violations.push({
|
|
504
|
+
path: entry.name,
|
|
505
|
+
type: 'untracked-worktree-in-root',
|
|
506
|
+
message: `Worktree '${entry.name}' found inside repo root (untracked). Should use sanctioned ../aesop-wt-* sibling instead.`,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
// If tracked, it's a legitimate source directory; don't flag
|
|
510
|
+
}
|
|
511
|
+
} catch (e) {
|
|
512
|
+
// Entry doesn't have .git or is inaccessible; skip
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
} catch (e) {
|
|
516
|
+
// Directory read failed; fail-open with no violations
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return { violations, count: violations.length };
|
|
520
|
+
}
|
|
521
|
+
|
|
458
522
|
// === AUTO Actions ===
|
|
459
523
|
// Log rotation: invoke rotate_logs.py if available and log needs rotation
|
|
460
524
|
function performAutoLogRotation(logFiles, actionsLogPath) {
|
|
@@ -661,6 +725,7 @@ const staleLoops = checkHeartbeats();
|
|
|
661
725
|
const gitState = checkGitState();
|
|
662
726
|
const memory = checkMemoryFreshness();
|
|
663
727
|
const logFiles = checkLogFiles();
|
|
728
|
+
const isolationViolations = detectIsolationViolations();
|
|
664
729
|
|
|
665
730
|
// Extended signal checks (5, 6, 8, 10) — skipped if extended_signals is OFF
|
|
666
731
|
const junk = extendedSignals ? detectJunkScripts() : { skipped: true };
|
|
@@ -709,6 +774,7 @@ const signals = {
|
|
|
709
774
|
respawnWatch,
|
|
710
775
|
costTick,
|
|
711
776
|
unreviewedPrompts,
|
|
777
|
+
isolationViolations,
|
|
712
778
|
};
|
|
713
779
|
|
|
714
780
|
const brief = [];
|
|
@@ -756,6 +822,17 @@ if (needsRotation.length === 0) {
|
|
|
756
822
|
}
|
|
757
823
|
brief.push('');
|
|
758
824
|
|
|
825
|
+
brief.push('## Isolation violations');
|
|
826
|
+
if (isolationViolations.count === 0) {
|
|
827
|
+
brief.push('✓ No worktrees detected inside repo root (sanctioned siblings OK).');
|
|
828
|
+
} else {
|
|
829
|
+
brief.push(`🚨 **${isolationViolations.count} isolation violation(s)** detected (worktrees inside repo root):`);
|
|
830
|
+
for (const v of isolationViolations.violations) {
|
|
831
|
+
brief.push(` - ${v.path}: ${v.message}`);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
brief.push('');
|
|
835
|
+
|
|
759
836
|
// Extended signals section (if disabled, just note they're off; if enabled, show details)
|
|
760
837
|
if (extendedSignals) {
|
|
761
838
|
brief.push('## Junk-script sprawl (temp/scratch)');
|
|
@@ -839,6 +916,14 @@ if (extendedSignals) {
|
|
|
839
916
|
}
|
|
840
917
|
|
|
841
918
|
// Core proposals (always emitted)
|
|
919
|
+
if (isolationViolations.count > 0) {
|
|
920
|
+
emitProposal(
|
|
921
|
+
'isolation-violation-detected',
|
|
922
|
+
`${isolationViolations.count} worktree(s) detected inside repo root. Agents should create worktrees as sanctioned siblings (../aesop-wt-*), not inside the repo root to maintain isolation.`,
|
|
923
|
+
`Review the violation(s): ${isolationViolations.violations.map(v => v.path).join(', ')}. Move worktrees to ../aesop-wt-<name> location outside repo root. Add pre-commit hook or monitoring to prevent future in-root worktrees.`
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
|
|
842
927
|
if (alerts.highMedCount > 0) {
|
|
843
928
|
emitProposal(
|
|
844
929
|
'security-alerts-high-med',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matt82198/aesop",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.5",
|
|
4
4
|
"description": "Multi-agent orchestration for AI coding agents, designed to survive failure \u00e2\u20ac\u201d a plain-file brain, git as the only durable layer, cheap subagent fleets, and guardrails enforced in code.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"aesop": "bin/cli.js"
|
|
@@ -11,11 +11,14 @@
|
|
|
11
11
|
"daemons/",
|
|
12
12
|
"dash/",
|
|
13
13
|
"monitor/",
|
|
14
|
+
"mcp/",
|
|
14
15
|
"tools/",
|
|
15
16
|
"ui/",
|
|
16
17
|
"docs/",
|
|
17
18
|
"hooks/",
|
|
18
19
|
"skills/",
|
|
20
|
+
"state_store/",
|
|
21
|
+
"scan/",
|
|
19
22
|
"aesop.config.example.json",
|
|
20
23
|
"CLAUDE-TEMPLATE.md",
|
|
21
24
|
"README.md",
|