AI runtime limits and Pi comparison¶
Normal agent behavior follows Pi v1.0.0 at
a13d35a742c6ef8462812a28fbe1d8c8b7431c32. Separate defensive limits
protect the hosted service from malformed streams, runaway work, and resource
exhaustion. These limits have substantial headroom for schedule workflows.
They do not determine when normal conversation compaction starts.
The tables show code defaults and fixed bounds. Defensive defaults are constants
in core/nurse_scheduling/ai/config.py. Optional overrides appear below.
Environment examples contain the normal deployment settings.
MB and GB use decimal bytes. MiB and GiB use powers of 1,024.
N/A marks a limit or component absent in the pinned Pi implementation.
Conversation and generation¶
| Policy | Pi v1.0.0 | This service |
|---|---|---|
| Automatic compaction | Context tokens exceed model window minus reserve | Same. |
| Context usage | Latest valid provider usage plus estimates for later messages | Same. Text estimates do not override valid same-model usage. |
| Output reserve | 16,384 tokens | AI_COMPACTION_RESERVE_TOKENS=16384 |
| Recent context retained | 20,000 estimated tokens | AI_COMPACTION_KEEP_RECENT_TOKENS=20000 |
| History summary output | 80% of reserve, limited by model output and available context | Same. |
| Split-turn prefix output | 50% of reserve, limited by model output and available context | Same. |
| Model context window | Supplied by model definitions | Requires positive context-window metadata before generation. |
| Context safety margin | 4,096 tokens | Same. |
| Tool-result text in summary input | Truncated to 2,000 characters | Same. |
| Cumulative file-operation records in summaries | Supported | Read, write, and edit paths are saved separately and appended to each summary. |
| Context overflow recovery | One compact-and-retry attempt | Same, including explicit overflow after partial output and a length stop below the normal output allowance before applying the remaining-context limit. Partial tool calls are not executed. Chats and exports label the omitted attempt. |
Each history or turn-prefix section is summarized in one request. Complete, successful summaries are accepted, including empty text, as in Pi. Summary failure preserves the original context and does not disable later automatic compaction attempts in the same run. Compaction skips when the retention boundary leaves no older messages to summarize. An immediately repeated compaction skips when the last transcript entry is already a summary. Summary text is preserved, including whitespace. A large text estimate clamps requested output to at least one token, as in Pi. The provider decides whether the request fits. A provider rejection preserves the original context. A recovery attempt can summarize a single oversized final input after omitting the failed response, as in Pi. Manual compaction keeps that input when there is no omitted recovery attempt. A split conversation turn can require two summary requests, as in Pi. Model token limits bound generated text and reasoning.
Integration differences¶
Pi's design guides the whole agent. These implementation differences remain:
- The provider adapter uses OpenAI-compatible messages. Fallback estimates use characters divided by four, count readable reasoning and tool arguments, and include the system prompt and tool definitions. Opaque signatures and encrypted replay data do not inflate these estimates. Message serialization and image representation differ from Pi's native message format.
- Requests default to a 32,768-token response allowance so underestimated large tool responses leave room in the context window. Explicit request limits replace this default. Model output and available-context limits still apply.
- The loop checks context before tool continuations as well as after a final response, so growing tool results can be summarized before the next request. Length-stop recovery compares reported completion usage with the normal output allowance, which applies model and explicit request limits before the remaining context limit. Filling a smaller context cap can trigger recovery. Filling the normal allowance does not. If usage is absent, recovery requires a known request cap below the known normal allowance. If recovery compaction skips or fails, normal output-limit handling retains the response and refuses its partial tool calls. Manual compaction uses shared run admission, Stop, replay, and persistence. It requires an idle chat instead of implicitly stopping an active schedule operation. Use Stop before compacting an active chat. The UI combines Pi's two ineligible cases into one skipped notice.
- Status messages stay with their user question. They describe the schedule workspace and cannot replace the request that starts a conversation turn. Summaries from failed or cancelled runs are invalidated because those runs discard their temporary workspace.
- Model discovery must supply a positive context window before paid generation. Pi supplies model definitions directly. Numeric reasoning settings must leave the provider's minimum thinking budget and room for an answer. Invalid settings fail before the request, rather than sending a known invalid payload.
- Pi's extension hooks and custom compaction instructions are not exposed by this service. HTTP, memory, storage, and runtime bounds below protect the shared deployment and can still refuse work at capacity. Manual compaction bypasses the active-history quota and summarizes the full retained history, so reaching that quota does not prevent reducing it. Shared memory and concurrency limits still apply to the compaction request.
Defensive execution and input bounds¶
| Limit | Pi v1.0.0 | This service |
|---|---|---|
| Active history characters | N/A | 64,000,000 characters |
| Active history messages | N/A | 100,000 messages |
| Provider response bytes | N/A | 67,108,864 bytes (64 MiB per HTTP attempt) |
| Calls in one model response | N/A | 1,024 |
| Arguments in one tool call | N/A | 4,000,000 characters |
| Tool rounds and calls per run | N/A | 2,000 tool rounds, 6,000 tool calls |
| Parallel tool executions | Parallel by default | AI_AGENT_MAX_PARALLEL_TOOLS=16 |
| Command deadline | Optional tool timeout, no default | AI_SANDBOX_COMMAND_TIMEOUT_SECONDS=60. A tool can request a shorter deadline. |
| Agent run deadline | N/A | AI_SANDBOX_TURN_TIMEOUT_SECONDS=21600 (6 hours) |
| Default sandbox file read | N/A | 64 MiB. Schedule and ZIP reads use their specific limits. |
| Captured command output | Bounded tail in memory, full output saved to a file | Same. Capture runs inside the sandbox. The model receives at most 2,000 lines or 50 KiB and the full log path when truncated. |
| JSON request body | N/A | 64 MiB, counted before JSON parsing. |
| Schedule YAML | N/A | 16,000,000 bytes (16 MB) |
The provider counts decoded HTTP bytes before buffering SSE lines or parsing JSON. An oversized or unterminated line cannot bypass the response bound. The byte budget applies separately to each HTTP attempt.
User messages have no separate character limit. The model context budget bounds the accepted provider request. The HTTP body bound protects request parsing.
A completed exchange remains available even if its output crosses a history or memory bound. Further work is refused until capacity is available, or the user exports the chat and starts another. A refused optimizer review appears in the chat as a failed review with the same message. Allow memory headroom for output from every admitted run.
Parallelism applies to independent read-only batches. A batch containing a mutating tool remains sequential. More slots can help large I/O batches. Set concurrency according to sandbox capacity.
Files, service capacity, and optimizer¶
These components have no corresponding hosted-service quota in Pi.
| Limit | Pi v1.0.0 | This service |
|---|---|---|
| Retained attachments | N/A | 128 files |
| One attachment | N/A | 50,000,000 bytes (50 MB) |
| Active attachment bytes per chat | N/A | 500,000,000 bytes (500 MB) |
| Archived file bytes per chat | N/A | AI_MAX_ARCHIVE_BYTES=10000000000 (10 GB, including removed files) |
| Generated ZIP and uncompressed contents | N/A | 250,000,000 bytes (250 MB) |
| Live sessions | N/A | AI_MAX_SESSIONS=1000 |
| Retained text and file bytes in memory | N/A | AI_MAX_SESSION_BYTES=1073741824 (1 GiB shared across sessions) |
| Concurrent AI runs | N/A | AI_MAX_CONCURRENT_REQUESTS=4 |
| Session lifetime | N/A | AI_SESSION_TTL_SECONDS=2592000 (30 days) |
| Optimizer runs per chat | N/A | 1,000 runs |
| Default solve timeout | N/A | AI_OPTIMIZER_DEFAULT_TIMEOUT_SECONDS=300 |
| Optimizer result bytes | N/A | 64,000,000 bytes (64 MB) |
| Optimizer result cache | N/A | AI_OPTIMIZER_RESULT_CACHE_BYTES=256000000 (256 MB) |
Shared memory, concurrency, deadlines, and storage must fit deployment capacity. They remain operator controls, even when legitimate traffic can fill the service. Raising a count alone cannot provide more CPU, model capacity, or storage. The default 50 MB attachment bound stays below NGINX's 64 MiB request bound.
Optional defensive overrides¶
Normal operation requires none of these settings. Set a positive integer in
docker/.env when a legitimate workload needs another bound. Both backend
Compose variants pass these values only when set, using Compose's
environment resolution.
The service uses its code defaults when an override is absent. Empty, invalid,
zero, and negative values fail startup. Keep attachment bytes plus multipart
overhead below the proxy request bound when changing upload capacity.
| Environment setting | Default | Scope |
|---|---|---|
AI_PROVIDER_MAX_RESPONSE_BYTES |
67,108,864 | Decoded HTTP response bytes per provider attempt. |
AI_MAX_HISTORY_MESSAGES |
100,000 | Active history messages per chat. |
AI_MAX_HISTORY_CHARS |
64,000,000 | Active history characters per chat. |
AI_MAX_SCHEDULE_BYTES |
16,000,000 | UTF-8 schedule YAML bytes. |
AI_MAX_ATTACHMENT_FILES |
128 | Active attachments per chat. |
AI_MAX_ATTACHMENT_BYTES |
50,000,000 | Source bytes per attachment. |
AI_MAX_ATTACHMENT_TOTAL_BYTES |
500,000,000 | Active attachment bytes per chat. |
AI_MAX_DOWNLOAD_BYTES |
250,000,000 | Generated ZIP bytes and total uncompressed contents. |
AI_OPTIMIZER_MAX_RUNS_PER_SESSION |
1,000 | Optimizer runs per chat. |
AI_OPTIMIZER_MAX_RESULT_BYTES |
64,000,000 | Bytes per optimizer workbook. |
AI_AGENT_MAX_TOOL_ROUNDS |
2,000 | Tool rounds per agent run. |
AI_AGENT_MAX_TOOL_CALLS |
6,000 | Tool calls per agent run. |
For failure ordering, persistence, and event behavior, see
AI Assistant Backend. The environment example and both backend
Compose variants expose the runtime controls. In Compose, the optimizer timeout
follows OPTIMIZE_DEFAULT_TIMEOUT_SECONDS so both services share the same default.
Provider streaming measurements¶
Turn details show estimated provider cost, output tokens per second, call count, and missing prices. Provider call records retain streaming duration and time to first output when measurable. Streaming duration runs from the first nonempty output chunk to the last. Output includes text, reasoning, and tool-call generation. Time to first output starts when the backend begins the HTTP request.
Speed uses provider-reported completion tokens, including reasoning tokens once. It excludes input tokens, time to first output, tool execution, VM work, retry backoff, and gaps between calls. A call needs reported positive output usage and at least two output chunks with distinct arrival times. Missing or incomplete measurements remain unavailable. Cost availability is independent of speed. When usage reports reasoning tokens without reasoning stream events, speed is unavailable because their generation time cannot be matched to the observed interval.
Run and conversation totals divide measured output tokens by measured streaming seconds. They do not average individual speeds. Turn details report how many calls contributed. Recovery and exports retain the same measurements. These are backend-observed delivery speeds. Provider buffering, hidden reasoning, network delays, and stream-consumer delays can affect the estimate.
Provider protocol failures log the underlying exception and gateway call ID when available. Use the call ID in turn details to find the matching backend log. Diagnostic logs omit provider response content and credentials.