Skip to content

[AAASM-1442] 🔌 (python-sdk): Bridge AuditEvent dataclass into Rust encoder + wire round-trip test#45

Merged
Chisanan232 merged 8 commits into
masterfrom
v0.0.1/AAASM-1442/feat/bridge_audit_event_ffi
May 20, 2026
Merged

[AAASM-1442] 🔌 (python-sdk): Bridge AuditEvent dataclass into Rust encoder + wire round-trip test#45
Chisanan232 merged 8 commits into
masterfrom
v0.0.1/AAASM-1442/feat/bridge_audit_event_ffi

Conversation

@Chisanan232
Copy link
Copy Markdown
Contributor

@Chisanan232 Chisanan232 commented May 20, 2026

Description

Bridges the pure-Python agent_assembly.AuditEvent / CallStackNode dataclasses (from AAASM-1435) into the Rust aa_proto encoder via PyO3, then ships the three deferred unit tests AAASM-1435 left behind.

Public surface added to the dataclass:

event.to_wire_bytes()           # → bytes (prost-encoded aa_proto::AuditEvent)
AuditEvent.from_wire_bytes(buf) # → AuditEvent (round-trip)

Both methods import agent_assembly._core lazily and raise an actionable ImportError in pure-Python installs, so types.py stays usable without the native crate.

Implementation lives in rust/aa-ffi-python/src/lib.rs:

  • action_type_from/to_str + decision_from/to_str — string ↔ proto-enum mapping so the dataclass's open-ended str fields round-trip cleanly through the enum-typed wire format.
  • call_stack_node_from/to_py — recursive PyO3 ↔ proto converter; latency_ms == 0None preserves the "producer did not record" semantic.
  • audit_event_from/to_py — top-level converter. The flat agent_id: str wraps to an AgentId { agent_id, org_id: "", team_id: "" } (org/team not surfaced on the dataclass today).
  • Two free #[pyfunction]s — audit_event_to_wire_bytes and audit_event_from_wire_bytes — registered on the _core module.

Type of Change

  • ✨ New feature
  • 🔧 Bug fix
  • ♻️ Refactoring
  • 🍀 Performance improvement
  • 📚 Documentation update
  • 🚀 Release

Breaking Changes

  • No

The proto contract is unchanged; only new methods are added to AuditEvent. No existing call-sites or wire bytes are altered.

Related Issues

Testing

  • Unit tests added — test/unit/test_audit_event_wire_roundtrip.py:
    • 3-level call_stack round-trip — LLM → tool → result tree with non-zero latency_ms + populated labels round-trips with no data loss.
    • Legacy-payload decode — events without call_stack (or call_stack=[]) decode to dataclass with call_stack == [].
    • Invalid kind round-tripkind is proto string (open-ended), so a value outside the CallStackNodeKind Literal survives the wire layer unchanged.
  • All three tests gated behind pytest.importorskip("agent_assembly._core"), matching test/bench/test_report_llm_call_roundtrip.py convention.
  • cargo check -p aa-ffi-python clean (5 pre-existing pyo3 0.20 warnings unchanged).
  • maturin develop --release builds the native crate; new tests pass with native module loaded.
  • pytest test/unit test/integration — 317 passed, 7 skipped (no regressions).
  • mypy agent_assembly — 13 pre-existing baseline errors, zero new errors in changed files.
  • ruff check clean on changed files; ruff format applied.

Out of scope

  • AAASM-1435's agent_id field on the dataclass is a flat str; the proto's AgentId carries org_id + team_id that the dataclass does not surface today. The bridge wraps with empty org_id/team_id and unwraps the agent_id string back — preserving the round-trip for what the dataclass actually exposes.
  • The pinned aa-proto rev (ed4aa11a) already carries the call_stack field added in AAASM-1419; no pin bump needed (per DoD).
  • Pre-existing failure in test/bench/test_report_llm_call_roundtrip.py (event_type "LlmCall" no longer matches the current aa_core::AuditEntry schema) is unrelated to this PR and only manifests locally when _core is built. CI's pure-Python install skips that test.

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Comments added for complex logic
  • Documentation updated if needed
  • All tests passing

🤖 Generated with Claude Code

Bridges the Python dataclass's open-ended `str` API to the proto's
enum-typed `ActionType` / `Decision` wire format. Used by the
forthcoming AuditEvent py↔proto converter (AAASM-1664).

Refs AAASM-1662.
`call_stack_node_from_py` reads the dataclass fields via PyO3
reflection and walks children recursively. `latency_ms == None` ↔
proto's int64 default of 0.

`call_stack_node_to_py` imports `agent_assembly.types.CallStackNode`
and constructs an instance per node. `latency_ms == 0` on the wire
projects back to `None` on the dataclass, preserving the "producer
did not record" semantic.

Refs AAASM-1663.
`audit_event_from_py` reflects on `event_id` / `agent_id` /
`action_type` / `decision` / `trace_id` / `span_id` /
`parent_span_id` / `labels` / `call_stack`. The dataclass's flat
`agent_id: str` wraps to an `AgentId` message with empty
`org_id`/`team_id` (round-trip preserves the string).

`audit_event_to_py` unwraps `AgentId.agent_id` back to the flat
string and applies the inverse enum mappings.

Refs AAASM-1664.
Two free `#[pyfunction]`s on `agent_assembly._core`:

* `audit_event_to_wire_bytes(event)` — converts the dataclass to
  `aa_proto::AuditEvent` and returns prost-encoded `bytes`.
* `audit_event_from_wire_bytes(data)` — decodes wire bytes back
  into a `agent_assembly.types.AuditEvent`. Raises `ValueError`
  on malformed input.

Refs AAASM-1665.
Public Python surface for the AAASM-1442 bridge. Methods import
`agent_assembly._core` lazily so `types.py` stays usable in
pure-Python installs. When the native crate is absent, both raise
`ImportError` with an actionable message pointing at
`maturin develop`.

Refs AAASM-1666.
Builds an AuditEvent with an LLM → tool → result call_stack, encodes
via to_wire_bytes(), decodes via from_wire_bytes(), and asserts
structural equality with the original. Covers the deferred test
case AAASM-1435 scope item #4 — verifying no data loss through the
PyO3 + prost pipeline.

Skips when the native _core extension is absent, matching the
test/bench/test_report_llm_call_roundtrip.py convention.

Refs AAASM-1667.
Asserts the back-compat path: an AuditEvent with call_stack=[]
encodes to bytes that match what a pre-AAASM-1419 producer would
emit, and the decoded dataclass surfaces the empty list — not None
or a missing attribute.

Refs AAASM-1668.
The CallStackNode `kind` field is proto `string`, not enum — the
Python `Literal` narrowing is author-side only. Asserts the bridge
preserves an out-of-Literal kind value verbatim, leaving room for
future producers to add categories without a wire-layer schema bump.

Refs AAASM-1669.
@sonarqubecloud
Copy link
Copy Markdown

@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

❌ Patch coverage is 28.57143% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
agent_assembly/types.py 28.57% 10 Missing ⚠️

📢 Thoughts on this report? Let us know!

@Chisanan232 Chisanan232 merged commit 6abb6c3 into master May 20, 2026
23 of 24 checks passed
@Chisanan232 Chisanan232 deleted the v0.0.1/AAASM-1442/feat/bridge_audit_event_ffi branch May 20, 2026 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant