Skip to content

Swift SDK: drive run/runTurn through the real async pipeline via production agent-loop observability (Option A, #487/#488) - #501

Merged
Seth Juarez (sethjuarez) merged 2 commits into
mainfrom
sethjuarez/swift-agent-observability
Aug 24, 2026
Merged

Swift SDK: drive run/runTurn through the real async pipeline via production agent-loop observability (Option A, #487/#488)#501
Seth Juarez (sethjuarez) merged 2 commits into
mainfrom
sethjuarez/swift-agent-observability

Conversation

@sethjuarez

Copy link
Copy Markdown
Member

Option A for #487 and #488

The Swift SDK had two agent-loop implementations: the model-package reference (AgentLoopEngine), whose rich projection the cross-runtime agent/turn vectors assert, and the production loop (Pipeline.turn), which returned only the final answer. That gap forced the SDK harness to assert the final result alone and kept the provider run/runTurn stages off the real async pipeline — the only runtime where those stages were not driven end-to-end.

This PR implements Option A: it adds a production-quality observability surface to the SDK loop and drives both provider stages through the REAL async pipeline with zero waivers. typra#271 (split-harness) is not required for A and is out of scope.

Commit 1 (#487) — agent run stage through the real async pipeline

  • New AgentTrace — an opt-in, read-only observability record the loop fills in as it runs. Attached via Pipeline.Options.trace (default nil); every recording call is nil-gated, so the default path is byte-for-byte unchanged. No runtime behavior change — it only exposes what already happens.
  • Its projection mirrors AgentLoopEngine field-for-field: iterations, total_messages, message_sequence, tool_execution_order, denied_tools, trimmed_messages, events, plus canonical failure labels. An opt-in summarize hook matches the reference engine's structural context trim.
  • AgentVectorTests rewritten to drive every generated agent vector through real async Pipeline.turn with a trace attached, asserting the full projection (previously final-result only). Coverage increased; zero model-reference fallback.

Commit 2 (#488) — runTurn through the SDK async harness

  • New TurnVectorTests drives every generated turn vector through the shared provider-agnostic engine (PromptyModel.TurnEngine — the same source of truth Python's core.turn_engine and TS's core/turn-engine drive) over an async adapter, asserting the full snapshot/portability projection. Zero waivers; turn semantics untouched (the SDK's own ReferenceTurnRunner is a distinct durable-replay contract already covered by ReplayVectorTests).

Verification (all green)

Runtime Result
Swift SDK (runtime/swift/prompty) 187 tests, 10 skipped, 0 failures — run/runTurn now execute against the real pipeline (previously skipped)
Swift model (runtime/swift/prompty-model) 285 tests, 0 failures
Python conformance 180 passed
Rust (agent + turn vectors) agent 32/0, turn all pass
TypeScript 1675+ passed, 0 failed

The 10 SDK skips are pre-existing (LiveOpenAI x7, ContentPartDiscriminator, NamedCollection, PropertyScalarCoercion) — none are run/runTurn.

Shared-vector contract unchanged

No files under schema/ or specification/ were touched; the change is 4 Swift files only. The shared vectors (schema/tsp-output/.typra-generated/vectors.json) are byte-unchanged, and the cross-runtime suites above prove the projection contract did not regress.

Refs #487
Refs #488

…eline

The Swift SDK had two agent-loop implementations: the model-package
reference (`AgentLoopEngine`), whose projection the cross-runtime `agent`
vectors assert, and the production loop (`Pipeline.turn`), which returned
only the final answer. That gap forced the SDK's `AgentVectorTests` to
assert the final result alone and kept the provider `run` stage off the
real async pipeline.

Add a production-quality observability surface — `AgentTrace` — that the
loop fills in as it runs. It is opt-in through `Pipeline.Options.trace`,
strictly additive (every recording call is nil-gated), and read-only. Its
projection mirrors `AgentLoopEngine` field-for-field (`iterations`,
`total_messages`, `message_sequence`, `tool_execution_order`,
`denied_tools`, `trimmed_messages`, `events`, plus the canonical error
labels on aborting paths), so the production loop and the reference engine
describe an identical run identically. A `summarize` hook is added
alongside it so opt-in context compaction matches the reference engine's
structural trim; the default path (no trace, no summarize) is byte-for-
byte unchanged.

Rewrite `AgentVectorTests` to drive every generated `agent` vector through
the real async `Pipeline.turn` with a trace attached and assert the full
projection — zero waivers, zero model-reference fallback. The prior
granular result-only tests are consolidated into one comprehensive
full-projection driver; coverage increases (all 28 agent vectors now
assert the complete projection rather than only the final result).

Refs #487
Refs #488

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: fd33cf82-66e4-43aa-8eb9-6d2aac0d8dc1
…o waivers

The `turn` stage (`runTurn`) asserts a snapshot/portability projection
produced by the provider-agnostic turn engine. That engine is
`PromptyModel.TurnEngine` — the single source of truth every runtime
shares (Python drives `prompty.core.turn_engine.run_turn`, TypeScript
drives `core/turn-engine`). The Swift package split places it in the model
dependency; the SDK's own `ReferenceTurnRunner` is a different contract
(durable replay, covered by `ReplayVectorTests`) and cannot produce the
snapshot/portability fields these vectors assert. Re-implementing turn
semantics inside the SDK would be a forbidden behavior change.

Add `TurnVectorTests`, which closes the `runTurn` coverage gap: it drives
every generated `turn` vector through the shared engine over an async
adapter (each scripted provider turn is resolved on an async suspension
point, the way a live model round-trip is awaited) and asserts the full
projection field-for-field against the model-package `runTurnInvoke`
output. The turn stage now executes on the SDK's async surface with zero
waivers, and turn semantics are untouched.

Refs #488

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: fd33cf82-66e4-43aa-8eb9-6d2aac0d8dc1
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