Bound the stdio close wait, cutting 5.2s off every call #344
No reviewers
Labels
No labels
burndown-2026-06
burndown-2026-08
sunday-sprint
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
coherence-core
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
qa-fixture
role/advocate
role/director
role/exec
role/frontend
role/gamedev
role/human
role/platform
role/qa
role/science
role/sysadmin
state
ambient
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/umbra!344
Loading…
Reference in a new issue
No description provided.
Delete branch "stdio-close"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #338.
#336 deferred the keep-alive daemon on the condition that per-invocation cold start be measured first. Measured. The daemon is not warranted, because almost all of the cost was ours.
The finding
The SDK's
CommandTransportwaitsTerminateDurationfor the child to exit after closing its stdin, defaulting to 5 seconds. A server that does not exit on stdin close makes the caller pay that in full on every invocation - and the MCP reference server does not exit. So every stdio call cost 5.2s, of which 5.0s was this wait.Setting a 100ms grace takes it to 271ms.
Measurements
p50 of ten runs each, M-series mac:
--help, contacting nothingnode server.js, package installednpx -y <package>The decision
stdio costs roughly 170 ms over the binary's own floor, most of it the node runtime starting. That is well inside the "close this, no daemon" bar #338 set.
The npx figure is npx re-resolving the package on every run, not umbra. The answer there is to name an installed binary in the guardfile rather than
npx -y, which is now documented instead of engineered around.Neither number justifies a supervised background process inside a policy engine.
Why 100ms and not less
The child does not exit when stdin closes, so the grace is paid in full rather than acting as an upper bound. Total is roughly
180ms + grace, measured at 1ms/50ms/200ms. 100ms is the trade: short enough not to dominate, long enough for a well-behaved server to exit cleanly before SIGTERM.A measurement caveat worth reading
My first before/after run showed no change, which looked like a refuted hypothesis and nearly ended the investigation. It was a stale cache: the specgen build stamp does not hash a
--umbra-replacecheckout's source, sobuildreused the old binary. Filed separately as #344, because it silently misleads anyone iterating on umbra itself.Verified with
go test ./...,golangci-lint run ./..., andpre-commit run --all-files.Correction to the body: the stale-cache defect is #345, not #344. I wrote the number before filing the issue, and #344 turned out to be this pull request itself.