Skip to content

feat(agent): add concurrent_invocation_mode parameter#1707

Open
zastrowm wants to merge 6 commits intomainfrom
agent-tasks/1702
Open

feat(agent): add concurrent_invocation_mode parameter#1707
zastrowm wants to merge 6 commits intomainfrom
agent-tasks/1702

Conversation

@zastrowm
Copy link
Member

Motivation

Users who need to re-trigger an agent during invocation (e.g., to provide feedback out-of-band) are currently blocked by ConcurrencyException introduced in #1453. While concurrent invocation detection is valuable for safety, power users requested an escape hatch to restore the previous behavior when they need concurrent re-invocations.

Resolves #1702

Public API Changes

Agent.__init__ now accepts a concurrent_invocation_mode parameter:

# Before: no way to allow concurrent invocations
agent = Agent(model=model)
# Concurrent calls raise ConcurrencyException

# After: opt into unsafe reentrant behavior
agent = Agent(model=model, concurrent_invocation_mode="unsafe_reentrant")
# Concurrent calls proceed without exception (user assumes responsibility)

The parameter accepts:

  • "throw" (default): Maintains existing behavior - raises ConcurrencyException on concurrent invocation attempts
  • "unsafe_reentrant": Skips lock acquisition entirely, allowing concurrent invocations

The new type ConcurrentInvocationMode is available via strands.types.agent.ConcurrentInvocationMode for type annotations.

Use Cases

Add a new parameter to Agent.__init__ that controls concurrent invocation
behavior:

- 'throw' (default): Raises ConcurrencyException if concurrent invocation
  is attempted, maintaining existing behavior
- 'unsafe_reentrant': Skips lock acquisition entirely, allowing concurrent
  invocations (restores pre-locking behavior)

This enables power users to re-invoke the same agent without exceptions
when needed, while preserving safe defaults for typical usage.

Resolves #1702
@codecov
Copy link

codecov bot commented Feb 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions
Copy link

Assessment: Approve

Clean implementation that provides an escape hatch for power users who need concurrent agent invocations while maintaining safe defaults.

Review Summary
  • API Design: Well-designed parameter with clear naming (unsafe_reentrant makes the risk explicit). Default "throw" maintains backward compatibility and safety.
  • Implementation: Lock handling is correctly conditional with proper cleanup in finally block.
  • Testing: Comprehensive coverage of both modes, sequential invocations, and exception scenarios.
  • Documentation: PR description and parameter docstrings are clear and complete.

Note: This PR introduces a new public API parameter. Per the API Bar Raising guidelines, moderate changes like this benefit from informal API review. Consider adding the needs-api-review label if not already informally reviewed, though the design looks sound and aligns with the issue requirements.

Address PR feedback:
- Update existing exception tests to explicitly pass concurrent_invocation_mode='throw'
- Remove redundant test_agent_concurrent_invocation_mode_throw_raises_exception test
- Rely on test_agent_concurrent_invocation_mode_default_is_throw for default verification
Address PR feedback:
- Add explicit warning about unsafe_reentrant making no guarantees
- Remove parenthetical about restoring pre-lock behavior
- Update both type definition and Agent.__init__ docstrings for consistency
@github-actions
Copy link

Thanks for the documentation update! The explicit warnings about unsafe_reentrant making no guarantees are a good addition. The changes look great and the PR is ready to merge. 👍

afarntrog
afarntrog previously approved these changes Feb 17, 2026
"Agent is already processing a request. Concurrent invocations are not supported."
)
lock_acquired = False
if self._concurrent_invocation_mode == "throw":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Let's use enums for the concurrent mode values

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been addressed in the latest commit - ConcurrentInvocationMode is now a str Enum that supports both enum values (ConcurrentInvocationMode.THROW) and string values ("throw").

@github-actions
Copy link

Re-review after enum refactor

The switch from Literal to str Enum is a good improvement:

Enum benefits: Better IDE autocompletion, self-documenting, can add methods later if needed
Backward compatible: Inheriting from str allows both ConcurrentInvocationMode.THROW and "throw" to work
Tests cover both patterns: Tests verify string and enum usage work correctly

The implementation looks good. Ready to merge once the design question about public vs private instance variables is resolved (I've added context in my reply to that thread).

mehtarac
mehtarac previously approved these changes Feb 17, 2026
pgrayy
pgrayy previously approved these changes Feb 17, 2026
def invoke():
try:
result = agent("test")
with lock:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I don't think this lock is necessary. Even if the GIL was disabled, list append is thread safe.

afarntrog
afarntrog previously approved these changes Feb 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Add option to allow concurrent invocations

5 participants