Skip to content

feat: concurrent mode — per-chat turn serialization + parallel tool execution#320

Merged
everettjf merged 7 commits intomainfrom
microclaw-concurrent_v1
Apr 3, 2026
Merged

feat: concurrent mode — per-chat turn serialization + parallel tool execution#320
everettjf merged 7 commits intomainfrom
microclaw-concurrent_v1

Conversation

@everettjf
Copy link
Copy Markdown
Contributor

Summary

Addresses #307 — moves MicroClaw toward a concurrent, non-blocking architecture.

  • Per-Chat Turn Serialization (ChatTurnQueue): Universal per-chat lock ensuring at most one agent run per (channel, chat_id) at a time. Messages arriving during an active run are queued and coalesced via configurable strategy (queue_then_rerun / queue_only). Replaces ad-hoc CHAT_LOCKS statics in Slack, Feishu, and Matrix. Adds missing per-chat serialization to Telegram and Discord (which previously had none — causing session race conditions).

  • Parallel Tool Execution (ParallelToolExecutor): When parallel_tool_execution: true, tool calls from a single LLM response are partitioned into execution waves by ToolConcurrencyClass:

    • ReadOnly tools (read_file, glob, web_search, etc.) run concurrently via futures::join_all
    • SideEffect tools (write_file, send_message, etc.) run sequentially
    • Exclusive tools (bash, activate_skill) run completely alone
    • Default: off (identical to current sequential behavior)

Key files

File Purpose
src/chat_turn_queue.rs ChatTurnQueue, TurnGuard, PendingMessage
src/tool_executor.rs Wave partitioning, parallel execution, tool batch context
crates/microclaw-tools/src/runtime.rs ToolConcurrencyClass enum + classification
src/agent_engine.rs Integration + extracted tool loop
src/channels/*.rs enqueue_if_busy + maybe_rerun_for_pending
src/config.rs 6 new config fields

Config

chat_turn_serialization: true          # default: true
chat_turn_queue_strategy: queue_then_rerun  # or queue_only
chat_turn_queue_max_pending: 20
parallel_tool_execution: false         # default: false (opt-in)
parallel_tool_max_concurrency: 8
tool_concurrency_overrides: {}         # e.g., mcp_custom_search: read_only

No database migrations needed

All new state is in-memory only.

Test plan

  • cargo build — zero errors, zero warnings
  • cargo clippy -- -D warnings — clean
  • cargo test -q — all tests pass (including 8 new ChatTurnQueue tests + 6 partition tests)
  • Manual: run with Telegram, send 2 rapid messages → verify second queued, both processed
  • Manual: enable parallel_tool_execution: true, ask bot to read 3 files → verify parallel execution in logs

🤖 Generated with Claude Code

everettjf and others added 7 commits April 1, 2026 18:09
…allel tool execution

Addresses #307 by adding two core concurrency features:

1. ChatTurnQueue: Universal per-chat turn serialization that ensures at most
   one agent run per (channel, chat_id) at a time. Messages arriving during
   an active run are queued and coalesced. Replaces ad-hoc per-channel locks
   in Slack, Feishu, and Matrix. Adds missing per-chat locking to Telegram
   and Discord. Configurable via chat_turn_serialization, chat_turn_queue_strategy,
   and chat_turn_queue_max_pending.

2. ParallelToolExecutor: When parallel_tool_execution is enabled, tool calls
   from a single LLM response are partitioned into execution waves based on
   ToolConcurrencyClass (ReadOnly/SideEffect/Exclusive). ReadOnly tools run
   concurrently via futures::join_all, SideEffect tools run sequentially,
   Exclusive tools run alone. Defaults to off (sequential, identical to
   current behavior).

Key changes:
- New src/chat_turn_queue.rs with ChatTurnQueue, TurnGuard, PendingMessage
- New src/tool_executor.rs with wave partitioning and parallel execution
- New ToolConcurrencyClass enum in microclaw-tools
- Extracted ~360-line tool execution loop from agent_engine.rs into tool_executor
- New AgentEvent variants: ToolWaveStart, ToolWaveComplete
- 6 new config fields for concurrency control
- No database migrations required — all new state is in-memory

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The coverage CI job (--all-features -Dwarnings) caught this unused import
after removing the ad-hoc matrix_chat_lock in favor of ChatTurnQueue.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…config

Remove `chat_turn_serialization` and `chat_turn_queue_strategy` config fields.
Turn serialization is now a built-in capability that is always enabled with
the queue_then_rerun strategy hardcoded. Only `chat_turn_queue_max_pending`
remains as a configurable safety limit.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Parallel tool execution is now a built-in core capability, always on.
The `parallel_tool_execution` config field is removed. Only
`parallel_tool_max_concurrency` and `tool_concurrency_overrides` remain
as tuning knobs.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…r_enqueue

The previous two-step pattern (enqueue_if_busy then acquire) had a race
window: rapid messages could all pass enqueue_if_busy before the first
message acquired the lock, causing all messages to serialize through
acquire() instead of being properly queued.

New try_start_or_enqueue() atomically either starts a turn (returns guard)
or queues the message (returns None), eliminating the race. Channel
adapters now pass the pre-acquired TurnGuard into the agent engine via
process_with_agent_with_events_guarded().

Also promoted queue log messages from debug to info level for visibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@everettjf everettjf merged commit 0e9d933 into main Apr 3, 2026
10 checks passed
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