@openduo/duoduo 0.2.11 → 0.3.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.
@@ -17,7 +17,16 @@
17
17
  # Example:
18
18
  # prompt_mode: append
19
19
  #
20
- # 3) Claude Agent SDK-aligned options (optional)
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
@@ -37,7 +37,7 @@ When a queue item contains `trigger job:<id>`:
37
37
 
38
38
  1. Use `ManageJob` (action: read) to verify the job exists.
39
39
  2. Read the job's `.state.json` sidecar file (path shown in job info).
40
- 3. Set `last_run_at` to `null` in that file and write it back.
40
+ 3. Set `last_scheduled_at` to `null` in that file and write it back.
41
41
  4. The job scheduler (60-second cycle) will see it as due and spawn it.
42
42
 
43
43
  If the job doesn't exist or is already running, note the error and
@@ -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)
@@ -34,11 +34,11 @@ To make the job scheduler pick up a job on its next 60-second scan,
34
34
  queue a state reset:
35
35
 
36
36
  ```
37
- - [ ] (cadence:fast) trigger job:<job-id> — reset last_run_at in its .state.json so the scheduler treats it as due
37
+ - [ ] (cadence:fast) trigger job:<job-id> — reset last_scheduled_at in its .state.json so the scheduler treats it as due
38
38
  ```
39
39
 
40
40
  The cadence-executor will read the job's `.state.json` sidecar
41
- (in `~/.aladuo/var/jobs/active/<job-id>.state.json`), set `last_run_at`
41
+ (in `~/.aladuo/var/jobs/active/<job-id>.state.json`), set `last_scheduled_at`
42
42
  to null, and the job scheduler spawns it within 60 seconds.
43
43
 
44
44
  ### Example: Memory Compression
@@ -32,7 +32,8 @@ created_at: 2026-02-17T10:00:00Z
32
32
 
33
33
  ```json
34
34
  {
35
- "last_run_at": "2026-02-17T12:00:00Z",
35
+ "last_scheduled_at": "2026-02-17T12:00:00Z",
36
+ "last_run_at": "2026-02-17T12:01:30Z",
36
37
  "last_result": "success",
37
38
  "run_count": 42
38
39
  }
@@ -42,27 +43,27 @@ created_at: 2026-02-17T10:00:00Z
42
43
 
43
44
  1. **Created** via `ManageJob` tool (action: create)
44
45
  2. **Scanned** by job scheduler every 60 seconds
45
- 3. **Spawned** as a session when `isJobDue(cron, last_run_at)` is true
46
+ 3. **Spawned** as a session when `isJobDue(cron, last_scheduled_at)` is true
46
47
  4. **Completed/Failed** — state updated, results routed to `notify` targets
47
48
  5. **Archived** via `ManageJob` tool (action: archive)
48
49
 
49
50
  ## How to Trigger a Job Immediately
50
51
 
51
52
  The job scheduler determines "due" by comparing `cron` against
52
- `last_run_at` in the state file. To force immediate execution:
53
+ `last_scheduled_at` in the state file. To force immediate execution:
53
54
 
54
55
  **Option A — Via cadence queue (recommended):**
55
56
 
56
57
  Drop a `.pending` file in `~/.aladuo/var/cadence/inbox/`:
57
58
 
58
59
  ```
59
- - [ ] (cadence:fast) trigger job:<job-id> — reset last_run_at so scheduler treats it as due
60
+ - [ ] (cadence:fast) trigger job:<job-id> — reset last_scheduled_at so scheduler treats it as due
60
61
  ```
61
62
 
62
63
  **Option B — Direct state edit (if you understand the implications):**
63
64
 
64
- Set `last_run_at` to `null` in `<job-id>.state.json`. The scheduler
65
- will see it as never-run and spawn it within 60 seconds.
65
+ Set `last_scheduled_at` to `null` in `<job-id>.state.json`. The scheduler
66
+ will see it as never-scheduled and spawn it within 60 seconds.
66
67
 
67
68
  Note: Option A is preferred because the cadence-executor can handle
68
69
  edge cases (check if job exists, verify it's not already running).
@@ -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, and tool call metrics across every `drainMailboxOnce()` invocation.
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