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
* Implement proposal 0043 (trace input/output)
Adds the observability §8.4.1 *Trace input/output sourcing* mechanism:
the Langfuse observer populates `trace.input` at invocation entry
and `trace.output` at invocation exit via a three-lever decision
tree (caller hook returning non-null → hook value; raw state when
`disable_state_payload=False` → serialized state; default → minimal
stub `{entry_node, correlation_id}` / `{final_node, status}` where
status is the closed `Literal["completed", "failed"]` enum).
Wires through two new observer event types delivered on the
existing serial-delivery queue: `InvocationStartedEvent` and
`InvocationCompletedEvent`. The engine enqueues both at the
invocation lifecycle's outermost boundaries (entry before any node
fires; exit on both success and failure paths). Mirrors the 0040
pattern used for `MetadataAugmentationEvent`. The `Observer.__call__`
signature widens to a four-variant union; the new `ObserverEvent`
type alias gives observer authors a one-name handle and is
re-exported from `openarmature.graph`.
The OTel observer no-ops on both new events (OTel has no Trace-
level input/output concept). The LangfuseSDKAdapter caches input
and output on `_trace_info`; live-Trace emission via the v4 SDK is
deferred to a follow-up (the InMemoryLangfuseClient used by tests
applies the fields directly so the contract is unit-test-pinned).
Bumps the spec pin from v0.34.0 to v0.35.0. `conformance.toml`
records 0043 as implemented since 0.11.0. Conformance fixture 037
is deferred because cases 3/4/5 need a caller-hook YAML directive
the cross-capability harness doesn't model yet; the five-case
decision tree is pinned by new unit tests at
`tests/unit/test_observability_langfuse.py::test_trace_input_output_*`.
* Fix final_node_box leak + JSON-mode state dump
Two issues surfaced by PR #99 review:
`final_node_box` is shared by reference across subgraph, fan-out,
and parallel-branches descents (`descend_into_*` propagates the
list). Inner-node writes leak into the outer box on the success
path, so the outermost `invoke()` reads the wrong `final_node`
when an outer wrapper is the last node before the END-routing
edge. For parallel-branches the leaked value depends on which
branch finishes last, making `InvocationCompletedEvent.final_node`
nondeterministic.
Restore the outer `current` to the box after each `_step_*` call
returns successfully. The restore is on the success path only —
the failure path's raise bypasses it, so the inner-most node that
raised stays in the box for the spec §4 attribution. A follow-up
race remains for parallel-branches and fan-out failure cases:
concurrent inner writes mean the box may end with a successful
sibling's inner rather than the failing sibling's. Addressing that
requires error-aware tracking the engine doesn't currently expose.
Pydantic's `model_dump()` defaults to Python mode and leaves
`datetime` / `UUID` / `Decimal` as Python objects. The downstream
`json.dumps` truncation path raises `TypeError` on those types,
and the observer raise is swallowed by the engine's warnings-only
observer-isolation contract, silently leaving `trace.input` /
`trace.output` blank under `disable_state_payload=False`.
`_state_to_jsonable` now calls `model_dump(mode="json")` so the
common non-JSON-native types serialize to their JSON-compatible
string forms before reaching the truncation step. Adds a
regression test using a State with `datetime`, `UUID`, and
`Decimal` fields.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+16-1Lines changed: 16 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,14 +6,29 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). The
6
6
7
7
## [Unreleased]
8
8
9
+
### Added
10
+
11
+
-**`LangfuseObserver` Trace input/output sourcing** (proposal 0043, observability §8.4.1). New observer construction knobs populate `trace.input` and `trace.output` per the three-lever decision tree:
12
+
-**`disable_state_payload: bool = True`** — privacy knob symmetric to `disable_llm_payload`. When ON (default), Trace fields receive the minimal stub `{entry_node, correlation_id}` / `{final_node, status}`; when OFF, the raw state object is serialized.
13
+
-**`trace_input_from_state` / `trace_output_from_state`** — optional caller hooks returning the domain-shaped value to use for `trace.input` / `trace.output`. Returning `None` falls through to the next applicable lever.
14
+
-`status` is the closed `Literal["completed", "failed"]` enum from spec §8.4.1.
15
+
-**Two new observer event types** delivered through the existing `graph.observer.Observer` queue:
16
+
-**`InvocationStartedEvent(initial_state, invocation_id, correlation_id, entry_node)`** — emitted once at invocation entry before any node fires.
17
+
-**`InvocationCompletedEvent(final_state, status, final_node, invocation_id, correlation_id)`** — emitted once at invocation exit on both the success path (`status="completed"`) and failure path (`status="failed"`).
18
+
19
+
The `Observer.__call__` signature widens to `NodeEvent | MetadataAugmentationEvent | InvocationStartedEvent | InvocationCompletedEvent`. The new `ObserverEvent` type alias (re-exported from `openarmature.graph`) gives observer authors a one-name handle on the union; existing observers that ignore non-`NodeEvent` variants early-return after an `isinstance(event, NodeEvent)` check.
20
+
-**`LangfuseTrace.input` / `LangfuseTrace.output` dataclass fields** on the in-memory recorder, populated by the new observer paths.
21
+
9
22
### Changed
10
23
11
24
-**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
25
-**`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.
26
+
-**`LangfuseClient.update_trace` Protocol grows `input` / `output` keyword parameters** so observer-supplied values land on the Trace's headline fields.
13
27
14
28
### Notes
15
29
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).
30
+
-**Pinned spec version bumped from v0.31.0 to v0.35.0.** Absorbs proposals 0042 (reserved-key extension), 0043 (Langfuse trace.input/output sourcing), and the textual additions in v0.32.0 (Gemini wire-format mapping, 0038, not yet implemented) and v0.33.0 (sessions capability, 0020, not yet implemented).
31
+
- The SDK adapter caches `input` / `output` in its `_trace_info` map; landing the values on the live Langfuse Trace from outside an active span context requires SDK-version-specific calls (v4's `langfuse.update_current_trace` works inside a context; cross-context REST updates need `client.api.trace.update`). The `InMemoryLangfuseClient` used by tests applies the fields directly. SDK-adapter end-to-end emit lands in a follow-up.
Copy file name to clipboardExpand all lines: src/openarmature/AGENTS.md
+2-2Lines changed: 2 additions & 2 deletions
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.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`.*
3
+
*This is the agent guide bundled with the openarmature Python package, version 0.10.0 (spec v0.35.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`.*
4
4
5
5
## TL;DR
6
6
@@ -10,7 +10,7 @@ OpenArmature is a workflow framework for LLM pipelines and tool-calling agents
10
10
11
11
## Capability contracts
12
12
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._
13
+
_Sourced from openarmature-spec v0.35.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._
0 commit comments