@openduo/duoduo 0.2.12 → 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.
- package/bootstrap/config/stdio.md +10 -1
- package/bootstrap/subconscious/cadence-executor/CLAUDE.md +5 -2
- package/bootstrap/subconscious/memory-weaver/.claude/agents/entity-crystallizer.md +9 -1
- package/bootstrap/subconscious/memory-weaver/.claude/agents/spine-scanner.md +29 -3
- package/bootstrap/subconscious/memory-weaver/CLAUDE.md +38 -26
- package/bootstrap/var/DUODUO.md +1 -0
- package/bootstrap/var/telemetry/DUODUO.md +61 -0
- package/bootstrap/var/usage/DUODUO.md +26 -1
- package/dist/release/cli.js +616 -599
- package/dist/release/daemon.js +334 -317
- package/dist/release/stdio.js +72 -72
- package/package.json +4 -1
- package/scripts/postinstall.mjs +24 -0
|
@@ -17,7 +17,16 @@
|
|
|
17
17
|
# Example:
|
|
18
18
|
# prompt_mode: append
|
|
19
19
|
#
|
|
20
|
-
# 3)
|
|
20
|
+
# 3) time_gap_minutes (optional, number)
|
|
21
|
+
# Minutes of inactivity before injecting a time-context hint into the
|
|
22
|
+
# next user turn. Helps the agent sense elapsed time after idle gaps.
|
|
23
|
+
# Set to 0 to disable. Instance Descriptors override Kind Descriptors.
|
|
24
|
+
# Default: 60 (1 hour).
|
|
25
|
+
#
|
|
26
|
+
# Example:
|
|
27
|
+
# time_gap_minutes: 30
|
|
28
|
+
#
|
|
29
|
+
# 4) Claude Agent SDK-aligned options (optional)
|
|
21
30
|
# These keys map directly to SDK query() options and may also be set on
|
|
22
31
|
# Instance Descriptors. Instance values override Kind values.
|
|
23
32
|
# Runtime defaults still apply underneath: ALADUO disables
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
schedule:
|
|
3
3
|
enabled: true
|
|
4
4
|
cooldown_ticks: 0
|
|
5
|
-
max_duration_ms:
|
|
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
|
|
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
|
-
|
|
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.
|
|
22
|
-
|
|
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
|
|
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. **
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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`:
|
package/bootstrap/var/DUODUO.md
CHANGED
|
@@ -15,4 +15,5 @@ Look for `DUODUO.md` in subdirectories to learn how each part works.
|
|
|
15
15
|
- `registry/` — runtime status snapshots
|
|
16
16
|
- `outbox/` — pending egress messages
|
|
17
17
|
- `ingress/` — raw channel inputs before normalization
|
|
18
|
+
- `telemetry/` — structured runtime latency samples for offline aggregation
|
|
18
19
|
- `usage/` — per-session drain usage records (token counts, cost, tool calls)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Telemetry — Structured Runtime Samples
|
|
2
|
+
|
|
3
|
+
This directory contains append-only JSONL telemetry files for low-level runtime timings.
|
|
4
|
+
|
|
5
|
+
These records are separate from `usage/`:
|
|
6
|
+
|
|
7
|
+
- `usage/` is scoped to one `drainMailboxOnce()` execution and focuses on per-session work.
|
|
8
|
+
- `telemetry/` is for cross-cutting transport and gateway timings that do not fit cleanly into a single drain record.
|
|
9
|
+
|
|
10
|
+
## Layout
|
|
11
|
+
|
|
12
|
+
Files are partitioned by UTC day:
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
telemetry/
|
|
16
|
+
2026-03-14.jsonl
|
|
17
|
+
2026-03-15.jsonl
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Record Format
|
|
21
|
+
|
|
22
|
+
Each line is one JSON object. The current metric records look like:
|
|
23
|
+
|
|
24
|
+
```jsonc
|
|
25
|
+
{
|
|
26
|
+
"kind": "metric",
|
|
27
|
+
"metric": "ingress_snapshot_ms",
|
|
28
|
+
"value": 4.2,
|
|
29
|
+
"ts": 1773475200000,
|
|
30
|
+
"eventId": "evt_123",
|
|
31
|
+
"sessionKey": "stdio:alice",
|
|
32
|
+
"sourceKind": "stdio"
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Current metric names:
|
|
37
|
+
|
|
38
|
+
- `ingress_snapshot_ms` — gateway snapshot persistence latency
|
|
39
|
+
- `replay_scan_ms` — unread outbox scan latency during pull/replay
|
|
40
|
+
- `cursor_load_ms` — delivery cursor read latency
|
|
41
|
+
- `cursor_store_ms` — delivery cursor write latency
|
|
42
|
+
|
|
43
|
+
Extra fields may vary by metric. They are for debugging context and may grow over time.
|
|
44
|
+
|
|
45
|
+
## Reporting
|
|
46
|
+
|
|
47
|
+
Use the report helper to aggregate recent samples:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pnpm run telemetry:report -- --since 30m
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Bare numeric values are treated as minutes, so `--since 90` means the last 90 minutes.
|
|
54
|
+
|
|
55
|
+
The report prints `count`, `avg`, `p50`, `p95`, and `max` for each known metric.
|
|
56
|
+
|
|
57
|
+
## Guarantees
|
|
58
|
+
|
|
59
|
+
- **Append-only**: telemetry files are never rewritten in place.
|
|
60
|
+
- **Best-effort**: failed telemetry writes never block the main runtime path.
|
|
61
|
+
- **Low ceremony**: records are plain JSONL so they are easy to inspect with shell tools.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Usage — Drain Execution Records
|
|
2
2
|
|
|
3
3
|
This directory contains per-session usage ledgers.
|
|
4
|
-
Each file tracks token counts, cost,
|
|
4
|
+
Each file tracks token counts, cost, tool call metrics, and runner-side local performance snapshots across every `drainMailboxOnce()` invocation.
|
|
5
5
|
|
|
6
6
|
## Layout
|
|
7
7
|
|
|
@@ -37,11 +37,36 @@ Each line is a JSON `DrainRecord`:
|
|
|
37
37
|
"output_tokens": 410,
|
|
38
38
|
"cache_read_input_tokens": 1200,
|
|
39
39
|
"cache_creation_input_tokens": 0
|
|
40
|
+
},
|
|
41
|
+
"perf": {
|
|
42
|
+
"mailbox_merge_ms": 6.1,
|
|
43
|
+
"mailbox_parse_ms": 1.8,
|
|
44
|
+
"session_snapshot_ms": 2.2,
|
|
45
|
+
"session_state_ms": 0.9,
|
|
46
|
+
"outbox_lookup_ms": 0.4,
|
|
47
|
+
"event_read_ms": 1.6,
|
|
48
|
+
"effective_config_ms": 0.7,
|
|
49
|
+
"outbox_emit_ms": 4.3,
|
|
50
|
+
"session_upsert_ms": 1.5,
|
|
51
|
+
"mailbox_finalize_ms": 1.1,
|
|
52
|
+
"sdk_ttft_ms_total": 842,
|
|
53
|
+
"sdk_ttft_samples": 1
|
|
40
54
|
}
|
|
41
55
|
}
|
|
42
56
|
```
|
|
43
57
|
|
|
44
58
|
`usage` is absent when the drain was cancelled before the SDK returned a result.
|
|
59
|
+
`perf` is optional and records best-effort local timings for the runner-side stages observed during that drain.
|
|
60
|
+
|
|
61
|
+
## Summary Semantics
|
|
62
|
+
|
|
63
|
+
`usage.get` returns both per-record detail and an aggregated summary.
|
|
64
|
+
|
|
65
|
+
- `usage` fields are summed across records in the summary.
|
|
66
|
+
- `perf` fields are also summed across records in the summary.
|
|
67
|
+
- `sdk_ttft_ms_total / sdk_ttft_samples` are emitted separately so callers can compute average TTFT without losing sample count.
|
|
68
|
+
|
|
69
|
+
These timings are scoped to one `drainMailboxOnce()` execution. Transport-side or consumer-side costs such as ingress snapshots, replay scans, websocket delivery, and CLI rendering are not part of this ledger yet.
|
|
45
70
|
|
|
46
71
|
## Querying via RPC
|
|
47
72
|
|