Skip to content

feat(agents): SubagentSessionPersistence capability on the wrap_run seam - #842

Open
mpfaffenberger wants to merge 2 commits into
mainfrom
feature/subagent-session-persistence-capability
Open

feat(agents): SubagentSessionPersistence capability on the wrap_run seam#842
mpfaffenberger wants to merge 2 commits into
mainfrom
feature/subagent-session-persistence-capability

Conversation

@mpfaffenberger

Copy link
Copy Markdown
Owner

What

Fourteenth entry in the capability-conversion series (#828#836, #838#841). Promotes sub-agent session persistence — the success-path _save_session_history call and the crash/interrupt _save_partial_session calls in _invoke_agent_impl — to a first-class pydantic-ai capability, SubagentSessionPersistence, on the wrap_run seam (a previously unclaimed seam; pydantic-ai documents it as the home for cancellation-safe cleanup, and its handler task delivers both run failures and CancelledError into the wrapping coroutine).

Design

  • New code_puppy/agents/_subagent_sessions.py — per-invocation dataclass capability (the sub-agent's pydantic agent is itself per-invocation, so instance state is naturally run-scoped; default for_run returns self, and one instance correctly sees every streaming_retry re-invocation — latest attempt's record wins).
    • success → full transcript from result.all_messages() saved at the run boundary (byte-identical payload to the eager save)
    • failure/cancel → partial progress saved via the same _save_partial_session helper the eager path used
    • eager triage mirrored exactly: Exception, KeyboardInterrupt, and (group-nested) CancelledError save; SystemExit/GeneratorExit pass through untouched
  • Explicit-when-ours, fallback-for-guests (the feat: deliver Logfire tracing as an explicit Instrumentation capability #838/feat(agents): promote tool-call JSON repair to a ToolCallJsonRepair capability #841 split): the invocation layer reads the capability's custody records via recorded_save() and keeps its messaging/breadcrumbs; the eager saves survive only as fallback for exits the boundary never sees (guest wrappers bypassing capabilities, failures before/after the run proper).
  • Test-patch compatibility: save helpers resolve through code_puppy.tools.subagent_invocation at call time, so the existing suites' patches on that module keep intercepting every write (pinned by test).

Bounded divergences (documented + pinned)

  1. Transient failures that streaming_retry recovers now checkpoint partial progress per failed attempt (eager saved only when the failure escaped every retry). Final file state converges; strictly more durable.
  2. The success save happens inside the run boundary, before high-mode fallback rendering and usage capture — one atomic local file write folds into invoke_agent_with_model's duration_ms.
  3. A crash between a successful run and the invocation layer's bookkeeping used to overwrite the session with the older live checkpoint; the boundary save now stands, so the persisted transcript keeps the final response.

Scope notes

  • Sub-agent site only: main-agent sessions are persisted by the autosave pipeline, not at the pydantic-ai run boundary (pinned by source test).
  • Not spec-constructible (carries a live BaseAgent config reference): get_serialization_name() -> None, per series precedent.

Tests

18 contract tests in tests/agents/test_subagent_session_persistence_capability.py, including real-Agent success/failure/cancellation custody, triage parity, retry latest-wins, and invocation-layer wiring (boundary-save skip, guest fallback, no-clobber guard).

Full suite: 7615 passed, 0 failed (28 skipped, 1 xpassed).

Do not merge yet — review round pending.

…ionPersistence capability

Sub-agent session saves previously lived as eager call sites scattered
around the run: a success-path _save_session_history after rendering and
usage capture, plus _save_partial_session calls in the except-BaseException
block for crashes and interruptions. The persistence contract now rides
pydantic-ai's wrap_run seam -- the documented home for cancellation-safe
cleanup -- as a per-invocation SubagentSessionPersistence capability:

- success: the full transcript from result.all_messages() is saved at the
  run boundary (byte-identical payload to the eager post-run save)
- failure/cancellation: partial progress is saved via the exact
  _save_partial_session helper the eager path used, with the eager triage
  mirrored precisely (SystemExit/GeneratorExit never save)
- the invocation layer keeps all messaging and reads the capability's
  custody records (recorded_save); the eager saves survive only as a
  fallback for exits the run boundary never sees (guest wrappers,
  failures before/after the run proper)

Save helpers resolve through code_puppy.tools.subagent_invocation at call
time, so existing test patches on that module keep intercepting writes.

Bounded divergences, documented in the module docstring and pinned in
tests: retried transient failures now checkpoint partial progress per
attempt (final state converges); the success save happens inside the run
boundary (one atomic file write folds into invoke_agent_with_model's
duration_ms); and a crash after a successful run no longer overwrites the
saved full transcript with the older live checkpoint.

18 contract tests in tests/agents/test_subagent_session_persistence_capability.py.
Full suite: 7615 passed, 0 failed.
- pin real CombinedCapability composition: the three-capability sub-agent
  shape routed through an actual pydantic-ai Agent run produces exactly
  one boundary save and records custody on the original instance
- pin resumed-session wiring through _invoke_agent_impl: baseline_count
  reflects the loaded history and initial_prompt stays None for both the
  capability and the eager fallback
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