Performance, Load, and Memory

🔵 How Agent OS behaves under load: how many concurrent requests it handles, how it limits overload, how much memory it consumes per session, and how to reproduce this. Plus a comparison with another agent server on the same test bench.

The numbers in this article come from one specific test bench (see §34.2) and serve as a guideline for planning, not a guarantee on your hardware.


34.1 What Limits Load

Agent OS is not a load balancer and does not perform inference itself: the LLM is an external provider, and the core schedules calls to it. Therefore the "ceiling" is set by three things: core limits, overload behavior, and the cost of persistence.

Lever Where Default What it does
kernel.concurrency_limit server.yaml 3 Maximum concurrent LLM streams to the provider (core semaphore). Excess turns are queued.
kernel.max_in_flight_turns server.yaml 0 (off) Admission gate at the HTTP edge: no more than N turns "in flight"; beyond that — immediately 429 Too Many Requests + Retry-After. A rejected request does not touch the session and does not cancel a running turn.
kernel.session_ttl_secs server.yaml 300 A session with no activity is killed after TTL; the cleaner runs every session_cleanup_interval_secs (60). Limits memory growth during long idle periods.

One session — one active turn. If a second request arrives for the same session, it cancels the previous one (last-wins) rather than being queued. This protects against races within a single session.

Persistence. Stores write turn data: the JSONL logger, EventDb (events.db), the message store (messages.db), and the access log (access.db). All of them are offloaded to background writer threads with batching (one transaction per batch), so they do not block the async path; but at very high rates their threads begin to compete with each other (see §34.3). JSONL is an optional layer; the DuckDB database (events/transcripts/access) carries the main functionality.

What to watch at runtime (GET /metrics, Prometheus):


34.2 How to Reproduce the Measurements

The test bench from the repository (requires mock_llm + loadgen, build release-fast):

cargo build -p agent_web_bot --profile release-fast \
    --bin agent-os --example mock_llm --example loadgen
./scripts/loadtest.ps1                 # свип 1,2,4,8,16,32,64
./scripts/loadtest.ps1 -Sweep 128,256,1000 -DurationSecs 15

Bench profile for the numbers below: mock TTFT=2000ms, 200 tok/s, 8 токенов (turn ≈ 2.04 s), 18 logical CPUs, mock + server + load generator on the same machine, 12–15 s per point. It is best to measure on a clean machine: extraneous processes and accumulated databases noticeably distort the result.


34.3 Throughput

Agent OS without persistence (pure scheduler + HTTP) — the core "ceiling":

c rps total p50 TTFT p50 errors
1 0.53 2135 ms 2028 ms 0
4 2.17 2126 ms 2027 ms 0
16 8.61 2140 ms 2029 ms 0
64 33.4 2138 ms 2040 ms 0
128 68.3 2151 ms 2045 ms 0
256 128.9 2163 ms 2065 ms 0
1000 459.5 2215 ms 2090 ms 0

Latency is practically flat: from c=1 to c=1000, TTFT grew from 2028 to 2090 ms, total turn time — from 2135 to 2215 ms. The core's own overhead is ≈ +30 ms to TTFT and +95 ms to the turn and is almost independent of contention. Throughput grows linearly as long as CPU and permits suffice.

Agent OS with the DuckDB database (EventDb + message store + access log; JSONL off):

c rps total p50 TTFT p50 note
1 0.53 2142 ms 2025 ms —
4 2.15 2150 ms 2032 ms —
16 8.58 2149 ms 2033 ms —
64 34.3 2146 ms 2045 ms —
128 68.4 2150 ms 2047 ms —
256 136.2 2159 ms 2059 ms —
1000 451.0 2135 ms 2055 ms message_store_queue up to 7k; event_db_dropped 0

Up to ~1000 concurrent turns, the database costs only ~2% of throughput (451 vs 459) and does not move latency. Above ~3000, throughput hits the competing DuckDB writers (events + transcripts + access), the message store queue overflows, and some events are dropped under overload (event_db_dropped) — consider retention/sharding if you keep thousands of concurrent sessions with full history.


34.4 Memory

We measure two quantities of the server process: WorkingSet (resident, including shared code/DLL pages) and Private (its own memory). On Windows — Get-Process; on Linux — VmRSS and Private_Dirty from /proc/<pid>/smaps_rollup.

State WorkingSet Private
No stores, startup (steady) 31 MB 9 MB
With DuckDB database, startup 108 MB 98 MB
No stores, ~1450 sessions 218 MB —
With DuckDB database, ~1470 sessions 385 MB —

34.5 Behavior under overload (429)

Enable kernel.max_in_flight_turns > 0 (for example, 8 with concurrency_limit: 3) — and overload stops accumulating queues:

Recommendation: in production always enable the admission gate, and set concurrency_limit according to the provider's quota (so as not to catch external 429s/timeouts).


34.6 Comparison with opencode

Same bench, same mock (TTFT=2000 ms, 8 tokens), one session per worker. opencode (opencode serve v1.18.32) is pointed at the same mock via a custom provider @ai-sdk/openai-compatible; the full turn time was measured (POST /session/:id/message).

Important — a fair comparison. opencode persists sessions/transcripts to SQLite, so it must be compared with Agent OS with the database (EventDb + message store + access; JSONL off), not with the bare core. At the same time, the opencode server is a server for a single-user coding agent (sessions are tied to a project, there is no admission control), whereas Agent OS is a multi-tenant chat/agent server. The comparison is about the nature of scaling, not about product parity.

Throughput (Agent OS with the database vs opencode)

c Agent OS rps opencode rps Agent OS p50 opencode p50 opencode errors
1 0.53 0.50 2142 ms 2229 ms 0
4 2.15 2.00 2150 ms 2285 ms 0
16 8.58 6.67 2149 ms 2791 ms 0
64 34.3 16.0 2146 ms 4386 ms 0
128 68.4 21.3 2150 ms 6611 ms 0
256 136.2 36.2 2159 ms 11883 ms 142
512 — hang — >27 s mass
1000 451.0 — 2135 ms — —

Memory

Agent OS (without stores) Agent OS (DuckDB database) opencode
Base (start, WorkingSet) 31 MB 108 MB 208–336 MB (Node, fluctuates due to GC)
Base (private) 9 MB 98 MB —
Growth per session ≈ 0.13 MB ≈ 0.19 MB ≈ 0.09 MB (empty)
~1500 sessions (WorkingSet) 218 MB 385 MB under load 500–680 MB

Comparison summary

Agent OS is built for concurrent multi-tenant load: flat latency, linear throughput growth, 429 protection, and TTL cleanup. The opencode server degrades in this role: latency grows linearly, and under overload there are errors/hangs without protection. This is expected: it has a different job (an interactive coding agent for a single user), not a server for thousands of concurrent sessions.


34.7 Practical recommendations