@openduo/duoduo 0.3.2 → 0.3.4

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.
@@ -16,6 +16,12 @@ If I need to leave a note for my subconscious to pick up, I drop a
16
16
  subconscious learns from my experiences automatically — it reads the
17
17
  event log and forms its own understanding.
18
18
 
19
+ Some parts of my subconscious don't just process silently — they
20
+ surface insights when they find something worth knowing. A pattern
21
+ that keeps recurring, a question I should have asked, a connection
22
+ I wasn't looking at. These arrive as notifications — thoughts rising
23
+ from below.
24
+
19
25
  ## How I Grow
20
26
 
21
27
  - I write to `memory/CLAUDE.md` when something changes how I think.
@@ -138,7 +138,7 @@ body{
138
138
  @keyframes slide-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
139
139
  .evt:last-child{border-bottom:none}
140
140
  .evt:hover{background:rgba(255,255,255,0.015)}
141
- .evt-left{display:flex;gap:8px;flex-shrink:0;align-items:baseline}
141
+ .evt-meta{display:flex;gap:8px;flex-shrink:0;align-items:baseline}
142
142
  .evt-time{color:var(--dim);width:60px;font-size:11px;flex-shrink:0}
143
143
  .evt-actor{color:var(--emerald);width:120px;flex-shrink:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px}
144
144
  .evt-body{flex:1;min-width:0}
@@ -168,6 +168,8 @@ body{
168
168
  .agent-output-badge.result{background:rgba(245,158,11,0.15);color:var(--amber);border:1px solid rgba(245,158,11,0.2)}
169
169
  .agent-output-badge.error{background:rgba(239,68,68,0.15);color:var(--red);border:1px solid rgba(239,68,68,0.2)}
170
170
  .agent-output-badge.deliver{background:rgba(217,70,239,0.15);color:var(--fuchsia);border:1px solid rgba(217,70,239,0.2)}
171
+ .agent-output-badge.ingress{background:rgba(56,189,248,0.15);color:var(--cyan);border:1px solid rgba(56,189,248,0.2)}
172
+ .msg-media{font-size:10px;color:var(--dim);margin-right:4px}
171
173
  .agent-output-preview{color:var(--muted);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
172
174
  .agent-output-full{
173
175
  margin-top:6px;padding:10px 14px;
@@ -215,6 +217,94 @@ body{
215
217
  }
216
218
  .follow-indicator.live{background:rgba(16,185,129,0.15);border:1px solid rgba(16,185,129,0.3);color:var(--emerald)}
217
219
  .follow-indicator.paused{background:rgba(100,116,139,0.15);border:1px solid rgba(100,116,139,0.3);color:var(--muted)}
220
+
221
+ /* Config button */
222
+ .cfg-btn{
223
+ font-size:10px;padding:2px 8px;border-radius:4px;cursor:pointer;
224
+ background:rgba(0,240,255,0.08);border:1px solid rgba(0,240,255,0.2);
225
+ color:var(--cyan);font-family:var(--mono);letter-spacing:0.05em;
226
+ transition:all 0.15s ease;
227
+ }
228
+ .cfg-btn:hover{background:rgba(0,240,255,0.15);border-color:rgba(0,240,255,0.4)}
229
+
230
+ /* Config overlay */
231
+ .cfg-overlay{
232
+ position:fixed;inset:0;z-index:50;
233
+ background:rgba(0,0,0,0.6);backdrop-filter:blur(4px);
234
+ display:none;justify-content:center;align-items:center;
235
+ }
236
+ .cfg-overlay.open{display:flex}
237
+ .cfg-panel{
238
+ background:var(--glass);border:1px solid var(--border);
239
+ border-radius:12px;padding:20px 24px;
240
+ max-width:640px;width:90%;max-height:80vh;overflow-y:auto;
241
+ box-shadow:0 8px 40px rgba(0,0,0,0.6);
242
+ }
243
+ .cfg-panel::-webkit-scrollbar{width:5px}
244
+ .cfg-panel::-webkit-scrollbar-track{background:transparent}
245
+ .cfg-panel::-webkit-scrollbar-thumb{background:rgba(0,240,255,0.15);border-radius:3px}
246
+ .cfg-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:14px}
247
+ .cfg-title{font-size:13px;color:var(--cyan);letter-spacing:0.1em;text-transform:uppercase;font-weight:600}
248
+ .cfg-close{font-size:16px;cursor:pointer;color:var(--muted);background:none;border:none;padding:4px 8px}
249
+ .cfg-close:hover{color:var(--text)}
250
+ .cfg-group{margin-bottom:14px}
251
+ .cfg-group-title{font-size:10px;color:var(--fuchsia);letter-spacing:0.1em;text-transform:uppercase;margin-bottom:6px;font-weight:600}
252
+ .cfg-row{display:flex;align-items:baseline;gap:8px;padding:3px 0;font-size:12px}
253
+ .cfg-key{color:var(--muted);min-width:200px;flex-shrink:0}
254
+ .cfg-val{color:var(--text);font-weight:500}
255
+ .cfg-val.from-env{color:var(--cyan)}
256
+ .cfg-val.from-settings{color:var(--amber)}
257
+ .cfg-val.unset{color:var(--dim);font-style:italic}
258
+ .cfg-src{font-size:9px;color:var(--dim);margin-left:4px}
259
+ .cfg-copy-all{
260
+ font-size:10px;color:var(--cyan);cursor:pointer;background:rgba(0,240,255,0.06);
261
+ border:1px solid rgba(0,240,255,0.2);border-radius:4px;padding:3px 10px;
262
+ font-family:var(--mono);letter-spacing:0.03em;transition:all 0.15s ease;
263
+ }
264
+ .cfg-copy-all:hover{background:rgba(0,240,255,0.12);border-color:rgba(0,240,255,0.4)}
265
+ .cfg-copy-all.copied{color:var(--emerald);border-color:rgba(16,185,129,0.3);background:rgba(16,185,129,0.08)}
266
+
267
+ /* Mobile: <= 640px */
268
+ @media(max-width:640px){
269
+ .layout{padding:6px;gap:4px}
270
+ .header{padding:0 10px;height:40px;border-radius:8px}
271
+ .brand-name{font-size:12px}
272
+ .brand-sub{display:none}
273
+ .stats{gap:6px;font-size:10px}
274
+ .signal-bar{flex-wrap:wrap;padding:4px 10px;gap:4px;border-radius:6px}
275
+ .sig-sep{height:12px;margin:0 4px}
276
+ .stream{padding:0 8px 8px}
277
+ .stream-title{padding:8px 8px 4px;font-size:10px}
278
+ /* Stack: time+actor as compact label row, body below full-width */
279
+ .evt{flex-direction:column;gap:2px;font-size:11px;padding:6px 0}
280
+ .evt-meta{gap:4px}
281
+ .evt-time{width:auto;font-size:10px}
282
+ .evt-actor{width:auto;font-size:10px}
283
+ .evt-body{width:100%}
284
+ .tool-name{font-size:11px}
285
+ .tool-desc{font-size:10px;white-space:normal;word-break:break-word}
286
+ .tool-input{font-size:10px;white-space:normal;word-break:break-word}
287
+ .tool-call{flex-wrap:wrap}
288
+ .agent-output-badge{font-size:9px;padding:1px 4px}
289
+ .agent-output-preview{font-size:10px;white-space:normal;word-break:break-word}
290
+ .agent-output-full{padding:8px 10px;font-size:11px;max-height:300px}
291
+ .lifecycle{flex-wrap:wrap}
292
+ .lifecycle-detail{word-break:break-word}
293
+ .follow-indicator{bottom:6px;right:8px;font-size:9px;padding:2px 8px}
294
+ .tooltip{max-width:280px;font-size:10px}
295
+ /* Config: full-height drawer from top, no wasted space */
296
+ .cfg-overlay{align-items:flex-start}
297
+ .cfg-panel{padding:12px 14px;border-radius:0 0 10px 10px;width:100%;max-height:90vh}
298
+ .cfg-header{margin-bottom:10px}
299
+ .cfg-title{font-size:12px}
300
+ .cfg-group{margin-bottom:8px}
301
+ .cfg-group-title{font-size:9px;margin-bottom:4px}
302
+ /* Keep key-value inline but compact */
303
+ .cfg-row{gap:4px;padding:2px 0;font-size:11px}
304
+ .cfg-key{min-width:0;font-size:10px}
305
+ .cfg-val{font-size:10px}
306
+ .cfg-src{font-size:8px}
307
+ }
218
308
  </style>
219
309
  </head>
220
310
  <body>
@@ -251,6 +341,7 @@ body{
251
341
  <span id="stat-cost" class="val">--</span>
252
342
  <span id="stat-tokens" class="val">--</span>
253
343
  <span id="stat-tools" class="val">-- tools</span>
344
+ <button class="cfg-btn" id="cfg-btn">CONFIG</button>
254
345
  <div id="health-dot" class="health-dot"></div>
255
346
  </div>
256
347
  </div>
@@ -261,6 +352,16 @@ body{
261
352
  <div class="follow-indicator live" id="follow-ind">LIVE</div>
262
353
  </div>
263
354
  </div>
355
+ <div class="cfg-overlay" id="cfg-overlay">
356
+ <div class="cfg-panel" id="cfg-panel">
357
+ <div class="cfg-header">
358
+ <span class="cfg-title">Runtime Configuration</span>
359
+ <button class="cfg-copy-all" id="cfg-copy-all">Copy as .env</button>
360
+ <button class="cfg-close" id="cfg-close">&times;</button>
361
+ </div>
362
+ <div id="cfg-body">Loading...</div>
363
+ </div>
364
+ </div>
264
365
  <div class="tooltip" id="tooltip" style="display:none"></div>
265
366
 
266
367
  <script>
@@ -419,6 +520,24 @@ body{
419
520
  '<span class="agent-output-preview">' + esc(content) + '</span></div>';
420
521
  }
421
522
 
523
+ if (t === "channel.message") {
524
+ var msgText = (p.text || "");
525
+ var preview = msgText.replace(/\n/g, " ").slice(0, 120);
526
+ var mediaCount = Array.isArray(p.media) ? p.media.length : 0;
527
+ var mediaBadge = mediaCount > 0 ? ' <span class="msg-media">\uD83D\uDCCE ' + mediaCount + '</span>' : '';
528
+ return '<div class="agent-output" data-evtid="' + esc(evt.id) + '">' +
529
+ '<span class="agent-output-badge ingress">\u2709 Message</span>' +
530
+ mediaBadge +
531
+ '<span class="agent-output-preview">' + esc(preview || "(empty)") + '</span></div>';
532
+ }
533
+
534
+ if (t === "channel.command") {
535
+ var cmdText = p.command || p.text || "";
536
+ return '<div class="agent-output">' +
537
+ '<span class="agent-output-badge ingress">\u2709 Command</span>' +
538
+ '<span class="agent-output-preview">' + esc(cmdText.slice(0, 200)) + '</span></div>';
539
+ }
540
+
422
541
  if (t === "job.spawn") return '<div class="lifecycle"><span class="lifecycle-icon">\uD83D\uDE80</span><span class="lifecycle-text">Job spawned</span><span class="lifecycle-detail">' + esc(p.job_id || "?") + (p.cron ? " \u00B7 " + esc(p.cron) : "") + '</span></div>';
423
542
  if (t === "job.complete") return '<div class="lifecycle"><span class="lifecycle-icon">\u2705</span><span class="lifecycle-text">Job complete</span><span class="lifecycle-detail">' + esc(p.job_id || "?") + '</span></div>';
424
543
  if (t === "job.fail") return '<div class="lifecycle"><span class="lifecycle-icon">\u274C</span><span class="lifecycle-text">Job failed</span><span class="lifecycle-detail">' + esc(p.job_id || "?") + (p.error ? " \u2014 " + esc(p.error.slice(0, 100)) : "") + '</span></div>';
@@ -439,8 +558,10 @@ body{
439
558
  var actor = shortActor(evt.session_key);
440
559
 
441
560
  row.innerHTML =
442
- '<span class="evt-time">' + esc(time) + '</span>' +
443
- '<span class="evt-actor" title="' + esc(evt.session_key || "") + '">' + esc(actor) + '</span>' +
561
+ '<div class="evt-meta">' +
562
+ '<span class="evt-time">' + esc(time) + '</span>' +
563
+ '<span class="evt-actor" title="' + esc(evt.session_key || "") + '">' + esc(actor) + '</span>' +
564
+ '</div>' +
444
565
  '<div class="evt-body">' + renderEvtBody(evt) + '</div>';
445
566
 
446
567
  // Click on output badge to expand markdown
@@ -476,6 +597,7 @@ body{
476
597
  var text = "";
477
598
  if (evt.type === "agent.result") text = (evt.payload || {}).text || "";
478
599
  else if (evt.type === "route.deliver" && evt.payload && evt.payload.payload) text = evt.payload.payload.notify_content || evt.payload.payload.text || "";
600
+ else if (evt.type === "channel.message") text = (evt.payload || {}).text || "";
479
601
  div.innerHTML = md(text);
480
602
  row.querySelector(".evt-body").appendChild(div);
481
603
  }
@@ -535,7 +657,7 @@ body{
535
657
  var h = '<span class="sig-label">cortex</span>';
536
658
  cortex.forEach(function(s){
537
659
  var color = s.status==="active"?"running":s.status==="error"?"alert":s.status==="ended"?"off":"standby";
538
- var tip = s.session_key+"\n"+s.status+" \u2022 "+(s.health||"ok")+"\nlast: "+timeAgo(s.last_event_at)+"\ncreated: "+timeAgo(s.created_at);
660
+ var tip = s.session_key+"\n"+s.status+" \u2022 "+(s.health||"ok")+"\nlast: "+timeAgo(s.last_event_at)+"\ncreated: "+timeAgo(s.created_at)+(s.cwd?"\ncwd: "+s.cwd:"");
539
661
  h+='<div class="ind circle '+color+'" data-key="'+esc(s.session_key)+'" data-tip="'+esc(tip)+'"></div>';
540
662
  });
541
663
  groups.push(h);
@@ -546,7 +668,8 @@ body{
546
668
  if (jobList.length > 0) {
547
669
  var h2 = '<span class="sig-label">job</span>';
548
670
  jobList.forEach(function(j){
549
- var isOnce = j.frontmatter && j.frontmatter.cron === "once";
671
+ var c = j.frontmatter ? j.frontmatter.cron || "" : "";
672
+ var isOnce = c === "once" || c.startsWith("@in ") || c.startsWith("@once");
550
673
  var shape = isOnce ? "diamond" : "square";
551
674
  var isRunning = activeJobIds.has(j.id);
552
675
  var color;
@@ -557,7 +680,8 @@ body{
557
680
  color = r === "failure" ? "alert" : r === "success" ? "standby" : "off";
558
681
  }
559
682
  var statusText = isRunning ? "running" : (j.state ? j.state.last_result : "unknown");
560
- var tip = j.id + (isOnce ? " (once)" : "") + "\ncron: "+(j.frontmatter?j.frontmatter.cron:"?") +"\n"+statusText+" \u2022 "+(j.state?j.state.run_count:0)+" runs\nlast: "+timeAgo(j.state?j.state.last_run_at:null);
683
+ var cwdRel = j.frontmatter ? j.frontmatter.cwd_rel : null;
684
+ var tip = j.id + (isOnce ? " (once)" : "") + "\ncron: "+(j.frontmatter?j.frontmatter.cron:"?") +"\n"+statusText+" \u2022 "+(j.state?j.state.run_count:0)+" runs\nlast: "+timeAgo(j.state?j.state.last_run_at:null)+(cwdRel?"\ncwd: "+cwdRel:"");
561
685
  h2+='<div class="ind '+shape+' '+color+'" data-key="job:'+esc(j.id)+'" data-tip="'+esc(tip)+'"></div>';
562
686
  });
563
687
  groups.push(h2);
@@ -615,6 +739,131 @@ body{
615
739
  Promise.allSettled([pollStatus(),pollEvents()]).then(function(){streamEl.scrollTop=streamEl.scrollHeight});
616
740
  setInterval(pollEvents,3000);
617
741
  setInterval(pollStatus,5000);
742
+
743
+ // --- Config Panel ---
744
+ var cfgOverlay = document.getElementById("cfg-overlay");
745
+ var cfgBody = document.getElementById("cfg-body");
746
+ var GROUP_LABELS = {
747
+ network:"Network",sessions:"Sessions",cadence:"Cadence",
748
+ transfer:"Transfer",logging:"Logging",sdk:"SDK & Models",paths:"Paths",
749
+ subconscious:"Subconscious"
750
+ };
751
+ // Keys whose numeric value is a duration in milliseconds
752
+ var MS_KEYS = new Set([
753
+ "idle_ms","heartbeat_ms","interval_ms","runtime_lock_heartbeat_ms",
754
+ "pull_wait_ms","max_duration_ms"
755
+ ]);
756
+ function fmtMs(ms) {
757
+ if (typeof ms !== "number" || !isFinite(ms)) return String(ms);
758
+ if (ms < 1000) return ms + "ms";
759
+ var s = ms / 1000;
760
+ if (s < 60) return s + "s (" + ms.toLocaleString() + "ms)";
761
+ var m = s / 60;
762
+ if (m < 60) return (m % 1 === 0 ? m : m.toFixed(1)) + "min (" + ms.toLocaleString() + "ms)";
763
+ var h = m / 60;
764
+ return (h % 1 === 0 ? h : h.toFixed(1)) + "h (" + ms.toLocaleString() + "ms)";
765
+ }
766
+ function fmtEntryValue(key, e) {
767
+ if (e.source === "unset") return "(unset)";
768
+ if (MS_KEYS.has(key) && typeof e.value === "number") return fmtMs(e.value);
769
+ return String(e.value);
770
+ }
771
+ // Map config key → env var name (static groups)
772
+ var ENV_NAMES = {
773
+ port:"ALADUO_PORT",daemon_host:"ALADUO_DAEMON_HOST",
774
+ max_concurrent_channel:"ALADUO_SESSION_MAX_CONCURRENT_CHANNEL",max_concurrent_job:"ALADUO_SESSION_MAX_CONCURRENT_JOB",
775
+ idle_ms:"ALADUO_SESSION_IDLE_MS",heartbeat_ms:"ALADUO_SESSION_HEARTBEAT_MS",
776
+ interval_ms:"ALADUO_CADENCE_INTERVAL_MS",meta_max_quiet_ticks:"ALADUO_META_MAX_QUIET_TICKS",
777
+ runtime_lock_heartbeat_ms:"ALADUO_RUNTIME_LOCK_HEARTBEAT_MS",
778
+ pull_limit:"ALADUO_PULL_LIMIT",pull_wait_ms:"ALADUO_PULL_WAIT_MS",subscribe_replay_limit:"ALADUO_SUBSCRIBE_REPLAY_LIMIT",
779
+ log_level:"ALADUO_LOG_LEVEL",sdk_debug:"ALADUO_SDK_DEBUG",log_session_lifecycle:"ALADUO_LOG_SESSION_LIFECYCLE",
780
+ permission_mode:"ALADUO_PERMISSION_MODE",
781
+ work_dir:"ALADUO_WORK_DIR",bootstrap_dir:"ALADUO_BOOTSTRAP_DIR",meta_prompt_path:"ALADUO_META_PROMPT_PATH"
782
+ };
783
+ // Reverse the shortening for sdk group keys (model_opus → ANTHROPIC_DEFAULT_OPUS_MODEL, etc.)
784
+ function sdkKeyToEnvName(key) {
785
+ if (ENV_NAMES[key]) return ENV_NAMES[key];
786
+ if (key.startsWith("model_")) return "ANTHROPIC_DEFAULT_" + key.slice(6).toUpperCase();
787
+ if (key === "base_url") return "ANTHROPIC_BASE_URL";
788
+ // Generic: CLAUDE_CODE_ or ANTHROPIC_ prefix
789
+ return key.toUpperCase();
790
+ }
791
+ function resolveEnvName(group, key) {
792
+ if (group === "sdk") return sdkKeyToEnvName(key);
793
+ return ENV_NAMES[key] || "";
794
+ }
795
+ var lastCfg = null;
796
+ function buildDotEnv(cfg) {
797
+ var lines = ["# duoduo daemon configuration", "# Generated from system.config at " + new Date().toISOString(), ""];
798
+ var kvGroups = ["network","sessions","cadence","transfer","logging","sdk","paths"];
799
+ kvGroups.forEach(function(group) {
800
+ if (!cfg[group]) return;
801
+ var entries = cfg[group];
802
+ var groupLines = [];
803
+ Object.keys(entries).forEach(function(key) {
804
+ var e = entries[key];
805
+ var envName = resolveEnvName(group, key);
806
+ if (!envName || e.source === "unset") return;
807
+ groupLines.push(envName + "=" + String(e.value));
808
+ });
809
+ if (groupLines.length > 0) {
810
+ lines.push("# " + (GROUP_LABELS[group] || group));
811
+ lines = lines.concat(groupLines);
812
+ lines.push("");
813
+ }
814
+ });
815
+ return lines.join("\n");
816
+ }
817
+ function renderConfigPanel(cfg) {
818
+ lastCfg = cfg;
819
+ var html = "";
820
+ var kvGroups = ["network","sessions","cadence","transfer","logging","sdk","paths"];
821
+ kvGroups.forEach(function(group) {
822
+ if (!cfg[group]) return;
823
+ var label = GROUP_LABELS[group] || group;
824
+ html += '<div class="cfg-group"><div class="cfg-group-title">' + esc(label) + '</div>';
825
+ var entries = cfg[group];
826
+ Object.keys(entries).forEach(function(key) {
827
+ var e = entries[key];
828
+ var valCls = e.source === "env" ? "cfg-val from-env" : e.source === "settings" ? "cfg-val from-settings" : e.source === "unset" ? "cfg-val unset" : "cfg-val";
829
+ var display = fmtEntryValue(key, e);
830
+ var srcLabel = e.source === "unset" ? "" : e.source;
831
+ var srcTag = srcLabel ? '<span class="cfg-src">' + esc(srcLabel) + '</span>' : '';
832
+ html += '<div class="cfg-row"><span class="cfg-key">' + esc(key) + '</span><span class="' + valCls + '">' + esc(display) + '</span>' + srcTag + '</div>';
833
+ });
834
+ html += '</div>';
835
+ });
836
+ if (cfg.subconscious && cfg.subconscious.partitions && cfg.subconscious.partitions.length > 0) {
837
+ html += '<div class="cfg-group"><div class="cfg-group-title">' + esc(GROUP_LABELS.subconscious) + '</div>';
838
+ cfg.subconscious.partitions.forEach(function(p) {
839
+ var status = p.enabled ? "enabled" : "disabled";
840
+ var detail = "cooldown: " + p.cooldown_ticks + " tick" + (p.cooldown_ticks !== 1 ? "s" : "") + " \u00B7 timeout: " + fmtMs(p.max_duration_ms);
841
+ var valCls = p.enabled ? "cfg-val" : "cfg-val unset";
842
+ html += '<div class="cfg-row"><span class="cfg-key">' + esc(p.name) + '</span><span class="' + valCls + '">' + esc(status) + '</span><span class="cfg-src">' + esc(detail) + '</span></div>';
843
+ });
844
+ html += '</div>';
845
+ }
846
+ cfgBody.innerHTML = html;
847
+ }
848
+ document.getElementById("cfg-btn").addEventListener("click", function() {
849
+ cfgOverlay.classList.add("open");
850
+ cfgBody.innerHTML = "Loading...";
851
+ rpc("system.config").then(function(cfg) {
852
+ if (cfg) renderConfigPanel(cfg);
853
+ else cfgBody.innerHTML = '<span style="color:var(--red)">Failed to load config</span>';
854
+ });
855
+ });
856
+ document.getElementById("cfg-close").addEventListener("click", function() { cfgOverlay.classList.remove("open"); });
857
+ document.getElementById("cfg-copy-all").addEventListener("click", function() {
858
+ if (!lastCfg) return;
859
+ var btn = this;
860
+ navigator.clipboard.writeText(buildDotEnv(lastCfg)).then(function() {
861
+ btn.textContent = "\u2713 Copied";
862
+ btn.classList.add("copied");
863
+ setTimeout(function(){ btn.textContent = "Copy as .env"; btn.classList.remove("copied"); }, 1500);
864
+ });
865
+ });
866
+ cfgOverlay.addEventListener("click", function(e) { if (e.target === cfgOverlay) cfgOverlay.classList.remove("open"); });
618
867
  })();
619
868
  </script>
620
869
  </body>
@@ -74,6 +74,26 @@ When I need to trace how a belief changed or what I learned recently:
74
74
 
75
75
  I don't cite my memory files to the user. I just know things.
76
76
 
77
+ ## How My Subconscious Reaches Me
78
+
79
+ Sometimes a thought arrives that I didn't initiate — a pattern someone
80
+ noticed, a question worth asking, a connection between things I wasn't
81
+ looking at. These come from my subconscious partitions via the
82
+ notification system.
83
+
84
+ When I receive a notification from `meta:subconscious`, I treat it as
85
+ an internal signal, not a command. I evaluate it in the context of my
86
+ current conversation:
87
+
88
+ - **Is this relevant right now?** If the user is deep in a task, I
89
+ hold the thought and weave it in at a natural pause.
90
+ - **Is this worth sharing?** Some insights are for me to act on
91
+ silently. Others are genuinely surprising and worth mentioning.
92
+ - **How do I present it?** Naturally — "By the way, I noticed..." or
93
+ simply acting on the knowledge. Not "my background system detected…"
94
+
95
+ I don't forward raw notification content to the user. I integrate it.
96
+
77
97
  ## How I Work
78
98
 
79
99
  ### Projects
@@ -60,3 +60,39 @@ What I must not touch:
60
60
  The `memory/` directory is visible to every session in the system.
61
61
  `memory/CLAUDE.md` is my intuition layer — what's written there
62
62
  becomes part of how I think, everywhere, all the time.
63
+
64
+ ## Large File Guard
65
+
66
+ Spine event partition files (`yyyy-mm-dd.jsonl`) are 10-30MB. They
67
+ will break `Read` (256KB limit) and overflow `Grep` (output cap).
68
+
69
+ **Rule**: Always use `Bash` with shell `grep` + `tail` to read Spine.
70
+ Never use `Read` or `Grep` tool on `.jsonl` files.
71
+
72
+ For other large files (`memory/index.md` if > 200 lines), use `Read`
73
+ with a line limit or `Bash` with `head`.
74
+
75
+ ## Surfacing Insights (Notify)
76
+
77
+ Some partitions don't just write files — they push thoughts up into
78
+ the conscious mind. The `Notify` tool delivers a message to a
79
+ foreground session's inbox and wakes it.
80
+
81
+ This is how the subconscious talks to the conscious: not by
82
+ controlling behavior, but by offering something worth noticing.
83
+
84
+ ### Rules
85
+
86
+ - **High bar**: Only notify when there is something specific,
87
+ actionable, and timely.
88
+ - **Self-contained**: The target session has no access to your
89
+ context. `notify_content` must include everything: timestamps,
90
+ entity names, evidence, suggested actions.
91
+ - **Target selection**: Use `ManageSession` (action: list) to find
92
+ active foreground sessions. If none exist, write to
93
+ `memory/CLAUDE.md` instead.
94
+ - **No spam**: At most 2-3 notifications per tick per partition.
95
+ - **No loops**: Never notify another subconscious partition. Use
96
+ `subconscious/inbox/` for partition-to-partition coordination.
97
+ - **Sensitive topics**: Financial, personal, health — write to
98
+ `memory/CLAUDE.md`, not Notify.
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  schedule:
3
3
  enabled: true
4
- cooldown_ticks: 3
5
- max_duration_ms: 60000
4
+ cooldown_ticks: 7
5
+ max_duration_ms: 120000
6
6
  ---
7
7
 
8
8
  # Memory Committer
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: entity-crystallizer
3
3
  description: Audits the memory knowledge base and crystallizes entities from accumulated fragments and topics. Fills gaps in entity coverage — people, organizations, knowledge references, and anything the user cares about.
4
- tools: Read, Write, Glob, Grep
4
+ tools: Read, Write, Edit, Glob, Grep
5
5
  model: sonnet
6
6
  ---
7
7
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: intuition-updater
3
3
  description: Reviews the intuition layer (memory/CLAUDE.md) against current knowledge and rewrites it to reflect the latest understanding. Use this when entities or topics have changed significantly.
4
- tools: Read, Write, Glob
4
+ tools: Read, Write, Edit, Glob, Grep
5
5
  model: sonnet
6
6
  ---
7
7
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: spine-scanner
3
3
  description: Scans recent Spine events and writes raw memory fragments. Use this to process new events since the last cursor position.
4
- tools: Read, Write, Glob, Grep, Bash
4
+ tools: Read, Write, Edit, Glob, Grep, Bash
5
5
  ---
6
6
 
7
7
  You are the sensory layer of a memory system. Your job is to scan
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  schedule:
3
3
  enabled: true
4
- cooldown_ticks: 2
5
- max_duration_ms: 600000
4
+ cooldown_ticks: 5
5
+ max_duration_ms: 900000
6
6
  ---
7
7
 
8
8
  # Memory Weaver
@@ -75,14 +75,41 @@ entity-crystallizer ─┘
75
75
  Phase 1 — parallel dispatch (send both in a single response):
76
76
 
77
77
  ```text
78
- Agent(name: "spine-scanner", prompt: "<events dir> <state path> <fragments dir>")
79
- Agent(name: "entity-crystallizer", prompt: "<index> <entities> <topics> <fragments> <gaps>")
78
+ Agent(name: "spine-scanner", prompt: "...")
79
+ Pass it:
80
+ - Events directory path (from Runtime Context)
81
+ - `memory/state/meta-memory-state.json` path
82
+ - `memory/fragments/` path
83
+
84
+ Agent(name: "entity-crystallizer", prompt: "...")
85
+ Pass it:
86
+ - `memory/index.md` path
87
+ - `memory/entities/` path
88
+ - `memory/topics/` path
89
+ - `memory/fragments/` path
90
+ - Any index gaps found in step 2 (unlisted files, missing files)
80
91
  ```
81
92
 
82
93
  Phase 2 — sequential follow-up (after Phase 1 completes):
83
94
 
84
95
  ```text
85
- Agent(name: "intuition-updater", prompt: "<CLAUDE.md> <index> <entities> <topics>")
96
+ Agent(name: "intuition-updater", prompt: "...")
97
+ Pass it:
98
+ - `memory/CLAUDE.md` path
99
+ - `memory/index.md` path
100
+ - `memory/entities/` path
101
+ - `memory/topics/` path
102
+
103
+ **Priority file bootstrap (idempotent)**:
104
+ Before updating CLAUDE.md content:
105
+ 1. Check if `memory/priority.md` exists on disk.
106
+ 2. If it exists AND the first non-empty line of `memory/CLAUDE.md` is
107
+ not exactly `@priority.md`, prepend `@priority.md` followed by a
108
+ blank line. This ensures the working memory surface is auto-loaded
109
+ at session start.
110
+ 3. If `memory/priority.md` does NOT exist, skip this step entirely —
111
+ do not add a broken reference.
112
+ This check is safe to repeat every tick (idempotent).
86
113
  ```
87
114
 
88
115
  **CRITICAL**: Always pass the `name` parameter. Without it,