acpmux: typed config for session task caps; slice cap scales with threads-max
Why
A fleet host's acpmuxsessions.slice ran into its fixed TasksMax=16384: 15,927 to 16,348 tasks in use, so forks failed now and then across all 105 session scopes. Memory had 44 GiB free and the kernel's threads-max was 514402, so the slice cap was the only limit hit. The largest scopes were codex app-server sessions with 280 to 600 threads each, well under the per-session 4096, so the shared slice was what bound. It was relieved by hand by raising the slice cap through an env drop-in on the daemon unit. That is a manual step, and Leo's standing order is: "Build systems, not chores ... A manual step or a new hard-coded constant is not a fix."
What
- Typed config keys, next to the existing
session.memoryMbandsession.swapMbin config.json:session.tasksMaxis each session scope's cap.session.sessionsTasksMaxis the shared slice's cap.- Both can be set with
acpmux config --set session.tasks_max=Norsession.sessions_tasks_max=N.0orinfinitymeans no cap, anddefaultclears the key. - Order: typed config, then env (the profile's, then the daemon's), then the computed default.
ACPMUX_SESSION_TASKS_MAXandACPMUX_SESSIONS_TASKS_MAXkeep working.
- One reader for the slice and every scope.
limit_sessions_slice, the session scope's properties and the agent-host scope (host_tasks_max) all go throughknob_readerwith the daemon config's limits. Once the config names a cap, profiles whose env disagrees no longer take turns rewriting the shared slice at each scope start (slice_changes). Before this, the host scope read only the daemon env. - A slice default that scales with the machine:
min(threads-max / 4, 262144), never below 16384. It reads/proc/sys/kernel/threads-maxand falls back to 16384 if the file can't be read.
| machine (threads-max is about RAM / 128 KiB) | threads-max | slice default |
|---|---|---|
| 8 GiB | ~64k | 16384 (floor) |
| 16 GiB | ~128k | ~32k |
| the incident host, 64 GiB | 514402 | 128600 |
| 128 GiB and up | 1M+ | 262144 (ceiling) |
The per-session default stays at 4096. The incident's largest session held about 600 tasks, so the per-session cap was never the one that bound. Every scope still gets an explicit TasksMax, never systemd's DefaultTasksMax:
- All four property sets that
prefixtries emitTasksMax=NorTasksMax=infinity. - The agent-host scope sets
TasksMaxthroughbusctl.
Red, then green (cmux-ci, 32 vCPU Linux)
- Red, tests only at 40d299c53ad: https://github.com/manaflow-ai/cmux/actions/runs/40947011907. It fails to compile (36 errors:
sessions_tasks_default,sessions_slice_wanted,scope_tasks_max,parse_threads_maxand the config fields don't exist yet). - Green at 0a73f17f083: https://github.com/manaflow-ai/cmux/actions/runs/40947016106. It ran:
cargo test -p acpmux --lib -- session_scope config:: hub::catalog_reload agent_host::scope: 69 passed.cargo test -p acpmux --test host_scope: 4 passed.cargo clippy -p acpmux --all-targets -D warnings: clean.
- Green at 5c27faca2c8 (after the first review's BLOCK on
hub_integration's fixedTasksMax=16384, now the machine's computed default): https://github.com/manaflow-ai/cmux/actions/runs/40953732213. It ran the same, pluscargo test -p acpmux --test hub_integration -- session_scope: 3 passed.
New tests:
- typed config wins over env;
- env wins over the computed default;
- the threads-max scaling, its floor and ceiling, and an unreadable or junk value;
0andinfinitybehave as before, through both config and env;- the slice and the scope read one source, so two profiles with different env ask for the same slice value;
- the CLI setter, and a camelCase JSON round trip;
- a hub config reload carries the new keys.
Risk
Low to medium.
- On large machines the slice's default cap rises from 16384 to as much as 262144. A fork storm spread across many sessions can therefore take more of the machine before the slice stops it. Each session's 4096 cap still holds, and a quarter of
threads-maxleaves three quarters for everything else. - For the two task caps, the typed config now wins over a profile's env. The memory knobs keep their order, where the profile's env wins. A profile that sets
ACPMUX_SESSION_TASKS_MAXloses tosession.tasksMaxonce that key is set. - A host that pins
ACPMUX_SESSIONS_TASKS_MAXin its daemon env keeps that value, because env still wins over the computed default.
Agent session: agent session
š¤ Generated with Claude Code