agent-tracegraph 0.3.1__tar.gz → 0.4.0__tar.gz
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.
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/CHANGELOG.md +25 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/PKG-INFO +16 -7
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/README.md +15 -6
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/pyproject.toml +1 -1
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/adapters/langgraph_checkpoint.py +107 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/artifact.py +9 -3
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/model.py +7 -1
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/.gitignore +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/LICENSE +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/SECURITY.md +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/contracts/analysis-report.schema.json +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/contracts/phoenix-runtime.json +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/contracts/review-candidates.schema.json +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/__init__.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/_atomic_write.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/adapters/__init__.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/adapters/base.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/adapters/otlp_spans.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/adapters/phoenix_export.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/analysis/__init__.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/analysis/ahu.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/analysis/diagnose.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/analysis/explain.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/analysis/memo.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/analysis/patterns.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/cli.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/input_validation.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/normalize.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/py.typed +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/review_candidates.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/sqlite_snapshot.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/store/__init__.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/store/base.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/store/in_memory.py +0 -0
- {agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/store/ladybug.py +0 -0
|
@@ -6,6 +6,31 @@ The **artifact** schema version and the **report**/**review-candidate** JSON sch
|
|
|
6
6
|
separate contracts from this version number; changes to any of them are called out
|
|
7
7
|
explicitly.
|
|
8
8
|
|
|
9
|
+
## 0.4.0 - 2026-10-14
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **LangGraph tasks paused by `interrupt()` are reported.** A task whose only write on the
|
|
14
|
+
thread's newest root checkpoint is `__interrupt__` — it paused and the history holds no
|
|
15
|
+
resume and no result — becomes an `unset` step with the new source value `interrupt`,
|
|
16
|
+
linked by `CAUSED_BY` to that checkpoint and named by the same task-id reconstruction as
|
|
17
|
+
failed tasks. The step describes the recorded history, not whether LangGraph would resume
|
|
18
|
+
the task now. An interrupt recorded beside a resume or an error and no result is ambiguous
|
|
19
|
+
in the checkpoint (under LangGraph 1.2.2 a resumed task that wrote nothing looks exactly
|
|
20
|
+
like one paused twice; 1.2.15 marks it finished), so it is disclosed at ingest instead. Trace `status` is unchanged: a pause is not an error.
|
|
21
|
+
- **Artifact schema version 5**, stamped by content: only an artifact carrying an
|
|
22
|
+
`interrupt` step declares it, so every other artifact serializes byte for byte as before.
|
|
23
|
+
0.3.1 and earlier refuse a version 5 artifact by name; this build reads versions 1 to 5,
|
|
24
|
+
and still rejects an envelope that declares less than its content needs.
|
|
25
|
+
|
|
26
|
+
### Known limitations
|
|
27
|
+
|
|
28
|
+
- Tasks scheduled but never started — the recursion limit, a killed process, an
|
|
29
|
+
`interrupt_before`/`interrupt_after` breakpoint — leave no write and are still not
|
|
30
|
+
reported. Telling them apart from finished work needs the compiled graph.
|
|
31
|
+
- An interrupt inside a subgraph is reported as the subgraph's node at the root, not as the
|
|
32
|
+
node inside the subgraph that paused.
|
|
33
|
+
|
|
9
34
|
## 0.3.1 - 2026-10-13
|
|
10
35
|
|
|
11
36
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agent-tracegraph
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Time-travel debugging and causal RCA for LangGraph agent traces, on a causal graph.
|
|
5
5
|
Project-URL: Homepage, https://github.com/memtomem/tracegraph
|
|
6
6
|
Project-URL: Repository, https://github.com/memtomem/tracegraph
|
|
@@ -289,9 +289,11 @@ and any change to them is called out in the [changelog](CHANGELOG.md).
|
|
|
289
289
|
|
|
290
290
|
Known limits:
|
|
291
291
|
|
|
292
|
-
- A LangGraph task that was scheduled but
|
|
293
|
-
|
|
294
|
-
such a run reads `ok` unless some other step recorded an
|
|
292
|
+
- A LangGraph task that was scheduled but never started (a run stopped by the recursion
|
|
293
|
+
limit, killed, or held at an `interrupt_before`/`interrupt_after` breakpoint) leaves no
|
|
294
|
+
write and is not reported, so such a run reads `ok` unless some other step recorded an
|
|
295
|
+
error. A task paused by `interrupt()` *is* reported (below), but only at the root graph:
|
|
296
|
+
the subgraph node is named, not the node inside it that paused.
|
|
295
297
|
- Phoenix exports carry no span links, so their artifacts are parent-only; additional fan-in
|
|
296
298
|
causes may be missing.
|
|
297
299
|
- Report privacy (`safe-v1`) applies to reports; OTLP and LangGraph artifacts keep the
|
|
@@ -376,9 +378,16 @@ cancels its siblings and records that through the same channel; a cancelled task
|
|
|
376
378
|
as an `unset` step with no message, because it was torn down rather than at fault. Only the
|
|
377
379
|
latest persisted error survives per task, so repeated attempts are not reconstructed, and
|
|
378
380
|
`Send` packets are nameable only where `langgraph.types` is importable — an ordinary install
|
|
379
|
-
discloses them instead.
|
|
380
|
-
|
|
381
|
-
|
|
381
|
+
discloses them instead. A task that called `interrupt()` and whose only write on the thread's
|
|
382
|
+
newest root checkpoint is that interrupt becomes an `unset` step with source `interrupt` under
|
|
383
|
+
that checkpoint: the recorded history holds no resume and no result for it. That describes the
|
|
384
|
+
history, not whether LangGraph would resume the task today. An interrupt recorded together with
|
|
385
|
+
a resume or an error and no result is ambiguous in the checkpoint itself — under LangGraph 1.2.2
|
|
386
|
+
a task resumed with nothing to write looks exactly like one paused a second time — so it is
|
|
387
|
+
disclosed at ingest, not reported. An artifact carrying interrupt steps declares artifact `schema_version` 5; one
|
|
388
|
+
whose derived tree relies on EdgeOrigin precedence declares 4; otherwise, an artifact carrying
|
|
389
|
+
derived task steps declares `schema_version` 3, and artifacts without them still serialize as
|
|
390
|
+
version 2, byte for byte.
|
|
382
391
|
Missing observed errors still do not
|
|
383
392
|
prove success: an artifact ingested by an older build, or one using an unrecognized checkpoint
|
|
384
393
|
layout, carries no derived step and looks exactly like a clean run, which is why the capture
|
|
@@ -258,9 +258,11 @@ and any change to them is called out in the [changelog](CHANGELOG.md).
|
|
|
258
258
|
|
|
259
259
|
Known limits:
|
|
260
260
|
|
|
261
|
-
- A LangGraph task that was scheduled but
|
|
262
|
-
|
|
263
|
-
such a run reads `ok` unless some other step recorded an
|
|
261
|
+
- A LangGraph task that was scheduled but never started (a run stopped by the recursion
|
|
262
|
+
limit, killed, or held at an `interrupt_before`/`interrupt_after` breakpoint) leaves no
|
|
263
|
+
write and is not reported, so such a run reads `ok` unless some other step recorded an
|
|
264
|
+
error. A task paused by `interrupt()` *is* reported (below), but only at the root graph:
|
|
265
|
+
the subgraph node is named, not the node inside it that paused.
|
|
264
266
|
- Phoenix exports carry no span links, so their artifacts are parent-only; additional fan-in
|
|
265
267
|
causes may be missing.
|
|
266
268
|
- Report privacy (`safe-v1`) applies to reports; OTLP and LangGraph artifacts keep the
|
|
@@ -345,9 +347,16 @@ cancels its siblings and records that through the same channel; a cancelled task
|
|
|
345
347
|
as an `unset` step with no message, because it was torn down rather than at fault. Only the
|
|
346
348
|
latest persisted error survives per task, so repeated attempts are not reconstructed, and
|
|
347
349
|
`Send` packets are nameable only where `langgraph.types` is importable — an ordinary install
|
|
348
|
-
discloses them instead.
|
|
349
|
-
|
|
350
|
-
|
|
350
|
+
discloses them instead. A task that called `interrupt()` and whose only write on the thread's
|
|
351
|
+
newest root checkpoint is that interrupt becomes an `unset` step with source `interrupt` under
|
|
352
|
+
that checkpoint: the recorded history holds no resume and no result for it. That describes the
|
|
353
|
+
history, not whether LangGraph would resume the task today. An interrupt recorded together with
|
|
354
|
+
a resume or an error and no result is ambiguous in the checkpoint itself — under LangGraph 1.2.2
|
|
355
|
+
a task resumed with nothing to write looks exactly like one paused a second time — so it is
|
|
356
|
+
disclosed at ingest, not reported. An artifact carrying interrupt steps declares artifact `schema_version` 5; one
|
|
357
|
+
whose derived tree relies on EdgeOrigin precedence declares 4; otherwise, an artifact carrying
|
|
358
|
+
derived task steps declares `schema_version` 3, and artifacts without them still serialize as
|
|
359
|
+
version 2, byte for byte.
|
|
351
360
|
Missing observed errors still do not
|
|
352
361
|
prove success: an artifact ingested by an older build, or one using an unrecognized checkpoint
|
|
353
362
|
layout, carries no derived step and looks exactly like a clean run, which is why the capture
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# the schema `kind` constants and the TRACEGRAPH_* environment variables — those are package
|
|
5
5
|
# identity, not distribution identity, and renaming them would break published contracts.
|
|
6
6
|
name = "agent-tracegraph"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Time-travel debugging and causal RCA for LangGraph agent traces, on a causal graph."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
{agent_tracegraph-0.3.1 → agent_tracegraph-0.4.0}/src/tracegraph/adapters/langgraph_checkpoint.py
RENAMED
|
@@ -54,6 +54,17 @@ steps carrying no message, and only the task that actually raised is an ``ERROR`
|
|
|
54
54
|
Only the *latest* persisted error survives per task id — a fail/fail/succeed sequence leaves
|
|
55
55
|
one record — so attempts are never reconstructed. Errors are recorded wherever they appear;
|
|
56
56
|
this module does not infer anything from their *absence*.
|
|
57
|
+
|
|
58
|
+
Interrupted tasks
|
|
59
|
+
-----------------
|
|
60
|
+
A node that calls ``interrupt()`` leaves an ``__interrupt__`` pending write on the checkpoint
|
|
61
|
+
that scheduled it, and LangGraph resumes from the thread's newest root checkpoint. A task whose
|
|
62
|
+
*only* write on that checkpoint is ``__interrupt__`` therefore never received a resume or
|
|
63
|
+
produced a result, and becomes an ``UNSET`` step with ``source=INTERRUPT``. That is a statement
|
|
64
|
+
about the recorded history, not a prediction that LangGraph would resume it now. Tasks that were
|
|
65
|
+
scheduled but never started (the recursion limit, a killed process, a static breakpoint) leave no
|
|
66
|
+
write at all and are not reported: telling them apart from finished work needs the compiled
|
|
67
|
+
graph, which this adapter does not have.
|
|
57
68
|
"""
|
|
58
69
|
|
|
59
70
|
from __future__ import annotations
|
|
@@ -108,6 +119,11 @@ _CONFIG_FIELDS = {name: TypeAdapter(str) for name in ("thread_id", "checkpoint_n
|
|
|
108
119
|
# them — so copying them is safer than importing a private module that may move.
|
|
109
120
|
_ERROR = "__error__"
|
|
110
121
|
_ERROR_SOURCE_NODE = "__error_source_node__"
|
|
122
|
+
_INTERRUPT = "__interrupt__"
|
|
123
|
+
_RESUME = "__resume__"
|
|
124
|
+
#: Writes that can sit beside an ``__interrupt__`` without recording whether the task is still
|
|
125
|
+
#: paused. Any other write is a result, and the task is no longer waiting.
|
|
126
|
+
_UNDECIDED_WITH_INTERRUPT = frozenset({_INTERRUPT, _RESUME, _ERROR, _ERROR_SOURCE_NODE})
|
|
111
127
|
_START = "__start__"
|
|
112
128
|
_TASKS = "__pregel_tasks"
|
|
113
129
|
_PULL = "__pregel_pull"
|
|
@@ -320,6 +336,11 @@ class LangGraphCheckpointAdapter:
|
|
|
320
336
|
ingest_warnings = self._derive_task_steps(
|
|
321
337
|
trace_id, steps, checkpoints, key_by_step_id, original_seq, writes_by_step, edges
|
|
322
338
|
)
|
|
339
|
+
ingest_warnings.extend(
|
|
340
|
+
self._derive_interrupt_steps(
|
|
341
|
+
trace_id, steps, checkpoints, key_by_step_id, original_seq, writes_by_step, edges
|
|
342
|
+
)
|
|
343
|
+
)
|
|
323
344
|
if malformed_writes:
|
|
324
345
|
ingest_warnings.append(
|
|
325
346
|
f"{malformed_writes} pending write(s) are not a (task id, channel, value) "
|
|
@@ -439,6 +460,92 @@ class LangGraphCheckpointAdapter:
|
|
|
439
460
|
)
|
|
440
461
|
return warnings
|
|
441
462
|
|
|
463
|
+
@classmethod
|
|
464
|
+
def _derive_interrupt_steps(
|
|
465
|
+
cls,
|
|
466
|
+
trace_id: str,
|
|
467
|
+
steps: dict[str, Step],
|
|
468
|
+
checkpoints: dict[str, dict],
|
|
469
|
+
key_by_step_id: dict[str, _CheckpointKey],
|
|
470
|
+
original_seq: dict[str, int],
|
|
471
|
+
writes_by_step: dict[str, dict[str, list[tuple[str, Any]]]],
|
|
472
|
+
edges: list[Edge],
|
|
473
|
+
) -> list[str]:
|
|
474
|
+
"""Add one UNSET step per task the head checkpoint records as interrupted, never resumed.
|
|
475
|
+
|
|
476
|
+
Only the thread's head — the newest root checkpoint, which is what LangGraph itself
|
|
477
|
+
resumes from — is read. An ``__interrupt__`` write on any earlier checkpoint was resumed,
|
|
478
|
+
forked past, or abandoned, and is history rather than an open pause.
|
|
479
|
+
|
|
480
|
+
A task is reported only when ``__interrupt__`` is its *only* write. Anything else that
|
|
481
|
+
accompanies it leaves the task's state unrecorded, and it is disclosed rather than
|
|
482
|
+
guessed: under LangGraph 1.2.2 a resumed task that returned nothing leaves
|
|
483
|
+
``{__interrupt__, __resume__}``, exactly like one that reached a second ``interrupt()``
|
|
484
|
+
(LangGraph overwrites the interrupt in place; 1.2.15 adds ``__no_writes__`` to the
|
|
485
|
+
first, which then reads as finished); a failure followed by a retry that interrupts leaves the same
|
|
486
|
+
``{__error__, __interrupt__}`` as the reverse order, because savers keep no order.
|
|
487
|
+
"""
|
|
488
|
+
root_ids = [sid for sid, (ns, _) in key_by_step_id.items() if ns == _ROOT_NS]
|
|
489
|
+
if not root_ids:
|
|
490
|
+
return []
|
|
491
|
+
head_id = max(root_ids, key=lambda sid: checkpoints[sid]["id"])
|
|
492
|
+
head = steps[head_id]
|
|
493
|
+
cp = checkpoints[head_id]
|
|
494
|
+
_, cid = key_by_step_id[head_id]
|
|
495
|
+
warnings: list[str] = []
|
|
496
|
+
undecided = 0
|
|
497
|
+
for task_id, task_writes in (writes_by_step.get(head_id) or {}).items():
|
|
498
|
+
if task_id == _NULL_TASK_ID:
|
|
499
|
+
continue
|
|
500
|
+
channels = {channel for channel, _ in task_writes}
|
|
501
|
+
if _INTERRUPT not in channels:
|
|
502
|
+
continue
|
|
503
|
+
if channels != {_INTERRUPT}:
|
|
504
|
+
if channels <= _UNDECIDED_WITH_INTERRUPT:
|
|
505
|
+
undecided += 1
|
|
506
|
+
continue
|
|
507
|
+
name = cls._resolve_task_name(cp, _ROOT_NS, original_seq[head_id], task_id, task_writes)
|
|
508
|
+
if name is None:
|
|
509
|
+
warnings.append(
|
|
510
|
+
f"checkpoint {cid}: interrupted task {task_id} could not be attributed to a "
|
|
511
|
+
"node; no candidate reproduced its task id, which is also what a change in "
|
|
512
|
+
"LangGraph's task-id algorithm would look like."
|
|
513
|
+
)
|
|
514
|
+
derived_id = f"{head_id}#interrupt:{task_id}"
|
|
515
|
+
if derived_id in steps:
|
|
516
|
+
raise ValueError(
|
|
517
|
+
f"derived interrupt step id {derived_id!r} collides with a checkpoint id "
|
|
518
|
+
"or another derived step"
|
|
519
|
+
)
|
|
520
|
+
steps[derived_id] = Step(
|
|
521
|
+
step_id=derived_id,
|
|
522
|
+
trace_id=trace_id,
|
|
523
|
+
seq=head.seq + 1,
|
|
524
|
+
ts=head.ts,
|
|
525
|
+
kind=StepKind.TOOL if name and _looks_like_tool_node(name) else StepKind.CHAIN,
|
|
526
|
+
source=StepSource.INTERRUPT,
|
|
527
|
+
name=name,
|
|
528
|
+
# The task has no outcome yet. The interrupt's value is whatever the node asked
|
|
529
|
+
# its caller, which is user content, so it is not carried as a message.
|
|
530
|
+
status=StepStatus.UNSET,
|
|
531
|
+
)
|
|
532
|
+
edges.append(
|
|
533
|
+
Edge(
|
|
534
|
+
type=EdgeType.CAUSED_BY,
|
|
535
|
+
src=derived_id,
|
|
536
|
+
dst=head_id,
|
|
537
|
+
# LangGraph keys the write to the head checkpoint itself.
|
|
538
|
+
origin=EdgeOrigin.CHECKPOINT_PARENT,
|
|
539
|
+
)
|
|
540
|
+
)
|
|
541
|
+
if undecided:
|
|
542
|
+
warnings.append(
|
|
543
|
+
f"checkpoint {cid}: {undecided} task(s) recorded an interrupt together with a "
|
|
544
|
+
"resume or an error and no result; the checkpoint does not record whether each "
|
|
545
|
+
"is still paused, so none is reported as interrupted."
|
|
546
|
+
)
|
|
547
|
+
return warnings
|
|
548
|
+
|
|
442
549
|
@staticmethod
|
|
443
550
|
def _writes_by_task(pending_writes: Any) -> tuple[dict[str, list[tuple[str, Any]]], int]:
|
|
444
551
|
"""Group a checkpoint's pending writes by task id, in first-seen order.
|
|
@@ -19,13 +19,13 @@ from tracegraph.input_validation import loads as json_loads
|
|
|
19
19
|
from tracegraph.model import NormalizedTrace, StepSource
|
|
20
20
|
|
|
21
21
|
#: Bump when the on-disk shape changes incompatibly.
|
|
22
|
-
ARTIFACT_SCHEMA_VERSION =
|
|
22
|
+
ARTIFACT_SCHEMA_VERSION = 5
|
|
23
23
|
|
|
24
24
|
#: Versions this build can read. 1 needs a migration (see ``_migrate_v1``); 2 -> 3 is the
|
|
25
25
|
#: identity, because 3 adds only a new ``Step.source`` value and changes nothing existing.
|
|
26
26
|
#: 4 signals that TREE_PARENT was derived under EdgeOrigin precedence rather than
|
|
27
|
-
#: legacy temporal-only selection.
|
|
28
|
-
_READABLE_VERSIONS = (1, 2, 3, ARTIFACT_SCHEMA_VERSION)
|
|
27
|
+
#: legacy temporal-only selection. 5 adds only the ``interrupt`` ``Step.source`` value.
|
|
28
|
+
_READABLE_VERSIONS = (1, 2, 3, 4, ARTIFACT_SCHEMA_VERSION)
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def _required_version(nt: NormalizedTrace) -> int:
|
|
@@ -38,6 +38,9 @@ def _required_version(nt: NormalizedTrace) -> int:
|
|
|
38
38
|
bump would invalidate digests already exported in review-candidate reports for artifacts
|
|
39
39
|
whose content did not change at all.
|
|
40
40
|
|
|
41
|
+
* An artifact that carries an interrupt step is stamped 5, whatever its projection: a
|
|
42
|
+
v5 reader also reads the v4 projection, and an older reader would otherwise fail on
|
|
43
|
+
the unknown enum value instead of refusing the version by name.
|
|
41
44
|
* An artifact whose derived TREE_PARENT relies on EdgeOrigin priority is stamped 4,
|
|
42
45
|
so older readers (which used temporal-only parent selection) refuse it with an
|
|
43
46
|
explicit "unsupported artifact schema_version 4" rather than failing canonical
|
|
@@ -47,6 +50,9 @@ def _required_version(nt: NormalizedTrace) -> int:
|
|
|
47
50
|
"""
|
|
48
51
|
from tracegraph.normalize import requires_origin_priority_projection
|
|
49
52
|
|
|
53
|
+
interrupt = StepSource.INTERRUPT
|
|
54
|
+
if any(step.source is interrupt for step in nt.steps):
|
|
55
|
+
return 5
|
|
50
56
|
if requires_origin_priority_projection(nt):
|
|
51
57
|
return 4
|
|
52
58
|
task = StepSource.TASK
|
|
@@ -54,10 +54,15 @@ class StepKind(str, Enum):
|
|
|
54
54
|
class StepSource(str, Enum):
|
|
55
55
|
"""How the step entered the trace (mirrors LangGraph ``metadata.source``).
|
|
56
56
|
|
|
57
|
-
``TASK``
|
|
57
|
+
``TASK`` and ``INTERRUPT`` do *not* come from ``metadata.source``. ``TASK`` marks a step
|
|
58
58
|
derived from a checkpoint's **pending writes** (one LangGraph task) rather than from a
|
|
59
59
|
checkpoint itself. It is explicit provenance, not a naming convention — ``step_id`` is an
|
|
60
60
|
unrestricted string, so nothing about an id may be read as evidence of how a step was made.
|
|
61
|
+
|
|
62
|
+
``INTERRUPT`` is likewise derived from pending writes: a LangGraph task that called ``interrupt()`` and
|
|
63
|
+
for which the recorded history holds no resume and no result. It states what the
|
|
64
|
+
checkpoints record, not that LangGraph would resume the task today — a thread re-run under
|
|
65
|
+
a revised graph can finish without writing a checkpoint, leaving the record unchanged.
|
|
61
66
|
"""
|
|
62
67
|
|
|
63
68
|
INPUT = "input"
|
|
@@ -65,6 +70,7 @@ class StepSource(str, Enum):
|
|
|
65
70
|
UPDATE = "update"
|
|
66
71
|
FORK = "fork"
|
|
67
72
|
TASK = "task"
|
|
73
|
+
INTERRUPT = "interrupt"
|
|
68
74
|
|
|
69
75
|
|
|
70
76
|
class StepStatus(str, Enum):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|