Bump spec submodule to v0.26.1 + Langfuse observer audit fixes#85
Merged
chris-colinsky merged 4 commits intoMay 28, 2026
Merged
Conversation
Submodule bump v0.26.0 -> v0.26.1 picks up proposal 0035's three
Langfuse graph-topology fixtures (031 / 032 / 033). Pinned spec
version, __spec_version__, conformance.toml, and the smoke test all
move together.
Two observer bugs surfaced during the placement audit and are fixed:
* entry_node correctness: when the outer graph's entry is a
SubgraphNode, the first event the observer sees comes from inside
the subgraph (event.namespace = (wrapper, inner),
event.node_name = inner). Old code used event.node_name for both
trace.metadata.entry_node and trace.name, producing the inner
node's name. Walk back to event.namespace[0] so the outer entry
name is the one recorded.
* Detached-mode link observation no longer carries subgraph_name.
Per discuss-observability-langfuse-mapping msg 07: in detached
mode the wrapper role migrates to the detached trace; the parent
trace's link observation IS the SubgraphNode span (no wrapper
role) and must not carry subgraph_name. The detached trace's
dispatch observation still carries it.
Harness extension: tests/conformance/test_observability_langfuse.py
now handles subgraph / fan_out / detached-trace topology shapes via
the cross-capability adapter.build_graph helper, plus multi-trace
assertions for fixtures using langfuse_traces: (plural). Both 022/023/
024 and the new topology fixtures share the same _run_case path; the
branch is on whether the fixture carries any topology constructs.
Fixtures 031 / 032 / 033 themselves stay deferred for now: they assert
metadata.subgraph_name = <subgraph identity> while both observers
emit subgraph_name = <wrapper node name>. Resolution queued in coord
thread clarify-subgraph-name-semantics; un-defer (and apply any
required impl change) once spec answers.
Manifest entry for proposal 0035 added as status=not-yet, matching
the 0031-0034 convention; the release PR will flip all five together.
There was a problem hiding this comment.
Pull request overview
Updates the project to spec v0.26.1 and extends the Langfuse conformance harness to support upcoming graph-topology fixtures (subgraphs, fan-out, detached traces), along with two LangfuseObserver correctness fixes found during a topology/placement audit.
Changes:
- Bump pinned spec version references from 0.26.0 → 0.26.1 across runtime, tests, and docs.
- Extend
tests/conformance/test_observability_langfuse.pyto build topology-shaped graphs via the shared conformance adapter and to assert multi-trace (langfuse_traces) fixtures. - Fix LangfuseObserver trace entry naming for “entry is a SubgraphNode” and adjust detached-mode link observation metadata (no
subgraph_nameon the parent-trace link observation).
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_smoke.py | Updates the expected __spec_version__ to 0.26.1. |
| tests/conformance/test_observability_langfuse.py | Adds topology-graph build path + multi-trace assertions and generalized placeholder handling. |
| tests/conformance/test_fixture_parsing.py | Defers new Langfuse topology fixtures from the typed round-trip parser (handled by Langfuse-specific harness). |
| src/openarmature/observability/langfuse/observer.py | Fixes entry node naming when entry is a subgraph; adjusts detached-mode link metadata. |
| src/openarmature/AGENTS.md | Updates embedded spec version reference to 0.26.1. |
| src/openarmature/init.py | Bumps __spec_version__ constant to 0.26.1. |
| pyproject.toml | Bumps [tool.openarmature].spec_version to 0.26.1. |
| conformance.toml | Adds proposal 0035 as not-yet in the v0.26.1 batch commentary. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Implements the coord-thread `clarify-subgraph-name-semantics` resolution (msg 02): `metadata.subgraph_name` carries the compiled subgraph's identity rather than the wrapper node name. Engine surface: - `SubgraphNode` gains `subgraph_identity: str | None = None` (optional, BC-preserving). - `FanOutConfig` gains `subgraph_identity: str | None = None` for the symmetric fan-out case; threaded through `GraphBuilder.add_fan_out_node`. - `_InvocationContext` gains `subgraph_identities: tuple[str | None, ...]`, parallel to `namespace_prefix` — index `i` is the identity of the wrapper at `namespace_prefix[i]`. `descend_into_subgraph` / `descend_into_fan_out_instance` / `descend_into_parallel_branch` extend the chain. - `NodeEvent.subgraph_identities: tuple[str | None, ...] = ()` carries the chain at event-emission time so observers can read the identity at each wrapper depth. Observer surface: - Langfuse `LangfuseObserver` and OTel `OTelObserver` both gain `_subgraph_identity_at(event, depth)` and emit identity (or empty string when None) as `metadata.subgraph_name` / `openarmature. subgraph.name` on wrapper observations / spans, per-instance fan-out dispatch observations, and detached-trace wrapper observations. - Detached subgraph's wrapper observation name now also uses the identity (falling back to the wrapper node name when None). - Langfuse wrapper observation synthesis now carries `namespace`, `step`, and `attempt_index` metadata (step from the first inner event; attempt_index hardcoded 0 since wrappers don't retry). Conformance adapter: - `_TracingSubgraphNode` and `_add_fan_out_node` set `subgraph_identity` from the fixture's `subgraphs:` block key when compiling, so every fixture-built subgraph carries its declared identity. Updates the pre-existing OTel-side test on fixture 002 to assert `openarmature.subgraph.name == "inner"` (identity) rather than `"outer_sub"` (wrapper node name) — the prior assertion was inconsistent with Option A semantics; the spec-side fixture 002 YAML doesn't assert this attribute, so the python test was a stricter local check that needed correction. Langfuse fixtures 031/032/033 stay deferred pending two additional spec/fixture ambiguities surfaced while wiring this up: (a) the `step` value on `outer_out` (fixture 031 says 2, but graph-engine §6 says inner-subgraph node executions increment the same counter so the engine emits 3) and (b) whether the `namespace` metadata on detached-trace inner observations should be rewritten to use the subgraph identity at the wrapper position. Both queued in coord thread `clarify-subgraph-name-semantics` msg 03.
Updates the module-header comment in tests/conformance/test_observability_langfuse.py to clarify that the harness *infrastructure* supports the 031/032/033 topology shapes while activation is currently deferred. Drops the forward-reference to fixture 027 in an internal comment that referenced fixtures this module doesn't currently run. Adds two focused unit tests for the observer behavior changes that were only exercised by deferred conformance fixtures: - test_entry_node_resolves_to_wrapper_when_entry_is_subgraph pins the entry_node correctness fix: when the outer entry IS a SubgraphNode, the first event arrives from inside the subgraph (event.namespace = (wrapper, inner), node_name = inner). Both trace.name and trace.metadata.entry_node must resolve to the wrapper, not the inner node. - test_detached_subgraph_subgraph_name_placement pins the audit fix from coord-thread msg 07: in detached mode the wrapper role migrates to the detached trace. The parent trace's link observation must not carry subgraph_name; the detached trace's dispatch observation must. Both tests sit alongside the existing dispatch-synthesis tests.
Clarifies the detached-trace wrapper comment in the Langfuse
observer to make the BC-path asymmetry explicit: the observation
NAME falls back to the wrapper node name when subgraph_identity
is empty (UX choice — better than an empty-string observation
name), but metadata.subgraph_name stays empty per §5.3's "empty
string when no identity is tracked" contract. This lets dashboard
filters on `metadata.subgraph_name == "X"` match only wrappers
explicitly registered with `subgraph_identity = "X"`, not every
wrapper that happens to be named X.
Fixes a latent bug in the multi-trace conformance assertion path:
_assert_multi_traces was passing `expected_invariants={}` to
_assert_trace, dropping per-trace invariants like
correlation_id_consistency. Fixture 033 doesn't currently use any
per-trace invariants, so the bug was dormant — any future
multi-trace fixture using correlation_id_consistency would
silently pass when broken.
Adds _PER_TRACE_INVARIANTS to classify invariants; the multi-trace
runner filters expected_invariants to that set when delegating
per-Trace assertions, leaving cross-trace invariants to
_assert_multi_traces.
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
Closes out the spec response on
clarify-subgraph-name-semantics(Option A:subgraph_namecarries the compiled subgraph's identity). Wiressubgraph_identitythroughSubgraphNode/FanOutConfig/_InvocationContext/NodeEventto both observers, plus two audit fixes from msg 07 ofdiscuss-observability-langfuse-mappingand the harness extension for graph-topology fixtures.Three of the new fixtures (031/032/033) stay deferred pending two additional spec/fixture ambiguities surfaced while implementing — see "Open questions" below; both queued in
clarify-subgraph-name-semanticsmsg 03.Observer bug fixes (msg 07 audit)
entry_nodecorrectness (observer.py:330) — when the outer graph's entry IS aSubgraphNode, the first event the observer sees comes from inside the subgraph. Walks back toevent.namespace[0]so the outer entry name is recorded.subgraph_name— per msg 07, in detached mode the wrapper role migrates to the detached trace; the parent trace's link observation IS theSubgraphNodespan and must not carrysubgraph_name.subgraph_identity wiring (Option A)
SubgraphNode.subgraph_identity: str | None = None(BC-preserving constructor kwarg)FanOutConfig.subgraph_identity: str | None = None(symmetric for fan-out)_InvocationContext.subgraph_identities: tuple[str | None, ...]— chain parallel tonamespace_prefix;descend_into_subgraph/descend_into_fan_out_instance/descend_into_parallel_branchextend the chainNodeEvent.subgraph_identities: tuple[str | None, ...] = ()— observers read at_subgraph_identity_at(event, depth)metadata.subgraph_name(Langfuse) /openarmature.subgraph.name(OTel) on wrapper observations / spans, per-instance fan-out dispatch observations, and detached-trace wrapper observationsnamenow also uses the identity (falls back to wrapper node name when None)namespace,step,attempt_indexmetadata (step from first inner event; attempt_index hardcoded 0)subgraph_identityfrom the fixture'ssubgraphs:block keyopenarmature.subgraph.name == "inner"(identity) rather than"outer_sub"(wrapper node name) — old assertion was inconsistent with Option A; spec-side fixture 002 YAML doesn't assert this attributeHarness extension
tests/conformance/test_observability_langfuse.pynow handles subgraph / fan_out / detached-trace topology shapes via the cross-capabilityadapter.build_graphhelper, plus multi-trace assertions for thelangfuse_traces:(plural) shape used by 033.conformance.toml
[proposals."0035"]withstatus = "not-yet", matches the 0031-0034 convention. Release PR flips them all toimplemented since = "0.10.0"together.Open questions
Two new fixture/spec ambiguities surfaced while wiring this up; both block fixture activation, both queued in coord thread
clarify-subgraph-name-semanticsmsg 03:stepvalue onouter_outin fixture 031. Fixture assertsstep: 2, but graph-engine §6 says inner-subgraph node executions increment the same counter, so the engine emitsstep: 3. Likely a fixture draft artifact.namespacerewrite for detached-trace inner observations. Fixture 033 case 1 expectsnamespace: ["long_running_workflow", "step"](uses subgraph identity for the wrapper position), but the engine fires("dispatch", "step")(wrapper node name) and the observer emits verbatim. Spec needs to confirm whether the wrapper-role migration in detached mode should also rewritenamespace, or whether the fixture should match the existing convention.Test plan
ruff check/ruff format --checkcleanpyrightclean across changed files