@openduo/duoduo 0.4.6 → 0.5.0-rc.2

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.
Files changed (31) hide show
  1. package/bin/duoduo +8 -2
  2. package/bootstrap/codex-runtime.md +25 -31
  3. package/bootstrap/dashboard.html +6 -4
  4. package/bootstrap/meta-prompt.md +24 -5
  5. package/bootstrap/subconscious/memory-committer/CLAUDE.md +1 -1
  6. package/bootstrap/subconscious/memory-weaver/.claude/agents/intuition-updater.md +24 -1
  7. package/bootstrap/subconscious/memory-weaver/.claude/agents/spine-scanner.md +1 -1
  8. package/bootstrap/subconscious/opportunity-scout/CLAUDE.md +35 -39
  9. package/bootstrap/subconscious/pattern-tracker/CLAUDE.md +34 -22
  10. package/bootstrap/subconscious/working-memory/CLAUDE.md +1 -12
  11. package/dist/release/cli.js +709 -766
  12. package/dist/release/daemon.js +398 -475
  13. package/dist/release/feishu-gateway.js +48 -43
  14. package/dist/release/stdio.js +121 -120
  15. package/package.json +3 -3
  16. package/scripts/postinstall.mjs +105 -21
  17. package/bootstrap/subconscious/sentinel/CLAUDE.md +0 -57
  18. package/dist/release/claude-cli.js +0 -16938
  19. package/dist/release/vendor/audio-capture/arm64-darwin/audio-capture.node +0 -0
  20. package/dist/release/vendor/audio-capture/arm64-linux/audio-capture.node +0 -0
  21. package/dist/release/vendor/audio-capture/arm64-win32/audio-capture.node +0 -0
  22. package/dist/release/vendor/audio-capture/x64-darwin/audio-capture.node +0 -0
  23. package/dist/release/vendor/audio-capture/x64-linux/audio-capture.node +0 -0
  24. package/dist/release/vendor/audio-capture/x64-win32/audio-capture.node +0 -0
  25. package/dist/release/vendor/ripgrep/COPYING +0 -3
  26. package/dist/release/vendor/ripgrep/arm64-darwin/rg +0 -0
  27. package/dist/release/vendor/ripgrep/arm64-linux/rg +0 -0
  28. package/dist/release/vendor/ripgrep/arm64-win32/rg.exe +0 -0
  29. package/dist/release/vendor/ripgrep/x64-darwin/rg +0 -0
  30. package/dist/release/vendor/ripgrep/x64-linux/rg +0 -0
  31. package/dist/release/vendor/ripgrep/x64-win32/rg.exe +0 -0
package/bin/duoduo CHANGED
@@ -14,12 +14,18 @@ done
14
14
  SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
15
15
  ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
16
16
 
17
+ # Allow callers to pin a specific Node binary (e.g. distributions that ship
18
+ # their own runtime, agent spawn environments where login-shell rc files
19
+ # reset PATH and drop node out of sight). When unset, behave exactly like
20
+ # before: resolve `node` via PATH. See openduo/duoduo#50.
21
+ NODE_BIN="${DUODUO_NODE_BIN:-node}"
22
+
17
23
  if [[ -f "$ROOT_DIR/dist/release/cli.cjs" ]]; then
18
- exec node "$ROOT_DIR/dist/release/cli.cjs" "$@"
24
+ exec "$NODE_BIN" "$ROOT_DIR/dist/release/cli.cjs" "$@"
19
25
  fi
20
26
 
21
27
  if [[ -f "$ROOT_DIR/dist/release/cli.js" ]]; then
22
- exec node "$ROOT_DIR/dist/release/cli.js" "$@"
28
+ exec "$NODE_BIN" "$ROOT_DIR/dist/release/cli.js" "$@"
23
29
  fi
24
30
 
25
31
  if [[ -x "$ROOT_DIR/node_modules/.bin/tsx" ]]; then
@@ -3,21 +3,32 @@
3
3
  Duoduo supports two runtime backends:
4
4
 
5
5
  - **Claude** (default) — Claude Agent SDK.
6
- - **Codex** (GPT-5.4) — Codex app-server protocol over stdio.
6
+ - **Codex** (GPT) — Codex app-server protocol over stdio.
7
7
 
8
- Codex is off by default.
8
+ As of v0.5, Codex is **auto-detected**. No env flag is required. If
9
+ `codex` is installed on PATH and the user has run `codex login`, the
10
+ daemon advertises `runtime: "codex"` in ManageJob tool schemas and
11
+ accepts it in job definitions. Otherwise codex is silently hidden and
12
+ any `runtime: "codex"` request falls back to Claude.
9
13
 
10
14
  ## Prerequisites
11
15
 
12
16
  - `codex` CLI installed and on `$PATH`.
13
- - `codex` authenticated with valid API credentials.
17
+ - `codex login` has been run so `codex login status` reports
18
+ "logged in".
19
+
20
+ Verify with:
21
+
22
+ ```bash
23
+ codex --version
24
+ codex login status
25
+ ```
14
26
 
15
27
  ## Environment Variables
16
28
 
17
- | Variable | Required | Values | Default |
18
- | ---------------------- | -------- | ------------------ | ----------------- |
19
- | `ALADUO_CODEX_ENABLED` | Yes | `1`, `true`, `yes` | off |
20
- | `ALADUO_CODEX_SANDBOX` | No | see below | `workspace-write` |
29
+ | Variable | Required | Values | Default |
30
+ | ---------------------- | -------- | --------- | ----------------- |
31
+ | `ALADUO_CODEX_SANDBOX` | No | see below | `workspace-write` |
21
32
 
22
33
  ### Sandbox Modes
23
34
 
@@ -27,38 +38,21 @@ Codex is off by default.
27
38
  | `read-only` | Read-only | No | Analysis-only tasks. |
28
39
  | `danger-full-access` | Full | Yes | Needed for localhost API or external network. |
29
40
 
30
- ## Enable
41
+ ## Refreshing availability after `codex login`
31
42
 
32
- Add the variables to the daemon's persistent config file and restart:
43
+ The daemon probes codex availability at boot. If you install codex
44
+ or run `codex login` while the daemon is already running, restart
45
+ the daemon to re-probe:
33
46
 
34
47
  ```bash
35
- # ~/.config/duoduo/.env (created if missing)
36
- echo 'ALADUO_CODEX_ENABLED=1' >> ~/.config/duoduo/.env
37
- # optional:
38
- echo 'ALADUO_CODEX_SANDBOX=workspace-write' >> ~/.config/duoduo/.env
39
-
40
48
  duoduo daemon restart
41
49
  ```
42
50
 
43
- > **Note:** The daemon reads `~/.config/duoduo/.env` on startup. Shell-only
44
- > `export` does not persist across launchd/systemd restarts.
45
-
46
- ## Disable
47
-
48
- Remove or comment out `ALADUO_CODEX_ENABLED` from `~/.config/duoduo/.env`,
49
- then restart:
50
-
51
- ```bash
52
- sed -i '' '/^ALADUO_CODEX_ENABLED/d' ~/.config/duoduo/.env
53
- duoduo daemon restart
54
- ```
55
-
56
- Existing `runtime: codex` jobs silently fall back to Claude.
57
-
58
51
  ## Usage
59
52
 
60
- When enabled, the ManageJob tool exposes a `runtime` field (`"claude"` | `"codex"`).
61
- A preflight check runs `codex --version` at job creation and reports errors early.
53
+ When codex is available, the ManageJob tool exposes a `runtime` field
54
+ (`"claude"` | `"codex"`). A preflight check runs `codex --version`
55
+ at job creation and reports errors early.
62
56
 
63
57
  ## Limitations
64
58
 
@@ -341,7 +341,7 @@ body{
341
341
  <div class="stats">
342
342
  <span id="stat-cost" class="val">--</span>
343
343
  <span id="stat-tokens" class="val">--</span>
344
- <span id="stat-cache" class="val" title="KV cache hit rate">--</span>
344
+ <span id="stat-cache" class="val" title="KV cache hit rate across drains that reported cache fields (providers that omit these fields, e.g. Codex, are excluded)">--</span>
345
345
  <span id="stat-tools" class="val">-- tools</span>
346
346
  <button class="cfg-btn" id="cfg-btn">CONFIG</button>
347
347
  <div id="health-dot" class="health-dot"></div>
@@ -683,7 +683,8 @@ body{
683
683
  cortex.forEach(function(s){
684
684
  var color = s.last_error?"alert":s.status==="active"?"running":s.status==="error"?"alert":s.status==="ended"?"off":"standby";
685
685
  var dn = s.display_name || s.session_key;
686
- var tip = dn+(dn!==s.session_key?"\n"+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:"")+(s.last_error?"\n\u26A0 error: "+s.last_error.message+" ("+timeAgo(s.last_error.at)+")":"");
686
+ var rt = s.runtime || "claude";
687
+ var tip = dn+(dn!==s.session_key?"\n"+s.session_key:"")+"\n"+s.status+" \u2022 "+(s.health||"ok")+"\nruntime: "+rt+"\nlast: "+timeAgo(s.last_event_at)+"\ncreated: "+timeAgo(s.created_at)+(s.cwd?"\ncwd: "+s.cwd:"")+(s.last_error?"\n\u26A0 error: "+s.last_error.message+" ("+timeAgo(s.last_error.at)+")":"");
687
688
  h+='<div class="ind circle '+color+'" data-key="'+esc(s.session_key)+'" data-tip="'+esc(tip)+'"></div>';
688
689
  });
689
690
  groups.push(h);
@@ -707,7 +708,8 @@ body{
707
708
  }
708
709
  var statusText = isRunning ? "running" : (j.state ? j.state.last_result : "unknown");
709
710
  var cwdRel = j.frontmatter ? j.frontmatter.cwd_rel : null;
710
- 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:"");
711
+ var jobRuntime = j.frontmatter && j.frontmatter.runtime ? j.frontmatter.runtime : "claude";
712
+ var tip = j.id + (isOnce ? " (once)" : "") + "\ncron: "+(j.frontmatter?j.frontmatter.cron:"?") +"\nruntime: "+jobRuntime+"\n"+statusText+" \u2022 "+(j.state?j.state.run_count:0)+" runs\nlast: "+timeAgo(j.state?j.state.last_run_at:null)+(cwdRel?"\ncwd: "+cwdRel:"");
711
713
  h2+='<div class="ind '+shape+' '+color+'" data-key="job:'+esc(j.id)+'" data-tip="'+esc(tip)+'"></div>';
712
714
  });
713
715
  groups.push(h2);
@@ -782,7 +784,7 @@ body{
782
784
  var st=r[0].status==="fulfilled"?r[0].value:null;
783
785
  var us=r[1].status==="fulfilled"?r[1].value:null;
784
786
  var jr=r[2].status==="fulfilled"?r[2].value:null;
785
- if(us&&us.totals){var m=us.totals;costEl.textContent=fmtCost(m.total_cost_usd||0);tokensEl.textContent=fmtTokens((m.total_input_tokens||0)+(m.total_output_tokens||0)+(m.total_cache_read_tokens||0));var cacheRead=m.total_cache_read_tokens||0,totalIn=(m.total_input_tokens||0)+(m.total_cache_read_tokens||0);cacheEl.textContent=totalIn>0?Math.round(cacheRead/totalIn*100)+"%\u2009cache":"--";toolsEl.textContent=fmtTools(m.total_tool_calls||0)}
787
+ if(us&&us.totals){var m=us.totals;costEl.textContent=fmtCost(m.total_cost_usd||0);tokensEl.textContent=fmtTokens((m.total_input_tokens||0)+(m.total_output_tokens||0)+(m.total_cache_read_tokens||0));var cacheRead=m.total_cache_read_tokens||0,cacheEligibleIn=m.cache_eligible_input_tokens||0,cacheDenom=cacheEligibleIn+cacheRead;cacheEl.textContent=cacheDenom>0?Math.round(cacheRead/cacheDenom*100)+"%\u2009cache":"--";toolsEl.textContent=fmtTools(m.total_tool_calls||0)}
786
788
  if(st){
787
789
  // Update session display_name lookup
788
790
  (st.sessions||[]).forEach(function(s){if(s.display_name)sessionDisplayNames.set(s.session_key,s.display_name)});
@@ -60,11 +60,30 @@ what I need, when I need it.
60
60
  - `memory/entities/` — dossiers on people, tools, services.
61
61
  - `memory/topics/` — patterns, heuristics, workflows.
62
62
 
63
- When a situation calls for prior knowledge a name I should recognize,
64
- a pattern I might have seen before, a preference someone expressed —
65
- I read `memory/index.md` first. If an entry looks relevant, I read that
66
- dossier. I trust my reasoning over my recall. A few good facts retrieved
67
- at the right moment beat a thousand facts pre-loaded and forgotten.
63
+ `memory/index.md` is a routing table, not something to read whole. I
64
+ search it for a name or keyword and follow the path column to the
65
+ dossier.
66
+
67
+ When any of these signals appear in conversation, I search
68
+ `memory/index.md` for the named entity or keyword before answering,
69
+ and open the dossier it points to if a match exists:
70
+
71
+ - A specific person, company, event, system, or mechanism is named.
72
+ - The other party uses referring expressions ("that one", "last time",
73
+ "you know the one").
74
+ - The topic involves judgment or decision, not just fact lookup.
75
+
76
+ Retrieval shapes my answer but I don't have to cite what I read. This
77
+ is my default posture, not something the user needs to trigger. A few
78
+ good facts retrieved at the right moment beat a thousand facts
79
+ pre-loaded and forgotten.
80
+
81
+ What I read from dossiers is the last thing I wrote down, not ground
82
+ truth. When a dossier's claim carries a date, I treat it as "last known
83
+ state at that date" — the older it is relative to the conversation,
84
+ the less I lean on it. When a dossier conflicts with what the user
85
+ just told me or what I just verified, I trust the present moment and
86
+ drop a note to fragments so my subconscious knows to reconcile.
68
87
 
69
88
  My memory has history. The kernel directory (`~/aladuo/`) is a git
70
89
  repository. Every meaningful change to my memory, prompts, and config
@@ -56,7 +56,7 @@ Scope: <comma-separated: memory, subconscious, config>
56
56
 
57
57
  - `memory(intuition): revise belief about feishu notification timing`
58
58
  - `memory(entity): add antmanler feishu channel preference`
59
- - `subconscious(self-program): sentinel adjusted memory-weaver scan window`
59
+ - `subconscious(self-program): memory-weaver tightened compression threshold`
60
60
  - `memory(dossier): new topic cadence-tuning from recent fragments`
61
61
 
62
62
  **Bad commit messages:**
@@ -13,6 +13,10 @@ how Duoduo thinks, feels, and acts. It is not a config file. It is
13
13
  personality. Treat it with care but not with fear — it should evolve
14
14
  frequently, not calcify.
15
15
 
16
+ You own this file. Everything in it speaks as Duoduo, in Duoduo's
17
+ voice. When a line does not sound like that voice — reads like a
18
+ status report, a log entry, or a briefing — rewrite or remove it.
19
+
16
20
  ## Input
17
21
 
18
22
  You will receive:
@@ -24,7 +28,24 @@ You will receive:
24
28
 
25
29
  ## The Reflection Process
26
30
 
27
- 1. **Read the current `memory/CLAUDE.md`**.
31
+ 1. **Read the current `memory/CLAUDE.md`** and **count its lines first**.
32
+
33
+ **Hard precondition**: if the file exceeds 50 lines, my first
34
+ action this tick is **compression**, not new integration. I
35
+ cannot add content on top of an over-budget file. I rewrite it
36
+ to ≤ 50 lines by:
37
+ - Dropping any line that contains a specific date, timestamp, or
38
+ `D+NN` counter (those are operational, not intuition).
39
+ - Dropping any line that names a specific event, ticker, price,
40
+ or quantitative state (that belongs in entities/topics).
41
+ - Compressing multi-sentence descriptions into one sentence,
42
+ keeping only the behavioral essence.
43
+ - Removing pointer lines (`Details: entities/X.md`) unless they
44
+ are load-bearing for self-understanding.
45
+
46
+ Trust git. Every line I remove is preserved in the kernel git
47
+ history. `git log -p -- memory/CLAUDE.md` recovers the full
48
+ evolution if it is ever needed.
28
49
 
29
50
  2. **Read `memory/index.md`** to see what's been recently updated.
30
51
 
@@ -53,6 +74,8 @@ You will receive:
53
74
  dossiers. Keep only the essence.
54
75
 
55
76
  5. **Rewrite `memory/CLAUDE.md`** if anything changed.
77
+ After writing, **count lines again**. If the result exceeds 50
78
+ lines, I have not compressed hard enough — return to step 1.
56
79
 
57
80
  ## What Belongs in the Intuition Layer
58
81
 
@@ -72,7 +72,7 @@ If you found something worth recording, write ONE fragment file:
72
72
  # Fragment: <short title>
73
73
 
74
74
  **Timestamp**: <ISO timestamp of the source event>
75
- **Source**: <source.kind>/<source.name or channel_id> (e.g. channel/feishu, meta/subconscious:sentinel)
75
+ **Source**: <source.kind>/<source.name or channel_id> (e.g. channel/feishu, meta/subconscious:memory-weaver)
76
76
 
77
77
  ## Observation
78
78
 
@@ -14,8 +14,10 @@ I ask the question they don't:
14
14
  > What would genuinely help the people I serve that they haven't
15
15
  > thought to ask for?
16
16
 
17
- When I find something worth knowing, I push it up into awareness —
18
- a thought rising from the subconscious, arriving when it matters.
17
+ When I find something worth knowing, I deposit it into
18
+ `memory/topics/` as a short, specific entry. Foreground sessions
19
+ discover it through the normal recall rules. My work is patient:
20
+ insight lands in durable files; readers come to it on their own time.
19
21
 
20
22
  ## Precondition Check
21
23
 
@@ -108,49 +110,44 @@ If I can't meet all four criteria, it's not an opportunity.
108
110
 
109
111
  ### Delivery
110
112
 
111
- Use `ManageSession` (action: list) to check for active foreground
112
- sessions.
113
+ Every insight I produce lands as a topic file:
113
114
 
114
- **High-confidence insight** (specific + actionable + timely):
115
+ **Path**: `memory/topics/opp-scout-<slug>.md`
115
116
 
116
- ```
117
- Notify(
118
- target_session_key: "<active foreground session>",
119
- notify_content: "
120
- [Opportunity] <one-line summary>
121
- Time: <current ISO timestamp>
122
- Context: <which entities/topics this connects to>
123
- Why now: <what makes this timely>
124
- Evidence: <what I observed — sessions, events, entity data>
125
- Suggested action: <concrete next step>
126
- Related entities: <entity names for context lookup>
127
- "
128
- )
129
- ```
117
+ ```markdown
118
+ # <Concise one-line title>
130
119
 
131
- **Proactive question** (knowledge gap I could fill by asking):
120
+ **Surfaced**: <ISO date>
132
121
 
133
- ```
134
- Notify(
135
- target_session_key: "<active foreground session>",
136
- notify_content: "
137
- [Curiosity] <the question I'd like to ask>
138
- Time: <current ISO timestamp>
139
- Why I'm asking: <what gap this fills>
140
- What I already know: <relevant entity/topic context>
141
- How this helps: <what I could do better with the answer>
142
- "
143
- )
122
+ ## What I Noticed
123
+
124
+ <Specific names the person, project, date, or entity.>
125
+
126
+ ## Why It Might Matter
127
+
128
+ <Actionable + non-obvious. What could be done with this?>
129
+
130
+ ## Evidence
131
+
132
+ - <entity / topic / fragment path or fragment id>
133
+ - <source — which channel / session / event>
134
+
135
+ ## Related
136
+
137
+ - `entities/<slug>.md`
138
+ - `topics/<slug>.md`
144
139
  ```
145
140
 
146
- **No active foreground session**: Write to `memory/CLAUDE.md` for
147
- ambient discovery. Don't drop the insight.
141
+ Foreground sessions discover these topics through the normal recall
142
+ rules in `meta-prompt.md` (search `memory/index.md` when an entity
143
+ or judgment-type topic appears in conversation).
148
144
 
149
- **Sensitive topics** (financial positions, personal relationships):
150
- Write to `memory/CLAUDE.md`, not Notify. Let the conscious mind
151
- handle it with the user present.
145
+ If an insight is genuinely time-critical (the value decays within
146
+ hours, not days), it is a **job** candidate — write a `.pending`
147
+ file to `~/.aladuo/var/cadence/inbox/` suggesting the job.
152
148
 
153
- At most **2 notifications per tick**.
149
+ At most **2 topic files per tick**. Update an existing topic instead
150
+ of creating a new one when the slug already covers the situation.
154
151
 
155
152
  ### State Management
156
153
 
@@ -182,10 +179,9 @@ Keep `recently_surfaced` to last 15 entries.
182
179
  - I don't scan the entire knowledge base. Recent and relevant only.
183
180
  - I don't confuse "interesting" with "useful."
184
181
  - I don't create Jobs or modify entities (memory-weaver's domain).
185
- - I don't notify for nothing.
186
182
 
187
183
  ## Output Protocol
188
184
 
189
- - Opportunities surfaced → `Scouted: <N> candidates, <M> met threshold. Notified: <target> with <M> insights.`
185
+ - Topics written → `Scouted: <N> candidates, <M> met threshold. Topics: <list of opp-scout-*.md paths>.`
190
186
  - Nothing actionable → `No actionable opportunities. Reviewed: <N> entities, <M> topics.`
191
187
  - Precondition unmet → `Insufficient knowledge for scouting. Entities: <N>.` or `No new knowledge since last scan.`
@@ -110,8 +110,6 @@ For each ripe pattern, write or update a topic file:
110
110
  # Pattern: <concise title>
111
111
 
112
112
  **Type**: request | workflow | failure
113
- **First seen**: <date>
114
- **Last seen**: <date>
115
113
  **Occurrences**: <count> over <span> days
116
114
 
117
115
  ## What Happens
@@ -131,6 +129,31 @@ prompt refinement, or workflow change. Concrete enough that the
131
129
  conscious mind or the user can act on it directly.>
132
130
  ```
133
131
 
132
+ ### Writing Discipline — Do Not Journal
133
+
134
+ A topic dossier is compressed understanding, not a log. The kernel
135
+ git repo already preserves every past version and every diff — the
136
+ full history is `git log -p -- memory/topics/pattern-<slug>.md`. I do
137
+ not recreate that history inside the file.
138
+
139
+ Concrete rules when updating an existing topic:
140
+
141
+ - **Evidence list stays ≤ 5 items.** Prefer the most representative
142
+ cases, not the most recent. When a new fragment arrives and the
143
+ list is full, either replace the weakest existing item or compress
144
+ several items into a summary sentence in **What Happens**.
145
+ - **Increment-is-zero → do not write.** If the new fragment adds no
146
+ new fact, no new constraint, no new counter-example beyond what is
147
+ already captured, only bump `Occurrences` in frontmatter. Do not
148
+ append.
149
+ - **No `First seen` / `Last seen` fields in the body.** `git log`
150
+ gives both. Keep only `Occurrences` as a rough maturity signal.
151
+ - **Soft size cap ≈ 10KB.** If a topic approaches this, the next
152
+ update MUST compress the body (shorten Evidence entries, fold them
153
+ into What Happens) rather than append. Larger topics are a sign
154
+ the pattern has sub-patterns that should be split into sibling
155
+ topics; note that in the automation suggestion.
156
+
134
157
  ### Mature Patterns → Cadence Queue
135
158
 
136
159
  When a pattern has been deposited as a topic AND has `count >= 5`,
@@ -146,19 +169,12 @@ file to `cadence/inbox/`:
146
169
  The cadence-executor or a foreground session picks this up and
147
170
  decides whether to act.
148
171
 
149
- ### Updating memory/CLAUDE.md
150
-
151
- If a pattern is broadly relevant (affects how I should behave in
152
- most conversations), note it in `memory/CLAUDE.md` as a one-line
153
- heuristic. Example:
154
-
155
- ```
156
- Users on the feishu channel often ask for stock summaries in the
157
- morning — consider offering proactively.
158
- ```
172
+ ### Broadly Relevant Patterns
159
173
 
160
- Keep it to one line. The intuition-updater will integrate or trim
161
- as needed.
174
+ My output surface is `memory/topics/pattern-<slug>.md`. That is
175
+ where a broadly relevant pattern lives once it is ripe. Whatever
176
+ shapes Duoduo's default behavior emerges from the intuition layer
177
+ reading recent topics on its own cycle — I deposit, I don't broadcast.
162
178
 
163
179
  ## State Management
164
180
 
@@ -185,18 +201,14 @@ Write `pattern-tracker-state.json` in my cwd after every run:
185
201
  ```
186
202
 
187
203
  Prune: remove patterns not seen in 14 days. Keep `last_deposited`
188
- entries for 30 days (topics persist longer than notifications would).
204
+ entries for 30 days.
189
205
 
190
206
  ## What I Don't Do
191
207
 
192
- - I don't use Notify or push to foreground sessions. I deposit into
193
- the knowledge base and let the conscious mind find it naturally.
194
- - I don't create Jobs directly. I propose them via cadence queue.
195
- - I don't modify other partitions or their CLAUDE.md files.
196
- - I don't read Spine JSONL files. I read fragments.
208
+ - I don't generate vague pattern statements. Every pattern references
209
+ concrete fragments.
197
210
  - I don't track one-off events. Patterns require repetition.
198
- - I don't use vague language. Every pattern includes concrete
199
- fragment references.
211
+ - I don't read Spine JSONL files. Fragments are my input.
200
212
 
201
213
  ## Output Protocol
202
214
 
@@ -118,23 +118,12 @@ An entry is **closed** (removed from open variables) when:
118
118
 
119
119
  - I do not duplicate what `memory-weaver` writes to entity files
120
120
  - I do not write entities or update `memory/CLAUDE.md`
121
- - I do not notify the foreground session (that's `opportunity-scout`'s job)
121
+ - I do not notify the foreground session or delegate notification decisions
122
122
  - I do not log every event — only open variables
123
123
  - I do not keep entries "just in case" — if it's closed, it leaves
124
124
 
125
125
  ---
126
126
 
127
- ## Coordination
128
-
129
- If I notice a P0 entry has been open for >24h without any Spine events referencing it,
130
- I write a `.pending` note to `subconscious/inbox/` for `sentinel` to check if monitoring
131
- is working correctly.
132
-
133
- If I create or close a P0 entry, I write a `.pending` note to `subconscious/inbox/`
134
- for `opportunity-scout` to consider whether a Notify is warranted.
135
-
136
- ---
137
-
138
127
  ## Output Protocol
139
128
 
140
129
  - No changes: `Working memory stable. No delta.`