mishkan-harness 0.2.5 → 0.2.6

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.
@@ -10,6 +10,7 @@ from mishkan_watchd.state import HarnessState
10
10
 
11
11
 
12
12
  def test_agent_spawn_creates_session_and_agent():
13
+ """tool_use_id is the key; display name is preserved in AgentState.name."""
13
14
  s = HarnessState()
14
15
  s.apply({"ts": "x", "session": "sess-1", "type": "session_start", "project": "/tmp/proj"})
15
16
  s.apply({
@@ -19,11 +20,50 @@ def test_agent_spawn_creates_session_and_agent():
19
20
  "type": "agent_spawn",
20
21
  "tool": "Task",
21
22
  "agent": "bezalel",
22
- "payload": {"subagent_type": "bezalel"},
23
+ "payload": {"subagent_type": "bezalel", "tool_use_id": "toolu_abc123"},
23
24
  })
24
25
  assert "sess-1" in s.sessions
25
- assert "bezalel" in s.sessions["sess-1"].agents_active
26
- assert s.sessions["sess-1"].agents_active["bezalel"].status == "running"
26
+ agents = s.sessions["sess-1"].agents_active
27
+ # Keyed by tool_use_id, not bare agent name.
28
+ assert "toolu_abc123" in agents
29
+ assert agents["toolu_abc123"].name == "bezalel"
30
+ assert agents["toolu_abc123"].status == "running"
31
+
32
+
33
+ def test_agent_spawn_complete_pair_by_tool_use_id():
34
+ """spawn + complete keyed by tool_use_id correctly decrements active count."""
35
+ s = HarnessState()
36
+ s.apply({"ts": "x", "session": "s", "type": "session_start", "project": "/p"})
37
+ # Spawn two concurrent agents with the same subagent_type.
38
+ for tid in ("tid-1", "tid-2"):
39
+ s.apply({
40
+ "ts": "x", "session": "s", "type": "agent_spawn", "tool": "Task",
41
+ "agent": "bezalel",
42
+ "payload": {"subagent_type": "bezalel", "tool_use_id": tid},
43
+ })
44
+ assert len(s.sessions["s"].agents_active) == 2
45
+ # Complete the first one.
46
+ s.apply({
47
+ "ts": "y", "session": "s", "type": "agent_complete", "tool": "Task",
48
+ "agent": "bezalel",
49
+ "payload": {"subagent_type": "bezalel", "tool_use_id": "tid-1"},
50
+ })
51
+ agents = s.sessions["s"].agents_active
52
+ assert "tid-1" not in agents
53
+ assert "tid-2" in agents
54
+ assert len(agents) == 1
55
+
56
+
57
+ def test_agent_spawn_legacy_fallback_no_tool_use_id():
58
+ """Legacy events without tool_use_id still key by agent name."""
59
+ s = HarnessState()
60
+ s.apply({"ts": "x", "session": "s", "type": "session_start", "project": "/p"})
61
+ s.apply({
62
+ "ts": "x", "session": "s", "type": "agent_spawn", "tool": "Task",
63
+ "agent": "caleb",
64
+ "payload": {"subagent_type": "caleb"},
65
+ })
66
+ assert "caleb" in s.sessions["s"].agents_active
27
67
 
28
68
 
29
69
  def test_token_usage_accumulates_per_session():
@@ -43,9 +83,15 @@ def test_token_usage_accumulates_per_session():
43
83
 
44
84
 
45
85
  def test_session_stop_removes_session():
86
+ """An idle session (no agents, last_event_mono expired) is cleaned up on session_stop."""
87
+ import mishkan_watchd.state as state_mod
88
+ from time import monotonic
89
+
46
90
  s = HarnessState()
47
91
  s.apply({"ts": "x", "session": "drop-me", "type": "session_start", "project": "/p"})
48
92
  assert "drop-me" in s.sessions
93
+ # Simulate idleness: backdate last_event_mono past the keepalive window.
94
+ s.sessions["drop-me"].last_event_mono = monotonic() - (state_mod.SESSION_KEEPALIVE_S + 10)
49
95
  s.apply({"ts": "y", "session": "drop-me", "type": "session_stop"})
50
96
  assert "drop-me" not in s.sessions
51
97
 
@@ -57,13 +103,55 @@ def test_snapshot_serializes_cleanly():
57
103
  s.apply({
58
104
  "ts": "x", "session": "snap", "project": "/p",
59
105
  "type": "agent_spawn", "tool": "Task", "agent": "caleb",
60
- "payload": {"subagent_type": "caleb"},
106
+ "payload": {"subagent_type": "caleb", "tool_use_id": "toolu_snap1"},
61
107
  })
62
108
  snap = s.to_snapshot()
109
+ # Agent is present and keyed by tool_use_id.
110
+ assert "toolu_snap1" in snap["sessions"]["snap"]["agents_active"]
63
111
  # Round-trips through JSON without error.
64
112
  json.dumps(snap, default=str)
65
113
 
66
114
 
115
+ def test_graphify_hook_event_increments_scan_count():
116
+ """graphify_scan from the Bash hook (no stats_only) increments scans."""
117
+ s = HarnessState()
118
+ assert s.graphify.scans == 0
119
+ s.apply({
120
+ "ts": "x", "session": None, "type": "graphify_scan",
121
+ "payload": {"project": "/p", "nodes": 100, "edges": 200},
122
+ })
123
+ assert s.graphify.scans == 1
124
+ assert s.graphify.nodes == 100
125
+ assert s.graphify.edges == 200
126
+
127
+
128
+ def test_graphify_tail_stats_only_does_not_increment_scan_count():
129
+ """graphify_scan with stats_only=True updates sizes but not the counter."""
130
+ s = HarnessState()
131
+ s.apply({
132
+ "ts": "x", "session": None, "type": "graphify_scan",
133
+ "payload": {
134
+ "project": "/p", "nodes": 2798, "edges": 3102,
135
+ "stats_only": True,
136
+ },
137
+ })
138
+ assert s.graphify.scans == 0
139
+ assert s.graphify.nodes == 2798
140
+ assert s.graphify.edges == 3102
141
+
142
+
143
+ def test_graphify_query_hook_event_increments_query_count():
144
+ """graphify_query from the Bash hook increments queries."""
145
+ s = HarnessState()
146
+ s.apply({"ts": "x", "session": "sq", "type": "session_start", "project": "/p"})
147
+ s.apply({
148
+ "ts": "x", "session": "sq", "type": "graphify_query",
149
+ "payload": {"project": "/p", "question": "who calls process_payment"},
150
+ })
151
+ assert s.graphify.queries == 1
152
+ assert s.graphify.last_query_text == "who calls process_payment"
153
+
154
+
67
155
  def test_unknown_event_type_is_ignored_gracefully():
68
156
  s = HarnessState()
69
157
  s.apply({"ts": "x", "session": "weird", "type": "session_start", "project": "/p"})
@@ -72,10 +160,420 @@ def test_unknown_event_type_is_ignored_gracefully():
72
160
  assert "weird" in s.sessions
73
161
 
74
162
 
163
+ # ---------------------------------------------------------------------------
164
+ # Fix A — last_context_tokens
165
+ # ---------------------------------------------------------------------------
166
+
167
+ def test_last_context_tokens_set_not_accumulated():
168
+ """last_context_tokens reflects the MOST RECENT turn, not a running sum."""
169
+ s = HarnessState()
170
+ s.apply({"ts": "x", "session": "ctx", "type": "session_start", "project": "/p"})
171
+ # Turn 1: small uncached input with large cache_read (typical caching profile).
172
+ s.apply({
173
+ "ts": "x", "session": "ctx", "type": "token_usage",
174
+ "payload": {
175
+ "tokens_in": 2, "tokens_out": 100,
176
+ "cache_read": 558453, "cache_write": 5141,
177
+ "cost_estimate_usd": 1.21,
178
+ },
179
+ })
180
+ sess = s.sessions["ctx"]
181
+ # last_context_tokens = cache_read + cache_write + tokens_in of THIS turn.
182
+ assert sess.last_context_tokens == 558453 + 5141 + 2
183
+ # Cumulative fields are unchanged.
184
+ assert sess.tokens_in == 2
185
+ assert sess.cache_read == 558453
186
+
187
+ # Turn 2: a different footprint — last_context_tokens is replaced, not added.
188
+ s.apply({
189
+ "ts": "y", "session": "ctx", "type": "token_usage",
190
+ "payload": {
191
+ "tokens_in": 5, "tokens_out": 200,
192
+ "cache_read": 600000, "cache_write": 0,
193
+ "cost_estimate_usd": 0.5,
194
+ },
195
+ })
196
+ assert sess.last_context_tokens == 600000 + 0 + 5
197
+ # Cumulative tokens_in did accumulate across both turns.
198
+ assert sess.tokens_in == 7
199
+
200
+
201
+ def test_last_context_tokens_in_snapshot():
202
+ """last_context_tokens is present in the per-session snapshot dict."""
203
+ import json
204
+ s = HarnessState()
205
+ s.apply({"ts": "x", "session": "ctx2", "type": "session_start", "project": "/p"})
206
+ s.apply({
207
+ "ts": "x", "session": "ctx2", "type": "token_usage",
208
+ "payload": {"tokens_in": 2, "tokens_out": 50,
209
+ "cache_read": 558453, "cache_write": 5141,
210
+ "cost_estimate_usd": 0.1},
211
+ })
212
+ snap = s.to_snapshot()
213
+ sess_snap = snap["sessions"]["ctx2"]
214
+ assert "last_context_tokens" in sess_snap
215
+ assert sess_snap["last_context_tokens"] == 558453 + 5141 + 2
216
+ # Verify it round-trips cleanly.
217
+ json.dumps(snap, default=str)
218
+
219
+
220
+ def test_last_context_tokens_zero_on_fresh_session():
221
+ """A session with no token_usage events has last_context_tokens == 0."""
222
+ s = HarnessState()
223
+ s.apply({"ts": "x", "session": "fresh", "type": "session_start", "project": "/p"})
224
+ snap = s.to_snapshot()
225
+ assert snap["sessions"]["fresh"]["last_context_tokens"] == 0
226
+
227
+
228
+ # ---------------------------------------------------------------------------
229
+ # Fix B — workflow stale sweep
230
+ # ---------------------------------------------------------------------------
231
+
232
+ def test_workflow_start_creates_run_in_session():
233
+ """workflow_start populates workflows_active with phase 'running'."""
234
+ s = HarnessState()
235
+ s.apply({"ts": "x", "session": "wf1", "type": "session_start", "project": "/p"})
236
+ s.apply({
237
+ "ts": "x", "session": "wf1", "type": "workflow_start",
238
+ "payload": {"name": "my-flow", "run_id": "run-abc", "scriptPath": "flow.yaml"},
239
+ })
240
+ wf = s.sessions["wf1"].workflows_active.get("run-abc")
241
+ assert wf is not None
242
+ assert wf.name == "my-flow"
243
+ assert wf.phase == "running"
244
+
245
+
246
+ def test_workflow_stale_sweep_in_snapshot():
247
+ """A workflow whose last_activity_mono is > TTL shows phase='stale' in snapshot."""
248
+ import mishkan_watchd.state as state_mod
249
+ from time import monotonic
250
+
251
+ s = HarnessState()
252
+ s.apply({"ts": "x", "session": "wf2", "type": "session_start", "project": "/p"})
253
+ s.apply({
254
+ "ts": "x", "session": "wf2", "type": "workflow_start",
255
+ "payload": {"name": "old-flow", "run_id": "run-old"},
256
+ })
257
+ # Backdate last_activity_mono to simulate a run that started long ago.
258
+ wf = s.sessions["wf2"].workflows_active["run-old"]
259
+ wf.last_activity_mono = monotonic() - (state_mod.WORKFLOW_STALE_TTL_S + 1)
260
+
261
+ snap = s.to_snapshot()
262
+ wf_snap = snap["sessions"]["wf2"]["workflows_active"]["run-old"]
263
+ assert wf_snap["phase"] == "stale"
264
+ # The live WorkflowState is NOT mutated — only the snapshot output changes.
265
+ assert wf.phase == "running"
266
+
267
+
268
+ def test_workflow_fresh_run_not_staled():
269
+ """A recently-started workflow is NOT staled in the snapshot."""
270
+ s = HarnessState()
271
+ s.apply({"ts": "x", "session": "wf3", "type": "session_start", "project": "/p"})
272
+ s.apply({
273
+ "ts": "x", "session": "wf3", "type": "workflow_start",
274
+ "payload": {"name": "new-flow", "run_id": "run-new"},
275
+ })
276
+ snap = s.to_snapshot()
277
+ wf_snap = snap["sessions"]["wf3"]["workflows_active"]["run-new"]
278
+ assert wf_snap["phase"] == "running"
279
+
280
+
281
+ def test_workflow_stale_field_not_in_snapshot():
282
+ """last_activity_mono (internal float) is stripped from the snapshot wire shape."""
283
+ s = HarnessState()
284
+ s.apply({"ts": "x", "session": "wf4", "type": "session_start", "project": "/p"})
285
+ s.apply({
286
+ "ts": "x", "session": "wf4", "type": "workflow_start",
287
+ "payload": {"name": "flow", "run_id": "run-x"},
288
+ })
289
+ snap = s.to_snapshot()
290
+ wf_snap = snap["sessions"]["wf4"]["workflows_active"]["run-x"]
291
+ assert "last_activity_mono" not in wf_snap
292
+
293
+
294
+ def test_cold_start_has_no_workflows():
295
+ """A fresh daemon with no workflow events shows an empty workflows_active."""
296
+ s = HarnessState()
297
+ s.apply({"ts": "x", "session": "cold", "type": "session_start", "project": "/p"})
298
+ snap = s.to_snapshot()
299
+ assert snap["sessions"]["cold"]["workflows_active"] == {}
300
+
301
+
302
+ # ---------------------------------------------------------------------------
303
+ # Fix C — session_stop busy-guard (liveness via bus activity)
304
+ # ---------------------------------------------------------------------------
305
+
306
+ def test_session_stop_ignored_when_agent_active():
307
+ """A session with a running agent survives session_stop from session_discover."""
308
+ s = HarnessState()
309
+ s.apply({"ts": "x", "session": "busy-1", "type": "session_start", "project": "/p"})
310
+ s.apply({
311
+ "ts": "x", "session": "busy-1", "type": "agent_spawn", "tool": "Task",
312
+ "agent": "bezalel",
313
+ "payload": {"subagent_type": "bezalel", "tool_use_id": "toolu_busy1"},
314
+ })
315
+ # Transcript goes quiet — session_discover emits session_stop.
316
+ s.apply({"ts": "y", "session": "busy-1", "type": "session_stop"})
317
+ # Session must still be present and agent still visible.
318
+ assert "busy-1" in s.sessions
319
+ assert "toolu_busy1" in s.sessions["busy-1"].agents_active
320
+
321
+
322
+ def test_session_stop_ignored_when_recent_bus_event():
323
+ """A session whose last bus event is within SESSION_KEEPALIVE_S survives session_stop."""
324
+ import mishkan_watchd.state as state_mod
325
+
326
+ s = HarnessState()
327
+ s.apply({"ts": "x", "session": "busy-2", "type": "session_start", "project": "/p"})
328
+ # A recent token_usage event stamps last_event_mono close to now.
329
+ s.apply({
330
+ "ts": "x", "session": "busy-2", "type": "token_usage",
331
+ "payload": {"tokens_in": 10, "tokens_out": 5, "cost_estimate_usd": 0.001},
332
+ })
333
+ # Confirm last_event_mono is within keepalive window.
334
+ from time import monotonic
335
+ assert (monotonic() - s.sessions["busy-2"].last_event_mono) < state_mod.SESSION_KEEPALIVE_S
336
+
337
+ s.apply({"ts": "y", "session": "busy-2", "type": "session_stop"})
338
+ assert "busy-2" in s.sessions
339
+
340
+
341
+ def test_session_stop_drops_genuinely_idle_session():
342
+ """A session with no active agents and an old last_event_mono is cleaned up normally."""
343
+ import mishkan_watchd.state as state_mod
344
+ from time import monotonic
345
+
346
+ s = HarnessState()
347
+ s.apply({"ts": "x", "session": "idle-1", "type": "session_start", "project": "/p"})
348
+ # Backdate last_event_mono to simulate a long-idle session.
349
+ sess = s.sessions["idle-1"]
350
+ sess.last_event_mono = monotonic() - (state_mod.SESSION_KEEPALIVE_S + 10)
351
+ # No agents_active (default empty dict).
352
+ s.apply({"ts": "y", "session": "idle-1", "type": "session_stop"})
353
+ assert "idle-1" not in s.sessions
354
+ assert "idle-1" in s._stopped_recently
355
+
356
+
357
+ def test_live_event_after_idle_stop_resurrects_session():
358
+ """A live bus event for a tombstoned-but-spuriously-stopped session
359
+ un-tombstones and re-confirms the session (Fix E).
360
+
361
+ agent_complete for a session with no spawned agents is a harmless
362
+ fall-through: the session exists but agents_active stays empty. This
363
+ is correct — the event was genuine activity, not a phantom replay.
364
+ """
365
+ import mishkan_watchd.state as state_mod
366
+ from time import monotonic
367
+
368
+ s = HarnessState()
369
+ s.apply({"ts": "x", "session": "gone-1", "type": "session_start", "project": "/p"})
370
+ sess = s.sessions["gone-1"]
371
+ sess.last_event_mono = monotonic() - (state_mod.SESSION_KEEPALIVE_S + 10)
372
+ s.apply({"ts": "y", "session": "gone-1", "type": "session_stop"})
373
+ assert "gone-1" not in s.sessions
374
+ assert "gone-1" in s._stopped_recently
375
+
376
+ # A live agent_complete arrives — session is resurrected (no agent to pop,
377
+ # but the session itself is re-confirmed and alive).
378
+ s.apply({
379
+ "ts": "z", "session": "gone-1", "type": "agent_complete",
380
+ "payload": {"tool_use_id": "toolu_late"},
381
+ })
382
+ assert "gone-1" in s.sessions
383
+ assert "gone-1" in s._confirmed_alive
384
+ assert "gone-1" not in s._stopped_recently
385
+ # No agent was ever spawned, so agents_active is empty — not an error.
386
+ assert s.sessions["gone-1"].agents_active == {}
387
+
388
+
389
+ # ---------------------------------------------------------------------------
390
+ # Fix D — live-event confirm (agent-only session, no prior session_start)
391
+ # ---------------------------------------------------------------------------
392
+
393
+ def test_agent_spawn_alone_confirms_session_and_populates_agents_active():
394
+ """Core regression: agent_spawn for a never-seen session (no session_start)
395
+ must create+confirm the session and populate agents_active immediately.
396
+
397
+ This is the agent-only-session blindspot: the parent transcript is quiet
398
+ during an agent run, so session_discover never fires session_start; but
399
+ bus events stream in live and tier-2 confirmation must pick them up.
400
+ """
401
+ s = HarnessState()
402
+ s.apply({
403
+ "ts": "2026-06-09T10:00:00.000Z",
404
+ "session": "agent-only-sess",
405
+ "project": "/tmp/wisemoney",
406
+ "type": "agent_spawn",
407
+ "tool": "Task",
408
+ "agent": "bezalel",
409
+ "payload": {"subagent_type": "bezalel", "tool_use_id": "toolu_live01"},
410
+ })
411
+ # Session must exist — created by tier-2 confirm.
412
+ assert "agent-only-sess" in s.sessions
413
+ # Agent must be visible immediately — event fell through to dispatch.
414
+ agents = s.sessions["agent-only-sess"].agents_active
415
+ assert "toolu_live01" in agents
416
+ assert agents["toolu_live01"].name == "bezalel"
417
+ assert agents["toolu_live01"].status == "running"
418
+ # Session is now confirmed.
419
+ assert "agent-only-sess" in s._confirmed_alive
420
+
421
+
422
+ def test_token_usage_alone_confirms_session_and_applies_tokens():
423
+ """token_usage for a never-seen session creates the session and lands tokens."""
424
+ s = HarnessState()
425
+ s.apply({
426
+ "ts": "2026-06-09T10:00:01.000Z",
427
+ "session": "tok-only-sess",
428
+ "type": "token_usage",
429
+ "payload": {"tokens_in": 42, "tokens_out": 7, "cost_estimate_usd": 0.005},
430
+ })
431
+ assert "tok-only-sess" in s.sessions
432
+ sess = s.sessions["tok-only-sess"]
433
+ assert sess.tokens_in == 42
434
+ assert sess.tokens_out == 7
435
+ assert "tok-only-sess" in s._confirmed_alive
436
+
437
+
438
+ def test_tombstoned_session_untombstoned_on_live_agent_spawn():
439
+ """Core regression — Fix E: a tombstoned session that receives a live
440
+ agent_spawn is un-tombstoned, re-confirmed, and the agent appears in
441
+ agents_active immediately.
442
+
443
+ Scenario: session went quiet (parent transcript stale >60 s during an
444
+ agent run), session_discover spuriously emitted session_stop, session was
445
+ tombstoned. A fresh agent_spawn then arrives on the bus. With the fix the
446
+ session must come back alive and the agent must be visible.
447
+ """
448
+ import mishkan_watchd.state as state_mod
449
+ from time import monotonic
450
+
451
+ s = HarnessState()
452
+ # Start and let it go idle so the stop is accepted (not busy-guarded).
453
+ s.apply({"ts": "x", "session": "quiet-sess", "type": "session_start", "project": "/p"})
454
+ s.sessions["quiet-sess"].last_event_mono = monotonic() - (state_mod.SESSION_KEEPALIVE_S + 10)
455
+ s.apply({"ts": "y", "session": "quiet-sess", "type": "session_stop"})
456
+ assert "quiet-sess" not in s.sessions
457
+ assert "quiet-sess" in s._stopped_recently
458
+
459
+ # Live agent_spawn arrives — session must be resurrected.
460
+ s.apply({
461
+ "ts": "z", "session": "quiet-sess",
462
+ "type": "agent_spawn", "tool": "Task", "agent": "bezalel",
463
+ "payload": {"subagent_type": "bezalel", "tool_use_id": "toolu_reborn"},
464
+ })
465
+ assert "quiet-sess" in s.sessions, "session must be un-tombstoned and re-confirmed"
466
+ assert "quiet-sess" in s._confirmed_alive
467
+ assert "quiet-sess" not in s._stopped_recently
468
+ agents = s.sessions["quiet-sess"].agents_active
469
+ assert "toolu_reborn" in agents, "agent must appear in agents_active after resurrection"
470
+ assert agents["toolu_reborn"].name == "bezalel"
471
+ assert agents["toolu_reborn"].status == "running"
472
+
473
+
474
+ def test_tombstoned_session_subsequent_agent_complete_works_normally():
475
+ """After resurrection via agent_spawn a following agent_complete removes
476
+ the agent normally — the un-tombstoned session behaves like any live session.
477
+ """
478
+ import mishkan_watchd.state as state_mod
479
+ from time import monotonic
480
+
481
+ s = HarnessState()
482
+ s.apply({"ts": "x", "session": "revived", "type": "session_start", "project": "/p"})
483
+ s.sessions["revived"].last_event_mono = monotonic() - (state_mod.SESSION_KEEPALIVE_S + 10)
484
+ s.apply({"ts": "y", "session": "revived", "type": "session_stop"})
485
+ assert "revived" in s._stopped_recently
486
+
487
+ # Resurrect via spawn.
488
+ s.apply({
489
+ "ts": "z", "session": "revived",
490
+ "type": "agent_spawn", "tool": "Task", "agent": "caleb",
491
+ "payload": {"subagent_type": "caleb", "tool_use_id": "toolu_rev1"},
492
+ })
493
+ assert "toolu_rev1" in s.sessions["revived"].agents_active
494
+
495
+ # Complete the agent — must be removed cleanly.
496
+ s.apply({
497
+ "ts": "w", "session": "revived",
498
+ "type": "agent_complete", "tool": "Task", "agent": "caleb",
499
+ "payload": {"subagent_type": "caleb", "tool_use_id": "toolu_rev1"},
500
+ })
501
+ assert "toolu_rev1" not in s.sessions["revived"].agents_active
502
+ assert len(s.sessions["revived"].agents_active) == 0
503
+
504
+
505
+ def test_tombstoned_session_with_no_further_events_stays_gone():
506
+ """A session that is tombstoned and receives no further events is never
507
+ resurrected — tombstone stays in place indefinitely.
508
+ """
509
+ import mishkan_watchd.state as state_mod
510
+ from time import monotonic
511
+
512
+ s = HarnessState()
513
+ s.apply({"ts": "x", "session": "truly-dead", "type": "session_start", "project": "/p"})
514
+ s.sessions["truly-dead"].last_event_mono = monotonic() - (state_mod.SESSION_KEEPALIVE_S + 10)
515
+ s.apply({"ts": "y", "session": "truly-dead", "type": "session_stop"})
516
+
517
+ # No further events arrive for this session. It must remain absent.
518
+ assert "truly-dead" not in s.sessions
519
+ assert "truly-dead" in s._stopped_recently
520
+ assert "truly-dead" not in s._confirmed_alive
521
+
522
+
523
+ def test_session_start_after_live_confirm_is_harmless_reconfirm():
524
+ """session_start arriving after tier-2 already confirmed the session is a
525
+ harmless re-confirm: no duplicate session created, project upgraded if needed.
526
+ """
527
+ s = HarnessState()
528
+ # Tier-2 confirm via agent_spawn (project unknown at this point).
529
+ s.apply({
530
+ "ts": "a", "session": "reconfirm-sess",
531
+ "type": "agent_spawn", "tool": "Task", "agent": "bezalel",
532
+ "payload": {"subagent_type": "bezalel", "tool_use_id": "toolu_rc1"},
533
+ })
534
+ assert "reconfirm-sess" in s.sessions
535
+ assert s.sessions["reconfirm-sess"].project == "unknown"
536
+
537
+ # session_discover fires session_start with the real project path.
538
+ s.apply({
539
+ "ts": "b", "session": "reconfirm-sess",
540
+ "type": "session_start", "project": "/real/project",
541
+ })
542
+ # Only one session entry — not duplicated.
543
+ assert len([sid for sid in s.sessions if sid == "reconfirm-sess"]) == 1
544
+ # Project upgraded from "unknown" to the real path.
545
+ assert s.sessions["reconfirm-sess"].project == "/real/project"
546
+ # Agent still present.
547
+ assert "toolu_rc1" in s.sessions["reconfirm-sess"].agents_active
548
+
549
+
75
550
  if __name__ == "__main__":
76
551
  test_agent_spawn_creates_session_and_agent()
552
+ test_agent_spawn_complete_pair_by_tool_use_id()
553
+ test_agent_spawn_legacy_fallback_no_tool_use_id()
77
554
  test_token_usage_accumulates_per_session()
78
555
  test_session_stop_removes_session()
79
556
  test_snapshot_serializes_cleanly()
557
+ test_graphify_hook_event_increments_scan_count()
558
+ test_graphify_tail_stats_only_does_not_increment_scan_count()
559
+ test_graphify_query_hook_event_increments_query_count()
80
560
  test_unknown_event_type_is_ignored_gracefully()
561
+ test_last_context_tokens_set_not_accumulated()
562
+ test_last_context_tokens_in_snapshot()
563
+ test_last_context_tokens_zero_on_fresh_session()
564
+ test_workflow_start_creates_run_in_session()
565
+ test_workflow_stale_sweep_in_snapshot()
566
+ test_workflow_fresh_run_not_staled()
567
+ test_workflow_stale_field_not_in_snapshot()
568
+ test_cold_start_has_no_workflows()
569
+ test_session_stop_ignored_when_agent_active()
570
+ test_session_stop_ignored_when_recent_bus_event()
571
+ test_session_stop_drops_genuinely_idle_session()
572
+ test_live_event_after_idle_stop_resurrects_session()
573
+ test_agent_spawn_alone_confirms_session_and_populates_agents_active()
574
+ test_token_usage_alone_confirms_session_and_applies_tokens()
575
+ test_tombstoned_session_untombstoned_on_live_agent_spawn()
576
+ test_tombstoned_session_subsequent_agent_complete_works_normally()
577
+ test_tombstoned_session_with_no_further_events_stays_gone()
578
+ test_session_start_after_live_confirm_is_harmless_reconfirm()
81
579
  print("all state tests passed")
@@ -87,6 +87,10 @@ async def m():
87
87
  print(">> memified", flush=True)
88
88
  asyncio.run(m())
89
89
  PY
90
+ # mktemp creates the runner 0600; docker cp preserves mode + host uid, so the
91
+ # container's non-root user can't read it (Errno 13 Permission denied). Make it
92
+ # world-readable before staging — this was silently blocking ingest fleet-wide.
93
+ chmod 0644 "$PY_SCRIPT"
90
94
  docker cp "$PY_SCRIPT" "${CONTAINER}:/home/cognee/_mishkan_ingest.py"
91
95
  rm -f "$PY_SCRIPT"
92
96
  docker exec -i -w /app/cognee-mcp "$CONTAINER" uv run python -u /home/cognee/_mishkan_ingest.py "$DATASET"