You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* chore(release): v0.10.0-rc1
Release prep for the v0.10.0 Langfuse observability release.
- CHANGELOG: [0.10.0] entry covering proposals 0031-0036 plus the
downstream-driven provider / observability hardening.
- conformance.toml: proposals 0031-0036 flipped not-yet →
implemented since = 0.10.0; spec_pin already at v0.27.1.
- Version → 0.10.0rc1 across pyproject, __init__, smoke test,
uv.lock; AGENTS.md regenerated with the new version stamp.
- README: added a native-LangfuseObserver bullet — the release
headline wasn't represented in the feature list.
rc1 publishes to TestPyPI only; the real-release bump to 0.10.0
is a separate commit after rc verification per RELEASING.md.
* Fix two CHANGELOG accuracy nits from spec review
Per spec's v0.10.0 sign-off (coord review-v0-10-0-release msg 02):
- 0033 bullet: replace the vague "typed prompt model-config" with
the actual surface — Prompt.sampling (a SamplingConfig subclass
of RuntimeConfig), Prompt.observability_entities, the LabelResolver
resolution chain, and filesystem layout / sampling-source.
- 0032 bullet: the declared-field promotion is llm-provider §6;
§8.1 is the OpenAI wire-mapping section. Attribute both correctly.
CHANGELOG-only; no behavior change.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+32Lines changed: 32 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,6 +4,38 @@ All notable changes to `openarmature-python` are documented in this file.
4
4
5
5
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).
6
6
7
+
## [0.10.0] — 2026-05-27
8
+
9
+
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.
10
+
11
+
### Added
12
+
13
+
-**`LangfuseObserver` — native Langfuse backend mapping** (proposal 0031, observability §8). An observer that consumes the §6 event stream as a sibling to the OTel observer (both can be attached to one graph; each honors its own opt-out). Maps invocation → Langfuse Trace, node / subgraph / fan-out → Span observation, LLM provider call → Generation observation. Sets the Trace `id` equal to the OA `invocation_id` so cross-system lookup is a direct hit; routes `correlation_id` to `trace.metadata` and every `observation.metadata`. Full subgraph dispatch, per-instance fan-out, and detached-trace-mode parenting (§8.3 / §8.5). Decoupled from the SDK via the `LangfuseClient` Protocol.
14
+
-**`InMemoryLangfuseClient`** — an in-process recorder satisfying `LangfuseClient`, used by the conformance harness and useful for unit tests; captures Traces / Observations verbatim for assertion.
15
+
-**`LangfuseSDKAdapter`** — bridges the real `langfuse>=4.6` SDK to the `LangfuseClient` Protocol (UUID4 → OTel-hex trace-id conversion, `propagate_attributes` on every observation, usage translation). Gated behind the new `[langfuse]` extra (`pip install openarmature[langfuse]`); the observer itself needs no SDK install because the Protocol decouples it.
16
+
-**Public `force_flush(timeout_ms=30_000)` on `OTelObserver` and `LangfuseObserver`** (downstream ask). Wraps the underlying provider / client flush so fast-teardown harnesses (serverless functions, CLI one-shots, FastAPI `TestClient` teardown) can drain the export buffer without reaching into the private `_provider` attribute. Distinct from `CompiledGraph.drain()`, which covers the engine's observer-event queue; `force_flush()` covers the outbound span-export buffer.
17
+
-**Caller-supplied invocation metadata** (proposal 0034, observability §3.4 + §5.6 + §8.4). `invoke(metadata={...})` accepts a per-invocation mapping of `str → AttributeValue` (OTel scalars or homogeneous arrays). The framework propagates every entry to all observability backends: the OTel observer emits each as an `openarmature.user.<key>` cross-cutting span attribute on every span; the Langfuse observer merges each as a top-level key into `trace.metadata` and every `observation.metadata`. `openarmature.observability.set_invocation_metadata(**entries)` augments the in-scope mapping mid-invocation (additive; respects fan-out / parallel-branches per-instance COW scoping); `current_invocation_metadata()` reads it. Boundary validation rejects keys under the reserved `openarmature.*` / `gen_ai.*` prefixes and non-OTel-compatible value types with a synchronous `ValueError`.
18
+
-**`concat_flatten` and `merge_all` reducers** (proposal 0036, graph-engine §2). The fan-out collection analogs of `append` / `merge`: a fan-out subgraph emitting `list[X]` per instance lands `list[list[X]]` at the parent `target_field` (use `concat_flatten` to flatten one level); emitting `dict[str, X]` lands `list[dict]` (use `merge_all` to fold with last-write-wins per key). Both are strict — they raise `ReducerError` (graph-engine §4) when an update element isn't the expected list / mapping shape. Exported from `openarmature.graph`; the required-built-in set grows from three to five.
19
+
-**Three new `RuntimeConfig` declared fields** (proposal 0032, llm-provider §6): `frequency_penalty`, `presence_penalty`, and `stop_sequences`. Surfaced on the OpenAI wire body per §8.1 (with `stop_sequences` renaming to OpenAI's `stop` key) and as `gen_ai.request.*` span attributes. Per the §6 null-skip rule, each declared field with value `None` is omitted from the wire body.
20
+
-**Prompt-management surface refinements** (proposal 0033). `Prompt.sampling` (a `SamplingConfig` subclass of `RuntimeConfig`), `Prompt.observability_entities`, the LabelResolver three-step resolution chain (explicit > resolver > `"production"`), and filesystem layout / sampling-source ergonomics for the prompt-management capability.
21
+
-**Self-hosted vLLM cookbook** at `docs/model-providers/vllm.md` — base-URL contract, the structured-output fallback flag, the `genai_system="vllm"` override, readiness-probe limitations + warm-up pattern, and tool calling.
22
+
-**`conformance.toml` manifest + CI guard.** A machine-readable record of which spec proposals are implemented and since which version, validated against the pinned spec submodule by `scripts/check_conformance_manifest.py` on every PR. Consumed by the spec docs site to render per-implementation status.
23
+
24
+
### Changed
25
+
26
+
-**`OpenAIProvider` rejects a `/v1` suffix on `base_url`** (downstream-surfaced bug). httpx joins base URLs by appending, so `base_url="https://host/v1"` plus the provider's `/v1/chat/completions` request produced a doubled `/v1/v1/...` wire path that silently 404/405'd on most backends while the readiness probe stayed green. The provider now raises `ValueError` at construction when `base_url`'s path ends in `/v1` (with or without a trailing slash, and through query strings / fragments). Other non-empty paths (proxy prefixes) are left intact. No existing users were affected; this is the first production integration.
27
+
-**`metadata.subgraph_name` / `openarmature.subgraph.name` carries the compiled-subgraph identity** (proposal 0035 resolution), not the wrapper node name. `SubgraphNode` and `FanOutConfig` gain an optional `subgraph_identity`; the engine threads it through `NodeEvent.subgraph_identities` to the observers. Falls back to the empty string when no identity is tracked (observability §5.3). Distinct from the observation's `name` / namespace, which remain the wrapper node name.
28
+
29
+
### Fixed
30
+
31
+
-**`entry_node` / trace name when the outer entry is a `SubgraphNode`.** Subgraph wrappers don't emit their own events, so the first event the observer saw came from inside the subgraph; the Langfuse observer recorded the inner node as the trace's `entry_node`. Now resolves to `event.namespace[0]` (the outer entry).
32
+
-**Detached-mode link observation no longer carries `subgraph_name`.** In detached-trace 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`.
33
+
34
+
### Notes
35
+
36
+
-**Pinned spec version bumped from v0.22.1 to v0.27.1 over the v0.10.0 cycle.** Six proposals absorbed: 0031 (observability Langfuse mapping, v0.23.0), 0032 (RuntimeConfig declared-field expansion, v0.24.0), 0033 (prompt-management surface refinements, v0.25.0), 0034 (caller-supplied invocation metadata, v0.26.0), 0035 (Langfuse graph-topology conformance fixtures, v0.26.1 + v0.27.1 fixture corrections), and 0036 (fan-out collection reducers `concat_flatten` / `merge_all`, v0.27.0). All conformance fixtures pass against the v0.27.1 pin, including the un-deferred Langfuse subgraph / fan-out / detached-trace fixtures and the two new reducer fixtures.
37
+
-**`langfuse>=4.6,<5` is the supported SDK range** for `LangfuseSDKAdapter`, validated end-to-end against Langfuse Cloud. The v4 SDK's `flush()` is synchronous but exposes no timeout parameter, so `LangfuseObserver.force_flush(timeout_ms=...)` accepts the argument for Protocol symmetry but the underlying flush honors the SDK's own deadlines (best-effort).
Copy file name to clipboardExpand all lines: README.md
+3Lines changed: 3 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -56,6 +56,9 @@ The OpenTelemetry mapping mandates a private `TracerProvider`. That prevents the
56
56
**LLM spans LLM-aware backends can actually read.**<br>
57
57
Each `provider.complete()` call emits a dedicated `openarmature.llm.complete` span carrying both the framework's `openarmature.llm.*` attributes and the cross-vendor OpenTelemetry GenAI semantic conventions (`gen_ai.system`, `gen_ai.request.*`, `gen_ai.response.*`, `gen_ai.usage.*`). Langfuse, Phoenix, Honeycomb's LLM lens — they render generations correctly out of the box, no per-service attribute-mapping shim required. Input/output payload emission is opt-in (`disable_llm_payload=False`), default-off because the payload may contain PII; image bytes are unconditionally redacted at the provider so they never enter the observability stream.
58
58
59
+
**Native Langfuse mapping, not just OTLP.**<br>
60
+
Alongside the OpenTelemetry mapping, `LangfuseObserver` (in `openarmature[langfuse]`) maps invocations to Langfuse Traces and Observations directly — subgraph hierarchy, per-instance fan-out, and detached-trace mode included. Both observers can run on one graph. Caller-supplied invocation metadata (`invoke(metadata={"tenantId": ...})`) propagates to every backend at once: `openarmature.user.*` span attributes on the OTel side, top-level `trace.metadata` / `observation.metadata` keys on the Langfuse side.
61
+
59
62
## Hello World
60
63
61
64
About a hundred lines that show the engine in action. Three reducer policies declared on one state class. Three LLM calls each returning typed structured output (Pydantic class on two, raw JSON Schema dict on the third). Conditional routing as a pure function of state, not a hidden state machine. An observer attached at compile time that sees every node boundary the engine emits. Requires Python 3.12 or later and an OpenAI-compatible endpoint (defaults to OpenAI public API; works against any local server too).
Copy file name to clipboardExpand all lines: conformance.toml
+13-11Lines changed: 13 additions & 11 deletions
Original file line number
Diff line number
Diff line change
@@ -151,25 +151,27 @@ since = "0.9.0"
151
151
note = "Drain snapshot semantic and timeout-input validation already implemented as part of the proposal 0010 impl PR (v0.9.0); no additional module-level work needed."
Copy file name to clipboardExpand all lines: src/openarmature/AGENTS.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# OpenArmature — Agent documentation
2
2
3
-
*This is the agent guide bundled with the openarmature Python package, version 0.9.0 (spec v0.27.1). 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.0rc1 (spec v0.27.1). 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`.*
0 commit comments