@matt82198/aesop 0.1.0 → 0.2.0
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 +89 -2
- package/README.md +63 -12
- package/bin/cli.js +164 -41
- package/daemons/run-watchdog.sh +16 -4
- package/daemons/selfheal.sh +231 -0
- package/docs/ANY-REPO.md +427 -0
- package/docs/CONTRIBUTING.md +72 -0
- package/docs/DEMO.md +334 -0
- package/docs/INSTALL.md +70 -1
- package/docs/QUICKSTART.md +80 -0
- package/docs/README.md +33 -3
- package/docs/TEAM-STATE.md +540 -0
- package/driver/CLAUDE.md +148 -0
- package/driver/README.md +383 -0
- package/driver/aesop.config.example.json +80 -0
- package/driver/agent_driver.py +355 -0
- package/driver/backend_config.py +253 -0
- package/driver/claude_code_driver.py +198 -0
- package/driver/codex_driver.py +626 -0
- package/driver/openai_compatible_driver.py +249 -0
- package/driver/openai_transport.py +146 -0
- package/driver/verification_policy.py +75 -0
- package/driver/wave_bridge.py +246 -0
- package/driver/wave_loop.py +1041 -0
- package/hooks/pre-push-policy.sh +109 -28
- package/mcp/server.mjs +320 -4
- package/package.json +23 -15
- package/skills/CLAUDE.md +132 -2
- package/skills/buildsystem/SKILL.md +330 -0
- package/skills/buildsystem/wave-flat-dispatch.template.mjs +658 -0
- package/skills/fleet/SKILL.md +113 -0
- package/skills/power/SKILL.md +246 -131
- package/state_store/__init__.py +2 -1
- package/state_store/api.py +19 -4
- package/state_store/coordination.py +209 -0
- package/state_store/identity.py +51 -0
- package/state_store/store.py +185 -73
- package/templates/wave-presets/data.json +65 -0
- package/templates/wave-presets/library.json +65 -0
- package/templates/wave-presets/saas.json +64 -0
- package/tools/audit_report.py +388 -0
- package/tools/bench_runner.py +100 -3
- package/tools/ci_merge_wait.py +256 -35
- package/tools/ci_workflow_lint.py +430 -0
- package/tools/claudemd_drift.py +394 -0
- package/tools/claudemd_lint.py +359 -0
- package/tools/common.py +39 -3
- package/tools/cost_ceiling.py +63 -31
- package/tools/cost_econ.py +480 -0
- package/tools/defect_escape.py +252 -0
- package/tools/doctor.js +1 -1
- package/tools/eod_sweep.py +58 -0
- package/tools/fleet.js +260 -0
- package/tools/fleet_ledger.py +209 -7
- package/tools/git_identity_check.py +315 -0
- package/tools/health-score.js +40 -0
- package/tools/health_score.py +361 -0
- package/tools/metrics_gate.py +13 -4
- package/tools/mutation_test.py +401 -0
- package/tools/portability_check.py +206 -0
- package/tools/reconcile.py +7 -4
- package/tools/secret_scan.py +137 -50
- package/tools/self_stats.py +20 -0
- package/tools/transcript_digest.py +380 -0
- package/tools/verify_activity_filter.py +437 -0
- package/tools/verify_agent_inspector.py +2 -0
- package/tools/verify_dash.py +2 -0
- package/tools/verify_dispatch_panel.py +301 -0
- package/tools/verify_failure_drilldown.py +188 -0
- package/tools/verify_prboard.py +2 -0
- package/tools/verify_scorecards.py +281 -0
- package/tools/verify_submit_encoding.py +2 -0
- package/tools/verify_ui_trio.py +409 -0
- package/tools/verify_wave_telemetry.py +268 -0
- package/tools/wave_backlog_analyzer.py +490 -0
- package/tools/wave_ledger_hook.py +150 -0
- package/tools/wave_preflight.py +512 -0
- package/tools/wave_resume.py +215 -0
- package/tools/wave_templates.py +215 -0
- package/ui/agents.py +68 -14
- package/ui/collectors.py +0 -55
- package/ui/config.py +7 -2
- package/ui/cost.py +231 -12
- package/ui/handler.py +183 -0
- package/ui/quality_scorecard.py +232 -0
- package/ui/wave_audit_tail.py +213 -0
- package/ui/wave_dispatch.py +280 -0
- package/ui/wave_failure.py +288 -0
- package/ui/wave_gantt.py +152 -0
- package/ui/wave_reasoning_tail.py +176 -0
- package/ui/wave_telemetry.py +377 -0
- package/ui/web/dist/assets/index-BGbgw2Nh.js +9 -0
- package/ui/web/dist/assets/index-DqZLgwNg.css +1 -0
- package/ui/web/dist/index.html +2 -2
- package/bin/CLAUDE.md +0 -76
- package/daemons/CLAUDE.md +0 -36
- package/dash/CLAUDE.md +0 -32
- package/docs/archive/README.md +0 -3
- package/docs/archive/spikes/tiered-cognition/ACTIVATION.md +0 -125
- package/docs/archive/spikes/tiered-cognition/DESIGN.md +0 -287
- package/docs/archive/spikes/tiered-cognition/FINDINGS.md +0 -113
- package/docs/archive/spikes/tiered-cognition/README.md +0 -27
- package/docs/archive/spikes/tiered-cognition/aesop-cognition.example.md +0 -32
- package/docs/archive/spikes/tiered-cognition/force-model-policy.merged.mjs +0 -673
- package/docs/archive/spikes/tiered-cognition/strip-tools-hook.mjs +0 -434
- package/hooks/CLAUDE.md +0 -89
- package/mcp/CLAUDE.md +0 -213
- package/monitor/CLAUDE.md +0 -40
- package/scan/CLAUDE.md +0 -30
- package/state_store/CLAUDE.md +0 -39
- package/tools/CLAUDE.md +0 -79
- package/ui/CLAUDE.md +0 -127
- package/ui/web/dist/assets/index-0qQYnvMC.js +0 -9
- package/ui/web/dist/assets/index-BdIlFieV.css +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,94 @@ All notable changes to Aesop are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.2.0] - 2026-07-21
|
|
9
|
+
|
|
10
|
+
Minor release shipping multi-model orchestration portability and adaptive verification safety.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **AgentDriver Phase 1-3 shipping** (wave-32+): Complete multi-model driver abstraction with three production drivers — Claude Code reference adapter, OpenAI-compatible backend (Ollama, OpenRouter, etc.), and Phase 3 wave bridge enabling any backend to drive coding tasks end-to-end with verified-honest verification tiers.
|
|
14
|
+
- **Multi-instance identity & coordination** (wave-32): Instance ID tagging (hostname:pid:nonce) and lease-by-append claims enable multi-writer coordination without collisions (fail-closed). Foundation for team-scale Aesop.
|
|
15
|
+
- **Honest verification-tier wiring** (wave-32): Weaker backends automatically trigger higher verification (tier 2→tier 4) — no code changes required; orchestrator transparently adapts rigor based on driver capabilities.
|
|
16
|
+
- **OpenAI-compatible backend configuration** (wave-32): `aesop.config.json` backend section configures model, base_url, and API key for local Ollama or OpenRouter inference.
|
|
17
|
+
- **Cost-ceiling enforcement** (wave-32): Per-wave spend ceiling enforced at dispatch time; blocks work if budget exceeded.
|
|
18
|
+
- **Transcript-sampled benchmark Phase 1** (wave-32): Infrastructure to extract coding tasks from real Claude Code session transcripts, enabling dynamic benchmark growth beyond hand-written examples.
|
|
19
|
+
- **Backend config & role resolution** (wave-32): `backend_config.py` for per-deployment model mappings (worker/setup/verify roles) without orchestrator changes.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- **Verification policy transparent** (wave-32): Verification tiers now driven by `AgentDriver.probe_capabilities()` — config-free, capability-driven safety.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- **Authorization header cross-origin stripping** (PR #221): Blocks Authorization headers on cross-origin redirects to prevent credential leakage; security hardening for urllib transport.
|
|
26
|
+
- **Secret-scan fail-closed on read errors** (PR #226): `secret_scan.py` now fails CLOSED when unable to read files or git data, blocking pushes instead of silently passing; P1 security.
|
|
27
|
+
- **Driver subsystem in npm package** (PR #220): Multi-model AgentDriver backend abstraction now ships in the npm package, enabling production use of alternative backends.
|
|
28
|
+
- **CI/publish Node version parity** (PR #225): Unified Node.js version across CI and npm publish workflows for reproducible builds.
|
|
29
|
+
- **Adversarial-review safety fixes** (wave-32): Multiple orchestration loop hardening fixes identified and validated by external review (agent-grading-agent risks eliminated).
|
|
30
|
+
|
|
31
|
+
### Security & Hardening (Post-Release Fixes)
|
|
32
|
+
|
|
33
|
+
Critical hardening round integrated after 0.2.0 release-artifact preparation (fe6bb04, 2026-07-21):
|
|
34
|
+
|
|
35
|
+
**AI & Prompt Security:**
|
|
36
|
+
- **Codex prompt-injection hardening** (fix/codex-prompt-injection): JSON-wrapped framing prevents prompt injection in orchestration context; multi-layer escaping for task/dispatch boundaries (P1 AI-security).
|
|
37
|
+
- **Codex frame integrity** (fix/codex-frame-integrity): SHA-256 digest + retry nudge for frame integrity verification across API boundaries; prevents frame corruption from in-flight mutations.
|
|
38
|
+
- **Codex path containment** (fix/codex-driver-path-containment): Cross-platform path normalization (Windows: `resolve()` + `commonpath()`, Unix: realpath) blocks directory traversal in task file operations (P2 security).
|
|
39
|
+
- **Codex escape-accounting** (fix/codex-prompt-injection): Measure `max_owned_bytes` post-escape to detect prompt-injection breakout payloads (P1 AI-security).
|
|
40
|
+
|
|
41
|
+
**System & Daemon Hardening:**
|
|
42
|
+
- **Daemon fail-closed on lock errors** (fix/daemons-lock-portability): Pre-push and coordination daemons now fail CLOSED on file-write errors, lock-acquisition timeout, or heartbeat stalls; prevents silently-skipped enforcements. CONDUCTOR_ROOT portability (Windows/Unix path handling).
|
|
43
|
+
|
|
44
|
+
**Data & Audit Security:**
|
|
45
|
+
- **Audit log JSON escaping** (fix/audit-log-repo-escape): Escape `repo_name` and payload fields in audit-log JSON emits to prevent injection attacks on durable audit trail (P1 security).
|
|
46
|
+
- **Audit-tail verdict fix** (fix/audit-tail-verdict): Correct column index in wave_audit_tail.py verdict parsing and add validation whitelist to prevent misclassified wave outcomes.
|
|
47
|
+
|
|
48
|
+
**Transcript & Observability Hardening:**
|
|
49
|
+
- **Redaction-proof transcript digest** (verify_ui_trio.py): Single-source redaction patterns (`transcript_digest.py`) ensure sensitive data (API keys, internal tokens, paths) are consistently masked across all observability exports — dashboard, audit, and external integrations.
|
|
50
|
+
|
|
51
|
+
**Pre-Push & CI Hardening:**
|
|
52
|
+
- **Pre-push delete-refspec hardening** (fix/prepush-delete-refspecs): Enforce branch-protection policy on force-delete and fast-forward-only mutations; prevent refspec conflicts from stalling the merge train.
|
|
53
|
+
- **Empty-stdin handling** (fix/prepush-delete-refspecs): Guard pre-push hook against stalled CI merge-wait (empty stdin → timeout → fail-closed block).
|
|
54
|
+
- **Cost-ceiling fail-closed** (fix/cost_ceiling): Per-wave spend ceiling now enforced at dispatch time; fails CLOSED on file/git read errors. **Caveat:** Enforced at every dispatch gate on ALL backends: drivers reporting live token spend are metered directly; drivers that cannot observe per-instance spend (the Claude Code reference driver, by honest contract) return None and the ceiling reads the outcomes ledger itself with proper period windowing.
|
|
55
|
+
|
|
56
|
+
### Documentation
|
|
57
|
+
- **Multi-model configuration guide** (wave-32): docs/INSTALL.md now includes "Using Non-Claude Backends" section with Ollama example, verification-tier table, and troubleshooting.
|
|
58
|
+
- **Driver domain map complete** (wave-32): driver/CLAUDE.md now lists all Files (openai_compatible_driver.py, backend_config.py) with descriptions for one-file-per-domain rule.
|
|
59
|
+
- **State_store coordination modules** (wave-32): state_store/CLAUDE.md Module Layout now includes coordination.py and identity.py.
|
|
60
|
+
|
|
61
|
+
## [0.1.1] - 2026-07-17
|
|
62
|
+
|
|
63
|
+
Patch release with first-hour adopter fixes, orchestration performance improvements, and observability enhancements. Targets users deploying 0.1.0 to production and the fleet during active wave execution.
|
|
64
|
+
|
|
65
|
+
### Adopter / First-hour Fixes
|
|
66
|
+
- **Git init + --no-git scaffold flag** (wave-rc6): New `--no-git` scaffolder option for adopters integrating into existing repos without re-initializing version control.
|
|
67
|
+
- **Port conflict detection** (wave-rc6): CLI and doctor now detect and report port-binding conflicts before dashboard startup; helpful error messages with conflict resolution steps.
|
|
68
|
+
- **Next-steps ordering** (wave-rc5): Orchestration template ordering fixed so next-steps appear after state blocks, improving readability for first runs.
|
|
69
|
+
- **Aesop doctor subcommand** (wave-rc4): New preflight check validates configuration, hooks, state store health, and port availability before wave startup.
|
|
70
|
+
- **Executable bits in tarball** (wave-rc4): Vendored dispatch template + scripts now ship with correct execution bits set (CLI was inventing a standalone validator; real one now included).
|
|
71
|
+
|
|
72
|
+
### Orchestration / Performance
|
|
73
|
+
- **Wave-dispatch template latency** (wave-rc5): Targeted performance repairs — self-check parallelization, postBuild hooks, multi-testCmd batching for faster feedback cycles.
|
|
74
|
+
- **Wave preflight validator** (wave-rc5): New structural validation before dispatch prevents malformed config from blocking the build.
|
|
75
|
+
- **CI merge-wait fail-closed** (wave-rc3): `ci_merge_wait` timeout now blocks dispatch (fail-closed) instead of silently passing; prevents merging while CI is still running.
|
|
76
|
+
|
|
77
|
+
### Observability / Instrumentation
|
|
78
|
+
- **OUTCOMES-LEDGER producer** (wave-rc5): Append-wave ledger tracks per-wave execution outcomes (dispatch time, wave duration, merge timing) for fleet analytics.
|
|
79
|
+
- **CI workflow linter** (wave-rc5): New `tools/ci_workflow_lint.py` statically validates GitHub Actions YAML (npm-ci lockfile presence, every package.json test script is invoked, file references resolve) — catches the green-means-never-ran class.
|
|
80
|
+
- **Failure drilldown** (wave-rc3): Enhanced error reporting in dashboard drill-down view — inspect failure reasons, cost metrics, and agent transcript timestamps per-incident.
|
|
81
|
+
- **Cost-economics dashboard** (wave-rc4): Wave-level cost analytics with model breakdown, per-day bar chart (pure SVG), verdict scorecard (success/failure/hung rates), pricing estimates from config.
|
|
82
|
+
- **MCP cost tools** (wave-rc3): New read-only MCP cost-ledger and cost-ceiling tools expose spend tracking for external Claude integrations.
|
|
83
|
+
- **Transcript digest + claudemd_lint** (wave-rc6-obs): New tools/artifact for post-wave transcript summarization and CLAUDE.md scope linting (3-line max per section, enforcement on drift).
|
|
84
|
+
|
|
85
|
+
### Security & Reliability
|
|
86
|
+
- **Gitignore-respecting secret scan** (wave-rc5): `secret_scan.py` now respects `.gitignore` patterns; skips ephemeral runtime files (state/, .env files, node_modules traces) to reduce false positives and scan time.
|
|
87
|
+
- **Aesop fleet CLI** (wave-rc3): New `aesop fleet` subcommand suite for production fleet inspection (list agents, query costs, export telemetry).
|
|
88
|
+
|
|
89
|
+
### Documentation & Portability
|
|
90
|
+
- **ANY-REPO portability** (wave-rc4): Aesop now scaffolds into any existing Node/Python repo; setup guides, CONTRIBUTING.md, GitHub issue templates (SECURITY.md, CODE_OF_CONDUCT) included.
|
|
91
|
+
- **Domain CLAUDE.md minification** (wave-rc6): All domain-specific documentation scoped to one file per Haiku task; root CLAUDE.md now a pure map for faster load and maintenance.
|
|
92
|
+
- **CONTRIBUTING guide** (wave-rc6): Contributor workflow, test running, release process, branching discipline documented with examples.
|
|
93
|
+
|
|
94
|
+
### Tests
|
|
95
|
+
- **Full gate green** (all waves): npm run test:py && test:node + all shell suites + secret-scan pass without signature warnings.
|
|
9
96
|
|
|
10
97
|
## [0.1.0] - 2026-07-17
|
|
11
98
|
|
|
@@ -251,7 +338,7 @@ package ships slim and reproducible from a clean clone.
|
|
|
251
338
|
|
|
252
339
|
## Initial Release
|
|
253
340
|
|
|
254
|
-
This is the first public release of Aesop, a clean-room
|
|
341
|
+
This is the first public release of Aesop, a clean-room source-available implementation of the fable-fleet orchestration harness. The codebase includes:
|
|
255
342
|
|
|
256
343
|
- The complete orchestration engine with watchdog and monitor
|
|
257
344
|
- Web dashboard for fleet observability
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<em>
|
|
6
|
+
<em>Autonomous Developer for Any Repository</em>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
@@ -12,13 +12,15 @@
|
|
|
12
12
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-PolyForm%20Strict%201.0.0-orange.svg" alt="License: PolyForm Strict 1.0.0 (source-available)"></a>
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
|
-
**Aesop** is a source-available
|
|
15
|
+
**Aesop** is a source-available autonomous developer that crawls into any repository and orchestrates intelligent work—**ranking tasks, dispatching parallel Haiku agents, verifying merges, auditing the work, and feeding the next iteration.**
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
What makes Aesop different: **durable state persists across multiple instances**, so your whole team uses one coordinated system. The state layer (`state_store/`) runs append-only events in SQLite (concurrent, transactional), rendered to git for checkpoints and hand-offs. Pair with [/power](#use-with-claude-code) (system init) and [/buildsystem](#core-concepts-and-operations) (the wave loop), and Aesop runs your delivery cycle—on any repository, any codebase shape, indefinitely. **This repo's own PRs are built by Aesop's own loop.** Dogfooding, not doctrine.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
As of **0.1.0**, that loop has carried the project to an installable, tested, audited, and benchmarked stable release—the fleet running the wave loop on its own, under a human who sets goals and owns the outward gates. The claims below are backed by committed artifacts you can check, not adjectives. See [Milestone: it shipped itself](#milestone-it-shipped-itself-010).
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
**Cost-optimized multi-agent dispatch** (Haiku-first subagents, lean orchestrator) · **Durable state** (SQLite events + git exports, survives wipes) · **Observable machinery** (every agent run logged, every cost tracked) · **Live dashboard** (real-time fleet health at http://localhost:8770) · **Security gates** (secret-scan blocks pushes, CI validates each merge).
|
|
22
|
+
|
|
23
|
+
## Milestone: it shipped itself (0.1.0)
|
|
22
24
|
|
|
23
25
|
Aesop reached its first release candidate by running its own wave loop—**audit → parallel build → verify → merge-train**—across the backlog that produced it. This is the version where the load-bearing claims stopped being illustrations and became measurements. The word "autonomous" has a precise, deliberately narrow meaning here.
|
|
24
26
|
|
|
@@ -36,9 +38,49 @@ The differentiator is not "an AI wrote code." It's that the credibility and safe
|
|
|
36
38
|
|
|
37
39
|
**Nobody outside this project has reproduced these results yet.** The evidence is committed so a skeptical reader can check it — that transparency is the point, not a substitute for independent replication. For the full, unhedged account of what is and isn't proven, read [docs/autonomous-swe.md](./docs/autonomous-swe.md) and the ["Honest limits" section of the release notes](./RELEASE-NOTES.md#honest-limits).
|
|
38
40
|
|
|
41
|
+
## Core Concepts and Operations
|
|
42
|
+
|
|
43
|
+
### What /power Does
|
|
44
|
+
|
|
45
|
+
`/power` initializes Aesop into a fresh repository. It:
|
|
46
|
+
- Loads your orchestrator brain (cardinal rules, domain map, team memory, system state)
|
|
47
|
+
- Verifies the system is healthy (filesystem, git, API keys, watchdog)
|
|
48
|
+
- Outputs a health brief and next-step recommendations
|
|
49
|
+
|
|
50
|
+
Run `/power` at the start of each session when using Claude Code. It's idempotent—safe to run multiple times. Setup once:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
cp -r skills/power/ ~/.claude/skills/power/
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Then in Claude Code, type `/power` to initialize.
|
|
57
|
+
|
|
58
|
+
### What /buildsystem Does
|
|
59
|
+
|
|
60
|
+
`/buildsystem` runs **one complete wave cycle** of the autonomous delivery loop. It:
|
|
61
|
+
1. **Ranks** the backlog (priority, dependencies, team affinity)
|
|
62
|
+
2. **Dispatches** parallel Haiku agents on file-disjoint domains (tests, build, docs, UI, review, etc.)
|
|
63
|
+
3. **Verifies** each merge (CI, security scans, audit spot-checks)
|
|
64
|
+
4. **Checkpoints** (STATE.md + BUILDLOG.md committed to git, survive wipes)
|
|
65
|
+
5. **Audits** fleet health and feeds next backlog (monitor signals, signal collectors, findings)
|
|
66
|
+
|
|
67
|
+
This is **not** a one-off fix or a single pass—it's the repeatable loop that runs your delivery cycle indefinitely, with each wave learning from the prior audit. You can run `/buildsystem` once per wave (typically 30 min–2 hours per wave depending on backlog size and scope).
|
|
68
|
+
|
|
69
|
+
See [docs/HOW-THE-LOOP-WORKS.md](./docs/HOW-THE-LOOP-WORKS.md) for a concrete walkthrough of one complete cycle.
|
|
70
|
+
|
|
71
|
+
### Team State & Multi-Instance Vision
|
|
72
|
+
|
|
73
|
+
**Current Status (0.1.0)**: Single-instance proven. A team uses Aesop by designating one operator who runs the wave loop. State is durably checkpointed in git (STATE.md, BUILDLOG.md, tracker.json exports).
|
|
74
|
+
|
|
75
|
+
**In Design**: Multi-instance coordination via the state_store substrate. The event-sourced SQLite layer (`state_store/`) is production-ready but currently opt-in. A future release will enable multiple Aesop instances (e.g., per-team subgroups, geographic regions, or specialized fleets) to coordinate around a single source of truth—a Postgres-backed event log, with git as a diffable export. See [docs/TEAM-STATE.md](./docs/TEAM-STATE.md) (design in progress) for the vision and current architecture decisions.
|
|
76
|
+
|
|
39
77
|
## Why Aesop?
|
|
40
78
|
|
|
41
|
-
Multi-agent AI fleets hit
|
|
79
|
+
Multi-agent AI fleets hit two walls: all-Opus orchestrators cost $10k+ per wave, and machine crashes lose state—losing work and context on restart. Aesop solves both:
|
|
80
|
+
|
|
81
|
+
- **Haiku-first dispatch** cuts costs to ~1/3 of Opus (Haiku subagents, Opus orchestrator).
|
|
82
|
+
- **Durable state** (SQLite event log + git-rendered exports) survives machine wipes with zero data loss and enables team/multi-instance coordination.
|
|
83
|
+
- **Portable orchestration** works on any repository; no custom agents per repo needed.
|
|
42
84
|
|
|
43
85
|
```
|
|
44
86
|
ranked backlog
|
|
@@ -66,7 +108,9 @@ audit + fleet-ops monitoring
|
|
|
66
108
|
- **Live web dashboard** — Real-time fleet health, security alerts, work-item kanban at `http://localhost:8770`.
|
|
67
109
|
- **Secret-scan gates** — Pre-push hook blocks leaks; audit trail logged. Pair with GitHub branch protection for enforcement.
|
|
68
110
|
- **Read-only MCP Fleet Server** — Expose fleet status, active agents, work items, and cost metrics to Claude Code (fleet_status, fleet_agents, fleet_tracker, fleet_cost tools). See [mcp/CLAUDE.md](./mcp/CLAUDE.md) for setup.
|
|
111
|
+
- **Multi-model portability** — AgentDriver abstraction decouples the wave loop from Claude Code; drivers for Claude Code, OpenAI-compatible backends (Ollama, OpenRouter), and Codex. Honest verification tiers: weaker backends get more checking. See [driver/README.md](./driver/README.md).
|
|
69
112
|
- **Self-diagnosing npm publish** — OIDC token generation and publish reliability verified on each release; workflow surfaces diagnostics inline.
|
|
113
|
+
- **Verification & quality tooling** — mutation_test.py for test-quality assessment, defect_escape.py for first-try-green telemetry, held-out benchmark for robustness, and adversarial review to break and harden the orchestration loop.
|
|
70
114
|
|
|
71
115
|
## Get Started (3 steps, 5 min)
|
|
72
116
|
|
|
@@ -130,17 +174,23 @@ Aesop is built entirely by its own `/buildsystem` wave cycle—running parallel
|
|
|
130
174
|
|
|
131
175
|
| Metric | Value |
|
|
132
176
|
| --- | --- |
|
|
133
|
-
| Merged PRs |
|
|
134
|
-
| Total Commits |
|
|
135
|
-
| Project Age |
|
|
177
|
+
| Merged PRs | 223 <!-- metrics-verified: self_stats.py (git log) --> |
|
|
178
|
+
| Total Commits | 880 <!-- metrics-verified: self_stats.py (git log) --> |
|
|
179
|
+
| Project Age | 10 days <!-- metrics-verified: self_stats.py (git log) --> |
|
|
136
180
|
| Waves | 30 <!-- metrics-verified: self_stats.py (git log) --> |
|
|
137
|
-
| Insertions + Deletions |
|
|
138
|
-
| Files Tracked |
|
|
181
|
+
| Insertions + Deletions | 161,064 <!-- metrics-verified: self_stats.py (git log) --> |
|
|
182
|
+
| Files Tracked | 500 <!-- metrics-verified: self_stats.py (git log) --> |
|
|
139
183
|
| Distinct Co-authors | 9 <!-- metrics-verified: self_stats.py (git log) --> |
|
|
140
184
|
|
|
141
185
|
<!-- STATS:END -->
|
|
142
186
|
|
|
143
187
|
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
144
194
|
## Recommended Agents
|
|
145
195
|
|
|
146
196
|
Aesop pairs well with the open-source catalog of ~130 community-authored specialized agent definitions (TDD orchestrators, security reviewers, performance engineers, etc.). For optimal results, install agents from the upstream source and pair them with Aesop's cost-optimized Haiku dispatch. This is optional; Aesop works standalone with general-purpose Claude Code agents.
|
|
@@ -269,6 +319,7 @@ The dev server proxies `/data`, `/api`, `/events`, `/agent`, `/submit` to the Py
|
|
|
269
319
|
- [docs/PUBLISHING.md](./docs/PUBLISHING.md) — Release Aesop to npm
|
|
270
320
|
- [docs/autonomous-swe.md](./docs/autonomous-swe.md) — The 0.1.0-rc.1 milestone told honestly: what "autonomous SWE" means here, the evidence behind each claim, and the limits the project owns
|
|
271
321
|
- [docs/case-study-portfolio.md](./docs/case-study-portfolio.md) — How Aesop built its own portfolio site; full audit trail and cost breakdown
|
|
322
|
+
- [docs/TEAM-STATE.md](./docs/TEAM-STATE.md) — Multi-instance and team coordination via state_store (design in progress)
|
|
272
323
|
|
|
273
324
|
See [CHANGELOG.md](./CHANGELOG.md) and [RELEASE-NOTES.md](./RELEASE-NOTES.md) for release notes.
|
|
274
325
|
|
|
@@ -295,4 +346,4 @@ Copyright 2026 Matt Culliton.
|
|
|
295
346
|
|
|
296
347
|
---
|
|
297
348
|
|
|
298
|
-
**Aesop**:
|
|
349
|
+
**Aesop**: Autonomous developer for any repository, built by Aesop itself. May your orchestrator be wise and your subagents swift.
|
package/bin/cli.js
CHANGED
|
@@ -11,8 +11,8 @@ const helpFlag = args.includes('--help') || args.includes('-h');
|
|
|
11
11
|
const forceFlag = args.includes('--force');
|
|
12
12
|
const yesFlag = args.includes('--yes');
|
|
13
13
|
|
|
14
|
-
// Check for runtime subcommands (doctor, watch, dash, status)
|
|
15
|
-
const runtimeCommands = ['doctor', 'watch', 'dash', 'status'];
|
|
14
|
+
// Check for runtime subcommands (doctor, watch, dash, status, fleet, health-score)
|
|
15
|
+
const runtimeCommands = ['doctor', 'watch', 'dash', 'status', 'fleet', 'health-score'];
|
|
16
16
|
const isRuntimeCommand = runtimeCommands.includes(args[0]);
|
|
17
17
|
|
|
18
18
|
if (isRuntimeCommand) {
|
|
@@ -20,7 +20,9 @@ if (isRuntimeCommand) {
|
|
|
20
20
|
'doctor': '../tools/doctor.js',
|
|
21
21
|
'watch': '../tools/watch.js',
|
|
22
22
|
'dash': '../tools/dash.js',
|
|
23
|
-
'status': '../tools/status.js'
|
|
23
|
+
'status': '../tools/status.js',
|
|
24
|
+
'fleet': '../tools/fleet.js',
|
|
25
|
+
'health-score': '../tools/health-score.js'
|
|
24
26
|
};
|
|
25
27
|
// Load and run the appropriate runtime module
|
|
26
28
|
// These modules run async code that sets process.exitCode and will cause Node to exit
|
|
@@ -71,6 +73,66 @@ function validatePort(port) {
|
|
|
71
73
|
return !isNaN(p) && p > 0 && p < 65536 ? p : null;
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
// Test if a port is available (returns Promise<boolean>)
|
|
77
|
+
function isPortAvailable(port) {
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
const net = require('net');
|
|
80
|
+
const sock = net.createConnection({ port, host: '127.0.0.1', timeout: 500 });
|
|
81
|
+
let resolved = false;
|
|
82
|
+
|
|
83
|
+
const cleanup = () => {
|
|
84
|
+
try {
|
|
85
|
+
sock.destroy();
|
|
86
|
+
} catch (e) {
|
|
87
|
+
// Ignore cleanup errors
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
sock.on('connect', () => {
|
|
92
|
+
if (!resolved) {
|
|
93
|
+
resolved = true;
|
|
94
|
+
cleanup();
|
|
95
|
+
resolve(false); // Port is in use
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
sock.on('error', () => {
|
|
100
|
+
if (!resolved) {
|
|
101
|
+
resolved = true;
|
|
102
|
+
cleanup();
|
|
103
|
+
resolve(true); // Port is free
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
sock.on('timeout', () => {
|
|
108
|
+
if (!resolved) {
|
|
109
|
+
resolved = true;
|
|
110
|
+
cleanup();
|
|
111
|
+
resolve(true); // Assume free on timeout
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Fallback timeout
|
|
116
|
+
setTimeout(() => {
|
|
117
|
+
if (!resolved) {
|
|
118
|
+
resolved = true;
|
|
119
|
+
cleanup();
|
|
120
|
+
resolve(true);
|
|
121
|
+
}
|
|
122
|
+
}, 2000);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Find next available port starting from basePort
|
|
127
|
+
async function findAvailablePort(basePort = 8770) {
|
|
128
|
+
for (let port = basePort; port < basePort + 100; port++) {
|
|
129
|
+
if (await isPortAvailable(port)) {
|
|
130
|
+
return port;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return basePort; // Fallback to basePort
|
|
134
|
+
}
|
|
135
|
+
|
|
74
136
|
// Parse named flags
|
|
75
137
|
function getFlag(flagName) {
|
|
76
138
|
const idx = args.findIndex(arg => arg === flagName);
|
|
@@ -91,12 +153,14 @@ Usage:
|
|
|
91
153
|
npx @matt82198/aesop watch
|
|
92
154
|
npx @matt82198/aesop dash
|
|
93
155
|
npx @matt82198/aesop status
|
|
156
|
+
npx @matt82198/aesop fleet
|
|
94
157
|
|
|
95
158
|
Commands:
|
|
96
159
|
doctor Preflight readiness check (Node.js, Python, git, config, dirs, hook, port)
|
|
97
160
|
watch Launch the watchdog daemon (spawns daemons/run-watchdog.sh)
|
|
98
161
|
dash Launch the web dashboard (spawns python3 ui/serve.py or python fallback)
|
|
99
162
|
status One-shot fleet status snapshot (heartbeats, dashboard port, git branch)
|
|
163
|
+
fleet One-shot fleet snapshot (agents, heartbeats, tracker lanes, orchestrator status)
|
|
100
164
|
wizard Interactive onboarding (prompts for project name, repos, port)
|
|
101
165
|
|
|
102
166
|
Arguments:
|
|
@@ -116,6 +180,7 @@ Examples:
|
|
|
116
180
|
npx @matt82198/aesop watch # Launch watchdog daemon
|
|
117
181
|
npx @matt82198/aesop dash # Launch web dashboard (default localhost:8770)
|
|
118
182
|
npx @matt82198/aesop status # Show fleet status (heartbeats, port, git)
|
|
183
|
+
npx @matt82198/aesop fleet # Show fleet snapshot (JSON agents, heartbeats, tracker, orchestrator)
|
|
119
184
|
npx @matt82198/aesop # Creates ./aesop-fleet/ with template
|
|
120
185
|
npx @matt82198/aesop my-fleet # Creates ./my-fleet/ with template
|
|
121
186
|
npx @matt82198/aesop wizard # Interactive onboarding (60-second setup)
|
|
@@ -314,7 +379,7 @@ function substituteTemplate(templateContent, projectName, domainsStr, reposStr)
|
|
|
314
379
|
return result;
|
|
315
380
|
}
|
|
316
381
|
|
|
317
|
-
function generateConfigJson(targetDir, templateRoot, projectName, reposStr, repoUrlsStr) {
|
|
382
|
+
function generateConfigJson(targetDir, templateRoot, projectName, reposStr, repoUrlsStr, port = 8770) {
|
|
318
383
|
// Read example config
|
|
319
384
|
const exampleConfigPath = path.join(templateRoot, 'aesop.config.example.json');
|
|
320
385
|
let exampleConfig;
|
|
@@ -348,7 +413,8 @@ function generateConfigJson(targetDir, templateRoot, projectName, reposStr, repo
|
|
|
348
413
|
dashboard: {
|
|
349
414
|
refresh_seconds: 1,
|
|
350
415
|
enable_jq_parsing: true,
|
|
351
|
-
theme: 'dark'
|
|
416
|
+
theme: 'dark',
|
|
417
|
+
port: 8770
|
|
352
418
|
},
|
|
353
419
|
cardinal_rules: {
|
|
354
420
|
subagent_model: 'haiku',
|
|
@@ -366,6 +432,8 @@ function generateConfigJson(targetDir, templateRoot, projectName, reposStr, repo
|
|
|
366
432
|
exampleConfig.scripts_root = '~/scripts';
|
|
367
433
|
exampleConfig.temp_root = '~/.aesop-temp';
|
|
368
434
|
exampleConfig.fleet_root = os.homedir();
|
|
435
|
+
exampleConfig.dashboard = exampleConfig.dashboard || {};
|
|
436
|
+
exampleConfig.dashboard.port = port;
|
|
369
437
|
exampleConfig._generated_note = 'This config uses portable ~ paths which expand at runtime on all platforms. Config loaders in ui/config.py (Python) and monitor/collect-signals.mjs (Node.js) automatically expand ~ paths to your home directory.';
|
|
370
438
|
|
|
371
439
|
// Parse repos if provided
|
|
@@ -392,14 +460,14 @@ function generateConfigJson(targetDir, templateRoot, projectName, reposStr, repo
|
|
|
392
460
|
|
|
393
461
|
// Print next steps and optionally run watchdog (returns a Promise)
|
|
394
462
|
async function printNextStepsAndWatchdog(rl, targetDir, configPath, port) {
|
|
395
|
-
console.log('\n🎯 Next
|
|
463
|
+
console.log('\n🎯 Next 2 commands to get started:\n');
|
|
396
464
|
console.log(` 1. cd ${targetDir}`);
|
|
397
465
|
console.log(' 2. bash daemons/run-watchdog.sh --once (one-time watchdog smoke test)');
|
|
398
466
|
console.log(` 3. python3 ui/serve.py (or python as fallback; launch dashboard on localhost:${port})`);
|
|
399
|
-
console.log('\n📖
|
|
400
|
-
console.log('
|
|
401
|
-
console.log(
|
|
402
|
-
console.log(
|
|
467
|
+
console.log('\n📖 Optional: Set up your brain (Claude Code orchestration memory):\n');
|
|
468
|
+
console.log(' mkdir -p ~/.claude/memory');
|
|
469
|
+
console.log(` cp ${targetDir}/CLAUDE.md ~/.claude/CLAUDE.md (review and customize)`);
|
|
470
|
+
console.log(` cp ${targetDir}/MEMORY-SEED.md ~/.claude/MEMORY.md (then add your facts)\n`);
|
|
403
471
|
|
|
404
472
|
return new Promise((resolve) => {
|
|
405
473
|
rl.question('Run watchdog --once now? (y/N): ', (answer) => {
|
|
@@ -422,6 +490,37 @@ async function printNextStepsAndWatchdog(rl, targetDir, configPath, port) {
|
|
|
422
490
|
});
|
|
423
491
|
}
|
|
424
492
|
|
|
493
|
+
// Initialize git repo if it doesn't exist
|
|
494
|
+
function initializeGitRepo(targetDir, noGitFlag = false) {
|
|
495
|
+
if (noGitFlag) {
|
|
496
|
+
return false; // User explicitly requested no git
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const gitDir = path.join(targetDir, '.git');
|
|
500
|
+
if (fs.existsSync(gitDir)) {
|
|
501
|
+
return false; // Git already initialized
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
// Initialize git repo
|
|
506
|
+
execSync('git init -q', { cwd: targetDir, stdio: 'pipe' });
|
|
507
|
+
console.log('✓ Initialized git repository');
|
|
508
|
+
|
|
509
|
+
// Create initial commit
|
|
510
|
+
execSync('git config user.email "aesop-scaffold@local"', { cwd: targetDir, stdio: 'pipe' });
|
|
511
|
+
execSync('git config user.name "Aesop Scaffold"', { cwd: targetDir, stdio: 'pipe' });
|
|
512
|
+
execSync('git add -A', { cwd: targetDir, stdio: 'pipe' });
|
|
513
|
+
execSync('git commit -q -m "Initial aesop scaffold"', { cwd: targetDir, stdio: 'pipe' });
|
|
514
|
+
console.log('✓ Created initial git commit');
|
|
515
|
+
return true;
|
|
516
|
+
} catch (e) {
|
|
517
|
+
// Git operations failed, continue anyway
|
|
518
|
+
console.warn('⚠ Warning: Failed to initialize git repo automatically');
|
|
519
|
+
console.warn(' Run manually: cd ' + targetDir + ' && git init && git add -A && git commit -m "Initial commit"');
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
425
524
|
function installPreCommitWaveguard(targetDir, templateRoot) {
|
|
426
525
|
// Install the pre-commit waveguard hook to prevent commits during a wave
|
|
427
526
|
// Try to locate .git directory
|
|
@@ -664,6 +763,7 @@ if (!isRuntimeCommand) {
|
|
|
664
763
|
let finalTargetDir = targetDir;
|
|
665
764
|
let configPath = path.join(targetDir, 'aesop.config.json');
|
|
666
765
|
let dashboardPort = 8770;
|
|
766
|
+
const noGitFlag = args.includes('--no-git');
|
|
667
767
|
|
|
668
768
|
let wizardRl = null;
|
|
669
769
|
|
|
@@ -704,7 +804,7 @@ if (!isRuntimeCommand) {
|
|
|
704
804
|
}
|
|
705
805
|
finalReposStr = selectedRepos;
|
|
706
806
|
|
|
707
|
-
// Q3: Dashboard port
|
|
807
|
+
// Q3: Dashboard port (with availability check)
|
|
708
808
|
let port = '';
|
|
709
809
|
while (!port) {
|
|
710
810
|
port = await promptUser(wizardRl, 'Dashboard port', '8770');
|
|
@@ -713,7 +813,21 @@ if (!isRuntimeCommand) {
|
|
|
713
813
|
console.log(' ✗ Invalid port. Must be a number between 1 and 65535.');
|
|
714
814
|
port = '';
|
|
715
815
|
} else {
|
|
716
|
-
|
|
816
|
+
// Check if port is available
|
|
817
|
+
const available = await isPortAvailable(validPort);
|
|
818
|
+
if (!available) {
|
|
819
|
+
console.log(` ⚠ Port ${validPort} is in use. Trying next available port...`);
|
|
820
|
+
const nextPort = await findAvailablePort(validPort);
|
|
821
|
+
if (nextPort !== validPort) {
|
|
822
|
+
console.log(` → Using port ${nextPort} instead`);
|
|
823
|
+
dashboardPort = nextPort;
|
|
824
|
+
} else {
|
|
825
|
+
dashboardPort = validPort;
|
|
826
|
+
}
|
|
827
|
+
port = validPort.toString(); // Exit loop
|
|
828
|
+
} else {
|
|
829
|
+
dashboardPort = validPort;
|
|
830
|
+
}
|
|
717
831
|
}
|
|
718
832
|
}
|
|
719
833
|
|
|
@@ -728,8 +842,15 @@ if (!isRuntimeCommand) {
|
|
|
728
842
|
// Non-interactive defaults for wizard mode with --yes
|
|
729
843
|
finalProjectName = 'my-fleet';
|
|
730
844
|
finalReposStr = '';
|
|
731
|
-
|
|
845
|
+
// Find available port
|
|
846
|
+
dashboardPort = await findAvailablePort(8770);
|
|
732
847
|
console.log('🪄 Running onboarding wizard with defaults (--yes)');
|
|
848
|
+
} else if (!wizardMode && finalProjectName) {
|
|
849
|
+
// Headless mode: check and find available port
|
|
850
|
+
dashboardPort = await findAvailablePort(8770);
|
|
851
|
+
if (dashboardPort !== 8770) {
|
|
852
|
+
console.log(`ℹ Port 8770 is in use, using port ${dashboardPort} instead`);
|
|
853
|
+
}
|
|
733
854
|
}
|
|
734
855
|
|
|
735
856
|
// Copy template files
|
|
@@ -767,8 +888,8 @@ if (!isRuntimeCommand) {
|
|
|
767
888
|
// But if it does, warn and skip
|
|
768
889
|
console.warn(`⚠ Warning: aesop.config.json already exists at ${configPath}`);
|
|
769
890
|
} else {
|
|
770
|
-
// Generate aesop.config.json
|
|
771
|
-
const config = generateConfigJson(finalTargetDir, templateRoot, finalProjectName, finalReposStr, finalRepoUrlsStr);
|
|
891
|
+
// Generate aesop.config.json with the determined port
|
|
892
|
+
const config = generateConfigJson(finalTargetDir, templateRoot, finalProjectName, finalReposStr, finalRepoUrlsStr, dashboardPort);
|
|
772
893
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
773
894
|
console.log('✓ Generated aesop.config.json (configured for your repos)');
|
|
774
895
|
}
|
|
@@ -782,10 +903,13 @@ if (!isRuntimeCommand) {
|
|
|
782
903
|
console.log('✓ Copied MEMORY-SEED.md (template for your facts)');
|
|
783
904
|
}
|
|
784
905
|
|
|
785
|
-
//
|
|
906
|
+
// Initialize git repo if needed (BEFORE installing hooks)
|
|
907
|
+
initializeGitRepo(finalTargetDir, noGitFlag);
|
|
908
|
+
|
|
909
|
+
// Install the pre-push hook (only works if .git exists)
|
|
786
910
|
installPrePushHook(finalTargetDir, templateRoot);
|
|
787
911
|
|
|
788
|
-
// Install the pre-commit waveguard hook
|
|
912
|
+
// Install the pre-commit waveguard hook (only works if .git exists)
|
|
789
913
|
installPreCommitWaveguard(finalTargetDir, templateRoot);
|
|
790
914
|
|
|
791
915
|
console.log(`\n✅ Scaffolded aesop template into "${finalTargetDir}" (${copiedCount} files)`);
|
|
@@ -800,35 +924,34 @@ if (!isRuntimeCommand) {
|
|
|
800
924
|
console.log(` 1. cd ${finalTargetDir}`);
|
|
801
925
|
console.log(' 2. bash daemons/run-watchdog.sh --once (one-time watchdog smoke test)');
|
|
802
926
|
console.log(` 3. python3 ui/serve.py (or python as fallback; launch dashboard on localhost:${dashboardPort})`);
|
|
803
|
-
console.log('\n📖
|
|
804
|
-
console.log('
|
|
805
|
-
console.log(
|
|
806
|
-
console.log(
|
|
927
|
+
console.log('\n📖 Optional: Set up your brain (Claude Code orchestration memory):\n');
|
|
928
|
+
console.log(' mkdir -p ~/.claude/memory');
|
|
929
|
+
console.log(` cp ${finalTargetDir}/CLAUDE.md ~/.claude/CLAUDE.md (review and customize)`);
|
|
930
|
+
console.log(` cp ${finalTargetDir}/MEMORY-SEED.md ~/.claude/MEMORY.md (then add your facts)`);
|
|
807
931
|
process.exit(0);
|
|
808
932
|
} else if (finalProjectName) {
|
|
809
|
-
console.log('\
|
|
933
|
+
console.log('\n✅ Headless scaffolding complete!\n');
|
|
934
|
+
console.log('🎯 Next 3 commands to get started:\n');
|
|
810
935
|
console.log(` 1. cd ${finalTargetDir}`);
|
|
811
|
-
console.log(' 2.
|
|
812
|
-
console.log(
|
|
813
|
-
console.log('\
|
|
814
|
-
console.log('
|
|
815
|
-
console.log(`
|
|
816
|
-
console.log(`
|
|
817
|
-
console.log('\nRun the daemon and dashboard:');
|
|
818
|
-
console.log(' 7. bash daemons/run-watchdog.sh --once (test run)');
|
|
819
|
-
console.log(' 8. python3 ui/serve.py (or python as fallback; launch dashboard on localhost:8770)');
|
|
936
|
+
console.log(' 2. bash daemons/run-watchdog.sh --once (one-time watchdog smoke test)');
|
|
937
|
+
console.log(` 3. python3 ui/serve.py (or python as fallback; launch dashboard on localhost:${dashboardPort})`);
|
|
938
|
+
console.log('\n📖 Optional: Set up your brain (Claude Code orchestration memory):\n');
|
|
939
|
+
console.log(' mkdir -p ~/.claude/memory');
|
|
940
|
+
console.log(` cp ${finalTargetDir}/CLAUDE.md ~/.claude/CLAUDE.md (review and customize)`);
|
|
941
|
+
console.log(` cp ${finalTargetDir}/MEMORY-SEED.md ~/.claude/MEMORY.md (then add your facts)`);
|
|
820
942
|
} else {
|
|
821
|
-
console.log('\
|
|
943
|
+
console.log('\n✅ Scaffold complete!\n');
|
|
944
|
+
console.log('🎯 Next 3 commands to get started:\n');
|
|
822
945
|
console.log(` 1. cd ${finalTargetDir}`);
|
|
823
|
-
console.log(' 2.
|
|
824
|
-
console.log(
|
|
825
|
-
console.log('\
|
|
826
|
-
console.log('
|
|
827
|
-
console.log(
|
|
828
|
-
console.log(
|
|
829
|
-
console.log('
|
|
830
|
-
console.log(
|
|
831
|
-
console.log(
|
|
946
|
+
console.log(' 2. bash daemons/run-watchdog.sh --once (one-time watchdog smoke test)');
|
|
947
|
+
console.log(` 3. python3 ui/serve.py (or python as fallback; launch dashboard on localhost:${dashboardPort})`);
|
|
948
|
+
console.log('\n📖 Configuration steps (optional):\n');
|
|
949
|
+
console.log(' cp aesop.config.example.json aesop.config.json (if not generated)');
|
|
950
|
+
console.log(' Edit aesop.config.json with your configuration');
|
|
951
|
+
console.log('\n📖 Optional: Set up your brain (Claude Code orchestration memory):\n');
|
|
952
|
+
console.log(' mkdir -p ~/.claude/memory');
|
|
953
|
+
console.log(` cp ${finalTargetDir}/CLAUDE-TEMPLATE.md ~/.claude/CLAUDE.md (then edit domains/team info)`);
|
|
954
|
+
console.log(` cp ${finalTargetDir}/MEMORY-SEED.md ~/.claude/MEMORY.md (then add your facts)`);
|
|
832
955
|
}
|
|
833
956
|
console.log('\nFor full documentation, see the README.md in the scaffolded directory.');
|
|
834
957
|
process.exit(0);
|
package/daemons/run-watchdog.sh
CHANGED
|
@@ -25,8 +25,8 @@ elif command -v python >/dev/null 2>&1; then
|
|
|
25
25
|
PYTHON_EXE="python"
|
|
26
26
|
fi
|
|
27
27
|
|
|
28
|
-
# Resolve conductor3 directory (sibling of AESOP_ROOT)
|
|
29
|
-
CONDUCTOR_ROOT="$(dirname "$AESOP_ROOT")/conductor3"
|
|
28
|
+
# Resolve conductor3 directory (sibling of AESOP_ROOT); env var override for portability
|
|
29
|
+
CONDUCTOR_ROOT="${CONDUCTOR_ROOT:-$(dirname "$AESOP_ROOT")/conductor3}"
|
|
30
30
|
MONITOR_HB_FILE="$CONDUCTOR_ROOT/monitor/.monitor-heartbeat"
|
|
31
31
|
MONITOR_HB_STALE_THRESHOLD=600
|
|
32
32
|
|
|
@@ -59,7 +59,11 @@ acquire_lock() {
|
|
|
59
59
|
# Check if both required files exist
|
|
60
60
|
if [ -f "$lock_dir/timestamp" ] && [ -f "$lock_dir/pid" ] && [ -n "$lock_pid" ]; then
|
|
61
61
|
# Both files present and pid is readable - use timestamp for stale check
|
|
62
|
-
local
|
|
62
|
+
local lock_ts=$(cat "$lock_dir/timestamp" 2>/dev/null)
|
|
63
|
+
local lock_mtime=${lock_ts:-0}
|
|
64
|
+
if [ -z "$lock_ts" ]; then
|
|
65
|
+
echo "empty/corrupt lock timestamp — treating lock as stale" >&2
|
|
66
|
+
fi
|
|
63
67
|
local now=$(date +%s)
|
|
64
68
|
local lock_age=$((now - lock_mtime))
|
|
65
69
|
|
|
@@ -87,7 +91,11 @@ acquire_lock() {
|
|
|
87
91
|
|
|
88
92
|
# Case 2: no pid file, but timestamp exists and is old (definitely abandoned)
|
|
89
93
|
if [ $should_reclaim -eq 0 ] && [ -f "$lock_dir/timestamp" ]; then
|
|
90
|
-
local
|
|
94
|
+
local lock_ts=$(cat "$lock_dir/timestamp" 2>/dev/null)
|
|
95
|
+
local lock_mtime=${lock_ts:-0}
|
|
96
|
+
if [ -z "$lock_ts" ]; then
|
|
97
|
+
echo "empty/corrupt lock timestamp — treating lock as stale" >&2
|
|
98
|
+
fi
|
|
91
99
|
local now=$(date +%s)
|
|
92
100
|
local lock_age=$((now - lock_mtime))
|
|
93
101
|
if [ "$lock_age" -gt "$stale_threshold" ]; then
|
|
@@ -125,6 +133,7 @@ release_lock() {
|
|
|
125
133
|
}
|
|
126
134
|
|
|
127
135
|
# Check monitor heartbeat staleness; log to fleet-backup.log if missing or >600s old
|
|
136
|
+
# Gracefully skips if CONDUCTOR_ROOT doesn't exist (portability for non-conductor deployments)
|
|
128
137
|
check_monitor_staleness() {
|
|
129
138
|
local hb_file="$1"
|
|
130
139
|
local stale_threshold="$2"
|
|
@@ -132,6 +141,9 @@ check_monitor_staleness() {
|
|
|
132
141
|
if [ -z "$hb_file" ] || [ -z "$log_file" ]; then
|
|
133
142
|
return
|
|
134
143
|
fi
|
|
144
|
+
if [ ! -d "$(dirname "$hb_file")" ]; then
|
|
145
|
+
return
|
|
146
|
+
fi
|
|
135
147
|
if [ ! -f "$hb_file" ]; then
|
|
136
148
|
echo "[$(date '+%F %T')] SIGNAL: monitor heartbeat missing ($hb_file)" >> "$log_file"
|
|
137
149
|
return
|