feat(agents): SubagentSessionPersistence capability on the wrap_run seam - #842
Open
mpfaffenberger wants to merge 2 commits into
Open
feat(agents): SubagentSessionPersistence capability on the wrap_run seam#842mpfaffenberger wants to merge 2 commits into
mpfaffenberger wants to merge 2 commits into
Conversation
…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
This was referenced Aug 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Fourteenth entry in the capability-conversion series (#828–#836, #838–#841). Promotes sub-agent session persistence — the success-path
_save_session_historycall and the crash/interrupt_save_partial_sessioncalls in_invoke_agent_impl— to a first-class pydantic-ai capability,SubagentSessionPersistence, on thewrap_runseam (a previously unclaimed seam; pydantic-ai documents it as the home for cancellation-safe cleanup, and its handler task delivers both run failures andCancelledErrorinto the wrapping coroutine).Design
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; defaultfor_runreturnsself, and one instance correctly sees everystreaming_retryre-invocation — latest attempt's record wins).result.all_messages()saved at the run boundary (byte-identical payload to the eager save)_save_partial_sessionhelper the eager path usedException,KeyboardInterrupt, and (group-nested)CancelledErrorsave;SystemExit/GeneratorExitpass through untouchedrecorded_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).code_puppy.tools.subagent_invocationat call time, so the existing suites' patches on that module keep intercepting every write (pinned by test).Bounded divergences (documented + pinned)
streaming_retryrecovers now checkpoint partial progress per failed attempt (eager saved only when the failure escaped every retry). Final file state converges; strictly more durable.invoke_agent_with_model'sduration_ms.Scope notes
get_serialization_name() -> None, per series precedent.Tests
18 contract tests in
tests/agents/test_subagent_session_persistence_capability.py, including real-Agentsuccess/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.