Context
Third friction signal from the first-real-world cloud-deployment exercise (sibling to #12014, #12015). An IDE editing the external mirror compose flagged cpus: ${NEO_LOCAL_MODEL_CPU_LIMIT:-4.0} as "Numeric value expected" against the Compose JSON schema. The runtime accepts it; the schema-strict validator does not. The same warning fires against the canonical neo compose.
The Problem
ai/deploy/docker-compose.yml:255-256 — the local-model profile is the only service using unquoted env-interpolation under deploy.resources.limits:
local-model:
...
deploy:
resources:
limits:
memory: ${NEO_LOCAL_MODEL_MEMORY_LIMIT:-8g}
cpus: ${NEO_LOCAL_MODEL_CPU_LIMIT:-4.0}YAML interpolation produces a string. The Compose schema declares cpus as number and accepts memory as string-with-suffix OR number-of-bytes. A schema-validating IDE flags cpus (sometimes memory, depending on validator strictness). Compose's runtime coerces correctly, so deployments work — but the editor experience is noisy.
V-B-A: all other services in the canonical use the quoted form (lines 35, 76, 124, 188, 224):
memory: 2g
cpus: "2.0"memory: 2g is a single YAML scalar (no interpolation) so the parser tags it as a string without ambiguity. The interpolated form needs the explicit quote.
The Architectural Reality
Affected:
ai/deploy/docker-compose.yml:255 (memory interpolation).
ai/deploy/docker-compose.yml:256 (cpus interpolation).
No other compose files in ai/deploy/ use the unquoted-interpolation pattern (verified via grep -rn '\${[A-Z_]*:-[0-9]' ai/deploy/).
The Fix
Quote the interpolated values:
memory: "${NEO_LOCAL_MODEL_MEMORY_LIMIT:-8g}"
cpus: "${NEO_LOCAL_MODEL_CPU_LIMIT:-4.0}"Two-line edit. No runtime behavior change. Matches the convention already used by the other five services in the same file.
Contract Ledger
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
ai/deploy/docker-compose.yml:255-256 local-model deploy.resources.limits |
Compose v2 JSON schema (cpus: number, memory: string|number); sibling-service convention in the same file |
Quote both interpolations to YAML-tag as strings; Compose runtime coerces at parse time |
None — runtime already coerces correctly |
None — convention is self-documenting via sibling examples |
grep evidence: lines 35/76/124/188/224 use cpus: "X.X"; only 256 deviates |
Decision Record impact
none — pure consistency fix.
Acceptance Criteria
Out of Scope
- Schema-validation across other compose files (none use the unquoted pattern).
- Standardizing the
memory: unquoted-literal form (memory: 2g) — that already parses as a string and is the file's prevailing convention for non-interpolated values.
- IDE-side schema-config recommendations.
Avoided Traps
- Dropping the env-interpolation entirely (hardcoding
cpus: "4.0") — rejected; loses the operator-tunable override the local-model profile is designed for.
- Filing as
bug — rejected; the deployment functions correctly. Schema-strict IDE warning is a DX issue, not a correctness defect.
Related
- #12014 — sibling first-real-world deployment friction (orchestrator co-location follow-up).
- #12015 — sibling Day-0 macOS prerequisites friction.
Handoff Retrieval Hints
- Grep:
grep -n '\${[A-Z_]*:-[0-9]' ai/deploy/docker-compose.yml — pre-fix returns lines 255-256; post-fix returns nothing.
- File anchor:
ai/deploy/docker-compose.yml:255-256.
- Semantic: "compose env-interpolation IDE numeric-expected warning", "local-model resource limits".
Context
Third friction signal from the first-real-world cloud-deployment exercise (sibling to #12014, #12015). An IDE editing the external mirror compose flagged
cpus: ${NEO_LOCAL_MODEL_CPU_LIMIT:-4.0}as "Numeric value expected" against the Compose JSON schema. The runtime accepts it; the schema-strict validator does not. The same warning fires against the canonical neo compose.The Problem
ai/deploy/docker-compose.yml:255-256 — the
local-modelprofile is the only service using unquoted env-interpolation underdeploy.resources.limits:local-model: ... deploy: resources: limits: memory: ${NEO_LOCAL_MODEL_MEMORY_LIMIT:-8g} cpus: ${NEO_LOCAL_MODEL_CPU_LIMIT:-4.0}YAML interpolation produces a string. The Compose schema declares
cpusasnumberand acceptsmemoryasstring-with-suffix ORnumber-of-bytes. A schema-validating IDE flagscpus(sometimesmemory, depending on validator strictness). Compose's runtime coerces correctly, so deployments work — but the editor experience is noisy.V-B-A: all other services in the canonical use the quoted form (lines 35, 76, 124, 188, 224):
memory: 2g cpus: "2.0"memory: 2gis a single YAML scalar (no interpolation) so the parser tags it as a string without ambiguity. The interpolated form needs the explicit quote.The Architectural Reality
Affected:
ai/deploy/docker-compose.yml:255(memory interpolation).ai/deploy/docker-compose.yml:256(cpus interpolation).No other compose files in
ai/deploy/use the unquoted-interpolation pattern (verified viagrep -rn '\${[A-Z_]*:-[0-9]' ai/deploy/).The Fix
Quote the interpolated values:
memory: "${NEO_LOCAL_MODEL_MEMORY_LIMIT:-8g}" cpus: "${NEO_LOCAL_MODEL_CPU_LIMIT:-4.0}"Two-line edit. No runtime behavior change. Matches the convention already used by the other five services in the same file.
Contract Ledger
ai/deploy/docker-compose.yml:255-256local-modeldeploy.resources.limitscpus: number,memory: string|number); sibling-service convention in the same filecpus: "X.X"; only 256 deviatesDecision Record impact
none— pure consistency fix.Acceptance Criteria
ai/deploy/docker-compose.yml:255-256interpolations are double-quoted.grep -n '\${[A-Z_]*:-[0-9]' ai/deploy/docker-compose.ymlreturns zero unquoted matches (regression guard).docker compose -f ai/deploy/docker-compose.yml --profile cloud --profile local-model configparses without error (compose-level smoke test).docker compose --profile local-model up(sanity-only — runtime already coerced).Out of Scope
memory:unquoted-literal form (memory: 2g) — that already parses as a string and is the file's prevailing convention for non-interpolated values.Avoided Traps
cpus: "4.0") — rejected; loses the operator-tunable override thelocal-modelprofile is designed for.bug— rejected; the deployment functions correctly. Schema-strict IDE warning is a DX issue, not a correctness defect.Related
Handoff Retrieval Hints
grep -n '\${[A-Z_]*:-[0-9]' ai/deploy/docker-compose.yml— pre-fix returns lines 255-256; post-fix returns nothing.ai/deploy/docker-compose.yml:255-256.