Skip to content

feat(agent): add Agent.shutdown() for scope-based cleanup - #4519

Merged
opieter-aws merged 3 commits into
strands-agents:mainfrom
opieter-aws:opieter-aws/harness-memory-flush-finally
Sep 22, 2026
Merged

opieter-aws merged 3 commits into
strands-agents:mainfrom
opieter-aws:opieter-aws/harness-memory-flush-finally

Conversation

@opieter-aws

@opieter-aws opieter-aws commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Description

Memory extraction runs in the background and is turn-triggered, so a short run can end with its latest turns never persisted. Today the only fix is a hand-written try { ... } finally { await agent.memoryManager?.flush() } at every call site, and on the async path there is no built-in place to hang that cleanup at all — the sync __call__ already auto-flushes, but invoke_async/invoke do not.

This adds a public shutdown() on the core SDK Agent that runs the agent's end-of-life procedures (today: flush pending memory) — safe to call more than once and a no-op when there's nothing to release. Scope-based disposal (async with in Python, await using in TypeScript) delegates to it, so the flush runs on normal exit and on a thrown error without a manual finally. shutdown() is deliberately named for the lifecycle stage, not memory, so future teardown has a single home.

Public API Changes

Python — strands.Agent:

async def shutdown(self) -> None: ...        # run end-of-life procedures (flushes memory)
async def __aenter__(self) -> "Agent": ...   # `async with agent:` returns the agent
async def __aexit__(self, *_) -> None: ...   # runs shutdown() on scope exit, re-raises

# usage
async with create_harness() as agent:
    await agent.invoke_async(task)
# shutdown() runs here, on exit and on throw

TypeScript — Agent:

async shutdown(): Promise<void>              // run end-of-life procedures (flushes memory)
async [Symbol.asyncDispose](): Promise<void> // runs shutdown() on scope exit

// usage
await using agent = await createHarness()
await agent.invoke(task)
// shutdown() runs here, on exit and on throw

Related Issues

N/A

Documentation PR

included

Type of Change

New feature

Testing

  • Python: hatch test tests/strands/agent/test_agent.py — covers direct shutdown(), async with normal exit, exception propagation with flush, and the no-memory-manager no-op.

  • TypeScript: npm test (strands-ts) — covers direct shutdown(), await using normal exit, scope-throw flush + re-throw, and the no-memory-manager no-op.

  • End-to-end against a real FileMemoryStore under a temp dir (mocked LLM, no AWS creds): confirmed memory files are written on scope exit and on throw with no manual flush, nothing persists without shutdown, and a 25-concurrent-agent stress run wrote all 25 files.

  • Format/lint clean: ruff format/ruff check/mypy (Python), prettier/lint/type-check (TypeScript).

  • I ran hatch run prepare

Checklist

  • I have read the CONTRIBUTING document
  • I have performed a self-review of my own code, including reviewing and understanding every line of code in this PR (including any generated by AI tools)
  • My changes are focused and reasonably small; I have split unrelated work into separate PRs
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added an appropriate example to the documentation to enable users to understand how it works
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@github-actions github-actions Bot added typescript Pull requests that update typescript code enhancement New feature or request area-agent Related to the agent class or general agent questions area-devx Developer experience improvements complexity/low Touched functions have low cognitive complexity (<=10) size/m labels Sep 22, 2026
@opieter-aws
opieter-aws requested a review from pgrayy September 22, 2026 20:08
@codecov

codecov Bot commented Sep 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@opieter-aws
opieter-aws force-pushed the opieter-aws/harness-memory-flush-finally branch from 2cf90dd to 7e87c79 Compare September 22, 2026 20:12
@opieter-aws
opieter-aws marked this pull request as ready for review September 22, 2026 20:12
@opieter-aws
opieter-aws requested a review from a team as a code owner September 22, 2026 20:12
@opieter-aws opieter-aws added the api/needs-review Makes changes to the public API surface label Sep 22, 2026
@github-actions

github-actions Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Documentation Preview Ready

Your documentation preview has been successfully deployed!

Changed pages:

Updated at: 2026-09-22T22:03:00.265Z

@pgrayy pgrayy added the api/review-complete An API Bar-raiser reviewed and accepted the APIs label Sep 22, 2026
pgrayy
pgrayy previously approved these changes Sep 22, 2026
Comment thread strands-py/src/strands/agent/agent.py Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Assessment: Comment

Clean, well-scoped change that gives memory flushing a proper lifecycle home and removes the manual try/finally boilerplate. Cross-SDK parity is solid, the Symbol.asyncDispose usage matches the existing McpClient pattern, docs and tests are thorough, and coverage is complete. The comments below are non-blocking; the main item is an API-design question for the api/needs-review reviewer.

Review Themes
  • API design (for the API reviewer): Disposal is async-only in both SDKs (async with / await using), with no sync counterpart and an async-only shutdown(). Worth an explicit decision given the sync __call__ path exists and auto-flushes. Also worth a quick sanity check on the name shutdown() vs. Python's more conventional aclose()/close() for cleanup — the "lifecycle stage, single teardown home" rationale in the description is reasonable, just confirm it with the reviewer.
  • Testing: The documented "safe to call more than once" contract isn't exercised — a double-shutdown() test would lock it in.
  • Robustness/docs: Scope-exit correctness relies on flush() never throwing; Python documents this, TS leaves it implicit. Suggest documenting the TS guarantee.

Nicely done removing a footgun — the scope-based flush on both normal exit and throw is a real DX win.

@opieter-aws
opieter-aws force-pushed the opieter-aws/harness-memory-flush-finally branch from 3463a88 to a1c15ce Compare September 22, 2026 21:59
Comment thread strands-py/src/strands/agent/agent.py
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for the quick turnaround on the API-design thread — the revision lands it cleanly:

  • shutdown() (sync) → run_async(self.shutdown_async) mirrors the established __call__/invoke_async pattern, and shutdown_async() is the async variant.
  • Delegation is wired correctly: __exit__self.shutdown() (sync) and __aexit__await self.shutdown_async() (async, so no needless thread/loop bridge from inside a running loop). Both sync with and async async with are now supported with symmetric error-propagation tests. 👍

One non-blocking note on the Codecov patch report (81.81%, "1 missing + 1 partial"): I ran the shutdown/context-manager tests locally with --cov-branch and the entire shutdown/shutdown_async/__enter__/__exit__/__aenter__/__aexit__ block came back fully covered. The CI gap is most likely a run_async artifact — the sync path executes shutdown_async inside a ThreadPoolExecutor worker, and coverage.py won't trace lines run in spawned threads without concurrency=thread. Since test_agent_shutdown_async_flushes_memory_manager covers those lines directly on the main thread, this is a measurement quirk rather than a real coverage hole — worth a quick confirm but not a blocker.

@opieter-aws
opieter-aws merged commit fec0427 into strands-agents:main Sep 22, 2026
130 of 151 checks passed

This branch was successfully deployed

1 active deployment
auto-approve a1c15ce4 Deployed Sep 22, 2026 by opieter-aws via Build and Deploy Preview #1988
manual-approval 2cf90dd3 Deployed Sep 22, 2026 by opieter-aws via Build and Deploy Preview #1975
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api/needs-review Makes changes to the public API surface api/review-complete An API Bar-raiser reviewed and accepted the APIs area-agent Related to the agent class or general agent questions area-devx Developer experience improvements complexity/low Touched functions have low cognitive complexity (<=10) enhancement New feature or request size/m typescript Pull requests that update typescript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants