Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8573622
feat: record recursive RLM lineage
hallerite Aug 26, 2026
b67d227
test: cover resumed compaction manifests
hallerite Aug 26, 2026
cdb4822
feat: generalize lineage as an ACP extension
hallerite Aug 27, 2026
9b82ecd
fix: enforce a single ACP lineage root
hallerite Aug 27, 2026
34e9387
refactor: simplify lineage session views
hallerite Aug 27, 2026
484ceed
chore: update nano-RLM lineage pin
hallerite Aug 27, 2026
6c54c5c
fix: allow lineage on streaming calls
hallerite Aug 27, 2026
6dca9d3
fix: preserve provider idempotency keys
hallerite Aug 27, 2026
b2a4e48
fix: key rlm installs by version
hallerite Aug 27, 2026
8465071
fix: require aiohttp>=3.14.1 (interception uses web.RequestKey) (#2451)
hallerite Aug 27, 2026
f62cee6
refactor: join lineage by request ID (#2452)
hallerite Aug 27, 2026
0b50722
chore: pin merged nano-rlm lineage
hallerite Aug 27, 2026
a1a05a9
refactor: separate branches from lineage
hallerite Aug 27, 2026
cee1ba2
Merge remote-tracking branch 'origin/main' into codex/rlm-lineage
hallerite Aug 27, 2026
deb4fba
refactor: resolve ACP semantic edges
hallerite Aug 27, 2026
6a56992
Merge remote-tracking branch 'origin/main' into codex/rlm-lineage
hallerite Aug 27, 2026
d55cca5
feat: preserve semantic continuations
hallerite Aug 27, 2026
ea11820
fix: handle deep manifests and committed retries
hallerite Aug 27, 2026
7f3ce1b
refactor: attach semantics to message nodes
hallerite Aug 27, 2026
4bf8f0b
chore: update nano-rlm lineage pin
hallerite Aug 28, 2026
4b4e074
refactor: simplify semantic edge vocabulary
hallerite Aug 28, 2026
16df4b3
chore: pin merged nano-rlm lineage
hallerite Aug 28, 2026
ce54823
refactor: apply semantic edges incrementally
hallerite Aug 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
356 changes: 356 additions & 0 deletions tests/v1/test_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@
import verifiers.v1 as vf
from verifiers.v1.agent import Interaction
from verifiers.v1.graph import MessageNode
from verifiers.v1.harnesses.rlm.harness import (
RLM_SESSION_METADATA_KEY,
RLMHarness,
RLMHarnessConfig,
)
from verifiers.v1.lineage import (
ACP_LINEAGE_HEADERS,
ACP_LINEAGE_METADATA_KEY,
extract_call_lineage,
)
from verifiers.v1.rollout import Rollout, RolloutTimeouts
from verifiers.v1.types import AssistantMessage, UserMessage

Expand Down Expand Up @@ -170,3 +180,349 @@ def test_wire_trace_round_trip():

# the env-server wire form (a plain model_dump) loads too
assert vf.WireTrace.model_validate(tr.model_dump()).num_branches == 2


def _lineage_manifest(
root_session_id: str, root_status: str = "completed"
) -> vf.LineageManifest:
return vf.LineageManifest(
sessions=[
vf.LineageSession(
session_id=root_session_id,
depth=0,
initial_context_id="ctx-root",
status=root_status,
),
vf.LineageSession(
session_id="child",
parent_session_id=root_session_id,
depth=1,
initial_context_id="ctx-child",
spawned_by_request_id="root-turn",
status="completed",
),
],
contexts=[
vf.LineageContext(
context_id="ctx-root", session_id=root_session_id, transition="root"
),
vf.LineageContext(
context_id="ctx-child", session_id="child", transition="spawn"
),
vf.LineageContext(
context_id="ctx-root-2",
session_id=root_session_id,
previous_context_id="ctx-root",
transition="compact",
compaction_id="compact-1",
),
],
compactions=[
vf.LineageCompaction(
compaction_id="compact-1",
session_id=root_session_id,
source_context_id="ctx-root",
target_context_id="ctx-root-2",
summary_request_id="root-compact",
status="completed",
)
],
requests=[
vf.LineageRequest(
request_id="root-turn",
session_id=root_session_id,
context_id="ctx-root",
kind="turn",
),
vf.LineageRequest(
request_id="child-turn",
session_id="child",
context_id="ctx-child",
kind="turn",
),
vf.LineageRequest(
request_id="root-compact",
session_id=root_session_id,
context_id="ctx-root",
kind="compaction",
compaction_id="compact-1",
),
vf.LineageRequest(
request_id="root-after",
session_id=root_session_id,
context_id="ctx-root-2",
kind="turn",
compaction_id="compact-1",
),
],
)


def test_exact_lineage_groups_interleaved_calls_and_round_trips():
"""Recursive sessions are grouped by exact IDs, not call adjacency or graph shape."""
tr = vf.Trace(
agent=vf.AgentInfo(config=vf.AgentConfig()),
task=vf.TraceTask(type="Task", data=vf.TaskData(idx=0, prompt="q")),
nodes=[
MessageNode(parent=None, message=UserMessage(content="root")),
MessageNode(
parent=0, message=AssistantMessage(content="root turn"), sampled=True
),
MessageNode(parent=None, message=UserMessage(content="child")),
MessageNode(
parent=2, message=AssistantMessage(content="child turn"), sampled=True
),
MessageNode(parent=None, message=UserMessage(content="summarize")),
MessageNode(
parent=4, message=AssistantMessage(content="summary"), sampled=True
),
MessageNode(parent=None, message=UserMessage(content="resume")),
MessageNode(
parent=6, message=AssistantMessage(content="done"), sampled=True
),
],
)
tr.calls = [
vf.ModelCall(
node=1,
lineage=vf.CallLineage(
request_id="root-turn",
session_id=tr.id,
context_id="ctx-root",
transition="root",
depth=0,
),
),
vf.ModelCall(
node=3,
lineage=vf.CallLineage(
request_id="child-turn",
session_id="child",
parent_session_id=tr.id,
context_id="ctx-child",
transition="spawn",
depth=1,
),
),
vf.ModelCall(
node=5,
lineage=vf.CallLineage(
request_id="root-compact",
session_id=tr.id,
context_id="ctx-root",
transition="root",
compaction_id="compact-1",
depth=0,
),
),
vf.ModelCall(
node=7,
lineage=vf.CallLineage(
request_id="root-after",
session_id=tr.id,
context_id="ctx-root-2",
previous_context_id="ctx-root",
transition="compact",
compaction_id="compact-1",
depth=0,
),
),
]

manifest = _lineage_manifest(tr.id)
manifest.sessions.append(
vf.LineageSession(
session_id="idle-child",
parent_session_id=tr.id,
depth=1,
initial_context_id="ctx-idle-child",
spawned_by_request_id="root-turn",
status="cancelled",
)
)
manifest.contexts.append(
vf.LineageContext(
context_id="ctx-idle-child",
session_id="idle-child",
transition="spawn",
)
)
tr.reconcile_lineage(vf.LineageManifest.model_validate(manifest.model_dump()))
calls_by_session = tr.calls_by_session
assert list(calls_by_session) == [tr.id, "child", "idle-child"]
assert [call.lineage.request_id for call in calls_by_session[tr.id]] == [
"root-turn",
"root-compact",
"root-after",
]
assert [call.lineage.request_id for call in calls_by_session["child"]] == [
"child-turn"
]
assert calls_by_session["idle-child"] == []
assert [branch.index for branch in tr.branches_by_session[tr.id]] == [0, 2, 3]
assert [branch.index for branch in tr.branches_by_session["child"]] == [1]
assert tr.branches_by_session["idle-child"] == []
assert tr.branches[1].session_ids == ("child",)
assert tr.branches[3].context_ids == ("ctx-root-2",)
assert tr.branches[3].compaction_ids == ("compact-1",)

restored = vf.WireTrace.model_validate_json(tr.model_dump_json())
assert restored.lineage == tr.lineage
assert [call.lineage for call in restored.calls] == [
call.lineage for call in tr.calls
]
assert list(restored.calls_by_session) == [tr.id, "child", "idle-child"]

# The base ACP layer consumes the generic manifest before the harness's own metadata.
harness = RLMHarness(RLMHarnessConfig(id="rlm"))
turn_metadata = {
ACP_LINEAGE_METADATA_KEY: _lineage_manifest(
restored.id, root_status="running"
).model_dump(mode="json"),
RLM_SESSION_METADATA_KEY: {
"session_id": restored.id,
"metrics": {"turns": 4},
},
}
harness._consume_protocol_metadata(restored, turn_metadata)
harness.acp_turn_result(
restored, vf.ACPTurn(reply="done", response_metadata=turn_metadata)
)
assert restored.metrics["turns"] == 4
assert restored.lineage.sessions[0].status == "running"

# session/close carries the terminal snapshot; cumulative metrics overwrite by key.
close_metadata = {
ACP_LINEAGE_METADATA_KEY: _lineage_manifest(restored.id).model_dump(
mode="json"
),
RLM_SESSION_METADATA_KEY: {
"session_id": restored.id,
"metrics": {"turns": 4},
},
}
harness._consume_protocol_metadata(restored, close_metadata)
harness.acp_close_result(restored, close_metadata)
assert restored.metrics["turns"] == 4
assert restored.lineage.sessions[0].status == "completed"

# A failed provider exchange and its SDK retry share one logical request ID.
restored.calls.append(
vf.ModelCall(
lineage=restored.calls[0].lineage, error=vf.Error(type="E", message="x")
)
)
restored.reconcile_lineage(_lineage_manifest(restored.id))


def test_lineage_headers_are_complete_validated_and_stripped():
headers = {
"Authorization": "Bearer local",
"Idempotency-Key": "request-1",
"X-ACP-Lineage-Request-ID": "request-1",
"X-ACP-Lineage-Session-ID": "session-1",
"X-ACP-Lineage-Context-ID": "context-1",
"X-ACP-Lineage-Transition": "root",
"X-ACP-Lineage-Depth": "0",
"OpenAI-Beta": "feature",
}
lineage, forwarded = extract_call_lineage(headers)
assert lineage == vf.CallLineage(
request_id="request-1",
session_id="session-1",
context_id="context-1",
transition="root",
depth=0,
)
assert not ACP_LINEAGE_HEADERS.intersection(map(str.lower, forwarded))
assert forwarded["OpenAI-Beta"] == "feature"

absent, unchanged = extract_call_lineage({"OpenAI-Beta": "feature"})
assert absent is None and unchanged == {"OpenAI-Beta": "feature"}

with pytest.raises(ValueError, match="missing X-ACP-Lineage-Context-ID"):
extract_call_lineage(
{
"Idempotency-Key": "request-1",
"X-ACP-Lineage-Request-ID": "request-1",
"X-ACP-Lineage-Session-ID": "session-1",
"X-ACP-Lineage-Transition": "root",
"X-ACP-Lineage-Depth": "0",
}
)


def test_acp_lineage_metadata_is_optional_and_agent_session_ids_are_opaque():
trace = vf.Trace(
agent=vf.AgentInfo(config=vf.AgentConfig()),
task=vf.TraceTask(type="Task", data=vf.TaskData(idx=0, prompt="q")),
)
harness = RLMHarness(RLMHarnessConfig(id="rlm"))

harness._consume_protocol_metadata(trace, {})

assert trace.lineage is None

harness._consume_protocol_metadata(
trace,
{
ACP_LINEAGE_METADATA_KEY: _lineage_manifest("agent-owned-root").model_dump(
mode="json"
)
},
)

assert trace.lineage is not None
assert trace.lineage.sessions[0].session_id == "agent-owned-root"


def test_lineage_manifest_enforces_root_and_compaction_consistency():
manifest = _lineage_manifest("root-session").model_dump(mode="json")
resumed = next(
request
for request in manifest["requests"]
if request["request_id"] == "root-after"
)
resumed.pop("compaction_id")

with pytest.raises(ValueError, match="missing its context compaction"):
vf.LineageManifest.model_validate(manifest)

manifest = _lineage_manifest("root-session").model_dump(mode="json")
manifest["sessions"].append(
{
"session_id": "second-root",
"depth": 0,
"initial_context_id": "second-root-context",
"status": "completed",
}
)
manifest["contexts"].append(
{
"context_id": "second-root-context",
"session_id": "second-root",
"transition": "root",
}
)

with pytest.raises(ValueError, match="exactly one root session"):
vf.LineageManifest.model_validate(manifest)

manifest = _lineage_manifest("root-session").model_dump(mode="json")
manifest["compactions"][0]["status"] = "failed"
manifest["contexts"] = [
context
for context in manifest["contexts"]
if context["context_id"] != "ctx-root-2"
]
manifest["requests"] = [
request
for request in manifest["requests"]
if request["request_id"] != "root-after"
]
with pytest.raises(ValueError, match="cannot have a target context"):
vf.LineageManifest.model_validate(manifest)

manifest["compactions"][0].pop("target_context_id")
failed = vf.LineageManifest.model_validate(manifest)
assert failed.compactions[0].target_context_id is None
16 changes: 16 additions & 0 deletions verifiers/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@
RubricJudge,
RubricJudgeConfig,
)
from verifiers.v1.lineage import (
ACP_LINEAGE_METADATA_KEY,
CallLineage,
LineageCompaction,
LineageContext,
LineageManifest,
LineageRequest,
LineageSession,
)
from verifiers.v1.mcp import (
SharedToolsetConfig,
Toolset,
Expand Down Expand Up @@ -226,6 +235,13 @@
"EvalRunInfo",
"EvalWorkInfo",
"ModelCall",
"ACP_LINEAGE_METADATA_KEY",
"CallLineage",
"LineageManifest",
"LineageSession",
"LineageContext",
"LineageCompaction",
"LineageRequest",
"PolicyEvent",
"TrainRunInfo",
"TrainWorkInfo",
Expand Down
Loading
Loading