perf(infinity-agent-core)!: cut idle-agent memory ~30% (154 → ~107 KB/agent) - #105
Merged
Merged
Conversation
shadaj
added a commit
that referenced
this pull request
Aug 26, 2026
…sand agents in 8 GB Optimizes per-agent resident memory for the `agent_scale` benchmark added by the parent commit: 154 → 107.4 KB per idle agent (measured slope), with 80,000 agents × 20 turns fitting in 8.89 GB and the 8 GB line crossed at ~71,700 agents. Three independent optimizations: ## Router owns driver futures directly (no `spawn_local` per driver) * `route_loop` drives all thread drivers through one `FuturesUnordered` pool instead of spawning each as its own `LocalSet` task. When a driver goes idle, its future yields its thread ID and the router immediately frees the future and the worker entry (input/subscribe channels). * Previously a finished driver's `JoinHandle`, task allocation, and channel blocks were retained in the `workers` map until the thread's *next* message — ~13 KB per idle agent. * Panic isolation preserved (each pooled future is wrapped in `rap_protocol::log_panic`); shutdown wind-down drains the pool. ## `InfinityMessage::SubscriptionEvent` payloads boxed * Boxed `result` and `invocation` in the rare `SubscriptionEvent` variant, shrinking `size_of::<InfinityMessage>()` from 352 → 184 bytes; every stored history message previously paid for the fattest variant inline. `Box` is serde-transparent so the persisted format is unchanged. * Added `InfinityMessage::tool_result()` helper. (Boxing only `invocation` was measured and rejected: the enum grows to 200 bytes because `SubscriptionEvent` with an inline result becomes the largest variant.) ## Tool-call dedup derived from history instead of a durable index (BREAKING) * `HistoryManager` no longer maintains `processed_tool_calls` / `pending_complete_tool_calls`. Incoming tool results are deduplicated by walking the history tail just in time: scan back across trailing tool calls/results (future-proof for concurrent calls, e.g. `tc tc tr tr`), accept on a matching unanswered call, reject as duplicate on a matching result, discard as stale on any other message (user text, assistant content, subscription events — all turn boundaries, since a subscription event is only injected once pending calls are settled). * `safe_spawn_point` uses the same walk, tracking answered calls during the scan. * Durable message-ID dedup is limited to inputs that are not naturally idempotent: user text and subscription events (a redelivered subscription event would mint a fresh injected invocation). Tool results and assistant/tool-call items no longer persist IDs. * **BREAKING**: `StateStore::get_processed_ids` returns a single `HashSet<String>`; `add_processed_tool_calls` removed. Updated `InMemoryStateStore`, the daemon's `PersistentStateStore`, and the Lambda `DynamoDbStateStore` (old DynamoDB `processed_tool_calls` attributes are ignored). `ThreadState` keeps its `processed_tool_call_ids` field so old daemon snapshots still deserialize. ## Benchmark & docs * `agent_scale` drains lifecycle notifications per wave (like a real embedding) so they aren't counted as per-agent memory. * Landing page: `MemoryChart` regenerated from an 80,000-agent run (8.89 GB total, 108.5 KB/agent); hero, chapter title, and copy updated to "seventy thousand agents on a Raspberry Pi" (measured 8 GB crossing: ~71,700). * `history-manager.md` updated for the new dedup model. All workspace tests pass; clippy clean. Co-authored-by: Infinity 🤖 <infinity@hydro.run> PR: #105
shadaj
force-pushed
the
sandbox-c621cbab-f468-4f5c-ae56-a75a42f439df
branch
from
August 26, 2026 21:39
0c5f87c to
9fc0e58
Compare
Deploying infinity with
|
| Latest commit: |
844f909
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://bcbf8910.infinity-dc7.pages.dev |
| Branch Preview URL: | https://sandbox-c621cbab-f468-4f5c-a.infinity-dc7.pages.dev |
shadaj
changed the base branch from
main
to
sandbox-c6b6c253-5de3-4526-ba0f-e98fe288bc7e
August 26, 2026 21:40
MingweiSamuel
approved these changes
Aug 26, 2026
Comment on lines
+268
to
+272
| // Only remove the exited driver's own entry: if the thread | ||
| // already respawned, the new entry's channel is still open. | ||
| if workers.get(&exited).is_some_and(|w| w.input_tx.is_closed()) { | ||
| workers.remove(&exited); | ||
| } |
Member
There was a problem hiding this comment.
ABA problem here, ideally would use a slotmap or something. but probably fine?
| @@ -249,11 +252,26 @@ async fn route_loop<C, S, H, O, F>( | |||
| { | |||
| let mut workers: HashMap<String, WorkerChannels<O::SubscribeRequest>> = HashMap::new(); | |||
Member
There was a problem hiding this comment.
What are the string keys here?
Member
Author
There was a problem hiding this comment.
Thread IDs, we should probably newtype hmm.
Comment on lines
+284
to
+288
| if call.id == result_id { | ||
| // Its result would have been seen before the call in | ||
| // a backwards walk, so this call is unanswered. | ||
| return ToolResultMatch::Unanswered; | ||
| } |
Member
There was a problem hiding this comment.
Nice, so the assumption is the tool call and (eventual) tool result will not ever be too far apart in history?
Member
Author
There was a problem hiding this comment.
Yes because the only trailing tool calls / results will be for concurrent calls, of which there should not be that many.
Comment on lines
+395
to
398
| processed_ids: Arc<Mutex<HashMap<String, HashSet<String>>>>, | ||
| metadata: Arc<Mutex<HashMap<String, serde_json::Value>>>, | ||
| subscriptions: Arc<Mutex<HashMap<String, HashSet<String>>>>, | ||
| pending_user_choices: Arc<Mutex<HashMap<String, Vec<UserChoice>>>>, |
Member
There was a problem hiding this comment.
we gotta get rid of these bare strings
shadaj
added a commit
that referenced
this pull request
Aug 27, 2026
…sand agents in 8 GB Optimizes per-agent resident memory for the `agent_scale` benchmark added by the parent commit: 154 → 107.4 KB per idle agent (measured slope), with 80,000 agents × 20 turns fitting in 8.89 GB and the 8 GB line crossed at ~71,700 agents. Three independent optimizations: ## Router owns driver futures directly (no `spawn_local` per driver) * `route_loop` drives all thread drivers through one `FuturesUnordered` pool instead of spawning each as its own `LocalSet` task. When a driver goes idle, its future yields its thread ID and the router immediately frees the future and the worker entry (input/subscribe channels). * Previously a finished driver's `JoinHandle`, task allocation, and channel blocks were retained in the `workers` map until the thread's *next* message — ~13 KB per idle agent. * Panic isolation preserved (each pooled future is wrapped in `rap_protocol::log_panic`); shutdown wind-down drains the pool. ## `InfinityMessage::SubscriptionEvent` payloads boxed * Boxed `result` and `invocation` in the rare `SubscriptionEvent` variant, shrinking `size_of::<InfinityMessage>()` from 352 → 184 bytes; every stored history message previously paid for the fattest variant inline. `Box` is serde-transparent so the persisted format is unchanged. * Added `InfinityMessage::tool_result()` helper. (Boxing only `invocation` was measured and rejected: the enum grows to 200 bytes because `SubscriptionEvent` with an inline result becomes the largest variant.) ## Tool-call dedup derived from history instead of a durable index (BREAKING) * `HistoryManager` no longer maintains `processed_tool_calls` / `pending_complete_tool_calls`. Incoming tool results are deduplicated by walking the history tail just in time: scan back across trailing tool calls/results (future-proof for concurrent calls, e.g. `tc tc tr tr`), accept on a matching unanswered call, reject as duplicate on a matching result, discard as stale on any other message (user text, assistant content, subscription events — all turn boundaries, since a subscription event is only injected once pending calls are settled). * `safe_spawn_point` uses the same walk, tracking answered calls during the scan. * Durable message-ID dedup is limited to inputs that are not naturally idempotent: user text and subscription events (a redelivered subscription event would mint a fresh injected invocation). Tool results and assistant/tool-call items no longer persist IDs. * **BREAKING**: `StateStore::get_processed_ids` returns a single `HashSet<String>`; `add_processed_tool_calls` removed. Updated `InMemoryStateStore`, the daemon's `PersistentStateStore`, and the Lambda `DynamoDbStateStore` (old DynamoDB `processed_tool_calls` attributes are ignored). `ThreadState` drops its `processed_tool_call_ids` field entirely — serde ignores unknown fields by default, so snapshots from older versions still deserialize. ## Benchmark & docs * `agent_scale` drains lifecycle notifications per wave (like a real embedding) so they aren't counted as per-agent memory. * Landing page: `MemoryChart` regenerated from an 80,000-agent run (8.89 GB total, 108.5 KB/agent); hero, chapter title, and copy updated to "seventy thousand agents on a Raspberry Pi" (measured 8 GB crossing: ~71,700). * `history-manager.md` updated for the new dedup model. All workspace tests pass; clippy clean. BREAKING CHANGE: `StateStore::get_processed_ids` now returns `HashSet<String>` (message IDs only) instead of a two-set tuple. BREAKING CHANGE: `StateStore::add_processed_tool_calls` is removed; tool results are deduplicated against conversation history. BREAKING CHANGE: `ThreadState::processed_tool_call_ids` is removed (old serialized snapshots still deserialize; the field is ignored). Co-authored-by: Infinity 🤖 <infinity@hydro.run> PR: #105
shadaj
force-pushed
the
sandbox-c621cbab-f468-4f5c-ae56-a75a42f439df
branch
from
August 27, 2026 00:00
9fc0e58 to
f5ec9ea
Compare
shadaj
added a commit
that referenced
this pull request
Aug 27, 2026
…sand agents in 8 GB Optimizes per-agent resident memory for the `agent_scale` benchmark added by the parent commit: 154 → 107.4 KB per idle agent (measured slope), with 80,000 agents × 20 turns fitting in 8.89 GB and the 8 GB line crossed at ~71,700 agents. Three independent optimizations: ## Router owns driver futures directly (no `spawn_local` per driver) * `route_loop` drives all thread drivers through one `FuturesUnordered` pool instead of spawning each as its own `LocalSet` task. When a driver goes idle, its future yields its thread ID and the router immediately frees the future and the worker entry (input/subscribe channels). * Previously a finished driver's `JoinHandle`, task allocation, and channel blocks were retained in the `workers` map until the thread's *next* message — ~13 KB per idle agent. * Panic isolation preserved (each pooled future is wrapped in `rap_protocol::log_panic`); shutdown wind-down drains the pool. ## `InfinityMessage::SubscriptionEvent` payloads boxed * Boxed `result` and `invocation` in the rare `SubscriptionEvent` variant, shrinking `size_of::<InfinityMessage>()` from 352 → 184 bytes; every stored history message previously paid for the fattest variant inline. `Box` is serde-transparent so the persisted format is unchanged. * Added `InfinityMessage::tool_result()` helper. (Boxing only `invocation` was measured and rejected: the enum grows to 200 bytes because `SubscriptionEvent` with an inline result becomes the largest variant.) ## Tool-call dedup derived from history instead of a durable index (BREAKING) * `HistoryManager` no longer maintains `processed_tool_calls` / `pending_complete_tool_calls`. Incoming tool results are deduplicated by walking the history tail just in time: scan back across trailing tool calls/results (future-proof for concurrent calls, e.g. `tc tc tr tr`), accept on a matching unanswered call, reject as duplicate on a matching result, discard as stale on any other message (user text, assistant content, subscription events — all turn boundaries, since a subscription event is only injected once pending calls are settled). * `safe_spawn_point` uses the same walk, tracking answered calls during the scan. * Durable message-ID dedup is limited to inputs that are not naturally idempotent: user text and subscription events (a redelivered subscription event would mint a fresh injected invocation). Tool results and assistant/tool-call items no longer persist IDs. * **BREAKING**: `StateStore::get_processed_ids` returns a single `HashSet<String>`; `add_processed_tool_calls` removed. Updated `InMemoryStateStore`, the daemon's `PersistentStateStore`, and the Lambda `DynamoDbStateStore` (old DynamoDB `processed_tool_calls` attributes are ignored). `ThreadState` drops its `processed_tool_call_ids` field entirely — serde ignores unknown fields by default, so snapshots from older versions still deserialize. ## Benchmark & docs * `agent_scale` drains lifecycle notifications per wave (like a real embedding) so they aren't counted as per-agent memory. * Landing page: `MemoryChart` regenerated from an 80,000-agent run (8.89 GB total, 108.5 KB/agent); hero, chapter title, and copy updated to "seventy thousand agents on a Raspberry Pi" (measured 8 GB crossing: ~71,700). * `history-manager.md` updated for the new dedup model. All workspace tests pass; clippy clean. BREAKING CHANGE: `StateStore::get_processed_ids` now returns `HashSet<String>` (message IDs only) instead of a two-set tuple. BREAKING CHANGE: `StateStore::add_processed_tool_calls` is removed; tool results are deduplicated against conversation history. BREAKING CHANGE: `ThreadState::processed_tool_call_ids` is removed (old serialized snapshots still deserialize; the field is ignored). Co-authored-by: Infinity 🤖 <infinity@hydro.run> PR: #105
shadaj
force-pushed
the
sandbox-c621cbab-f468-4f5c-ae56-a75a42f439df
branch
from
August 27, 2026 00:01
f5ec9ea to
d5eaaa8
Compare
shadaj
force-pushed
the
sandbox-c6b6c253-5de3-4526-ba0f-e98fe288bc7e
branch
5 times, most recently
from
August 27, 2026 18:04
d6c4d7c to
fab57ef
Compare
Base automatically changed from
sandbox-c6b6c253-5de3-4526-ba0f-e98fe288bc7e
to
main
August 27, 2026 19:29
shadaj
added a commit
that referenced
this pull request
Aug 27, 2026
…sand agents in 8 GB Optimizes per-agent resident memory for the `agent_scale` benchmark added by the parent commit: 154 → 107.4 KB per idle agent (measured slope), with 80,000 agents × 20 turns fitting in 8.89 GB and the 8 GB line crossed at ~71,700 agents. Three independent optimizations: ## Router owns driver futures directly (no `spawn_local` per driver) * `route_loop` drives all thread drivers through one `FuturesUnordered` pool instead of spawning each as its own `LocalSet` task. When a driver goes idle, its future yields its thread ID and the router immediately frees the future and the worker entry (input/subscribe channels). * Previously a finished driver's `JoinHandle`, task allocation, and channel blocks were retained in the `workers` map until the thread's *next* message — ~13 KB per idle agent. * Panic isolation preserved (each pooled future is wrapped in `rap_protocol::log_panic`); shutdown wind-down drains the pool. ## `InfinityMessage::SubscriptionEvent` payloads boxed * Boxed `result` and `invocation` in the rare `SubscriptionEvent` variant, shrinking `size_of::<InfinityMessage>()` from 352 → 184 bytes; every stored history message previously paid for the fattest variant inline. `Box` is serde-transparent so the persisted format is unchanged. * Added `InfinityMessage::tool_result()` helper. (Boxing only `invocation` was measured and rejected: the enum grows to 200 bytes because `SubscriptionEvent` with an inline result becomes the largest variant.) ## Tool-call dedup derived from history instead of a durable index (BREAKING) * `HistoryManager` no longer maintains `processed_tool_calls` / `pending_complete_tool_calls`. Incoming tool results are deduplicated by walking the history tail just in time: scan back across trailing tool calls/results (future-proof for concurrent calls, e.g. `tc tc tr tr`), accept on a matching unanswered call, reject as duplicate on a matching result, discard as stale on any other message (user text, assistant content, subscription events — all turn boundaries, since a subscription event is only injected once pending calls are settled). * `safe_spawn_point` uses the same walk, tracking answered calls during the scan. * Durable message-ID dedup is limited to inputs that are not naturally idempotent: user text and subscription events (a redelivered subscription event would mint a fresh injected invocation). Tool results and assistant/tool-call items no longer persist IDs. * **BREAKING**: `StateStore::get_processed_ids` returns a single `HashSet<String>`; `add_processed_tool_calls` removed. Updated `InMemoryStateStore`, the daemon's `PersistentStateStore`, and the Lambda `DynamoDbStateStore` (old DynamoDB `processed_tool_calls` attributes are ignored). `ThreadState` drops its `processed_tool_call_ids` field entirely — serde ignores unknown fields by default, so snapshots from older versions still deserialize. ## Benchmark & docs * `agent_scale` drains lifecycle notifications per wave (like a real embedding) so they aren't counted as per-agent memory. * Landing page: `MemoryChart` regenerated from an 80,000-agent run (8.89 GB total, 108.5 KB/agent); hero, chapter title, and copy updated to "seventy thousand agents on a Raspberry Pi" (measured 8 GB crossing: ~71,700). * `history-manager.md` updated for the new dedup model. All workspace tests pass; clippy clean. BREAKING CHANGE: `StateStore::get_processed_ids` now returns `HashSet<String>` (message IDs only) instead of a two-set tuple. BREAKING CHANGE: `StateStore::add_processed_tool_calls` is removed; tool results are deduplicated against conversation history. BREAKING CHANGE: `ThreadState::processed_tool_call_ids` is removed (old serialized snapshots still deserialize; the field is ignored). Co-authored-by: Infinity 🤖 <infinity@hydro.run> PR: #105
shadaj
force-pushed
the
sandbox-c621cbab-f468-4f5c-ae56-a75a42f439df
branch
from
August 27, 2026 19:41
d5eaaa8 to
581eec7
Compare
shadaj
marked this pull request as ready for review
August 27, 2026 19:41
shadaj
added a commit
that referenced
this pull request
Aug 27, 2026
…sand agents in 8 GB Optimizes per-agent resident memory for the `agent_scale` benchmark added by the parent commit: 154 → 107.4 KB per idle agent (measured slope), with 80,000 agents × 20 turns fitting in 8.89 GB and the 8 GB line crossed at ~71,700 agents. Three independent optimizations: ## Router owns driver futures directly (no `spawn_local` per driver) * `route_loop` drives all thread drivers through one `FuturesUnordered` pool instead of spawning each as its own `LocalSet` task. When a driver goes idle, its future yields its thread ID and the router immediately frees the future and the worker entry (input/subscribe channels). * Previously a finished driver's `JoinHandle`, task allocation, and channel blocks were retained in the `workers` map until the thread's *next* message — ~13 KB per idle agent. * Panic isolation preserved (each pooled future is wrapped in `rap_protocol::log_panic`); shutdown wind-down drains the pool. ## `InfinityMessage::SubscriptionEvent` payloads boxed * Boxed `result` and `invocation` in the rare `SubscriptionEvent` variant, shrinking `size_of::<InfinityMessage>()` from 352 → 184 bytes; every stored history message previously paid for the fattest variant inline. `Box` is serde-transparent so the persisted format is unchanged. * Added `InfinityMessage::tool_result()` helper. (Boxing only `invocation` was measured and rejected: the enum grows to 200 bytes because `SubscriptionEvent` with an inline result becomes the largest variant.) ## Tool-call dedup derived from history instead of a durable index (BREAKING) * `HistoryManager` no longer maintains `processed_tool_calls` / `pending_complete_tool_calls`. Incoming tool results are deduplicated by walking the history tail just in time: scan back across trailing tool calls/results (future-proof for concurrent calls, e.g. `tc tc tr tr`), accept on a matching unanswered call, reject as duplicate on a matching result, discard as stale on any other message (user text, assistant content, subscription events — all turn boundaries, since a subscription event is only injected once pending calls are settled). * `safe_spawn_point` uses the same walk, tracking answered calls during the scan. * Durable message-ID dedup is limited to inputs that are not naturally idempotent: user text and subscription events (a redelivered subscription event would mint a fresh injected invocation). Tool results and assistant/tool-call items no longer persist IDs. * **BREAKING**: `StateStore::get_processed_ids` returns a single `HashSet<String>`; `add_processed_tool_calls` removed. Updated `InMemoryStateStore`, the daemon's `PersistentStateStore`, and the Lambda `DynamoDbStateStore` (old DynamoDB `processed_tool_calls` attributes are ignored). `ThreadState` drops its `processed_tool_call_ids` field entirely — serde ignores unknown fields by default, so snapshots from older versions still deserialize. ## Benchmark & docs * `agent_scale` drains lifecycle notifications per wave (like a real embedding) so they aren't counted as per-agent memory. * Landing page: `MemoryChart` regenerated from an 80,000-agent run (8.89 GB total, 108.5 KB/agent); hero, chapter title, and copy updated to "seventy thousand agents on a Raspberry Pi" (measured 8 GB crossing: ~71,700). * `history-manager.md` updated for the new dedup model. All workspace tests pass; clippy clean. BREAKING CHANGE: `StateStore::get_processed_ids` now returns `HashSet<String>` (message IDs only) instead of a two-set tuple. BREAKING CHANGE: `StateStore::add_processed_tool_calls` is removed; tool results are deduplicated against conversation history. BREAKING CHANGE: `ThreadState::processed_tool_call_ids` is removed (old serialized snapshots still deserialize; the field is ignored). Co-authored-by: Infinity 🤖 <infinity@hydro.run> PR: #105 Co-authored-by: Mingwei Samuel <mingwes@amazon.com> PR: #105
shadaj
force-pushed
the
sandbox-c621cbab-f468-4f5c-ae56-a75a42f439df
branch
from
August 27, 2026 19:45
e36365b to
e3776c8
Compare
…sand agents in 8 GB Optimizes per-agent resident memory for the `agent_scale` benchmark added by the parent commit: 154 → 107.4 KB per idle agent (measured slope), with 80,000 agents × 20 turns fitting in 8.89 GB and the 8 GB line crossed at ~71,700 agents. Three independent optimizations: ## Router owns driver futures directly (no `spawn_local` per driver) * `route_loop` drives all thread drivers through one `FuturesUnordered` pool instead of spawning each as its own `LocalSet` task. When a driver goes idle, its future yields its thread ID and the router immediately frees the future and the worker entry (input/subscribe channels). * Previously a finished driver's `JoinHandle`, task allocation, and channel blocks were retained in the `workers` map until the thread's *next* message — ~13 KB per idle agent. * Panic isolation preserved (each pooled future is wrapped in `rap_protocol::log_panic`); shutdown wind-down drains the pool. ## `InfinityMessage::SubscriptionEvent` payloads boxed * Boxed `result` and `invocation` in the rare `SubscriptionEvent` variant, shrinking `size_of::<InfinityMessage>()` from 352 → 184 bytes; every stored history message previously paid for the fattest variant inline. `Box` is serde-transparent so the persisted format is unchanged. * Added `InfinityMessage::tool_result()` helper. (Boxing only `invocation` was measured and rejected: the enum grows to 200 bytes because `SubscriptionEvent` with an inline result becomes the largest variant.) ## Tool-call dedup derived from history instead of a durable index (BREAKING) * `HistoryManager` no longer maintains `processed_tool_calls` / `pending_complete_tool_calls`. Incoming tool results are deduplicated by walking the history tail just in time: scan back across trailing tool calls/results (future-proof for concurrent calls, e.g. `tc tc tr tr`), accept on a matching unanswered call, reject as duplicate on a matching result, discard as stale on any other message (user text, assistant content, subscription events — all turn boundaries, since a subscription event is only injected once pending calls are settled). * `safe_spawn_point` uses the same walk, tracking answered calls during the scan. * Durable message-ID dedup is limited to inputs that are not naturally idempotent: user text and subscription events (a redelivered subscription event would mint a fresh injected invocation). Tool results and assistant/tool-call items no longer persist IDs. * **BREAKING**: `StateStore::get_processed_ids` returns a single `HashSet<String>`; `add_processed_tool_calls` removed. Updated `InMemoryStateStore`, the daemon's `PersistentStateStore`, and the Lambda `DynamoDbStateStore` (old DynamoDB `processed_tool_calls` attributes are ignored). `ThreadState` drops its `processed_tool_call_ids` field entirely — serde ignores unknown fields by default, so snapshots from older versions still deserialize. ## Benchmark & docs * `agent_scale` drains lifecycle notifications per wave (like a real embedding) so they aren't counted as per-agent memory. * Landing page: `MemoryChart` regenerated from an 80,000-agent run (8.89 GB total, 108.5 KB/agent); hero, chapter title, and copy updated to "seventy thousand agents on a Raspberry Pi" (measured 8 GB crossing: ~71,700). * `history-manager.md` updated for the new dedup model. All workspace tests pass; clippy clean. BREAKING CHANGE: `StateStore::get_processed_ids` now returns `HashSet<String>` (message IDs only) instead of a two-set tuple. BREAKING CHANGE: `StateStore::add_processed_tool_calls` is removed; tool results are deduplicated against conversation history. BREAKING CHANGE: `ThreadState::processed_tool_call_ids` is removed (old serialized snapshots still deserialize; the field is ignored). Co-authored-by: Infinity 🤖 <infinity@hydro.run> PR: #105 Co-authored-by: Mingwei Samuel <mingwes@amazon.com> PR: #105
shadaj
force-pushed
the
sandbox-c621cbab-f468-4f5c-ae56-a75a42f439df
branch
from
August 27, 2026 20:29
e3776c8 to
844f909
Compare
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.
Optimizes per-agent resident memory for the
agent_scalebenchmark added by the parent commit, in three independent pieces:Router owns driver futures directly (no
spawn_localper driver)route_loopnow drives all thread drivers through oneFuturesUnorderedpool instead of spawning each as its ownLocalSettask. When a driver goes idle, its future yields its thread ID and the router immediately drops both the future's memory and the worker entry (input/subscribe channels).JoinHandle, task allocation, and channel blocks were retained in theworkersmap until the thread's next message — ~13 KB per idle agent.rap_protocol::log_panic); shutdown wind-down now just drains the pool.InfinityMessage::SubscriptionEventpayloads boxedresultandinvocationin the rareSubscriptionEventvariant, shrinkingsize_of::<InfinityMessage>()from 352 → 184 bytes. Every stored history message paid for the fattest variant inline.Boxis serde-transparent, so the wire/persisted format is unchanged.InfinityMessage::tool_result()helper (the boxed field made cross-variant or-patterns impossible).invocationwas measured and rejected: it grows the enum to 200 bytes becauseSubscriptionEventwith an inline result becomes the largest variant again.)Tool-call dedup derived from history instead of a durable index (BREAKING)
HistoryManagerno longer maintainsprocessed_tool_calls/pending_complete_tool_calls. Incoming tool results are deduplicated by walking the history tail just in time: scan back across trailing tool calls/results (future-proof for concurrent calls, e.g.tc tc tr tr), accept on a matching unanswered call, reject as duplicate on a matching result, and discard as stale on any other message (user text, assistant content, subscription events — all turn boundaries, since a subscription event can only be injected once pending calls are settled).safe_spawn_pointuses the same walk (tracking answered calls) instead of the set.StateStore::get_processed_idsnow returns a singleHashSet<String>andadd_processed_tool_callsis removed — updatedInMemoryStateStore, the daemon'sPersistentStateStore, and the LambdaDynamoDbStateStore(old DynamoDBprocessed_tool_callsattributes are simply ignored).ThreadStatekeeps itsprocessed_tool_call_idsfield so old daemon snapshots still deserialize.Benchmark & docs
agent_scaleexample now drains lifecycle notifications per wave (like a real embedding), so they aren't counted as per-agent memory.MemoryChartdata from a post-optimization 50k-agent run (6.08 GB vs 7.90 GB before; 118.8 KB/agent at that point, ~107 KB/agent after the dedup changes) and updated the copy; updated the history-manager doc for the new dedup model.MemoryChartdata and copy may deserve a refresh from that run. Also left open: making the per-message dedup ID inConversationStore::append_messagesoptional (user/subscription messages only), which needs a decision about the Lambda DSQLmessage_id NOT NULLcolumn and daemon session-file compat.All workspace tests pass (
cargo test --workspace), clippy clean.