hebbian 0.7.1 → 0.8.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/dist/index.js CHANGED
@@ -73,6 +73,8 @@ function resolveSharedBrain(brainRoot) {
73
73
  }
74
74
  var AGENTS_DIR = "agents";
75
75
  var SHARED_DIR = "shared";
76
+ var SKILLS_DIR = "skills";
77
+ var PROPAGATION_EPISODE_TYPES = ["tool-failure", "retry-pattern"];
76
78
 
77
79
  // src/scanner.ts
78
80
  import { readdirSync, statSync, readFileSync, existsSync as existsSync2 } from "fs";
@@ -198,6 +200,11 @@ function walkRegion(dir, regionRoot, depth) {
198
200
  }
199
201
  return neurons;
200
202
  }
203
+ function scanSkills(brainRoot) {
204
+ const skillsPath = join(brainRoot, SKILLS_DIR);
205
+ if (!existsSync2(skillsPath)) return [];
206
+ return walkRegion(skillsPath, skillsPath, 0);
207
+ }
201
208
  function readAxons(regionPath) {
202
209
  const axonPath = join(regionPath, ".axon");
203
210
  if (!existsSync2(axonPath)) return [];
@@ -413,8 +420,8 @@ function growNeuron(brainRoot, neuronPath) {
413
420
  }
414
421
  const parts = neuronPath.split("/");
415
422
  const regionName = parts[0];
416
- if (!REGIONS.includes(regionName)) {
417
- throw new Error(`Invalid region: ${regionName}. Valid: ${REGIONS.join(", ")}`);
423
+ if (regionName !== SKILLS_DIR && !REGIONS.includes(regionName)) {
424
+ throw new Error(`Invalid region: ${regionName}. Valid: ${REGIONS.join(", ")}, ${SKILLS_DIR}`);
418
425
  }
419
426
  const leafName = parts[parts.length - 1];
420
427
  const newPrefix = leafName.match(/^(NO|DO|MUST|WARN)_/)?.[1] || "";
@@ -952,6 +959,12 @@ ${template.description}
952
959
  }
953
960
  }
954
961
  mkdirSync4(join10(brainPath, "_agents", "global_inbox"), { recursive: true });
962
+ mkdirSync4(join10(brainPath, "skills"), { recursive: true });
963
+ writeFileSync7(
964
+ join10(brainPath, "skills", "_rules.md"),
965
+ "# Skills Library\n\nExecutable patterns learned through experience.\nNot part of the subsumption cascade \u2014 retrieval only.\n",
966
+ "utf8"
967
+ );
955
968
  autoGitignore(brainPath);
956
969
  console.log(`\u{1F9E0} Brain initialized at ${brainPath}`);
957
970
  console.log(` 7 regions created: ${REGIONS.join(", ")}`);
@@ -993,8 +1006,73 @@ import { readFileSync as readFileSync5, writeFileSync as writeFileSync9, existsS
993
1006
  import { join as join13 } from "path";
994
1007
 
995
1008
  // src/candidates.ts
996
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync7, renameSync as renameSync3, rmSync, statSync as statSync3 } from "fs";
997
- import { join as join11, dirname as dirname3, relative as relative3 } from "path";
1009
+ import { existsSync as existsSync11, mkdirSync as mkdirSync6, readdirSync as readdirSync8, renameSync as renameSync3, rmSync, statSync as statSync3 } from "fs";
1010
+ import { join as join12, dirname as dirname3, relative as relative3 } from "path";
1011
+
1012
+ // src/episode.ts
1013
+ import { readdirSync as readdirSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5, existsSync as existsSync10 } from "fs";
1014
+ import { join as join11 } from "path";
1015
+ var MAX_EPISODES = 100;
1016
+ var SESSION_LOG_DIR = "hippocampus/session_log";
1017
+ function logEpisode(brainRoot, type, path, detail, extra) {
1018
+ const logDir = join11(brainRoot, SESSION_LOG_DIR);
1019
+ if (!existsSync10(logDir)) {
1020
+ mkdirSync5(logDir, { recursive: true });
1021
+ }
1022
+ const nextSlot = getNextSlot(logDir);
1023
+ const episode = {
1024
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1025
+ type,
1026
+ path,
1027
+ detail,
1028
+ ...extra?.outcome ? { outcome: extra.outcome } : {},
1029
+ ...extra?.neurons ? { neurons: extra.neurons } : {}
1030
+ };
1031
+ writeFileSync8(
1032
+ join11(logDir, `memory${nextSlot}.neuron`),
1033
+ JSON.stringify(episode),
1034
+ "utf8"
1035
+ );
1036
+ }
1037
+ function readEpisodes(brainRoot) {
1038
+ const logDir = join11(brainRoot, SESSION_LOG_DIR);
1039
+ if (!existsSync10(logDir)) return [];
1040
+ const episodes = [];
1041
+ let entries;
1042
+ try {
1043
+ entries = readdirSync7(logDir);
1044
+ } catch {
1045
+ return [];
1046
+ }
1047
+ for (const entry of entries) {
1048
+ if (!entry.startsWith("memory") || !entry.endsWith(".neuron")) continue;
1049
+ try {
1050
+ const content = readFileSync4(join11(logDir, entry), "utf8");
1051
+ if (content.trim()) {
1052
+ episodes.push(JSON.parse(content));
1053
+ }
1054
+ } catch {
1055
+ }
1056
+ }
1057
+ episodes.sort((a, b) => a.ts.localeCompare(b.ts));
1058
+ return episodes;
1059
+ }
1060
+ function getNextSlot(logDir) {
1061
+ let maxSlot = 0;
1062
+ try {
1063
+ for (const entry of readdirSync7(logDir)) {
1064
+ if (entry.startsWith("memory") && entry.endsWith(".neuron")) {
1065
+ const n = parseInt(entry.replace("memory", "").replace(".neuron", ""), 10);
1066
+ if (!isNaN(n) && n > maxSlot) maxSlot = n;
1067
+ }
1068
+ }
1069
+ } catch {
1070
+ }
1071
+ const next = maxSlot + 1;
1072
+ return next > MAX_EPISODES ? maxSlot % MAX_EPISODES + 1 : next;
1073
+ }
1074
+
1075
+ // src/candidates.ts
998
1076
  var CANDIDATE_THRESHOLD = 3;
999
1077
  var CANDIDATE_DECAY_DAYS = 14;
1000
1078
  var CANDIDATE_SEGMENT = "_candidates";
@@ -1011,20 +1089,21 @@ function growCandidate(brainRoot, neuronPath) {
1011
1089
  const result = growNeuron(brainRoot, candidatePath);
1012
1090
  if (result.counter >= CANDIDATE_THRESHOLD) {
1013
1091
  const ok = moveCandidate(brainRoot, candidatePath, neuronPath);
1092
+ if (ok) propagateToShared(brainRoot, neuronPath);
1014
1093
  return { ...result, path: ok ? neuronPath : result.path, promoted: ok };
1015
1094
  }
1016
1095
  console.log(` \u{1F331} candidate (${result.counter}/${CANDIDATE_THRESHOLD}): ${candidatePath}`);
1017
1096
  return { ...result, promoted: false };
1018
1097
  }
1019
1098
  function moveCandidate(brainRoot, candidatePath, targetPath) {
1020
- const src = join11(brainRoot, candidatePath);
1021
- if (!existsSync10(src)) return false;
1022
- const dst = join11(brainRoot, targetPath);
1023
- if (existsSync10(dst)) {
1099
+ const src = join12(brainRoot, candidatePath);
1100
+ if (!existsSync11(src)) return false;
1101
+ const dst = join12(brainRoot, targetPath);
1102
+ if (existsSync11(dst)) {
1024
1103
  fireNeuron(brainRoot, targetPath);
1025
1104
  rmSync(src, { recursive: true, force: true });
1026
1105
  } else {
1027
- mkdirSync5(dirname3(dst), { recursive: true });
1106
+ mkdirSync6(dirname3(dst), { recursive: true });
1028
1107
  renameSync3(src, dst);
1029
1108
  }
1030
1109
  console.log(`\u{1F393} promoted: ${candidatePath} \u2192 ${targetPath}`);
@@ -1036,15 +1115,16 @@ function promoteCandidates(brainRoot) {
1036
1115
  const decayMs = CANDIDATE_DECAY_DAYS * 24 * 60 * 60 * 1e3;
1037
1116
  const now = Date.now();
1038
1117
  for (const region of REGIONS) {
1039
- const candidateRoot = join11(brainRoot, region, CANDIDATE_SEGMENT);
1118
+ const candidateRoot = join12(brainRoot, region, CANDIDATE_SEGMENT);
1040
1119
  walkNeuronDirs(candidateRoot, (neuronDir) => {
1041
- const rel = relative3(join11(brainRoot, region), neuronDir);
1120
+ const rel = relative3(join12(brainRoot, region), neuronDir);
1042
1121
  const candidatePath = `${region}/${rel}`;
1043
1122
  const targetPath = fromCandidatePath(candidatePath);
1044
1123
  const counter = readCounter(neuronDir);
1045
1124
  const mtime = statSync3(neuronDir).mtimeMs;
1046
1125
  if (counter >= CANDIDATE_THRESHOLD) {
1047
1126
  moveCandidate(brainRoot, candidatePath, targetPath);
1127
+ propagateToShared(brainRoot, targetPath);
1048
1128
  promoted.push(targetPath);
1049
1129
  } else if (now - mtime > decayMs) {
1050
1130
  rmSync(neuronDir, { recursive: true, force: true });
@@ -1059,9 +1139,9 @@ function listCandidates(brainRoot) {
1059
1139
  const results = [];
1060
1140
  const now = Date.now();
1061
1141
  for (const region of REGIONS) {
1062
- const candidateRoot = join11(brainRoot, region, CANDIDATE_SEGMENT);
1142
+ const candidateRoot = join12(brainRoot, region, CANDIDATE_SEGMENT);
1063
1143
  walkNeuronDirs(candidateRoot, (neuronDir) => {
1064
- const rel = relative3(join11(brainRoot, region), neuronDir);
1144
+ const rel = relative3(join12(brainRoot, region), neuronDir);
1065
1145
  const candidatePath = `${region}/${rel}`;
1066
1146
  const targetPath = fromCandidatePath(candidatePath);
1067
1147
  const counter = readCounter(neuronDir);
@@ -1073,9 +1153,9 @@ function listCandidates(brainRoot) {
1073
1153
  return results;
1074
1154
  }
1075
1155
  function walkNeuronDirs(dir, cb) {
1076
- if (!existsSync10(dir)) return;
1156
+ if (!existsSync11(dir)) return;
1077
1157
  try {
1078
- const entries = readdirSync7(dir, { withFileTypes: true });
1158
+ const entries = readdirSync8(dir, { withFileTypes: true });
1079
1159
  const hasNeuron = entries.some((e) => e.isFile() && e.name.endsWith(".neuron"));
1080
1160
  if (hasNeuron) {
1081
1161
  cb(dir);
@@ -1083,7 +1163,7 @@ function walkNeuronDirs(dir, cb) {
1083
1163
  }
1084
1164
  for (const entry of entries) {
1085
1165
  if (entry.isDirectory() && !entry.name.startsWith(".")) {
1086
- walkNeuronDirs(join11(dir, entry.name), cb);
1166
+ walkNeuronDirs(join12(dir, entry.name), cb);
1087
1167
  }
1088
1168
  }
1089
1169
  } catch {
@@ -1091,75 +1171,32 @@ function walkNeuronDirs(dir, cb) {
1091
1171
  }
1092
1172
  function readCounter(dir) {
1093
1173
  try {
1094
- const files = readdirSync7(dir).filter((f) => /^\d+\.neuron$/.test(f));
1174
+ const files = readdirSync8(dir).filter((f) => /^\d+\.neuron$/.test(f));
1095
1175
  if (files.length === 0) return 0;
1096
1176
  return Math.max(...files.map((f) => parseInt(f, 10)));
1097
1177
  } catch {
1098
1178
  return 0;
1099
1179
  }
1100
1180
  }
1101
-
1102
- // src/episode.ts
1103
- import { readdirSync as readdirSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync11 } from "fs";
1104
- import { join as join12 } from "path";
1105
- var MAX_EPISODES = 100;
1106
- var SESSION_LOG_DIR = "hippocampus/session_log";
1107
- function logEpisode(brainRoot, type, path, detail, extra) {
1108
- const logDir = join12(brainRoot, SESSION_LOG_DIR);
1109
- if (!existsSync11(logDir)) {
1110
- mkdirSync6(logDir, { recursive: true });
1111
- }
1112
- const nextSlot = getNextSlot(logDir);
1113
- const episode = {
1114
- ts: (/* @__PURE__ */ new Date()).toISOString(),
1115
- type,
1116
- path,
1117
- detail,
1118
- ...extra?.outcome ? { outcome: extra.outcome } : {},
1119
- ...extra?.neurons ? { neurons: extra.neurons } : {}
1120
- };
1121
- writeFileSync8(
1122
- join12(logDir, `memory${nextSlot}.neuron`),
1123
- JSON.stringify(episode),
1124
- "utf8"
1125
- );
1126
- }
1127
- function readEpisodes(brainRoot) {
1128
- const logDir = join12(brainRoot, SESSION_LOG_DIR);
1129
- if (!existsSync11(logDir)) return [];
1130
- const episodes = [];
1131
- let entries;
1132
- try {
1133
- entries = readdirSync8(logDir);
1134
- } catch {
1135
- return [];
1136
- }
1137
- for (const entry of entries) {
1138
- if (!entry.startsWith("memory") || !entry.endsWith(".neuron")) continue;
1139
- try {
1140
- const content = readFileSync4(join12(logDir, entry), "utf8");
1141
- if (content.trim()) {
1142
- episodes.push(JSON.parse(content));
1143
- }
1144
- } catch {
1145
- }
1146
- }
1147
- episodes.sort((a, b) => a.ts.localeCompare(b.ts));
1148
- return episodes;
1149
- }
1150
- function getNextSlot(logDir) {
1151
- let maxSlot = 0;
1181
+ function propagateToShared(brainRoot, targetPath) {
1152
1182
  try {
1153
- for (const entry of readdirSync8(logDir)) {
1154
- if (entry.startsWith("memory") && entry.endsWith(".neuron")) {
1155
- const n = parseInt(entry.replace("memory", "").replace(".neuron", ""), 10);
1156
- if (!isNaN(n) && n > maxSlot) maxSlot = n;
1157
- }
1158
- }
1183
+ const agentsIdx = brainRoot.indexOf("/agents/");
1184
+ if (agentsIdx === -1) return false;
1185
+ const multiBrainRoot = brainRoot.slice(0, agentsIdx);
1186
+ const sharedRoot = join12(multiBrainRoot, "shared");
1187
+ if (!existsSync11(sharedRoot)) return false;
1188
+ const episodes = readEpisodes(brainRoot);
1189
+ const neuronName = targetPath.split("/").pop() || "";
1190
+ const hasRelevantEpisode = episodes.some(
1191
+ (ep) => PROPAGATION_EPISODE_TYPES.includes(ep.type) && (ep.path.includes(neuronName) || ep.detail.includes(neuronName))
1192
+ );
1193
+ if (!hasRelevantEpisode) return false;
1194
+ growNeuron(sharedRoot, targetPath);
1195
+ console.log(` \u{1F4E1} propagated to shared: ${targetPath}`);
1196
+ return true;
1159
1197
  } catch {
1198
+ return false;
1160
1199
  }
1161
- const next = maxSlot + 1;
1162
- return next > MAX_EPISODES ? maxSlot % MAX_EPISODES + 1 : next;
1163
1200
  }
1164
1201
 
1165
1202
  // src/inbox.ts
@@ -2566,6 +2603,7 @@ function validateActions(actions, _brain) {
2566
2603
  return false;
2567
2604
  }
2568
2605
  const region = action.path.split("/")[0];
2606
+ if (region === SKILLS_DIR) return true;
2569
2607
  if (!region || PROTECTED_REGIONS.includes(region)) {
2570
2608
  console.log(` \u{1F6E1}\uFE0F blocked: ${action.type} ${action.path} (protected region)`);
2571
2609
  return false;
@@ -2590,7 +2628,11 @@ function executeActions(brainRoot, actions) {
2590
2628
  fireNeuron(brainRoot, action.path);
2591
2629
  break;
2592
2630
  case "grow":
2593
- growCandidate(brainRoot, action.path);
2631
+ if (action.path.startsWith(SKILLS_DIR + "/")) {
2632
+ growNeuron(brainRoot, action.path);
2633
+ } else {
2634
+ growCandidate(brainRoot, action.path);
2635
+ }
2594
2636
  break;
2595
2637
  case "signal":
2596
2638
  signalNeuron(brainRoot, action.path, action.signal || "dopamine");
@@ -2626,6 +2668,226 @@ function actionIcon(type) {
2626
2668
  return "\u2753";
2627
2669
  }
2628
2670
  }
2671
+
2672
+ // src/cron.ts
2673
+ import { writeFileSync as writeFileSync14, existsSync as existsSync17, unlinkSync } from "fs";
2674
+ import { join as join18 } from "path";
2675
+ import { execSync as execSync4 } from "child_process";
2676
+ var PLIST_LABEL = "com.hebbian.nightly-prune";
2677
+ var FEEDBACK_PLIST_LABEL = "com.hebbian.feedback";
2678
+ function getLaunchAgentsDir() {
2679
+ return join18(process.env.HOME || "~", "Library", "LaunchAgents");
2680
+ }
2681
+ function getPlistPath(label) {
2682
+ return join18(getLaunchAgentsDir(), `${label}.plist`);
2683
+ }
2684
+ function getNpxPath() {
2685
+ try {
2686
+ return execSync4("which npx", { encoding: "utf8" }).trim();
2687
+ } catch {
2688
+ return "/opt/homebrew/bin/npx";
2689
+ }
2690
+ }
2691
+ function generatePrunePlist(brainRoot, hour = 2, minute = 0) {
2692
+ const npx = getNpxPath();
2693
+ const apiKey = process.env.GEMINI_API_KEY || "";
2694
+ const home = process.env.HOME || "/Users/sweetheart";
2695
+ return `<?xml version="1.0" encoding="UTF-8"?>
2696
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2697
+ <plist version="1.0">
2698
+ <dict>
2699
+ <key>Label</key>
2700
+ <string>${PLIST_LABEL}</string>
2701
+ <key>ProgramArguments</key>
2702
+ <array>
2703
+ <string>${npx}</string>
2704
+ <string>hebbian</string>
2705
+ <string>evolve</string>
2706
+ <string>prune</string>
2707
+ <string>--brain</string>
2708
+ <string>${brainRoot}</string>
2709
+ </array>
2710
+ <key>EnvironmentVariables</key>
2711
+ <dict>
2712
+ <key>GEMINI_API_KEY</key>
2713
+ <string>${apiKey}</string>
2714
+ <key>PATH</key>
2715
+ <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
2716
+ </dict>
2717
+ <key>StartCalendarInterval</key>
2718
+ <dict>
2719
+ <key>Hour</key>
2720
+ <integer>${hour}</integer>
2721
+ <key>Minute</key>
2722
+ <integer>${minute}</integer>
2723
+ </dict>
2724
+ <key>StandardOutPath</key>
2725
+ <string>${home}/Library/Logs/hebbian-prune.log</string>
2726
+ <key>StandardErrorPath</key>
2727
+ <string>${home}/Library/Logs/hebbian-prune.log</string>
2728
+ </dict>
2729
+ </plist>`;
2730
+ }
2731
+ function generateFeedbackPlist(brainRoot, intervalMinutes = 15) {
2732
+ const npx = getNpxPath();
2733
+ const home = process.env.HOME || "/Users/sweetheart";
2734
+ return `<?xml version="1.0" encoding="UTF-8"?>
2735
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2736
+ <plist version="1.0">
2737
+ <dict>
2738
+ <key>Label</key>
2739
+ <string>${FEEDBACK_PLIST_LABEL}</string>
2740
+ <key>ProgramArguments</key>
2741
+ <array>
2742
+ <string>${npx}</string>
2743
+ <string>hebbian</string>
2744
+ <string>feedback</string>
2745
+ <string>scan</string>
2746
+ <string>--brain</string>
2747
+ <string>${brainRoot}</string>
2748
+ </array>
2749
+ <key>EnvironmentVariables</key>
2750
+ <dict>
2751
+ <key>PATH</key>
2752
+ <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
2753
+ </dict>
2754
+ <key>StartInterval</key>
2755
+ <integer>${intervalMinutes * 60}</integer>
2756
+ <key>StandardOutPath</key>
2757
+ <string>${home}/Library/Logs/hebbian-feedback.log</string>
2758
+ <key>StandardErrorPath</key>
2759
+ <string>${home}/Library/Logs/hebbian-feedback.log</string>
2760
+ </dict>
2761
+ </plist>`;
2762
+ }
2763
+ function installCron(brainRoot, type = "prune") {
2764
+ const label = type === "prune" ? PLIST_LABEL : FEEDBACK_PLIST_LABEL;
2765
+ const plistPath = getPlistPath(label);
2766
+ const plistContent = type === "prune" ? generatePrunePlist(brainRoot) : generateFeedbackPlist(brainRoot);
2767
+ try {
2768
+ execSync4(`launchctl unload ${plistPath} 2>/dev/null`, { encoding: "utf8" });
2769
+ } catch {
2770
+ }
2771
+ writeFileSync14(plistPath, plistContent, "utf8");
2772
+ execSync4(`launchctl load ${plistPath}`, { encoding: "utf8" });
2773
+ console.log(`\u2705 ${type} cron installed: ${plistPath}`);
2774
+ }
2775
+ function uninstallCron(type = "prune") {
2776
+ const label = type === "prune" ? PLIST_LABEL : FEEDBACK_PLIST_LABEL;
2777
+ const plistPath = getPlistPath(label);
2778
+ if (!existsSync17(plistPath)) {
2779
+ console.log(`\u26A0\uFE0F ${type} cron not installed`);
2780
+ return;
2781
+ }
2782
+ try {
2783
+ execSync4(`launchctl unload ${plistPath}`, { encoding: "utf8" });
2784
+ } catch {
2785
+ }
2786
+ unlinkSync(plistPath);
2787
+ console.log(`\u{1F5D1}\uFE0F ${type} cron uninstalled`);
2788
+ }
2789
+ function checkCron(type = "prune") {
2790
+ const label = type === "prune" ? PLIST_LABEL : FEEDBACK_PLIST_LABEL;
2791
+ const plistPath = getPlistPath(label);
2792
+ return { installed: existsSync17(plistPath), path: plistPath };
2793
+ }
2794
+
2795
+ // src/feedback.ts
2796
+ import { existsSync as existsSync18, readdirSync as readdirSync11, statSync as statSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync15, mkdirSync as mkdirSync11 } from "fs";
2797
+ import { join as join19 } from "path";
2798
+ var WATERMARK_FILE = "_feedback_watermark.json";
2799
+ var WARN_PREFIX = "WARN_shared_";
2800
+ function scanSharedBrain(brainRoot) {
2801
+ const sharedRoot = join19(brainRoot, SHARED_DIR);
2802
+ if (!existsSync18(sharedRoot)) return [];
2803
+ const watermark = readWatermark(sharedRoot);
2804
+ const deltas = [];
2805
+ for (const region of REGIONS) {
2806
+ const regionPath = join19(sharedRoot, region);
2807
+ if (!existsSync18(regionPath)) continue;
2808
+ walkForNeurons(regionPath, regionPath, (neuronDir, counter) => {
2809
+ const modTime = statSync5(neuronDir).mtime;
2810
+ if (modTime.getTime() <= watermark) return;
2811
+ const name = neuronDir.split("/").pop() || "";
2812
+ if (name.startsWith(WARN_PREFIX)) return;
2813
+ const relPath = region + "/" + neuronDir.slice(regionPath.length + 1);
2814
+ deltas.push({ path: relPath, counter, modTime });
2815
+ });
2816
+ }
2817
+ return deltas;
2818
+ }
2819
+ function propagateToAgents(brainRoot, deltas) {
2820
+ const agentsDir = join19(brainRoot, AGENTS_DIR);
2821
+ if (!existsSync18(agentsDir) || deltas.length === 0) {
2822
+ return { scanned: deltas.length, propagated: 0, agents: [] };
2823
+ }
2824
+ const agentNames = readdirSync11(agentsDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && !e.name.startsWith("_")).map((e) => e.name);
2825
+ let propagated = 0;
2826
+ const touchedAgents = /* @__PURE__ */ new Set();
2827
+ for (const delta of deltas) {
2828
+ const neuronName = delta.path.split("/").pop() || "";
2829
+ const warnPath = delta.path.replace(/\/([^/]+)$/, `/${WARN_PREFIX}${neuronName}`);
2830
+ for (const agent of agentNames) {
2831
+ const agentBrain = join19(agentsDir, agent);
2832
+ if (!existsSync18(join19(agentBrain, "cortex")) && !existsSync18(join19(agentBrain, "brainstem"))) continue;
2833
+ try {
2834
+ growNeuron(agentBrain, warnPath);
2835
+ logEpisode(agentBrain, "feedback", warnPath, `shared learning: ${delta.path}`);
2836
+ propagated++;
2837
+ touchedAgents.add(agent);
2838
+ } catch {
2839
+ }
2840
+ }
2841
+ }
2842
+ return { scanned: deltas.length, propagated, agents: [...touchedAgents] };
2843
+ }
2844
+ function runFeedback(brainRoot) {
2845
+ const deltas = scanSharedBrain(brainRoot);
2846
+ if (deltas.length === 0) {
2847
+ console.log("\u{1F4E1} feedback: no new shared neurons");
2848
+ return { scanned: 0, propagated: 0, agents: [] };
2849
+ }
2850
+ const result = propagateToAgents(brainRoot, deltas);
2851
+ const latestTime = Math.max(...deltas.map((d) => d.modTime.getTime()));
2852
+ writeWatermark(join19(brainRoot, SHARED_DIR), latestTime);
2853
+ console.log(`\u{1F4E1} feedback: ${result.scanned} shared neuron(s) \u2192 ${result.propagated} warning(s) to ${result.agents.join(", ")}`);
2854
+ return result;
2855
+ }
2856
+ function readWatermark(sharedRoot) {
2857
+ const wmPath = join19(sharedRoot, WATERMARK_FILE);
2858
+ if (!existsSync18(wmPath)) return 0;
2859
+ try {
2860
+ const data = JSON.parse(readFileSync10(wmPath, "utf8"));
2861
+ return data.timestamp || 0;
2862
+ } catch {
2863
+ return 0;
2864
+ }
2865
+ }
2866
+ function writeWatermark(sharedRoot, timestamp) {
2867
+ const wmPath = join19(sharedRoot, WATERMARK_FILE);
2868
+ mkdirSync11(sharedRoot, { recursive: true });
2869
+ writeFileSync15(wmPath, JSON.stringify({ timestamp, ts: new Date(timestamp).toISOString() }), "utf8");
2870
+ }
2871
+ function walkForNeurons(dir, regionRoot, cb) {
2872
+ let entries;
2873
+ try {
2874
+ entries = readdirSync11(dir, { withFileTypes: true });
2875
+ } catch {
2876
+ return;
2877
+ }
2878
+ const neuronFiles = entries.filter((e) => e.isFile() && /^\d+\.neuron$/.test(e.name));
2879
+ if (neuronFiles.length > 0) {
2880
+ const counter = Math.max(...neuronFiles.map((f) => parseInt(f.name, 10)));
2881
+ cb(dir, counter);
2882
+ return;
2883
+ }
2884
+ for (const entry of entries) {
2885
+ if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
2886
+ if (entry.isDirectory()) {
2887
+ walkForNeurons(join19(dir, entry.name), regionRoot, cb);
2888
+ }
2889
+ }
2890
+ }
2629
2891
  export {
2630
2892
  AGENTS_DIR,
2631
2893
  DECAY_DAYS,
@@ -2640,6 +2902,7 @@ export {
2640
2902
  MAX_DEPTH,
2641
2903
  MIN_CORRECTION_LENGTH,
2642
2904
  OUTCOME_TYPES,
2905
+ PROPAGATION_EPISODE_TYPES,
2643
2906
  PROTECTED_REGIONS_CONTRA,
2644
2907
  REGIONS,
2645
2908
  REGION_ICONS,
@@ -2648,10 +2911,12 @@ export {
2648
2911
  SESSION_STATE_DIR,
2649
2912
  SHARED_DIR,
2650
2913
  SIGNAL_TYPES,
2914
+ SKILLS_DIR,
2651
2915
  SPOTLIGHT_DAYS,
2652
2916
  appendCorrection,
2653
2917
  buildOutcomeSummary,
2654
2918
  captureSessionStart,
2919
+ checkCron,
2655
2920
  checkHooks,
2656
2921
  classifyOutcome,
2657
2922
  clearReports,
@@ -2668,6 +2933,8 @@ export {
2668
2933
  extractCorrections,
2669
2934
  fireNeuron,
2670
2935
  fromCandidatePath,
2936
+ generateFeedbackPlist,
2937
+ generatePrunePlist,
2671
2938
  getCurrentCounter,
2672
2939
  getLastActivity,
2673
2940
  getPendingReports,
@@ -2675,6 +2942,7 @@ export {
2675
2942
  growCandidate,
2676
2943
  growNeuron,
2677
2944
  initBrain,
2945
+ installCron,
2678
2946
  installHooks,
2679
2947
  jaccardSimilarity,
2680
2948
  listCandidates,
@@ -2683,6 +2951,8 @@ export {
2683
2951
  printDiag,
2684
2952
  processInbox,
2685
2953
  promoteCandidates,
2954
+ propagateToAgents,
2955
+ propagateToShared,
2686
2956
  readEpisodes,
2687
2957
  readHookInput,
2688
2958
  resolveAgentBrain,
@@ -2692,14 +2962,18 @@ export {
2692
2962
  runDecay,
2693
2963
  runDedup,
2694
2964
  runEvolve,
2965
+ runFeedback,
2695
2966
  runSubsumption,
2696
2967
  scanBrain,
2968
+ scanSharedBrain,
2969
+ scanSkills,
2697
2970
  signalNeuron,
2698
2971
  startAPI,
2699
2972
  startWatch,
2700
2973
  stem,
2701
2974
  toCandidatePath,
2702
2975
  tokenize,
2976
+ uninstallCron,
2703
2977
  uninstallHooks,
2704
2978
  writeAllTiers
2705
2979
  };