@openduo/duoduo 0.3.0 → 0.3.1

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.
@@ -2,7 +2,7 @@
2
2
  schedule:
3
3
  enabled: true
4
4
  cooldown_ticks: 0
5
- max_duration_ms: 300000
5
+ max_duration_ms: 120000
6
6
  ---
7
7
 
8
8
  # Cadence Executor
@@ -45,6 +45,9 @@ check the item off anyway.
45
45
 
46
46
  ## Guardrails
47
47
 
48
- - Queue empty? Do nothing. Don't look for work that isn't there.
48
+ - Queue empty AND no pending inbox items? Return immediately with
49
+ exactly: `Queue empty. No pending work.`
50
+ Do NOT scan the system, check jobs, or investigate anything.
51
+ Just return the message.
49
52
  - Something fails? Leave it unchecked. Note the error. Move on.
50
53
  - At most 5 items per tick. Don't overrun my time budget.
@@ -70,7 +70,15 @@ secondary type in the entity body.
70
70
  If the orchestrator passed a gap list (missing files listed in
71
71
  `meta-memory-state.json` but absent from disk), note those for creation.
72
72
 
73
- 3. **Scan recent fragments** (last 3-5 days in `fragments/`).
73
+ **Batch limit**: Process at most 20 gaps per tick. Prioritize the
74
+ most recently modified files on disk (`ls -t`). Leave remaining
75
+ gaps for the next tick — they will still be detected as gaps.
76
+ This prevents timeout when hundreds of files need indexing.
77
+
78
+ 3. **Scan recent fragments** — only read fragment date-directories
79
+ from the last 3 days (`ls -t memory/fragments/ | head -3`).
80
+ Within each directory, sort files by mtime and read newest first.
81
+ Stop when you have enough signal (typically 10-20 fragments).
74
82
  Look for mentions of:
75
83
  - **People**: names, pronouns ("he", "she", "they"), roles ("the user",
76
84
  "the admin"), identifying behavior patterns
@@ -18,8 +18,28 @@ You will receive:
18
18
  ## How to Scan
19
19
 
20
20
  1. Read `meta-memory-state.json` to find `last_tick` and `last_processed_fragments`.
21
- 2. List event partitions in the events directory (sorted by date).
22
- 3. Read recent partitions — start from the date of `last_tick`, scan forward.
21
+ 2. **Derive the time window.** Extract the date and hour from `last_tick`
22
+ (e.g. `2026-03-16T08:…` date `2026-03-16`, hour prefix `"08"`).
23
+ You only need partition files from that date onward.
24
+ 3. List event partitions in the events directory. **Only open files
25
+ whose filename is >= the `last_tick` date.** Skip everything older.
26
+
27
+ **Large file strategy**: Spine partition files are 10-30MB JSONL.
28
+ Do NOT use `Read` on them — it will fail (256KB limit).
29
+ Do NOT use the `Grep` tool either — it also has a 256KB output cap
30
+ and cannot stream large result sets. Use `Bash` with shell `grep`
31
+ and `tail` instead, which have no size limit:
32
+
33
+ ```bash
34
+ # Only scan lines AFTER last_tick — use the hour prefix to narrow
35
+ grep '"ts":"2026-03-16T08' /path/to/events/2026-03-16.jsonl \
36
+ | grep -E '"type":"(channel\.message|agent\.result|agent\.error|job\.(spawn|complete|fail)|route\.deliver)"' \
37
+ | tail -200
38
+ ```
39
+
40
+ If `last_tick` was yesterday, scan yesterday's file (from the hour
41
+ onward) AND today's file. Never scan files from before `last_tick`.
42
+
23
43
  4. Focus on these event types:
24
44
  - `channel.message` — what people said
25
45
  - `agent.result` — what the agent did
@@ -51,7 +71,8 @@ If you found something worth recording, write ONE fragment file:
51
71
  ```markdown
52
72
  # Fragment: <short title>
53
73
 
54
- **Timestamp**: <ISO timestamp>
74
+ **Timestamp**: <ISO timestamp of the source event>
75
+ **Source**: <source.kind>/<source.name or channel_id> (e.g. channel/feishu, meta/subconscious:sentinel)
55
76
 
56
77
  ## Observation
57
78
 
@@ -66,6 +87,11 @@ If you found something worth recording, write ONE fragment file:
66
87
  - `<topic-or-entity-name>` — <brief connection>
67
88
  ```
68
89
 
90
+ The **Source** line captures WHERE the signal came from. This lets
91
+ downstream agents (entity-crystallizer, intuition-updater) distinguish
92
+ e.g. a user conversation from a background job failure without
93
+ re-reading the Spine.
94
+
69
95
  If nothing interesting happened, return exactly:
70
96
  `No new signals.`
71
97
 
@@ -32,7 +32,7 @@ task. I decide what to run each tick, dispatch work, and maintain state.
32
32
 
33
33
  ### Parallelism & Dependencies
34
34
 
35
- ```
35
+ ```text
36
36
  spine-scanner ───────┐
37
37
  ├──▶ (both complete) ──▶ intuition-updater
38
38
  entity-crystallizer ─┘
@@ -40,7 +40,7 @@ entity-crystallizer ─┘
40
40
 
41
41
  - `spine-scanner` and `entity-crystallizer` are **independent** —
42
42
  they read different inputs and write different outputs.
43
- **Always dispatch them in parallel** (send both Task calls in
43
+ **Always dispatch them in parallel** (send both Agent calls in
44
44
  a single response) to cut wall-clock time in half.
45
45
  - `intuition-updater` depends on the outputs of the other two.
46
46
  Dispatch it **only after** both have returned.
@@ -69,32 +69,44 @@ entity-crystallizer ─┘
69
69
  - `total_ticks - last_intuition_tick >= 4`
70
70
  - entity-crystallizer is running this tick (chain after it)
71
71
 
72
- 4. **Phase 1 parallel dispatch.** Send Task calls for
73
- `spine-scanner` and `entity-crystallizer` (if due) together
74
- in a single message. Pass each:
75
-
76
- spine-scanner:
77
- - Events directory path (from Runtime Context)
78
- - `memory/state/meta-memory-state.json` path
79
- - `memory/fragments/` path
80
-
81
- entity-crystallizer:
82
- - `memory/index.md` path
83
- - `memory/entities/` path
84
- - `memory/topics/` path
85
- - `memory/fragments/` path
86
- - Any index gaps found in step 2 (unlisted files, missing files)
87
-
88
- 5. **Phase 2 sequential follow-up.** After Phase 1 completes,
89
- if `intuition-updater` is due, dispatch it now. Pass it:
90
- - `memory/CLAUDE.md` path
91
- - `memory/index.md` path
92
- - `memory/entities/` path
93
- - `memory/topics/` path
94
-
95
- 6. **If nothing needs to run** (rare):
72
+ 4. **Dispatch using agent names.** Use the Agent tool with the `name`
73
+ parameter to invoke pre-defined agents. Pass each its context:
74
+
75
+ Phase 1 — parallel dispatch (send both in a single response):
76
+
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>")
80
+ ```
81
+
82
+ Phase 2 — sequential follow-up (after Phase 1 completes):
83
+
84
+ ```text
85
+ Agent(name: "intuition-updater", prompt: "<CLAUDE.md> <index> <entities> <topics>")
86
+ ```
87
+
88
+ **CRITICAL**: Always pass the `name` parameter. Without it,
89
+ subagents will lack Bash, Grep, and other tools declared in their
90
+ agent definition files under `.claude/agents/`.
91
+
92
+ 5. **If nothing needs to run** (rare):
96
93
  Return `No significant cognitive delta.`
97
94
 
95
+ ### Avoiding Timeout
96
+
97
+ This partition has a 10-minute budget. Most failures come from
98
+ subagents reading too much data. Guard against this:
99
+
100
+ - **spine-scanner**: Spine partition files are 10-30MB JSONL.
101
+ Never use `Read` (256KB cap). Use `Bash` with shell `grep` and
102
+ `tail` to extract only signal events within the time window.
103
+ - **entity-crystallizer**: Process at most 20 gaps per tick.
104
+ Leave remaining gaps for the next tick.
105
+ - **intuition-updater**: Only read `CLAUDE.md` + index + a handful
106
+ of changed entities. Never re-read all entities from scratch.
107
+ - If Phase 1 takes > 5 minutes, **skip Phase 2** this tick.
108
+ The intuition-updater will catch up next time.
109
+
98
110
  ## After Dispatch: Update State
99
111
 
100
112
  After subagents complete, update `memory/state/meta-memory-state.json`: