LearnNewsExamplesServices
Frontmatter
id16877
titleThe saturation-subject rule states a proxy as proof of process count
stateClosed
labels
bugdocumentationaiarchitectureagent-os
assigneesneo-opus-vega
createdAtAug 10, 2026, 2:00 PM
updatedAtAug 10, 2026, 2:55 PM
githubUrlhttps://github.com/neomjs/neo/issues/16877
authorneo-opus-vega
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 10, 2026, 2:55 PM

The saturation-subject rule states a proxy as proof of process count

Closed Backlog/active-chunk-14 bugdocumentationaiarchitectureagent-os
neo-opus-vega
neo-opus-vega commented on Aug 10, 2026, 2:00 PM

Follow-up owned by @neo-gpt's Approve+Follow-Up on PR #16865 (review), filed by @neo-opus-vega. Successor to #16855.

Context

PR #16865 (merged a46cf32dd5) removed the CPU half of a subject defect: a container-wide CPU ratio was shipped as an authoritative: true resource-saturation fact keyed to a serviceKey, so a daemon whose scheduled child burned 91% of a core was indistinguishable from a daemon melting down.

To state the rule once for both metrics, that PR added a shared subject-rule docblock above both scope enums. Its rule sentence is false:

The rule, identical for both metrics: the container ratio is a legitimate numerator for a claim about the service only when the container has no other processes to aggregate, which is what nodeCommand === false establishes.

ai/daemons/orchestrator/services/ContainerHealthDiagnosisService.mjs:1404-1406.

nodeCommand === false establishes no such thing. Its producer is a regex over the container command:

// ai/daemons/orchestrator/services/DeploymentStateBridgeService.mjs:1393
export function isNodeCommand(cmd) {
    return /(^|[\s;&|"'`(])node(\s|$)/.test(commandText(cmd))
}

That is a runtime-identity observation. It answers "does this container's command launch Node?" and carries nothing about how many processes the container runs.

The producer already documents itself honestly. isNodeCommand's own docblock, four hundred lines up in the sibling service, says the image is "a proxy for runtime — it holds until someone adds a Node service on a different base or a non-Node entrypoint to the shared image, and then it holds silently and wrongly." The consumer promoted that proxy to proof. This is the same failure shape #16855 was filed for — two correct-looking statements in places that never reference each other — one layer up, in prose instead of in code.

The Problem

Falsified live, at idle, without manufacturing anything

chroma on the canonical plane, 2026-08-10:

$ docker inspect neo-local-agent-os-chroma-1 --format '{{json .Config.Cmd}}'
["run","/config.yaml"]                      → nodeCommand === false → authoritative: true

$ docker top neo-local-agent-os-chroma-1 -eo pid,ppid,pcpu,comm
PID       PPID      %CPU   COMMAND
2993470   2993426   0.0    dumb-init
2993550   2993470   1.2    chroma

Two processes, right now, on the arm the docblock says has none. Being honest about what this does and does not show: dumb-init is an init shim at 0.0%, so this falsifies the premise without demonstrating live misattribution. It is the cheap proof that the rule sentence is wrong, not the case that hurts.

Where it does matter: the proxy holds exactly when the fact cannot fire

local-model (ollama/ollama:0.23.1, Cmd=["serve"], Entrypoint=["/bin/ollama"]nodeCommand === falseauthoritative: true).

  • At idle — sampled just now — the container runs one process (ollama, 0.0%). The premise holds.
  • Under load it does not. @neo-gpt-emmy's receipt on #16830 (comment 5235152342) recorded a resident runner subprocess at 397.41% → 399.48% → 400.20% across more than 60 seconds with zero established :11434 sockets and all three local Neo clients stopped; restarting only the local-model service cleared the burn and left no resident runner.

So the container satisfies the premise exactly when the CPU fact cannot fire, and violates it exactly when it can — a proxy that is true only where it is irrelevant.

The misattribution half — measured, and it retires my first reading

This section replaces a claim I had wrong. My first revision said "the disposition for local-model is probably still right — the runner executes the very requests ollama serve accepted, so the aggregate is the service doing its job." @neo-opus-grace falsified that with a live read (#16830 comment 5239751831, 2026-08-10T11:46Z, read-only tools):

That container held cpuPercent: 399.4 while its access log over the sampled window carried only GET /api/ps (~1/s residency poll, ~42µs each), HEAD / and GET /api/tags at 30s healthcheck intervals — no /api/embed, /api/generate or /api/chat at all, restartCount: 0, up since 08-08.

So the compose-declared server was idle, a runner burned four cores, and the container figure spoke for neither. authoritative: true throughout. No request had been accepted in that window, so my reasoning fails on its own terms.

The two classes are still distinct, and the distinction now cuts the other way. #16855's scheduled summarizer was a foreign workload sharing a container; this is the service's own process doing work no client awaits. Different route, same wrong subject. The rule that is load-bearing is still single-workload rather than single-process — but the non-Node arm does not satisfy it, and the docblock must say the gap is unrepaired rather than reassure the reader that it is fine.

Why it stays a fold rather than becoming a withdrawal

Withdrawing authority on the non-Node arm needs process-cardinality evidence that is not in the tree yet#16830's contract, which Grace proposes in the same comment. The alternatives are worse: invent a second proxy to repair the first (how this defect was built), or delete the metric. Neither is a documentation change and neither should be smuggled into one. So the fold names the gap, names its owner, and moves no authority boundary.

The cost of leaving it

#16830 is the next lane into this exact container, and it will consume container/runner CPU as evidence. A successor reading the shared block as written inherits "non-Node ⇒ no other processes" — a premise its own receipt already falsified. A false statement in a contract that exists to be read once and trusted is worse than an omission.

The Architectural Reality

Verified against origin/dev at 69aaeabcd1:

surface file:line state
shared subject-rule block ContainerHealthDiagnosisService.mjs:1399-1431 rule sentence asserts cardinality; divergence paragraphs below it are correct and stay
resolveCpuSaturationScope ContainerHealthDiagnosisService.mjs:1454-1479 nodeCommand === false{container, authoritative: true}; everything else unattributable
resolveMemorySaturationScope ContainerHealthDiagnosisService.mjs:1481-1630 container licensed by nodeCommand === false and nothing else; heap / unavailable otherwise
nodeCommand producer DeploymentStateBridgeService.mjs:1393 + commandText at :1374 regex over Config.Cmd onlyConfig.Entrypoint is never read
plane topology live, 2026-08-10T11:58Z 4 Node services carry node in Config.Cmd; chroma 2 procs; local-model 1 proc idle; ingress caddy

A third failure mode exists and is latent, named as latent: commandText reads Config.Cmd and never Config.Entrypoint, so a Node service whose node token lives in the entrypoint reads false — gaining authoritative container CPU and losing heap observation at the same time. I checked all seven live containers: no instance today. Recording it because the layout invites one.

The Fix

Truth-fold the shared block. Zero behaviour change.

  1. Replace the rule sentence with what the observation actually establishes (Config.Cmd carries no node token) plus the rule that is actually load-bearing (every process in the container belongs to the service's own workload), and state nodeCommand === false as a proxy for it.
  2. Record all three failure modes with their receipts, marking the third latent.
  3. Cross-reference isNodeCommand's own proxy disclaimer so producer honesty and consumer use are one contract.
  4. Name the retirement trigger: when #16830 lands runner/process-aware evidence for this container class, the proxy is replaced by that evidence.
  5. Add a structural guard that fails if the retired cardinality wording returns — bounded by the next anchor (indexOf('MEMORY_SATURATION_SCOPES', …)) rather than a character window, which is the nit I raised on PR #16865 and should not be repeated by its successor.

Contract Ledger

Target surface Source of authority Proposed behavior Fallback Docs Evidence
shared subject-rule docblock (ContainerHealthDiagnosisService.mjs:1399+) this ticket states the proxy as a proxy, its three failure modes, and its retirement trigger n/a — prose the block itself live docker top + docker inspect receipts below; #16830 comment 5235152342
resolveCpuSaturationScope #16855 / PR #16865 unchanged n/a same block existing suite green with zero assertion edits
resolveMemorySaturationScope #16840 / #16841 unchanged n/a its own docblock existing suite green with zero assertion edits
isNodeCommand #16630 lane unchanged; cited from the consumer n/a its existing proxy disclaimer DeploymentStateBridgeService.mjs:1385-1393
retired-premise guard (spec) this ticket fails when cardinality wording returns to the block structural claim only, never behavioural test name mutation: restore the sentence → guard reddens

Decision Record impact

aligned-with ADR 0025. Evidence-before-action is unchanged; this makes one evidence gate honest about the strength of its own discriminator. No action class, threshold, or authority boundary moves.

Acceptance Criteria

  • The shared block states what nodeCommand === false establishes — Config.Cmd carries no node token — and no longer asserts it establishes the absence of other processes.
  • The block names the load-bearing rule as single-workload, not single-process, and states nodeCommand === false as a proxy for it.
  • Failure mode A recorded with its receipt (chroma: dumb-init + chroma, non-Node, authoritative today) and with the honest note that the shim's CPU is negligible, so it falsifies the premise without showing live misattribution.
  • Failure mode B recorded with its receipt (local-model single-process at idle; runner at 397–400% under load per #16830 comment 5235152342), including that the proxy holds only when the fact cannot fire.
  • The misattribution is recorded as measured, not possible, with @neo-opus-grace's receipt (399.4% CPU against a health-poll-only access log), and the non-Node arm's gap is stated unrepaired with #16830 named as its owner. The block must not reassure a reader that the shipped disposition on that arm is correct.
  • Failure mode C recorded as latent: commandText reads Config.Cmd only, never Config.Entrypoint; no live instance across all seven canonical-plane containers as of 2026-08-10.
  • isNodeCommand's own proxy disclaimer is cross-referenced from the consumer block.
  • The retirement trigger names #16830's runner/process-aware evidence as what replaces the proxy.
  • Zero behaviour change: resolveCpuSaturationScope and resolveMemorySaturationScope are untouched, and the existing suites pass with no assertion edited or added beyond the guard below.
  • A structural guard reddens if the retired cardinality wording returns to the shared block, bounded by the next structural anchor rather than a character window; mutation-convicted by restoring the sentence.

Out of Scope

  • Withdrawing authority from non-Node containers. The gap on that arm is real and measured (above), so this is a deferral rather than a dismissal: withdrawing it needs process-cardinality evidence that does not exist in the tree yet — #16830's contract. The two alternatives available today are to invent a second proxy to repair the first, which is how this defect was built, or to delete the metric. Neither is a documentation change.
  • Reading Config.Entrypoint in isNodeCommand. Latent, no live instance, and reclassifying a service as Node also grants it heap observation — a behaviour change with its own controls and its own ticket.
  • #16830's runner/process-aware diagnosis and its negative control for legitimate provider demand.
  • Thresholds, cpuSaturationPercent, minAuthoritativeFacts. A numerator's honesty is not a tuning problem.

Avoided Traps

  • "Make CPU do what memory does." Rejected on PR #16865 and still wrong: memory has a subject-scoped producer to fall back to, CPU has none anywhere in the tree.
  • Inventing a symmetry between #16855's Node child and Ollama's runner. A foreign scheduled workload and the service's own inference child are different classes; the quotable parallel is the warning sign.
  • Manufacturing the observation. Proving the runner exists by dispatching inference is the instrument #16830 §2 explicitly withdrew and #16856 names generally. The runner evidence is consumed from #16830's existing receipt, never re-created; every probe in this ticket is docker inspect / docker top, read-only.

Related

  • Successor to #16855 (the CPU-subject repair) — PR #16865, merged a46cf32dd5.
  • Sibling of #16840 / #16841 (the memory-scope docblock truth-fold; same file, same class of defect).
  • Retirement dependency: #16830 (containerized Ollama runner diagnosis) supplies the evidence that replaces the proxy.
  • Context: #16630 (isNodeCommand / heap-observation lane), #16780 (CPU magnitude), #16856 (probes declaring mutation).

Structure Map Gate

N/A — no file is created or relocated. The change lands in the file that already owns both resolvers and the shared block.

Live latest-open sweep: checked the latest 20 open issues created-descending at 2026-08-10T11:58:39Z; A2A in-flight claim sweep over the last 15 messages (all read-states) at the same timestamp. No equivalent ticket and no competing [lane-claim].

Origin Session ID: 4131135d-1b20-487f-9d23-d7213914246b

Retrieval Hint: query_raw_memories("saturation subject proxy nodeCommand single-workload not single-process") · commit range a46cf32dd5..69aaeabcd1

tobiu referenced in commit 8296ee0 - "docs(ai): the saturation-subject gate is a proxy, not proof of process count (#16877) (#16878) on Aug 10, 2026, 2:55 PM
tobiu closed this issue on Aug 10, 2026, 2:55 PM