Skip to content

Commit ecf4216

Browse files
Implement proposal 0042 (reserved keys) (#98)
Extends the observability spec.4 reserved exact-key-name set from 21 to 24 entries with `branch_name`, `detached`, and `detached_from_invocation_id`. These three are top-level Langfuse metadata keys the observer mapping already writes; without reservation a caller key matching one would silently shadow the OA-emitted field at the boundary, the same hazard 0041 closed for its 20 names. Also relocates `observation.metadata.detached: true` from the detached-side dispatch observation onto the parent-side dispatching observation in the main trace (link observation for detached subgraphs; parent fan-out node observation for detached fan-outs), matching the .4.2 row 0042 added and the corresponding fixture 033 assertions. Bumps the spec pin from v0.31.0 to v0.34.0, absorbing 0042 plus the two textual additions in v0.32.0 (Gemini wire-format mapping, 0038, not yet implemented) and v0.33.0 (sessions capability, 0020, not yet implemented). Updates `conformance.toml` accordingly: 0040 flipped not-yet to implemented (shipped in PR #96); 0042 added as implemented; 0020 and 0038 added as not-yet. Defers the 10 new Gemini conformance fixtures in both the cross-capability parser and the LLM-provider harness to match the not-yet status.
1 parent 03dd599 commit ecf4216

12 files changed

Lines changed: 162 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ All notable changes to `openarmature-python` are documented in this file.
44

55
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). The package follows [Semantic Versioning](https://semver.org/); pre-1.0 minor bumps may carry behavioral changes per [spec governance](https://github.com/LunarCommand/openarmature-spec/blob/main/GOVERNANCE.md).
66

7+
## [Unreleased]
8+
9+
### Changed
10+
11+
- **Reserved-key extension** (proposal 0042, observability §3.4). Three additional bare key names — `branch_name`, `detached`, `detached_from_invocation_id` — are reserved against caller-supplied `invocation_metadata` and `set_invocation_metadata` collision; the framework rejects them at the `invoke()` boundary and at the mid-invocation augmentation helper with `ValueError`. The reserved-name set grows from 21 to 24. These three are top-level Langfuse metadata keys the observer mapping already writes; without reservation a caller key matching one would silently shadow the OA-emitted field.
12+
- **`observation.metadata.detached: true` moves to the parent-side dispatching observation** (proposal 0042, observability §8.4.2). The Langfuse mapping previously emitted `detached: true` on the dispatch observation inside the detached child trace; the §8.4.2 row added by 0042 places it on the **parent-side** dispatching observation that fires the detached child (the link observation in the main trace for detached subgraphs; the parent fan-out node observation for detached fan-outs). The detached-side observation no longer carries the flag.
13+
14+
### Notes
15+
16+
- **Pinned spec version bumped from v0.31.0 to v0.34.0.** Absorbs proposals 0042 (reserved-key extension; observation.metadata.detached + branch_name + trace.metadata.detached_from_invocation_id rows), 0038 (Google Gemini wire-format mapping — not yet implemented in python), and 0020 (sessions capability — not yet implemented in python).
17+
718
## [0.10.0] — 2026-05-27
819

920
Langfuse observability release. The pinned spec advances from v0.22.1 to v0.27.1, absorbing six accepted proposals (0031-0036). The headline is a native Langfuse backend mapping (a sibling to the OTel mapping) driven by a downstream production project integrating OpenArmature with Langfuse; this release also adds caller-supplied invocation metadata, two fan-out collection reducers, and a batch of provider / observability hardening surfaced by that same downstream integration.

conformance.toml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
[manifest]
3434
implementation = "openarmature-python"
35-
spec_pin = "v0.31.0"
35+
spec_pin = "v0.34.0"
3636

3737
# Status values:
3838
# implemented — shipped behavior matches the proposal's contract
@@ -179,8 +179,7 @@ since = "0.10.0"
179179
status = "implemented"
180180
since = "0.10.0"
181181

182-
# Spec v0.28.0-v0.31.0 (proposals 0037, 0039, 0040, 0041). 0038
183-
# (Gemini) is mid-accept on spec side and not in v0.31.0 yet.
182+
# Spec v0.28.0-v0.31.0 (proposals 0037, 0039, 0040, 0041).
184183
[proposals."0037"]
185184
status = "not-yet"
186185

@@ -189,8 +188,20 @@ status = "implemented"
189188
since = "0.11.0"
190189

191190
[proposals."0040"]
192-
status = "not-yet"
191+
status = "implemented"
192+
since = "0.11.0"
193193

194194
[proposals."0041"]
195195
status = "implemented"
196196
since = "0.11.0"
197+
198+
# Spec v0.32.0-v0.34.0 (proposals 0038, 0020, 0042).
199+
[proposals."0038"]
200+
status = "not-yet"
201+
202+
[proposals."0020"]
203+
status = "not-yet"
204+
205+
[proposals."0042"]
206+
status = "implemented"
207+
since = "0.11.0"

openarmature-spec

Submodule openarmature-spec updated 64 files

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Specification = "https://github.com/LunarCommand/openarmature-spec"
5858
openarmature = "openarmature.cli:main"
5959

6060
[tool.openarmature]
61-
spec_version = "0.31.0"
61+
spec_version = "0.34.0"
6262

6363
[dependency-groups]
6464
dev = [

src/openarmature/AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# OpenArmature — Agent documentation
22

3-
*This is the agent guide bundled with the openarmature Python package, version 0.10.0 (spec v0.31.0). For the full docs site see [openarmature.ai](https://openarmature.ai). For the canonical spec text see [openarmature.org/capabilities](https://openarmature.org/capabilities/). For project-specific conventions for the code you're editing, see the host project's `AGENTS.md` or `CLAUDE.md`.*
3+
*This is the agent guide bundled with the openarmature Python package, version 0.10.0 (spec v0.34.0). For the full docs site see [openarmature.ai](https://openarmature.ai). For the canonical spec text see [openarmature.org/capabilities](https://openarmature.org/capabilities/). For project-specific conventions for the code you're editing, see the host project's `AGENTS.md` or `CLAUDE.md`.*
44

55
## TL;DR
66

@@ -10,7 +10,7 @@ OpenArmature is a workflow framework for LLM pipelines and tool-calling agents
1010

1111
## Capability contracts
1212

13-
_Sourced from openarmature-spec v0.31.0. Each entry below reproduces §1 (Purpose) and §2 (Concepts) of the capability's `spec.md`. For the full spec text (execution model, error semantics, determinism, observer hooks, etc.) see the linked docs site._
13+
_Sourced from openarmature-spec v0.34.0. Each entry below reproduces §1 (Purpose) and §2 (Concepts) of the capability's `spec.md`. For the full spec text (execution model, error semantics, determinism, observer hooks, etc.) see the linked docs site._
1414

1515
### Capability: `graph-engine`
1616

src/openarmature/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@
2525
"""
2626

2727
__version__ = "0.10.0"
28-
__spec_version__ = "0.31.0"
28+
__spec_version__ = "0.34.0"

src/openarmature/observability/langfuse/observer.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -726,13 +726,18 @@ def _open_detached_subgraph_trace(
726726
# "string array, one entry per detached child" shape so
727727
# later detached siblings under the same parent can append.
728728
#
729+
# `detached: True` per §8.4.2 (proposal 0042) — the
730+
# parent-side dispatching observation marks itself when it
731+
# fires a detached child.
732+
#
729733
# Note: `subgraph_name` is intentionally NOT on this link
730734
# observation. Per §5.3 + §8.5, in detached mode the wrapper
731735
# role migrates to the detached trace's dispatch observation;
732736
# the main trace's link observation IS the SubgraphNode span
733737
# (no wrapper role) and so does not carry `subgraph_name`.
734738
link_metadata: dict[str, Any] = {
735739
"detached_child_trace_ids": [detached_trace_id],
740+
"detached": True,
736741
}
737742
if correlation_id is not None:
738743
link_metadata["correlation_id"] = correlation_id
@@ -783,9 +788,13 @@ def _open_detached_subgraph_trace(
783788
# happens to be named ``X``.
784789
wrapper_obs_name = identity or prefix[-1]
785790
self.client.trace(id=detached_trace_id, name=wrapper_obs_name, metadata=detached_metadata)
791+
# §8.4.2 (proposal 0042): `detached: true` lives on the
792+
# PARENT-side dispatching observation (the link observation
793+
# above), not on the dispatch observation IN the detached
794+
# trace. The detached-side observation is the migrated
795+
# SubgraphNode wrapper and carries `subgraph_name` only.
786796
dispatch_metadata: dict[str, Any] = {
787797
"subgraph_name": identity,
788-
"detached": True,
789798
}
790799
if correlation_id is not None:
791800
dispatch_metadata["correlation_id"] = correlation_id
@@ -827,8 +836,14 @@ def _open_detached_fan_out_instance_trace(
827836
ids_list.append(detached_trace_id)
828837
fan_out_open = self._find_fan_out_node_observation(inv_state, prefix)
829838
if fan_out_open is not None:
839+
# `detached: True` per §8.4.2 (proposal 0042) — the
840+
# parent-side fan-out node observation marks itself when
841+
# its instances are detached. Re-sent on every instance
842+
# update; the Langfuse client merges metadata, so this is
843+
# idempotent.
830844
link_metadata: dict[str, Any] = {
831845
"detached_child_trace_ids": list(ids_list),
846+
"detached": True,
832847
}
833848
if correlation_id is not None:
834849
link_metadata["correlation_id"] = correlation_id
@@ -847,11 +862,15 @@ def _open_detached_fan_out_instance_trace(
847862
name=prefix[-1],
848863
metadata=detached_metadata,
849864
)
865+
# §8.4.2 (proposal 0042): `detached: true` lives on the
866+
# PARENT-side fan-out node observation (link_metadata above),
867+
# not on the per-instance dispatch observation IN the detached
868+
# trace. The detached-side per-instance observation carries
869+
# only `fan_out_parent_node_name` + `fan_out_index`.
850870
parent_node_name = inv_state.fan_out_parent_node_name.get(prefix, prefix[-1])
851871
dispatch_metadata: dict[str, Any] = {
852872
"fan_out_parent_node_name": parent_node_name,
853873
"fan_out_index": event.fan_out_index,
854-
"detached": True,
855874
}
856875
if correlation_id is not None:
857876
dispatch_metadata["correlation_id"] = correlation_id

src/openarmature/observability/metadata.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,11 @@
6666
# boundary so observers never see a colliding key.
6767
_RESERVED_PREFIXES: tuple[str, ...] = ("openarmature.", "gen_ai.")
6868

69-
# Reserved exact key NAMES per §3.4 (proposal 0041): the top-level
70-
# metadata keys an OA-emitted §8 backend mapping writes alongside
71-
# caller keys (the §8.4 Langfuse set, plus invocation_id). A caller
72-
# key matching one exactly would silently overwrite an OA field in a
69+
# Reserved exact key NAMES per §3.4 (proposals 0041, 0042): the
70+
# top-level metadata keys an OA-emitted §8 backend mapping writes
71+
# alongside caller keys (the §8.4 Langfuse set, plus invocation_id,
72+
# branch_name, detached, detached_from_invocation_id). A caller key
73+
# matching one exactly would silently overwrite an OA field in a
7374
# backend's flat top-level metadata, so it is rejected at the boundary
7475
# the same way as the prefix reservation. Backend-set-independent:
7576
# rejected regardless of which observers are attached.
@@ -96,6 +97,9 @@
9697
"response_id",
9798
"prompt",
9899
"invocation_id",
100+
"branch_name",
101+
"detached",
102+
"detached_from_invocation_id",
99103
}
100104
)
101105

tests/conformance/test_fixture_parsing.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,41 @@ def _id(case: tuple[str, Path]) -> str:
156156
"llm-provider/042-anthropic-thinking-block-round-trip": (
157157
"Anthropic provider not implemented (0037 not-yet in conformance.toml)"
158158
),
159+
# Proposal 0038 (Google Gemini wire-format mapping) shipped in spec
160+
# v0.32.0 but python marks it not-yet in conformance.toml — the
161+
# Gemini provider isn't implemented in this release. Defer the
162+
# cross-capability parse tests for the 044-053 fixtures; the
163+
# `mapping: gemini` discriminator is harness-extension territory.
164+
"llm-provider/044-gemini-basic-message-round-trip": (
165+
"Gemini provider not implemented (0038 not-yet in conformance.toml)"
166+
),
167+
"llm-provider/045-gemini-function-call-flow": (
168+
"Gemini provider not implemented (0038 not-yet in conformance.toml)"
169+
),
170+
"llm-provider/046-gemini-image-content-blocks": (
171+
"Gemini provider not implemented (0038 not-yet in conformance.toml)"
172+
),
173+
"llm-provider/047-gemini-tool-choice-modes": (
174+
"Gemini provider not implemented (0038 not-yet in conformance.toml)"
175+
),
176+
"llm-provider/048-gemini-runtime-config-mapping": (
177+
"Gemini provider not implemented (0038 not-yet in conformance.toml)"
178+
),
179+
"llm-provider/049-gemini-error-mapping": (
180+
"Gemini provider not implemented (0038 not-yet in conformance.toml)"
181+
),
182+
"llm-provider/050-gemini-structured-output-native": (
183+
"Gemini provider not implemented (0038 not-yet in conformance.toml)"
184+
),
185+
"llm-provider/051-gemini-structured-output-fallback": (
186+
"Gemini provider not implemented (0038 not-yet in conformance.toml)"
187+
),
188+
"llm-provider/052-gemini-thought-signature-round-trip": (
189+
"Gemini provider not implemented (0038 not-yet in conformance.toml)"
190+
),
191+
"llm-provider/053-cross-provider-signature-strip": (
192+
"Gemini provider not implemented (0038 not-yet in conformance.toml)"
193+
),
159194
# Proposal 0040 (open-span metadata update) — task #22 implements
160195
# the §6 augmentation-event mechanism + un-defers 029/030 + 034.
161196
# Fixture 034 lands in the Langfuse-specific harness directly

tests/conformance/test_llm_provider.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,19 @@
8181
"041-anthropic-structured-output-fallback": "Anthropic provider not implemented (0037 not-yet)",
8282
"042-anthropic-thinking-block-round-trip": "Anthropic provider not implemented (0037 not-yet)",
8383
"043-openai-strips-thinking-blocks": "Anthropic provider not implemented (0037 not-yet)",
84+
# Proposal 0038 (Google Gemini wire-format mapping) shipped in spec
85+
# v0.32.0 but python marks it not-yet — the Gemini provider isn't
86+
# implemented in this release.
87+
"044-gemini-basic-message-round-trip": "Gemini provider not implemented (0038 not-yet)",
88+
"045-gemini-function-call-flow": "Gemini provider not implemented (0038 not-yet)",
89+
"046-gemini-image-content-blocks": "Gemini provider not implemented (0038 not-yet)",
90+
"047-gemini-tool-choice-modes": "Gemini provider not implemented (0038 not-yet)",
91+
"048-gemini-runtime-config-mapping": "Gemini provider not implemented (0038 not-yet)",
92+
"049-gemini-error-mapping": "Gemini provider not implemented (0038 not-yet)",
93+
"050-gemini-structured-output-native": "Gemini provider not implemented (0038 not-yet)",
94+
"051-gemini-structured-output-fallback": "Gemini provider not implemented (0038 not-yet)",
95+
"052-gemini-thought-signature-round-trip": "Gemini provider not implemented (0038 not-yet)",
96+
"053-cross-provider-signature-strip": "Gemini provider not implemented (0038 not-yet)",
8497
}
8598

8699

0 commit comments

Comments
 (0)