feat(instrument): Standardize handoff seq + context hash + preview across 5 lighter adapters (cross-poll #7)#112
Closed
mmercuri wants to merge 1 commit into
Conversation
…ross 5 lighter adapters Cross-pollination item #7 from the adapter audit. Mature adapters (LangChain, LangGraph, CrewAI, AutoGen) emit agent.handoff events with consistent metadata: monotonic handoff_seq, SHA-256 context_hash of canonical-JSON state, and bounded context_preview text. The 5 target lighter adapters either omitted these fields or implemented them inconsistently per-adapter. Adds a shared helper at src/layerlens/instrument/adapters/_base/handoff.py: - compute_context_hash() - SHA-256 of canonical-JSON-encoded state, default=str fallback so non-JSON-native values never raise. - make_preview() - length-bounded preview, U+2026 ellipsis on truncation, str() coercion, returns "<unrepresentable>" on __str__ failure. - HandoffSequencer - thread-safe monotonic counter scoped per adapter instance; concurrent agent runs draw from one lock- protected source. - HandoffMetadata dataclass + build_handoff_payload() - assembles the full standardised payload in one call. Standard fields cannot be overridden via the extra= merge. Wires all 5 target adapters to the new helper: - agno: 2 emit sites (team-delegation in _extract_run_details + on_handoff hook). - openai_agents: 2 emit sites (HandoffSpanData span path + on_handoff hook); seqs stay monotonic across detection paths. - llama_index: 1 emit site (on_handoff hook). - google_adk: 1 emit site (transfer_to_agent on_handoff hook). - ms_agent_framework: 2 emit sites (group-chat-turn detection in _process_message + on_handoff hook); single shared sequencer. Tests: - tests/instrument/adapters/_base/test_handoff.py (33 tests): helper correctness, canonical-key-order hashing, None handling, non-JSON-native value coercion, ellipsis truncation, default 256-char cap, faulty __str__ handling, 1-indexed monotonic seqs, 20-thread x 200-iteration concurrency stress, sequencer reset, instance independence, payload assembly, preview fallback, extras-cannot-clobber-standard-fields, re-export wiring. - 6 new per-adapter integration tests verify standardised metadata appears at each emit site (12 -> 13/14 tests per adapter). Documentation: docs/adapters/handoff-standardization.md describes the contract, lists all standardised adapters, and gives the authoring guide for new adapters. mypy --strict on the helper file: clean. ruff on _base + tests: clean. 99 tests pass (33 helper + 66 across the 5 adapter test files).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Cross-pollination item #7 from the adapter audit
(
A:/tmp/adapter-cross-pollination-audit.md§2.5). Mature adapters(LangChain, LangGraph, CrewAI, AutoGen) emit
agent.handoffeventswith a consistent metadata contract — monotonic
handoff_seq,SHA-256
context_hashof canonical-JSON state, and boundedcontext_previewtext — but the 5 target lighter adapters eitheromitted these fields or implemented them inconsistently per-adapter.
This PR introduces a shared helper at
src/layerlens/instrument/adapters/_base/handoff.pyand rewires all5 target adapters to use it.
What changed
Shared helper (new file)
src/layerlens/instrument/adapters/_base/handoff.pycompute_context_hash(state)— deterministic SHA-256 of canonical-JSON-encoded state.
default=strfallback so non-JSON-native values(sets, datetimes, custom objects) never raise.
Noneand{}bothhash to the digest of
"{}"so the field is never absent.make_preview(content, max_chars=256)— length-bounded preview withU+2026 ellipsis on truncation. Returns
"<unrepresentable>"when__str__raises rather than propagating.HandoffSequencer— thread-safe monotonic counter, 1-indexed,scoped per adapter instance. Lock-protected
next()so concurrentagent runs (asyncio gathers, threadpool workers, callbacks firing
from multiple OS threads) all draw unique IDs from one source.
HandoffMetadatadataclass +build_handoff_payload()— assemblesthe full payload in one call. Standard fields cannot be
overridden via the
extra=merge (passingextra={"handoff_seq": 999}does not shadow the real seq).Re-exported from
layerlens.instrument.adapters._base.Per-adapter wiring (5 adapters)
agno_extract_run_details+on_handoffhook.openai_agentsHandoffSpanDataspan path +on_handoffhook. Single shared sequencer keeps seqs monotonic across detection paths.llama_indexon_handoffhook (workflow handoff).google_adktransfer_to_agentcallback path.ms_agent_framework_process_message+on_handoffhook. Single shared sequencer.Removed the previously-duplicated bespoke
hashlib.sha256(...)callsand ad-hoc
[:500]truncations from each adapter — now flows throughthe shared helper.
Tests
tests/instrument/adapters/_base/test_handoff.py— 33 unittests covering helper correctness:
non-JSON-native value coercion / determinism.
faulty-
__str__resilience.reset / instance independence.
extras-cannot-clobber-standards / parametric coverage.
_base/__init__.py.6 new per-adapter integration tests — each adapter now has a
test_*_emits_standardized_metadatatest verifying:handoff_seqadvances monotonically across multiple emit calls.context_hashstarts with"sha256:"and differs for differentcontexts.
context_previewis present and length-bounded.timestampis present and ISO 8601.frameworkfield reflects the originating adapter.Documentation
docs/adapters/handoff-standardization.md— contract spec, helperAPI reference, adapter authoring guide, and inventory of which
adapters currently follow the contract.
Test results
uv run mypy --strict src/layerlens/instrument/adapters/_base/handoff.py— clean.uv run ruff check src/layerlens/instrument/adapters/_base tests/instrument/adapters/_base— clean.Test plan
uv run pytest tests/instrument/adapters/_base/test_handoff.py -x)uv run pytest tests/instrument/adapters/frameworks/test_{agno,ms_agent_framework,openai_agents,llama_index,google_adk}_adapter.py -x)_base+tests/_baseOut of scope
Semantic Kernel) keep their bespoke implementations of the same
contract for now — migration to the shared helper is deferred to a
follow-up so their independent test suites stay isolated. The
contract surface they emit is already compatible.
emission) are tracked separately.