@adaptic/maestro 1.8.3 → 1.9.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/.claude/settings.json +11 -0
- package/agents/engineering-oversight/agent.md +44 -0
- package/agents/github-operator/agent.md +38 -0
- package/agents/inbox-processor/agent.md +39 -0
- package/bin/maestro.mjs +302 -4
- package/framework-features.json +107 -0
- package/lib/feature-init.mjs +297 -0
- package/package.json +5 -2
- package/scaffold/config/known-agents.json +57 -8
- package/scripts/cost/track-claude-usage.mjs +154 -0
- package/scripts/daemon/cadence-consumer.mjs +73 -2
- package/scripts/decisions/capture-decision.mjs +116 -0
- package/scripts/emergency-stop.sh +56 -19
- package/scripts/hooks/session-start-banner.sh +79 -0
- package/scripts/maintenance/backup-to-cloud.sh +124 -0
- package/scripts/rag/ingest.mjs +111 -0
- package/scripts/rag/search.mjs +119 -0
- package/scripts/resume-operations.sh +50 -13
- package/scripts/setup/init-backup.mjs +54 -0
- package/scripts/setup/init-cadence-bus.mjs +60 -0
- package/scripts/setup/init-cost-tracking.mjs +45 -0
- package/scripts/setup/init-decision-capture.mjs +66 -0
- package/scripts/setup/init-known-agents.mjs +57 -0
- package/scripts/setup/init-memory-executive.mjs +45 -0
- package/scripts/setup/init-rag.mjs +103 -0
- package/scripts/setup/init-session-router.mjs +38 -0
- package/workflows/continuous/backlog-executor.yaml +1 -1
- package/workflows/continuous/inbound-monitor.yaml +10 -10
- package/workflows/daily/applicant-triage.yaml +1 -1
- package/workflows/daily/comms-triage.yaml +2 -2
- package/workflows/daily/evening-wrap.yaml +1 -1
- package/workflows/daily/morning-brief.yaml +1 -1
- package/workflows/daily/slack-followup-sweep.yaml +2 -2
- package/workflows/event-driven/README.md +5 -5
- package/workflows/event-driven/agent-failure-investigation.yaml +1 -1
- package/workflows/event-driven/pr-review.yaml +6 -3
- package/workflows/monthly/board-readiness.yaml +1 -1
- package/workflows/quarterly/strategic-scenario-analysis.yaml +1 -1
- package/workflows/session-protocol.md +7 -7
- package/workflows/weekly/engineering-health.yaml +1 -1
- package/workflows/weekly/hiring-review.yaml +1 -1
- package/workflows/weekly/rollup-pipeline-review.yaml +1 -1
- package/workflows/weekly/strategic-memo.yaml +1 -1
|
@@ -1,40 +1,77 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# Resume Operations — Restarts Maestro agent after an emergency stop
|
|
2
|
+
# Resume Operations — Restarts Maestro agent after an emergency stop.
|
|
3
3
|
# Usage: ./scripts/resume-operations.sh
|
|
4
|
+
#
|
|
5
|
+
# Reverses emergency-stop.sh:
|
|
6
|
+
# 1. Verifies health check passes.
|
|
7
|
+
# 2. Removes the .emergency-stop flag.
|
|
8
|
+
# 3. Reloads every installed `ai.adaptic.<agent>-*` launchd job.
|
|
9
|
+
#
|
|
10
|
+
# Agent first-name slug resolved from config/agent.json (SOT).
|
|
4
11
|
|
|
5
12
|
set -e
|
|
6
13
|
|
|
7
14
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
-
|
|
9
|
-
LOG_FILE="$
|
|
15
|
+
AGENT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
16
|
+
LOG_FILE="$AGENT_DIR/logs/operations.log"
|
|
10
17
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
18
|
+
mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true
|
|
11
19
|
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
# Resolve agent first-name slug.
|
|
21
|
+
AGENT_FIRST=""
|
|
22
|
+
if [ -f "$AGENT_DIR/config/agent.json" ]; then
|
|
23
|
+
if command -v jq >/dev/null 2>&1; then
|
|
24
|
+
AGENT_FIRST=$(jq -r '.firstName // empty' "$AGENT_DIR/config/agent.json" | tr '[:upper:]' '[:lower:]')
|
|
25
|
+
else
|
|
26
|
+
AGENT_FIRST=$(awk -F'"' '/"firstName"[[:space:]]*:/ { print tolower($4); exit }' "$AGENT_DIR/config/agent.json")
|
|
27
|
+
fi
|
|
28
|
+
fi
|
|
29
|
+
[ -z "$AGENT_FIRST" ] && AGENT_FIRST=$(basename "$AGENT_DIR" | sed 's/-ai$//')
|
|
30
|
+
|
|
31
|
+
LAUNCH_AGENTS_DIR="$HOME/Library/LaunchAgents"
|
|
32
|
+
PLIST_GLOB="$LAUNCH_AGENTS_DIR/ai.adaptic.${AGENT_FIRST}-*.plist"
|
|
33
|
+
|
|
34
|
+
# Check if stop flag exists.
|
|
35
|
+
if [ ! -f "$AGENT_DIR/.emergency-stop" ]; then
|
|
36
|
+
echo "No emergency stop flag found — system is already running."
|
|
37
|
+
# Still verify launchd jobs are loaded; if not, load them.
|
|
38
|
+
loaded=0
|
|
39
|
+
for plist in $PLIST_GLOB; do
|
|
40
|
+
[ -f "$plist" ] || continue
|
|
41
|
+
label=$(basename "$plist" .plist)
|
|
42
|
+
if ! launchctl list | grep -q "$label"; then
|
|
43
|
+
launchctl load "$plist" 2>/dev/null && loaded=$((loaded + 1))
|
|
44
|
+
fi
|
|
45
|
+
done
|
|
46
|
+
[ "$loaded" -gt 0 ] && echo "Loaded $loaded missing launchd job(s)"
|
|
15
47
|
exit 0
|
|
16
48
|
fi
|
|
17
49
|
|
|
18
|
-
echo "[$TIMESTAMP] RESUMING OPERATIONS" | tee -a "$LOG_FILE"
|
|
50
|
+
echo "[$TIMESTAMP] RESUMING OPERATIONS (agent=$AGENT_FIRST)" | tee -a "$LOG_FILE"
|
|
19
51
|
|
|
20
|
-
# 1. Run health check first
|
|
52
|
+
# 1. Run health check first.
|
|
21
53
|
echo "Running health check..."
|
|
22
54
|
if ! "$SCRIPT_DIR/healthcheck.sh"; then
|
|
23
55
|
echo "ERROR: Health check failed. Fix issues before resuming."
|
|
24
56
|
exit 1
|
|
25
57
|
fi
|
|
26
58
|
|
|
27
|
-
# 2. Remove stop flag
|
|
28
|
-
rm "$
|
|
59
|
+
# 2. Remove stop flag.
|
|
60
|
+
rm "$AGENT_DIR/.emergency-stop"
|
|
29
61
|
echo "[$TIMESTAMP] Stop flag removed" >> "$LOG_FILE"
|
|
30
62
|
|
|
31
|
-
# 3. Reload
|
|
32
|
-
echo "Reloading
|
|
33
|
-
|
|
63
|
+
# 3. Reload installed launchd jobs.
|
|
64
|
+
echo "Reloading installed launchd jobs ($PLIST_GLOB)..."
|
|
65
|
+
loaded=0
|
|
66
|
+
for plist in $PLIST_GLOB; do
|
|
34
67
|
if [ -f "$plist" ]; then
|
|
68
|
+
# Unload first in case it was left in a half-loaded state.
|
|
69
|
+
launchctl unload "$plist" 2>/dev/null || true
|
|
35
70
|
launchctl load "$plist" 2>/dev/null || true
|
|
36
71
|
echo "[$TIMESTAMP] Loaded: $(basename "$plist")" >> "$LOG_FILE"
|
|
72
|
+
loaded=$((loaded + 1))
|
|
37
73
|
fi
|
|
38
74
|
done
|
|
75
|
+
echo "[$TIMESTAMP] Loaded $loaded launchd job(s)" >> "$LOG_FILE"
|
|
39
76
|
|
|
40
77
|
echo "[$TIMESTAMP] OPERATIONS RESUMED" | tee -a "$LOG_FILE"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* init-backup.mjs — Off-machine state backup wizard.
|
|
4
|
+
*
|
|
5
|
+
* Configures `.maestro/backup-config.yaml` with bucket details and seeds
|
|
6
|
+
* a daily launchd plist that runs scripts/maintenance/backup-to-cloud.sh.
|
|
7
|
+
* Requires interactive input — does NOT auto-run.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, writeFileSync, mkdirSync } from "node:fs";
|
|
10
|
+
import { join, resolve, dirname } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const AGENT_DIR = process.env.AGENT_ROOT || process.env.AGENT_DIR || resolve(__dirname, "..", "..");
|
|
15
|
+
|
|
16
|
+
const ok = (m) => process.stdout.write(`[init-backup] ✓ ${m}\n`);
|
|
17
|
+
const warn = (m) => process.stdout.write(`[init-backup] ⚠ ${m}\n`);
|
|
18
|
+
|
|
19
|
+
mkdirSync(join(AGENT_DIR, ".maestro"), { recursive: true });
|
|
20
|
+
const cfg = join(AGENT_DIR, ".maestro/backup-config.yaml");
|
|
21
|
+
if (existsSync(cfg)) {
|
|
22
|
+
ok("backup-config.yaml already present");
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
writeFileSync(cfg, `# Off-machine state backup configuration
|
|
27
|
+
# Fill in the bucket details, then run:
|
|
28
|
+
# maestro init backup-replication --apply
|
|
29
|
+
#
|
|
30
|
+
# Backups cover (configurable below):
|
|
31
|
+
# - state/ (queues, dashboards, inboxes — minus state/tmp/)
|
|
32
|
+
# - knowledge/
|
|
33
|
+
# - outputs/
|
|
34
|
+
# - logs/ (rotated archives only — current-day logs excluded)
|
|
35
|
+
# - config/agent.json (identity SOT)
|
|
36
|
+
|
|
37
|
+
enabled: false
|
|
38
|
+
provider: gcs # gcs | s3 | rsync
|
|
39
|
+
bucket: "" # e.g. adaptic-maestro-backups
|
|
40
|
+
prefix: agent-name-here # e.g. ravi-ai (typically the repoSlug)
|
|
41
|
+
schedule: "0 3 * * *" # daily 03:00 local (overrides via launchd plist)
|
|
42
|
+
include:
|
|
43
|
+
- state
|
|
44
|
+
- knowledge
|
|
45
|
+
- outputs
|
|
46
|
+
- logs
|
|
47
|
+
- config/agent.json
|
|
48
|
+
exclude:
|
|
49
|
+
- state/tmp
|
|
50
|
+
- state/rag/index/*.bin
|
|
51
|
+
retention_days: 30
|
|
52
|
+
`);
|
|
53
|
+
ok(`wrote ${cfg}`);
|
|
54
|
+
warn("Backup is configured but NOT enabled. Edit .maestro/backup-config.yaml + set enabled: true, then re-run init.");
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* init-cadence-bus.mjs — Idempotent init step for the cadence-bus feature.
|
|
4
|
+
*
|
|
5
|
+
* Called by `maestro init` / `maestro upgrade` (via the feature-init
|
|
6
|
+
* tracker). Safe to run repeatedly. Verifies state/cadence-bus/{inbox,
|
|
7
|
+
* claimed,processed,failed,dlq} exist + logs/cadence-bus/ exists +
|
|
8
|
+
* generated launchd plists are on the cadence-bus arch.
|
|
9
|
+
*/
|
|
10
|
+
import { dirname, join, resolve } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const AGENT_DIR = process.env.AGENT_ROOT || process.env.AGENT_DIR || resolve(__dirname, "..", "..");
|
|
16
|
+
|
|
17
|
+
const ok = (m) => process.stdout.write(`[init-cadence-bus] ✓ ${m}\n`);
|
|
18
|
+
const warn = (m) => process.stdout.write(`[init-cadence-bus] ⚠ ${m}\n`);
|
|
19
|
+
|
|
20
|
+
// Directory tree (mirror of cadence-bus.mjs's bootstrap).
|
|
21
|
+
for (const d of [
|
|
22
|
+
"state/cadence-bus/inbox",
|
|
23
|
+
"state/cadence-bus/claimed",
|
|
24
|
+
"state/cadence-bus/processed",
|
|
25
|
+
"state/cadence-bus/failed",
|
|
26
|
+
"state/cadence-bus/dlq",
|
|
27
|
+
"logs/cadence-bus",
|
|
28
|
+
]) {
|
|
29
|
+
const full = join(AGENT_DIR, d);
|
|
30
|
+
if (!existsSync(full)) {
|
|
31
|
+
mkdirSync(full, { recursive: true });
|
|
32
|
+
ok(`created ${d}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Version marker file.
|
|
37
|
+
const versionFile = join(AGENT_DIR, "state/cadence-bus/VERSION");
|
|
38
|
+
if (!existsSync(versionFile)) {
|
|
39
|
+
writeFileSync(versionFile, "1\n");
|
|
40
|
+
ok("seeded state/cadence-bus/VERSION");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Plist arch verification — flag (don't fix here, that's upgrade's job).
|
|
44
|
+
const plistDir = join(AGENT_DIR, "scripts/local-triggers/plists");
|
|
45
|
+
if (existsSync(plistDir)) {
|
|
46
|
+
let legacy = 0;
|
|
47
|
+
for (const name of readdirSync(plistDir)) {
|
|
48
|
+
if (!name.endsWith(".plist")) continue;
|
|
49
|
+
const body = readFileSync(join(plistDir, name), "utf-8");
|
|
50
|
+
if (body.includes("run-trigger.sh")) legacy++;
|
|
51
|
+
}
|
|
52
|
+
if (legacy > 0) {
|
|
53
|
+
warn(`${legacy} generated plist(s) still use the legacy spawn-per-tick pattern.`);
|
|
54
|
+
warn(" Run: npx @adaptic/maestro upgrade (will back up + regenerate)");
|
|
55
|
+
} else {
|
|
56
|
+
ok("plist arch verified");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
ok("done");
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* init-cost-tracking.mjs — Set up local cost tracking ledger + dashboard.
|
|
4
|
+
*
|
|
5
|
+
* State:
|
|
6
|
+
* state/cost-tracking/<date>.jsonl — per-day token ledger
|
|
7
|
+
* state/dashboards/cost-summary.yaml — rollup
|
|
8
|
+
*
|
|
9
|
+
* The actual tracker is scripts/cost/track-claude-usage.mjs (shipped by the
|
|
10
|
+
* framework) which is wired into the cadence-consumer's session-spawn path.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { join, resolve, dirname } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const AGENT_DIR = process.env.AGENT_ROOT || process.env.AGENT_DIR || resolve(__dirname, "..", "..");
|
|
18
|
+
|
|
19
|
+
const ok = (m) => process.stdout.write(`[init-cost-tracking] ✓ ${m}\n`);
|
|
20
|
+
|
|
21
|
+
for (const d of ["state/cost-tracking", "state/dashboards"]) {
|
|
22
|
+
const full = join(AGENT_DIR, d);
|
|
23
|
+
if (!existsSync(full)) {
|
|
24
|
+
mkdirSync(full, { recursive: true });
|
|
25
|
+
ok(`created ${d}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const summary = join(AGENT_DIR, "state/dashboards/cost-summary.yaml");
|
|
30
|
+
if (!existsSync(summary)) {
|
|
31
|
+
writeFileSync(summary, `# Cost summary — auto-regenerated by scripts/cost/track-claude-usage.mjs
|
|
32
|
+
generated: null
|
|
33
|
+
window_days: 7
|
|
34
|
+
totals:
|
|
35
|
+
sessions: 0
|
|
36
|
+
input_tokens: 0
|
|
37
|
+
output_tokens: 0
|
|
38
|
+
estimated_usd: 0
|
|
39
|
+
by_model: {}
|
|
40
|
+
by_cadence: {}
|
|
41
|
+
`);
|
|
42
|
+
ok("seeded cost-summary.yaml");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ok("done");
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* init-decision-capture.mjs — Wire decision-capture automation.
|
|
4
|
+
*
|
|
5
|
+
* Ensures knowledge/decisions/{decision-schema,index}.yaml exist and
|
|
6
|
+
* creates scripts/decisions/capture-decision.mjs locally (the helper
|
|
7
|
+
* that the agent invokes when recording a decision).
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, mkdirSync, writeFileSync, copyFileSync } from "node:fs";
|
|
10
|
+
import { join, resolve, dirname } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const AGENT_DIR = process.env.AGENT_ROOT || process.env.AGENT_DIR || resolve(__dirname, "..", "..");
|
|
15
|
+
const MAESTRO_ROOT = process.env.MAESTRO_ROOT || resolve(__dirname, "..", "..");
|
|
16
|
+
|
|
17
|
+
const ok = (m) => process.stdout.write(`[init-decision-capture] ✓ ${m}\n`);
|
|
18
|
+
|
|
19
|
+
for (const d of ["knowledge/decisions", "knowledge/decisions/archive", "scripts/decisions"]) {
|
|
20
|
+
const full = join(AGENT_DIR, d);
|
|
21
|
+
if (!existsSync(full)) {
|
|
22
|
+
mkdirSync(full, { recursive: true });
|
|
23
|
+
ok(`created ${d}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Copy the framework's capture-decision helper if present.
|
|
28
|
+
const src = join(MAESTRO_ROOT, "scripts/decisions/capture-decision.mjs");
|
|
29
|
+
const dst = join(AGENT_DIR, "scripts/decisions/capture-decision.mjs");
|
|
30
|
+
if (existsSync(src) && !existsSync(dst)) {
|
|
31
|
+
copyFileSync(src, dst);
|
|
32
|
+
ok(`copied capture-decision.mjs`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Seed index.yaml + schema if missing.
|
|
36
|
+
const schemaPath = join(AGENT_DIR, "knowledge/decisions/decision-schema.yaml");
|
|
37
|
+
if (!existsSync(schemaPath)) {
|
|
38
|
+
writeFileSync(schemaPath, `# Decision Record Schema (Maestro decision-capture v1)
|
|
39
|
+
# Each decision is a YAML doc named DEC-YYYY-MM-DD-NNN.yaml under knowledge/decisions/.
|
|
40
|
+
|
|
41
|
+
schema:
|
|
42
|
+
version: "1"
|
|
43
|
+
fields:
|
|
44
|
+
id: { type: string, format: "DEC-YYYY-MM-DD-NNN", required: true }
|
|
45
|
+
date: { type: date, format: "YYYY-MM-DD", required: true }
|
|
46
|
+
title: { type: string, required: true }
|
|
47
|
+
domain: { type: string, required: true }
|
|
48
|
+
decision_maker: { type: string, required: true }
|
|
49
|
+
decision_text: { type: string, required: true }
|
|
50
|
+
context: { type: string, required: true }
|
|
51
|
+
rationale: { type: string, required: true }
|
|
52
|
+
alternatives: { type: array, required: false }
|
|
53
|
+
stakeholders: { type: array, required: false }
|
|
54
|
+
expires_at: { type: date, required: false }
|
|
55
|
+
status: { type: string, enum: [active, superseded, reversed, completed], default: active }
|
|
56
|
+
`);
|
|
57
|
+
ok("seeded decision-schema.yaml");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const indexPath = join(AGENT_DIR, "knowledge/decisions/index.yaml");
|
|
61
|
+
if (!existsSync(indexPath)) {
|
|
62
|
+
writeFileSync(indexPath, "# Decision Index — auto-maintained by capture-decision.mjs\ndecisions: []\n");
|
|
63
|
+
ok("seeded decision index");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
ok("done");
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* init-known-agents.mjs — Scaffold config/known-agents.json
|
|
4
|
+
*
|
|
5
|
+
* The Adaptic agent roster is shipped as a starter; agents that have
|
|
6
|
+
* already populated their own known-agents.json keep theirs (this init
|
|
7
|
+
* step is idempotent and never overwrites).
|
|
8
|
+
*
|
|
9
|
+
* classifier.mjs reads this file at runtime to detect peer @mentions in
|
|
10
|
+
* Slack/Gmail so an agent doesn't respond to messages routed at another
|
|
11
|
+
* agent.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { join, dirname, resolve } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const AGENT_DIR = process.env.AGENT_ROOT || process.env.AGENT_DIR || resolve(__dirname, "..", "..");
|
|
19
|
+
|
|
20
|
+
const ok = (m) => process.stdout.write(`[init-known-agents] ✓ ${m}\n`);
|
|
21
|
+
|
|
22
|
+
const TARGET = join(AGENT_DIR, "config", "known-agents.json");
|
|
23
|
+
const MAESTRO_ROOT = process.env.MAESTRO_ROOT || resolve(__dirname, "..", "..");
|
|
24
|
+
const SCAFFOLD = join(MAESTRO_ROOT, "scaffold", "config", "known-agents.json");
|
|
25
|
+
|
|
26
|
+
mkdirSync(dirname(TARGET), { recursive: true });
|
|
27
|
+
|
|
28
|
+
if (existsSync(TARGET)) {
|
|
29
|
+
// Already present — verify shape and exit. We don't merge upstream
|
|
30
|
+
// additions into existing files to avoid surprising the operator.
|
|
31
|
+
try {
|
|
32
|
+
const current = JSON.parse(readFileSync(TARGET, "utf-8"));
|
|
33
|
+
const count = Array.isArray(current.agents) ? current.agents.length : 0;
|
|
34
|
+
ok(`already present (${count} agents)`);
|
|
35
|
+
process.exit(0);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
process.stderr.write(`[init-known-agents] existing file is invalid JSON: ${err.message}\n`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Copy from scaffold (the framework's canonical roster); fall back to a
|
|
43
|
+
// minimal stub if the scaffold is missing.
|
|
44
|
+
let payload;
|
|
45
|
+
if (existsSync(SCAFFOLD)) {
|
|
46
|
+
payload = readFileSync(SCAFFOLD, "utf-8");
|
|
47
|
+
ok(`copied from ${SCAFFOLD}`);
|
|
48
|
+
} else {
|
|
49
|
+
payload = JSON.stringify({
|
|
50
|
+
schema_version: "1",
|
|
51
|
+
agents: [],
|
|
52
|
+
}, null, 2) + "\n";
|
|
53
|
+
ok("scaffold not found — wrote empty roster");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
writeFileSync(TARGET, payload);
|
|
57
|
+
ok(`wrote ${TARGET}`);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* init-memory-executive.mjs — Create memory/executive/ + memory/workstreams/
|
|
4
|
+
* Referenced by agents/ceo-briefing/agent.md and several skill prompts.
|
|
5
|
+
*
|
|
6
|
+
* Seeds priorities.md and open-loops.md so morning-brief / evening-wrap
|
|
7
|
+
* don't fail on a missing file.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { join, resolve, dirname } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const AGENT_DIR = process.env.AGENT_ROOT || process.env.AGENT_DIR || resolve(__dirname, "..", "..");
|
|
15
|
+
|
|
16
|
+
const ok = (m) => process.stdout.write(`[init-memory-executive] ✓ ${m}\n`);
|
|
17
|
+
|
|
18
|
+
const DIRS = [
|
|
19
|
+
"memory/executive",
|
|
20
|
+
"memory/workstreams",
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
for (const d of DIRS) {
|
|
24
|
+
const full = join(AGENT_DIR, d);
|
|
25
|
+
if (!existsSync(full)) {
|
|
26
|
+
mkdirSync(full, { recursive: true });
|
|
27
|
+
ok(`created ${d}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const seeds = {
|
|
32
|
+
"memory/executive/priorities.md": "# Executive Priorities\n\nCurrent strategic priorities maintained by the agent. Updated weekly.\n\n_Populate via /init-maestro or by the weekly-priorities cadence._\n",
|
|
33
|
+
"memory/executive/open-loops.md": "# Open Loops\n\nOutstanding commitments awaiting closure. Updated by inbox-processor and evening-wrap cadences.\n\n_Format: each loop has owner, deadline, blocker, last-touched._\n",
|
|
34
|
+
"memory/workstreams/README.md": "# Workstreams\n\nOne markdown file per active workstream. Updated by the relevant cadence (weekly-execution, weekly-engineering-health, etc.) to reflect current status, blockers, and next milestones.\n",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
for (const [path, body] of Object.entries(seeds)) {
|
|
38
|
+
const full = join(AGENT_DIR, path);
|
|
39
|
+
if (!existsSync(full)) {
|
|
40
|
+
writeFileSync(full, body);
|
|
41
|
+
ok(`seeded ${path}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ok("done");
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* init-rag.mjs — Bootstrap the local RAG index.
|
|
4
|
+
*
|
|
5
|
+
* Auto-discovers candidate repos under ~/ and writes them to
|
|
6
|
+
* config/repo-registry.yaml. By default this script is *not* auto-run
|
|
7
|
+
* because picking the right repos requires operator judgement. When run
|
|
8
|
+
* interactively, it lists candidates and asks for confirmation; when run
|
|
9
|
+
* with --yes it picks all ~/{repo}-ai siblings + adapticai/* clones.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync, readFileSync } from "node:fs";
|
|
12
|
+
import { join, resolve, dirname } from "node:path";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const AGENT_DIR = process.env.AGENT_ROOT || process.env.AGENT_DIR || resolve(__dirname, "..", "..");
|
|
18
|
+
|
|
19
|
+
const ok = (m) => process.stdout.write(`[init-rag] ✓ ${m}\n`);
|
|
20
|
+
const warn = (m) => process.stdout.write(`[init-rag] ⚠ ${m}\n`);
|
|
21
|
+
|
|
22
|
+
const args = new Set(process.argv.slice(2));
|
|
23
|
+
const autoYes = args.has("--yes") || args.has("-y");
|
|
24
|
+
|
|
25
|
+
for (const d of ["state/rag", "state/rag/index", "scripts/rag"]) {
|
|
26
|
+
const full = join(AGENT_DIR, d);
|
|
27
|
+
if (!existsSync(full)) {
|
|
28
|
+
mkdirSync(full, { recursive: true });
|
|
29
|
+
ok(`created ${d}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Discover candidate repos under $HOME.
|
|
34
|
+
function discoverRepos() {
|
|
35
|
+
const home = homedir();
|
|
36
|
+
const candidates = [];
|
|
37
|
+
for (const name of readdirSync(home)) {
|
|
38
|
+
if (name.startsWith(".") || name === "ravi-ai" || name === "Library" || name === "Applications") continue;
|
|
39
|
+
const full = join(home, name);
|
|
40
|
+
try {
|
|
41
|
+
const st = statSync(full);
|
|
42
|
+
if (!st.isDirectory()) continue;
|
|
43
|
+
// Heuristic: looks like a code repo (.git or package.json or pyproject.toml).
|
|
44
|
+
const isRepo =
|
|
45
|
+
existsSync(join(full, ".git")) ||
|
|
46
|
+
existsSync(join(full, "package.json")) ||
|
|
47
|
+
existsSync(join(full, "pyproject.toml")) ||
|
|
48
|
+
existsSync(join(full, "go.mod")) ||
|
|
49
|
+
existsSync(join(full, "Cargo.toml"));
|
|
50
|
+
if (isRepo) {
|
|
51
|
+
candidates.push({ name, path: full });
|
|
52
|
+
}
|
|
53
|
+
} catch { /* ignore */ }
|
|
54
|
+
}
|
|
55
|
+
return candidates;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const candidates = discoverRepos();
|
|
59
|
+
ok(`discovered ${candidates.length} candidate repo(s) under ${homedir()}`);
|
|
60
|
+
|
|
61
|
+
const registryPath = join(AGENT_DIR, "config/repo-registry.yaml");
|
|
62
|
+
let registry = { repositories: [] };
|
|
63
|
+
if (existsSync(registryPath)) {
|
|
64
|
+
// Crude YAML re-read: just check whether `repositories: []` is empty.
|
|
65
|
+
const body = readFileSync(registryPath, "utf-8");
|
|
66
|
+
if (/repositories:\s*\[\s*\]/.test(body) || /repositories:\s*$/m.test(body)) {
|
|
67
|
+
// empty list — overwrite with discovered set if user opted in.
|
|
68
|
+
} else {
|
|
69
|
+
ok("config/repo-registry.yaml already populated — leaving as-is");
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!autoYes) {
|
|
75
|
+
warn("Not running interactively — to populate, run with --yes:");
|
|
76
|
+
warn(" node scripts/setup/init-rag.mjs --yes");
|
|
77
|
+
warn("Candidates that would be added:");
|
|
78
|
+
for (const c of candidates) warn(` - ${c.name} (${c.path})`);
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Build the YAML.
|
|
83
|
+
const lines = [
|
|
84
|
+
"# Source Repository Registry",
|
|
85
|
+
"# Populated by scripts/setup/init-rag.mjs. Add/remove entries as needed.",
|
|
86
|
+
"# Format:",
|
|
87
|
+
"# - name: repo-slug",
|
|
88
|
+
"# path: ~/repo-slug",
|
|
89
|
+
"# purpose: what this repo contains",
|
|
90
|
+
"# index: true | false (indexed by state/rag/)",
|
|
91
|
+
"",
|
|
92
|
+
"repositories:",
|
|
93
|
+
];
|
|
94
|
+
for (const c of candidates) {
|
|
95
|
+
lines.push(` - name: ${c.name}`);
|
|
96
|
+
lines.push(` path: ${c.path}`);
|
|
97
|
+
lines.push(` purpose: ""`);
|
|
98
|
+
lines.push(` index: true`);
|
|
99
|
+
}
|
|
100
|
+
writeFileSync(registryPath, lines.join("\n") + "\n");
|
|
101
|
+
ok(`wrote ${candidates.length} entries to config/repo-registry.yaml`);
|
|
102
|
+
|
|
103
|
+
ok("done");
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* init-session-router.mjs — Opt-in session router enablement.
|
|
4
|
+
*
|
|
5
|
+
* Session router enables LRU reuse of long-running Claude sessions for
|
|
6
|
+
* conversational items (Slack threads, Gmail threads). Requires
|
|
7
|
+
* SESSION_ROUTER_ENABLED=1 in .env. This init step appends that line if
|
|
8
|
+
* absent, but is intentionally `auto: false` in the registry so the
|
|
9
|
+
* operator confirms the decision.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
12
|
+
import { join, resolve, dirname } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const AGENT_DIR = process.env.AGENT_ROOT || process.env.AGENT_DIR || resolve(__dirname, "..", "..");
|
|
17
|
+
|
|
18
|
+
const ok = (m) => process.stdout.write(`[init-session-router] ✓ ${m}\n`);
|
|
19
|
+
const warn = (m) => process.stdout.write(`[init-session-router] ⚠ ${m}\n`);
|
|
20
|
+
|
|
21
|
+
const envFile = join(AGENT_DIR, ".env");
|
|
22
|
+
if (!existsSync(envFile)) {
|
|
23
|
+
warn(".env not present — create it first (copy .env.example).");
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const body = readFileSync(envFile, "utf-8");
|
|
28
|
+
if (/^SESSION_ROUTER_ENABLED=/m.test(body)) {
|
|
29
|
+
ok("SESSION_ROUTER_ENABLED already configured");
|
|
30
|
+
} else {
|
|
31
|
+
const updated = body.replace(/\n*$/, "") + `\n\n# Session router (LRU reuse) — disabled by default. Set to 1 to enable.\nSESSION_ROUTER_ENABLED=1\n`;
|
|
32
|
+
writeFileSync(envFile, updated);
|
|
33
|
+
ok("appended SESSION_ROUTER_ENABLED=1 to .env");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Touch the registry dir.
|
|
37
|
+
mkdirSync(join(AGENT_DIR, "state/sessions"), { recursive: true });
|
|
38
|
+
ok("done");
|
|
@@ -135,7 +135,7 @@ steps:
|
|
|
135
135
|
on_failure:
|
|
136
136
|
action: log-and-notify
|
|
137
137
|
message: "Backlog executor cycle failed at step {failed_step}"
|
|
138
|
-
notify:
|
|
138
|
+
notify: agent-self # the executing agent (operator)
|
|
139
139
|
severity: high
|
|
140
140
|
# If quota-gate fails, the entire cycle is skipped — this is by design
|
|
141
141
|
quota_gate_failure: skip_cycle
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Continuous Inbound Monitoring Loop
|
|
2
2
|
# Runs perpetually via launchd — polls all channels and dispatches responses
|
|
3
|
-
# This is the heartbeat of
|
|
3
|
+
# This is the heartbeat of the agent's always-on executive operations
|
|
4
4
|
|
|
5
5
|
name: inbound-monitor
|
|
6
6
|
type: continuous # Not scheduled — runs as a persistent loop
|
|
@@ -17,7 +17,7 @@ monitors:
|
|
|
17
17
|
interval_seconds: 60
|
|
18
18
|
scope:
|
|
19
19
|
- type: mentions
|
|
20
|
-
description: Any @mention of
|
|
20
|
+
description: Any @mention of the agent or the principal
|
|
21
21
|
- type: dms
|
|
22
22
|
description: All direct messages
|
|
23
23
|
- type: channels
|
|
@@ -65,8 +65,8 @@ dispatch:
|
|
|
65
65
|
|
|
66
66
|
routing:
|
|
67
67
|
direct-reply:
|
|
68
|
-
handler:
|
|
69
|
-
description:
|
|
68
|
+
handler: agent-self # the executing agent (operator)
|
|
69
|
+
description: The agent handles directly — default route for most inbound
|
|
70
70
|
max_response_time: 120 # 2 minutes for DMs, 5 minutes for channels
|
|
71
71
|
priority: highest
|
|
72
72
|
|
|
@@ -77,11 +77,11 @@ dispatch:
|
|
|
77
77
|
max_concurrent: 5
|
|
78
78
|
|
|
79
79
|
escalate-to-ceo:
|
|
80
|
-
handler:
|
|
80
|
+
handler: agent-self # the executing agent (operator)
|
|
81
81
|
description: >
|
|
82
|
-
Alert
|
|
83
|
-
deal terms, or genuinely strategic decisions.
|
|
84
|
-
method: slack-dm # DM
|
|
82
|
+
Alert the principal only for binding commitments, regulatory submissions,
|
|
83
|
+
deal terms, or genuinely strategic decisions. The agent decides when this applies.
|
|
84
|
+
method: slack-dm # DM the principal on Slack
|
|
85
85
|
fallback: whatsapp # If Slack unavailable, WhatsApp
|
|
86
86
|
|
|
87
87
|
defer:
|
|
@@ -90,11 +90,11 @@ dispatch:
|
|
|
90
90
|
output: outputs/inbound/deferred/
|
|
91
91
|
|
|
92
92
|
# Autonomous response rules
|
|
93
|
-
#
|
|
93
|
+
# The agent is fully autonomous — she responds to all inbound communications
|
|
94
94
|
# using her own judgement as Chief of Staff. No per-message approval gates.
|
|
95
95
|
autonomous_response:
|
|
96
96
|
enabled: true
|
|
97
|
-
mode: full_autonomy #
|
|
97
|
+
mode: full_autonomy # The agent decides what to send and when
|
|
98
98
|
categories:
|
|
99
99
|
- id: dm-response
|
|
100
100
|
description: Respond to any DM from any workspace user
|
|
@@ -191,7 +191,7 @@ steps:
|
|
|
191
191
|
on_failure:
|
|
192
192
|
action: log-and-notify
|
|
193
193
|
message: "Daily applicant triage failed at step {failed_step}"
|
|
194
|
-
notify:
|
|
194
|
+
notify: agent-self # the executing agent (operator)
|
|
195
195
|
severity: medium
|
|
196
196
|
|
|
197
197
|
final_output: outputs/briefs/daily/{date}/applicant-triage.md
|
|
@@ -63,7 +63,7 @@ steps:
|
|
|
63
63
|
update: knowledge/memory/executive-memory.yaml # add to open_loops
|
|
64
64
|
|
|
65
65
|
- id: route-actions
|
|
66
|
-
agent:
|
|
66
|
+
agent: agent-self # the executing agent (operator)
|
|
67
67
|
action: route-action-items
|
|
68
68
|
description: Route extracted action items to appropriate queues and agents
|
|
69
69
|
timeout: 120
|
|
@@ -76,5 +76,5 @@ steps:
|
|
|
76
76
|
on_failure:
|
|
77
77
|
action: log-and-notify
|
|
78
78
|
message: "Communications triage failed at step {failed_step}"
|
|
79
|
-
notify:
|
|
79
|
+
notify: agent-self # the executing agent (operator)
|
|
80
80
|
severity: high
|
|
@@ -99,7 +99,7 @@ steps:
|
|
|
99
99
|
on_failure:
|
|
100
100
|
action: log-and-notify
|
|
101
101
|
message: "Evening wrap workflow failed at step {failed_step}"
|
|
102
|
-
notify:
|
|
102
|
+
notify: agent-self # the executing agent (operator)
|
|
103
103
|
severity: medium
|
|
104
104
|
|
|
105
105
|
final_output: outputs/briefs/daily/{date}/ceo-evening-wrap.md
|