@tangle-network/harness-sessions 0.2.0 → 0.2.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/README.md CHANGED
@@ -15,7 +15,7 @@ npm install @tangle-network/harness-sessions
15
15
  - The sidecar's harness session contract (agent-container `packages/cli-agent-registry`) imports `STORES` and `SessionFormatId` from here; it keeps no second copy of where a harness writes.
16
16
  - The capture path reads captures through `listCapture` / `readCapture`.
17
17
  - Discovery's trace gate, agent-eval, agent-record and the blog read sessions only through this package.
18
- - Its only dependencies are `@tangle-network/agent-trace-contract` and Node built-ins (`node:sqlite` for OpenCode).
18
+ - Its dependencies are `@tangle-network/agent-core`, `@tangle-network/agent-trace-contract` and Node built-ins (`node:sqlite` for OpenCode).
19
19
 
20
20
  ## Formats
21
21
 
@@ -25,6 +25,8 @@ npm install @tangle-network/harness-sessions
25
25
  | `codex` | `codex.rollout-jsonl` | `rollout-*-{id}.jsonl` under `sessions/` and `archived_sessions/` of every Codex home (below) | turn context (the configured model) | per response (`last_token_usage`); session total from `total_token_usage` |
26
26
  | `opencode` | `opencode.sqlite` | `.local/share/opencode/opencode.db` (shared by every session) | session record (`providerID/modelID`) | per step (`step-finish`) |
27
27
  | `pi` | `pi.session-jsonl` | `.pi/agent/sessions/**/*_{id}.jsonl` | session record (`provider/model`) | per assistant message |
28
+ | `kimi-code` | `kimi.session-dir` | `.kimi/sessions/*/{id}/wire.jsonl`, child wires at `*/{id}/subagents/*/wire.jsonl` | none in the wire | per step, excluding wrapped child events |
29
+ | `factory-droids` | `factory.session-jsonl` | `.factory/sessions/*/{id}.jsonl` plus settings | configured model in settings (`turn-context`) | session total in settings |
28
30
 
29
31
  A HOME can hold several Codex homes, and `codexReader.locate` reads all of them (`codexHomes(home)` lists them):
30
32
 
@@ -35,7 +37,15 @@ A HOME can hold several Codex homes, and `codexReader.locate` reads all of them
35
37
 
36
38
  Homes that resolve to one directory count once. A session copied into several homes (`acct move`) is one ref, its largest copy. A lookup by id takes the rollout whose name ends in the id; when no name does, it reads the `session_meta` of each rollout modified since the id's UUIDv7 time.
37
39
 
38
- Readers for Kimi, Factory, Gemini, Amp, Qwen, Copilot, Forge, Hermes, Prime and OpenClaw are added with the same real-data proof.
40
+ `listReaders()` and the exported catalog own the complete installed format inventory.
41
+
42
+ ## Child execution evidence
43
+
44
+ Kimi records a child `agent_id` and `parent_tool_call_id` on each wrapped `SubagentEvent`, and writes the child's own wire under the parent's `subagents/<agent_id>/` directory. `kimiReader.locate(home, { nativeSessionId: parentId })` returns the parent and these child refs. A child-ID lookup selects that child. The reader keeps wrapped child events out of the parent's messages and usage; read each returned child ref for its own recorded work. `kimiRefForFile` preserves the same parent binding when a child wire is read directly. A missing child wire remains missing execution evidence, even when its ID appears in the parent.
45
+
46
+ Pi's session-header `parentSession` is fork/clone/handoff lineage; it does not prove a delegated subagent. The native reader does not turn it or conversation-entry `parentId` into subagent execution. Extension workflow execution remains a separate recorded source. Factory Droid also needs an explicit producer-recorded parent/child binding before this reader can expose delegated children. Neither reader guesses relationships from a tool name, working directory, timing, or current project.
47
+
48
+ A bounded local capture inspection on 2026-10-25 found explicit Kimi child events and separate child wires. Its Pi and Factory Droid samples contained no explicit subagent binding: the latest 20 files per harness held 7,724 and 245 records respectively; an additional size-stratified selection read seven Pi files (8,229 records) and 20 Factory files (252 records). These are sample limits, not a claim that every producer version lacks the metadata. The remaining Pi/Factory child coverage is unknown. Closing it requires a retained parent/child capture with an explicit source binding, followed by a reader-to-OTLP integration proof; the graph-corpus integrating owner owns that follow-up.
39
49
 
40
50
  ## Use
41
51
 
@@ -77,5 +87,5 @@ harness-sessions read <file|home|capture> [--harness h] [--session id] [--summar
77
87
 
78
88
  ## Tests
79
89
 
80
- The fixtures are sessions that the real harness CLIs wrote while talking to the trace-proof scripted model; the tests assert that script.
90
+ The baseline fixtures are sessions that the real harness CLIs wrote while talking to the trace-proof scripted model; the tests assert that script. The Kimi child fixture retains complete existing parent and child wire records with all content removed, IDs pseudonymized, and source hashes, record counts, redaction version and token counters recorded in `test/fixtures/kimi/provenance.json`. Its integration test exercises discovery, full/summary reads, in-memory input and contract-span projection, preserving parent and child usage separately.
81
91
  The proof against the readers this package replaces is a comparison over stored sessions, recorded with each release.
@@ -21,6 +21,13 @@ const STORE = STORES[FORMAT];
21
21
  const HARNESS = 'kimi-code';
22
22
  /** The event stream this reader folds; the store also holds context.jsonl and state.json. */
23
23
  const WIRE = '*/{id}/wire.jsonl';
24
+ const CHILD_WIRE = '*/*/subagents/{id}/wire.jsonl';
25
+ /** Kimi's producer places each agent's wire under its owning session's subagents directory. */
26
+ function sessionIdentity(path) {
27
+ const directory = dirname(path);
28
+ const container = dirname(directory);
29
+ return { id: basename(directory), parent: basename(container) === 'subagents' ? basename(dirname(container)) : null };
30
+ }
24
31
  function usageOf(value) {
25
32
  if (!isRecord(value))
26
33
  return null;
@@ -142,7 +149,7 @@ export function createFold(ref, mode, stats) {
142
149
  subagentEvents += 1;
143
150
  const agent = str(payload.agent_id);
144
151
  if (agent)
145
- builder.child(agent, str(payload.parent_tool_call_id));
152
+ builder.child(agent, str(payload.parent_tool_call_id) ?? str(payload.task_tool_call_id));
146
153
  break;
147
154
  }
148
155
  default:
@@ -155,7 +162,7 @@ export function createFold(ref, mode, stats) {
155
162
  if (compactions > 0)
156
163
  builder.gap(`${compactions} compactions replaced earlier context`);
157
164
  if (subagentEvents > 0)
158
- builder.gap(`${subagentEvents} subagent events are logged in this session; each subagent is its own session`);
165
+ builder.gap(`${subagentEvents} wrapped subagent events were excluded from this session's own messages and usage; separate child wires must be read for child execution`);
159
166
  builder.inferEnding(aborted ? { at: aborted.at, error: null } : undefined);
160
167
  return builder;
161
168
  };
@@ -166,10 +173,15 @@ export async function fold(ref, mode, options, source = fileRecords(ref.path, {
166
173
  }
167
174
  async function locate(home, opts) {
168
175
  const root = join(home, STORE.root);
169
- const pattern = storeGlob(WIRE, opts.nativeSessionId);
176
+ const pattern = storeGlob(WIRE);
177
+ const children = storeGlob(CHILD_WIRE);
170
178
  const refs = [];
171
179
  for (const rel of await walkFiles(root)) {
172
- if (!pattern.test(rel))
180
+ if (!pattern.test(rel) && !children.test(rel))
181
+ continue;
182
+ const { id, parent } = sessionIdentity(rel);
183
+ // A parent lookup includes its recorded child wires; a child lookup selects that agent.
184
+ if (opts.nativeSessionId && id !== opts.nativeSessionId && parent !== opts.nativeSessionId)
173
185
  continue;
174
186
  const path = join(root, rel);
175
187
  const mtime = await mtimeMs(path);
@@ -178,10 +190,9 @@ async function locate(home, opts) {
178
190
  // Kimi keys the session directory by a hash of its working directory and records no cwd.
179
191
  if (opts.cwd)
180
192
  continue;
181
- const id = basename(dirname(path));
182
193
  const context = join(dirname(path), 'context.jsonl');
183
194
  const files = (await mtimeMs(context)) > 0 ? [path, context] : [path];
184
- refs.push({ harness: HARNESS, format: FORMAT, nativeSessionId: id, path, files, home, cwd: null, mtimeMs: mtime, parentNativeSessionId: null });
195
+ refs.push({ harness: HARNESS, format: FORMAT, nativeSessionId: id, path, files, home, cwd: null, mtimeMs: mtime, parentNativeSessionId: parent });
185
196
  }
186
197
  return refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
187
198
  }
@@ -190,7 +201,7 @@ export const kimiReader = {
190
201
  aliases: ['kimi', 'kimi-cli'],
191
202
  formats: [FORMAT],
192
203
  stores: [STORE],
193
- globs: { session: WIRE },
204
+ globs: { session: WIRE, children: CHILD_WIRE },
194
205
  evidence: EVIDENCE[FORMAT],
195
206
  locate: (home, opts = {}) => locate(home, opts),
196
207
  async read(ref, opts = {}) {
@@ -202,5 +213,6 @@ export const kimiReader = {
202
213
  };
203
214
  /** A session read from its `wire.jsonl` outside a HOME layout. */
204
215
  export function kimiRefForFile(path) {
205
- return { harness: HARNESS, format: FORMAT, nativeSessionId: basename(dirname(path)), path, files: [path], home: null, cwd: null, mtimeMs: 0, parentNativeSessionId: null };
216
+ const { id, parent } = sessionIdentity(path);
217
+ return { harness: HARNESS, format: FORMAT, nativeSessionId: id, path, files: [path], home: null, cwd: null, mtimeMs: 0, parentNativeSessionId: parent };
206
218
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/harness-sessions",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "One reader for every coding-agent harness's native session (Claude Code, Codex, OpenCode, Pi): store layouts, a normalized session with messages, tool calls, served models, usage and ending errors, and projections to chat messages, OTLP spans and turns",
5
5
  "type": "module",
6
6
  "sideEffects": false,