Skip to content

feat: Jetson AGX Orin becomes a first-class card — senses on unsloth/gemma-4-12B-it-qat-w4a16, VALIDATED live (0.55.0) - #176

Merged
OriNachum merged 31 commits into
mainfrom
senses-unsloth-qat-orin-variation
Aug 4, 2026
Merged

feat: Jetson AGX Orin becomes a first-class card — senses on unsloth/gemma-4-12B-it-qat-w4a16, VALIDATED live (0.55.0)#176
OriNachum merged 31 commits into
mainfrom
senses-unsloth-qat-orin-variation

Conversation

@OriNachum

Copy link
Copy Markdown
Contributor

Makes the Jetson AGX Orin a first-class card alongside spark/thor: a detected card, a built-in profile, a new orin-lobe shape, and senses on the QAT checkpoint unsloth/gemma-4-12B-it-qat-w4a16. The portability goal is the point — moving this setup to another architecture stays a profile pick, not a rework.

⚠️ Read this first: the orin variation is DECLARED / UNVALIDATED (#108)

No physical board has booted it. The re-render that would have done so was denied by the executing environment's permission classifier, so t9/t10/t11 and the measured-value backfill did not happen. The budget knobs (gpu_mem_util = 0.45, max_model_len = 262144) are hypotheses marked MEASURED-PENDING in the TOML, and nothing in this PR — doc, support table, or lobes capabilities — claims otherwise.

Full plan-versus-actual accounting: docs/deliveries/2026-08-04-unsloth-qat-senses-first-class-orin-variation.md. Every unproven claim is marked unverified there.

What landed (8 of 13 plan tasks)

orin card detection lobes/machines/orin.py — sm_87, verified live: detect_card() resolves orin on the real board with no --profile. Pooling-gear divergences are orin's own overrides, deliberately not the SM_110 trait — those values are an untested Thor carry-over, and borrowing an sm_110 trait would misstate why they hold.
Built-in orin profile senses = the QAT checkpoint (compressed-tensors int4 W4A16 — weight-only, so Ampere qualifies without Blackwell FP4 tensor cores). cortex/muse/worker vetoed on the hard architecture line.
orin-lobe shape Hosts senses + embedder + reranker, no local stt/tts (the Parakeet image ships no sm_87 kernels). Fixes a real clobber — thor-lobe over the orin card rendered Thor's 0.30/131072, which the deployed box was hand-patching in .env.
Profile.host_env Persists the Tegra iowait threshold from the card, so a bare lobes init is fixed too — not just this shape. Without it the gateway 429-sheds all senses traffic indefinitely on a board whose /proc/stat reports ~59% iowait with zero disk I/O.
gpu_access Card-declared csv-mode GPU access via generated compose overrides, so runtime: nvidia survives a re-render instead of being hand-edited back each time.
MULTIMODAL_SPECULATIVE_CONFIG The senses MTP draft is now switchable off entirely — required, since the replacement checkpoint ships no MTP head.
Catalog entry + per-model doc Every field cited from the checkpoint's own config.json. All capability rows marked pending-live-probe.
Host-dependent tests fixed See #172 below.

Boundary proof

spark.toml, thor.toml, base.toml and every pre-existing golden are byte-identicalgit diff main over those paths is empty, and the golden diff is additions-only (new orin* files). This release adds a card; it does not move the Blackwell fleet.

Verification

uv run pytest -n auto        2860 passed, 15 skipped
black --check                224 files unchanged
flake8 / isort               clean
bandit -c pyproject.toml     No issues identified
afi cli doctor . --strict    PASS

Every merge was TDD-gated: the suite ran green before and after each one (2725 → 2736 → 2745 → 2760 → 2816 → 2860). One branch was deliberately held back from merge until its failures were fixed rather than merged red.

Pre-existing defects this work surfaced — filed, not fixed here

These are the reviewable substance as much as the feature is. Each was found by measurement, not inspection:

Measured on the incumbent, before the swap (unrecoverable afterwards)

docs/evidence/2026-08-04-accept-senses-unsloth-orin.txt records the baseline the playbook demands, plus a capability baseline that validated the probe harness:

  • decode 9.99 tok/s (medium) / 6.39 (long) from usage.completion_tokens; the chunk-count trap reproduced (~1.8× under-report), both figures kept so the difference is auditable
  • image PASS with an opposite-colour control; video ingested (+1,751 prompt tokens for 24 frames) but the forward clip and its reversed control both answered RIGHT-TO-LEFT — no directional discrimination, which only the control revealed
  • reasoning is opt-in (enable_thinking), and streaming sends the trace on delta.reasoning while delta.content stays empty for its entire duration — a client reading only content sees a hung lane

To finish this on hardware

lobes init --fleet --profile orin --shape orin-lobe --apply --force

Boot senses first (#171 — order changes the KV pool), restore the 12 operator .env lines immediately after (#174), then run the probe harness and backfill the measured budget into both orin.toml and orin-lobe.toml — a lockstep test fails CI if only one moves. Record the value that booted and any value refused; the refusal is the more useful datum.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD

OriNachum and others added 24 commits August 4, 2026 10:15
Scope-explored (13 surfaces), converged, and exported. User decisions:
orin-only pin, new orin-lobe shape, drop MTP if unsupported, model card
guides context (re-measured live). Capability goals image/video/audio/
reasoning are probe-gated; audio rides on vLLM #101, video unknown.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
Rigorous pass (hardware + hard-to-reverse + distributed-state signals).
Findings: config-verified 262144 ctx + declared video/audio + int4 quant
(c3 precedent narrowed); incumbent-first benchmark ordering; Tegra iowait
threshold persistence; snapshot rollback; evidence-key redaction; the
reasoning-field probe trap (h11 rejected, h20 replaces). q3 resolved:
accept the served-id break + role-name migration follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
…e /spec-to-plan)

Frame extended mid-plan with c29/h26 (worker->Thor proxy new, cortex->Spark
preserved) and re-exported. Plan covers all 48 targets; risks: KV-at-256K,
mesh-visible downtime, video/audio verdicts, operator-provided Thor key.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
t4: ~/.lobes snapshotted and verified byte-identical (rollback point).
t3: incumbent coolthor baseline on the current engine — decode 9.99 tok/s
(medium) / 6.39 (long) from usage.completion_tokens, MTP acceptance 69-83%,
KV 11.29 GiB / 480,431 tok / 3.67x.

d1 (approved): docs/orin-profiles.md overstates that KV pool 1.67x — boot
ORDER, not knobs, sets it on unified memory. Filed as #171; frame c20
amended to measured reality per h16.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
…a16 (t1)

Adds a candidate SupportedModel entry for the Gemma 4 12B QAT int4 W4A16
checkpoint (unsloth-qat-senses-first-class-orin-variation plan, t1),
mirroring the coolthor gemma-4-12B-it-NVFP4A16 gear in architecture
(Gemma4UnifiedForConditionalGeneration, gemma4 tool parser,
compressed-tensors) but citing its own config.json for the knobs that
differ: 262144 native context (double the incumbent 131072), INT4
pack-quantized weight-only quantization (not FP4), and a natively
declared video_token_id the repo's prior "text+image+audio" Gemma
capability line predates.

role_hint is deliberately "candidate", not the literal "multimodal" the
covering plan names, to preserve test_exactly_one_gemma_multimodal_gear's
singular-tier-owner invariant -- the same reason the sakamakismile coder
entry is "candidate" rather than "multimodal". This keeps the fleet-wide
multimodal/senses/normal tier default, and every thor/spark profile that
pins the raw coolthor id, untouched.

docs/gemma-4-12b-qat-w4a16.md documents the checkpoint with a capability
table whose rows are all marked pending-live-probe -- nothing is claimed
validated, and issue #101's audio-drop gap is flagged as an expected risk
carried over from the vLLM path, not resolved by this checkpoint's own
audio_config declaration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add lobes/machines/orin.py — the Jetson AGX Orin 64GB Developer Kit
(Ampere sm_87), following the thor.py CardStrategy convention exactly.
One new module + one register() import line in machines/__init__.py
(slotted after thor; its sole "orin" marker never overlaps
spark/thor/blackwell's markers, verified by test).

Ground truth (docs/orin-profiles.md, live 2026-07-16/17): compute
capability 8.7 (sm_87), hostname "orin", device-tree model "NVIDIA
Jetson AGX Orin Developer Kit". Legacy single-model defaults use the
measured 0.45 gpu_mem_util and TRITON_ATTN. The embedder/reranker
pooling divergences (TRITON_ATTN, reranker enforce_eager) are declared
as orin's OWN role_overrides rather than composing the shared SM_110
trait — the doc is explicit these are a conservative carry-over from
Thor's sm_110 finding, not independently proven on sm_87, so reusing a
trait named "sm_110" would misattribute the cause.

Detection-only scope: no lobes/profiles/builtin/orin.toml (that's a
later task) and no shapes/goldens/catalog/compose changes.

TDD: failing tests added first to test_detect.py (fact-set resolution,
device-tree fallback, hostname-alone resolution, still-UNKNOWN for
unrelated hardware) and test_machines.py (signature, non-shadowing
precedence, role_overrides provenance, no SM_110 composition), then
the implementation made them pass.

Verified on the physical Orin dev box: `pytest tests/ -k "detect or
machine or profile" -n auto` is green (268 passed). The unfiltered
full suite has 20 pre-existing failures, all in test_init.py/
test_cli_logs.py's `main(["init", ...])` helpers that don't mock
hardware detection (unlike the neighboring _inject_spark_detection
fixture) — now that this literal box resolves to the real "orin" card
instead of UNKNOWN, `lobes init`'s fleet path 404s on the
not-yet-created orin.toml profile. Confirmed via git-stash that these
same tests pass pre-orin on this exact box; they exercise the `init`/
`logs` verbs, not detection, and creating orin.toml is out of this
task's scope (it would break the builtin_names() goldens this task is
committed to leaving alone).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
gpu_mem_util is a fraction of the whole device, so co-resident engines are
deducted one-for-one from KV — the only elastic term. Arithmetic reconciles
to within ~0.5 GiB. Also records the 31% CUDA-graph under-estimate (~0.37 GiB
of unused headroom) vLLM's own log flags. Detail added to #171.

Records d2 (t1 role_hint precedent) and d3 (host-dependent tests, #172).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
…tion

The 20 failures the coordinator reproduced were a pre-existing
test-isolation gap: main(["init", ...]) helpers in both files called
real lobes.runtime._detect.detect_card() with no injection, unlike the
neighboring _fake_card/_patch_detect pattern already established in
test_init_profile.py, test_init_shape.py, test_cli_fleet.py and
test_upgrade_compat.py. That worked by luck as long as the box running
pytest resolved UNKNOWN -> the conservative 'base' profile; landing
the new orin CardStrategy exposed it, because this dev box IS a real
Jetson AGX Orin (hostname "orin") that now correctly resolves an
"orin" card with no built-in profile yet (still t6's job).

Fix: a file-local autouse pytest fixture (_pin_spark_detection) in
each file, monkeypatching _detect.detect_card to a fixed "spark" card
for every test. Chose autouse over the repo's more common per-test
_patch_detect() call because every test in these two files wants the
exact same neutral, resolvable card — the card's identity is
incidental to what's being asserted (scaffold mechanics / lobes logs
plumbing), unlike test_init_profile.py/test_init_shape.py where the
injected card IS the thing under test. Mirrors tests/conftest.py's
existing offline_runtime autouse fixture, which neutralises docker/
health/capabilities probes for the identical reason.

No assertions weakened or tests skipped — same expectations, now
driven by an injected fact instead of whatever hardware happens to run
pytest. Removed the now-fully-redundant explicit
_inject_spark_detection(monkeypatch) call sites in test_init.py (5)
now that the autouse fixture covers them.

Host-independence is proven, not just asserted: a new parametrized
test_detection_pin_drives_the_resolved_profile_not_the_host overrides
the fixture per-test with two different injected cards (spark, thor)
and checks the dry-run output's "Profile: <name> (...)" line tracks
the injected card — direct evidence the outcome is driven by the
fixture, never by socket.gethostname()/nvidia-smi/device-tree on the
box actually running pytest.

Does not create lobes/profiles/builtin/orin.toml (still out of scope
for t2/this fix) — the fix works by never letting detection resolve a
real card at all in these two files.

Verified on the physical Orin dev box: `uv run pytest -n auto` is now
fully green — 2730 passed, 15 skipped, 0 failed (up from 20 failed
pre-fix; +22 = the 20 previously-failing tests plus 2 new parametrize
cases).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds lobes/machines/orin.py (Ampere sm_87, 64GB) with orin's own
role_overrides (TRITON_ATTN pooling, reranker enforce_eager) carrying
provenance that flags them as unproven-on-sm_87 carry-overs rather than
composing the sm_110 trait.

Also makes test_init.py / test_cli_logs.py hermetic (autouse detection
pin) — they previously inherited the runner's real GPU card and passed
only because CI is not an Orin. See #172.
…off-switch)

The senses swap targets unsloth/gemma-4-12B-it-qat-w4a16, which ships no MTP
draft head. The recorded operator decision is to attempt the incumbent
google/gemma-4-12B-it-assistant draft at boot and, if vLLM refuses it, drop
speculative decoding rather than block the swap. That was impossible: the flag
was a hardcoded literal in the vllm-multimodal command.

MECHANISM — a shell-lexed STRING command:, scoped to this lane only.

`docker compose` cannot conditionally omit a `command:` LIST item. An item that
substitutes to "" renders as an empty argv element, and `vllm serve` reads that
as a second positional and exits 2 with `error: unrecognized arguments:` —
VERIFIED by parsing that exact argv through the served build's own serve parser
(vLLM 0.23.1rc1.dev672+g93d8f834d, image lobes/vllm-gemma4:local). So folding the
flag into one list item and blanking it is dead, as suspected.

A string `command:` is shell-lexed AFTER substitution, so a variable expanding to
nothing leaves no token at all — the flag is omitted ENTIRELY, the only "off"
rendering vLLM accepts. Verified end to end with real `docker compose config`
(Compose v5.3.1): unset renders argv byte-identical to before; MULTIMODAL_
SPECULATIVE_CONFIG= renders 13 tokens with no --speculative-config and no empty
element.

The dash-only default operator is deliberate — the colon-dash form every other
knob uses treats an empty value as unset and would put the draft straight back.

Every other lane keeps its list; the two other Gemma lanes are byte-identical.

- ZERO golden changes (template-defaults.env and friends untouched): the knob
  uses the dash-only operator, which regen's extractor documents as outside the
  tracked defaults surface.
- test_catalog.py's byte-guard is STRENGTHENED, not deleted: it now pins the
  catalog item as the knob's full substitution rather than as a bare substring —
  the item string also appears in the lane's new prose comments, which a
  substring check would match after someone deleted the actual flag. The same
  hole is closed in test_cli_fleet.py.
- test_tool_parser_plugin.py's vllm-multimodal service hash recomputed (the
  rendered argv is unchanged; only the template's representation of it moved).
- env.example documents unset / empty / retarget, including the two-layer
  quoting a custom value needs (bare single quotes silently mangle the JSON).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
…-switch)

String command: form so an empty MULTIMODAL_SPECULATIVE_CONFIG omits the
flag entirely; dash-only default operator (${VAR-default}) is load-bearing
so the off-switch stays reachable. Zero golden changes. Byte-guards
strengthened rather than deleted.
Checkpoint on disk (9.56 GiB, +1.91 vs incumbent), config.json read locally:
int4 W4A16, 262144 native, video_token_id declared. Projection from measured
incumbent facts: 262144 reachable at util 0.45 either way, but senses-first
gives 2.67x vs gears-first 1.52x.

Also records Thor peer state (keyless, worker live) and this box's 0.45.0
gateway gap (no worker role) — risk r5 / deviation d5.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
… lobes vetoed

Adds `lobes/profiles/builtin/orin.toml`, the fleet-shaped half of the Jetson
AGX Orin 64GB card (Ampere sm_87) whose detection landed in
lobes/machines/orin.py (t2). It supersedes the hand-authored operator profile
docs/orin-profiles.md records.

- `senses` = unsloth/gemma-4-12B-it-qat-w4a16 (compressed-tensors, TRITON_ATTN).
  Every checkpoint fact in the comment block is read off the checkpoint's own
  config.json, not the model card: Gemma4UnifiedForConditionalGeneration,
  262144 native, num_bits=4/type=int/strategy=group (INT4 weight-only, NOT
  FP4), vision_config + audio_config both present.
- `gpu_mem_util = 0.45` / `max_model_len = 262144` are marked MEASURED-PENDING
  in a prominent block, with the reason they cannot simply be inherited: the
  2026-08-04 live re-measurement on this box (approved deviation d1, issue
  #171) contradicts docs/orin-profiles.md — the incumbent at 0.45/131072 holds
  11.29 GiB KV = 480,431 tokens = 3.67x, not 18.86 GiB / 802,644 / 6.12x,
  because gpu_memory_utilization is a fraction of the WHOLE device and the
  doc's figure came from a senses-FIRST boot into an empty one. At 480,431
  tokens the 256K window is ~1.83x and may be refused outright. The live boot
  (t9) backfills both; a refusal is the more useful datum (thor-muse's 0.40 was
  refused, thor-worker's 0.45 booted first try).
- `cortex` / `muse` / `worker` feasible=false: all three are NVFP4 exports that
  quantize ACTIVATIONS to FP4, which needs Blackwell tensor cores. A hard
  architecture line, not a memory tradeoff — contrast senses's W4A16, whose
  weights dequantize through Marlin (cc >= 8.0) with 16-bit activations.
- The pooling gears' carried-over Jetson divergences (embedder/reranker
  TRITON_ATTN, reranker enforce_eager) are deliberately ABSENT from the TOML:
  `orin` joins `thor` in `_MACHINE_DERIVED_BUILTINS`, so the loader overlays
  them from lobes/machines/orin.py's role_overrides and the literals are never
  re-typed. A test pins both halves (values match the registry; the knob names
  do not appear in the file).

Goldens regenerated with tests/goldens/regen.py: six NEW files only
(tests/goldens/orin.env + five shapes/*__orin.env). Zero byte changes to the
existing base/spark/thor goldens or to spark.toml/thor.toml, and the spark/thor
asserts in tests/test_profile_schema.py are untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
senses = unsloth/gemma-4-12B-it-qat-w4a16 at util 0.45 / 262144 marked
MEASURED-PENDING (d1/#171 cited). cortex/muse/worker vetoed on the
FP4-activations-need-Blackwell line. loader now derives orin's role
overrides from lobes/machines/orin.py rather than re-typing them, exactly
as thor does.
image PASS (control held), video INGESTED but no directional discrimination,
audio encoded yet unusable, reasoning opt-in via enable_thinking, tools PASS.

Refines #101: audio tokens DO scale with duration (~25 tok/s), so the
documented '~19 placeholder tokens' signature is wrong on this build — the
honest test is discrimination, which the model fails (it reports hearing
nothing while carrying 112 audio tokens).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
All four stream through the gateway with usage in the final chunk. Records
the consumer trap: with thinking on, the trace streams on delta.reasoning
while delta.content stays empty for the whole trace.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
…n card

Two live-found regressions on the Jetson AGX Orin 64GB, both previously
survivable only by hand-patching the deployed box.

1. The budget clobber. The box ran `--profile orin --shape thor-lobe`, and a
   shape's [overrides.senses] WINS over the card profile at render time, so
   Thor's measured sm_110 values (0.30 / 131072) replaced the Orin card's own
   (0.45 / 262144) — visible in the wave-1 goldens: thor-lobe__orin.env vs
   orin.env. The new builtin `orin-lobe` shape hosts senses + embedder +
   reranker, drops cortex, and carries the Orin budget as its own declaration:
   orin-lobe__orin.env renders 0.45 / 262144. Unlike every other builtin shape
   it hosts NO stt/tts — the Parakeet base image (scitrera/dgx-spark-vllm)
   ships a torch with no sm_87 kernels (measured live: 8 restarts, "no kernel
   image is available"), so audio forwards to a peer via AUDIO_URL.
   DECLARED/UNVALIDATED per #108: no box has booted it, and its senses values
   are the card profile's MEASURED-PENDING hypothesis, pending the t9 boot.
   A lockstep test fails CI if the card is backfilled and the shape is not.

2. The Tegra iowait quirk, now persistent. /proc/stat here reports ~59% iowait
   with zero disk I/O (the sugov:* cpufreq-governor kthreads flicker in D
   state and inflate nr_iowait), so at the shipped
   LOBES_IOWAIT_DEGRADED_THRESHOLD=50 the gateway 429-sheds every full-tier
   request indefinitely — on this card, all of senses. The live box survived
   on an ephemeral shell-env override any `docker compose up` reverts.

   Mechanism: neither render.py nor shape_render.py could emit a non-role env
   key (only shape_render's hardcoded opt-in activation tables), so Profile
   grows one optional top-level `[host_env]` table — string-valued, validated,
   rendered BEFORE the role keys so a role knob always wins a collision — and
   builtin/orin.toml declares LOBES_IOWAIT_DEGRADED_THRESHOLD = "100" with the
   accounting quirk written out so nobody "fixes" it back to 50.

   It sits on the CARD, not the shape: it is a fact about the board, so every
   shape over `orin` inherits it, machine-as-brain (what a bare `lobes init`
   renders) included — a shape-scoped fix would leave the default path
   shedding. No other card declares a host_env, so base/spark/thor renderings
   are byte-unchanged; the six orin goldens each gain exactly one line.
   `lobes doctor` now names the key on a deployment still carrying 50.

Also: --shape metavar derived from builtin_shape_names() instead of a
hand-listed string that had already gone stale (thor-worker was missing), and
orin-lobe added to scripts/accept-shape.sh so t9 can produce its transcript.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t_env

orin-lobe hosts senses+embedder+reranker (no local stt/tts on sm_87) and
carries the orin senses budget so thor-lobe's Thor-measured 0.30/131072 no
longer clobbers it — pinned by a lockstep test against builtin/orin.toml.

New optional [host_env] table on Profile renders the Tegra iowait threshold
(100) from the CARD, so a bare machine-as-brain init is fixed too, not just
this shape. DECLARED/UNVALIDATED per #108.
…pu_access` (0.55.0)

This Jetson AGX Orin's NVIDIA container toolkit (1.19.1, `mode = "auto"`)
resolves to legacy **csv** mode, where the compose templates'
`deploy.resources.reservations.devices` GPU request fails at container CREATE:
"invoking the NVIDIA Container Runtime Hook directly … is not supported."
The live fix was a HAND EDIT of every GPU service's `deploy:` stanza into
`runtime: nvidia` — and a re-init reverts it (docs/orin-profiles.md
divergence 1), which is precisely what the next live boot is about to do.

Mechanism: a card profile declares `gpu_access = "devices"` (the default,
today's behaviour) or `"runtime"`, and `lobes init --apply` GENERATES two
compose overrides that `!reset` each GPU service's `deploy:` stanza and set
`runtime: nvidia` — on EVERY render, and removed when the resolved card no
longer declares it.

Why a generated override rather than a template `${VAR}`: docker-compose has no
conditional-block syntax, so a service needs one form or the other and no
substitution can pick between them — only a second compose file can. That is
the mechanism `docker-compose.shape.yml` already proved.

Why TWO files: verified with real `docker compose config` on this box, an
override naming a service no file in the same `-f` chain declares is a hard
error ("service chatterbox has neither an image nor a build context
specified"). The audio sidecars live in the opt-in audio overlay that
`lobes up <non-audio-role>` deliberately leaves out, so their half is paired
with that overlay and the base half with docker-compose.yml.

Why on the CARD and not a shape: which syntax the runtime accepts is a fact
about the board, true of every shape rendered over it — a shape-scoped fix
would leave a bare `lobes init` on the same board broken. Same reasoning as
the `[host_env]` iowait declaration.

`gpu_access` renders NO .env key, so every profile and shape golden — orin's
included — is byte-unchanged (regen.py reproduces them bit for bit).

UNVALIDATED (#108): `docker compose config` on the real csv-mode board renders
and merges the overrides correctly (all 12 GPU services convert, zero legacy
stanzas remain). That proves the compose merge, not a container create; only
the live boot can do that.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
…ed overrides

Profile.gpu_access ('devices' default | 'runtime') drives a generated
docker-compose.gpu.yml pair using 'deploy: !reset null' to drop the base
devices stanza. Card-scoped, so a bare machine-as-brain init on a csv-mode
board works too. Renders no .env key — every golden byte-unchanged.
Compose MERGE verified live; container create is UNVALIDATED until t9.
d6: operator profile shadowed the new builtin (#175, archived on-box).
d7: live re-render blocked by the environment's permission classifier —
t9/t11 and t12's measured backfill become operator follow-ups.
r6: help-surface follow-up (#173). r7: MEASURED-PENDING budgets unvalidated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
Accountability artifact for the assign-to-workforce run: every plan task
accounted for, 7 drift entries classified, delivery claims carrying evidence
or an explicit unverified marker. The four capability claims and the budget
hypothesis are marked UNVERIFIED — no boot occurred (d7).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
Adds the orin card/profile/shape entries, the speculative-config knob, the
host-dependent-test fix, and a 'Known issues found while building this
release' section pointing at #171/#173/#174/#175 and the #101 correction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Jetson AGX Orin first-class card + orin-lobe + QAT senses profile

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add Orin (sm_87) as a detected card with built-in profile and overrides.
• Introduce orin-lobe shape and QAT Gemma 4 12B senses catalog entry.
• Persist card-scoped host_env and gpu_access to make renders bootable and reproducible.
Diagram

graph TD
  A["lobes init / up"] --> B["detect_card()"] --> C["CardStrategy: orin"] --> D["Profile schema (host_env, gpu_access)"] --> E["Profile/shape render (.env)"] --> F["Compose chain (-f) resolution"] --> G["Generated GPU overrides"] --> H["Deploy shapes (orin-lobe)"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Runtime probe for GPU access mode
  • ➕ No need for a new persisted profile knob (gpu_access)
  • ➕ Could auto-adapt across JetPack/CDI vs csv-mode installs
  • ➖ Probe reliability varies by host packaging; adds non-deterministic init behavior
  • ➖ Harder to keep renders reproducible and reviewable (state depends on host)
2. Single template with env-driven YAML branching
  • ➕ Avoids generating extra compose files
  • ➕ Keeps everything in packaged templates
  • ➖ docker-compose has no conditional blocks; cannot switch deploy.resources vs runtime reliably
  • ➖ Would reintroduce hand-editing or fragile post-processing
3. Operator-only override (no built-in orin defaults)
  • ➕ Avoids shipping unvalidated Orin budgets/config in-tree
  • ➕ Keeps scope limited to local deployments
  • ➖ Portability goal fails: moving to a new host becomes rework, not profile selection
  • ➖ Continues drift and hand-patched deploy dirs (doesn’t survive re-render)

Recommendation: Keep the PR’s declarative approach: card detection + built-in profile/shape + explicit gpu_access/host_env. It makes portability a profile pick, keeps non-Orin cards byte-identical, and ensures re-renders preserve the csv-mode GPU runtime fix and Tegra iowait thresholds without host-dependent probing.

Files changed (58) +5231 / -165

Enhancement (12) +911 / -37
catalog.pyCatalog unsloth QAT Gemma 4 12B gear +82/-0

Catalog unsloth QAT Gemma 4 12B gear

• Adds SupportedModel entry for unsloth/gemma-4-12B-it-qat-w4a16 with config.json-derived fields, declares it as a candidate (non-default tier owner), and records that it ships no MTP head.

lobes/catalog.py

init.pyGenerate card-scoped GPU overrides and derive shape list +185/-12

Generate card-scoped GPU overrides and derive shape list

• Adds gpu_access-driven generation/scrubbing of docker-compose.gpu*.yml overrides on init --apply, includes dry-run planning/output, and updates --shape help text to derive from builtin shapes.

lobes/cli/_commands/init.py

up.pyInclude GPU overlay presence in compose chain +16/-3

Include GPU overlay presence in compose chain

• Extends 'lobes up' compose file argument resolution to include the generated GPU override overlay when present, keeping per-role up aligned with the central compose chain authority.

lobes/cli/_commands/up.py

__init__.pyRegister orin machine strategy in precedence order +9/-5

Register orin machine strategy in precedence order

• Adds orin import to the machine registry package and updates documentation about detection precedence and markers.

lobes/machines/init.py

orin.pyAdd Jetson AGX Orin detection strategy and role overrides +89/-0

Add Jetson AGX Orin detection strategy and role overrides

• Introduces Orin CardStrategy with signature markers (hostname/device-tree), compute capability sm_87, defaults, and conservative pooling role overrides with explicit provenance.

lobes/machines/orin.py

orin.tomlAdd built-in Orin fleet profile +221/-0

Add built-in Orin fleet profile

• Defines Orin profile: senses pinned to unsloth QAT checkpoint with MEASURED-PENDING budgets, vetoes NVFP4 roles as infeasible, declares host_env iowait threshold, and sets gpu_access=runtime for csv-mode boards.

lobes/profiles/builtin/orin.toml

orin-lobe.tomlAdd orin-lobe deployment shape +66/-0

Add orin-lobe deployment shape

• Adds orin-lobe shape hosting senses+embedder+reranker, dropping cortex and all audio services, and restating Orin senses budgets to prevent cross-card clobbering.

lobes/profiles/builtin_shapes/orin-lobe.toml

loader.pyApply machine-registry overrides to Orin built-in +18/-7

Apply machine-registry overrides to Orin built-in

• Extends machine-derived builtins to include orin and ensures host_env/gpu_access survive the registry overlay when rebuilding Profile instances.

lobes/profiles/loader.py

render.pyRender card host_env into .env before role keys +16/-3

Render card host_env into .env before role keys

• Adds host_env rendering ahead of role env rendering so card-scoped non-role keys persist while preserving role-key precedence.

lobes/profiles/render.py

schema.pyAdd Profile.host_env and Profile.gpu_access with validation +130/-4

Add Profile.host_env and Profile.gpu_access with validation

• Extends Profile schema with host_env (string-only, env-var-key validation) and gpu_access (devices vs runtime) plus strict load-time erroring and to_dict/from_dict support.

lobes/profiles/schema.py

shape_render.pyPreserve host_env and gpu_access through shape composition +11/-0

Preserve host_env and gpu_access through shape composition

• Ensures compose_profile carries card-scoped host_env and gpu_access unchanged when composing shapes over card profiles.

lobes/profiles/shape_render.py

_compose.pyAdd generated GPU overlays and compose chain dimension +68/-3

Add generated GPU overlays and compose chain dimension

• Introduces GPU_OVERLAY/GPU_AUDIO_OVERLAY constants, GPU service lists, gpu_overlay_present probing, and a new 'gpu' parameter to compose_file_args to inject GPU overrides into the -f chain.

lobes/runtime/_compose.py

Tests (27) +1551 / -47
orin.envAdd Orin profile golden .env output +21/-0

Add Orin profile golden .env output

• Adds/updates the golden rendered env for the orin card profile, including the new host_env key and Orin-specific senses budgets.

tests/goldens/orin.env

orin-lobe__base.envAdd orin-lobe golden for base card +12/-0

Add orin-lobe golden for base card

• Adds golden env output for rendering orin-lobe over the base card profile for contract matrix coverage.

tests/goldens/shapes/orin-lobe__base.env

orin-lobe__orin.envAdd orin-lobe golden for orin card +21/-0

Add orin-lobe golden for orin card

• Adds golden env output proving orin-lobe renders Orin senses budgets and QAT checkpoint selection.

tests/goldens/shapes/orin-lobe__orin.env

orin-lobe__spark.envAdd orin-lobe golden for spark card +15/-0

Add orin-lobe golden for spark card

• Adds golden env output for rendering orin-lobe over spark to ensure non-Orin cards remain stable and shape composition is pure.

tests/goldens/shapes/orin-lobe__spark.env

orin-lobe__thor.envAdd orin-lobe golden for thor card +18/-0

Add orin-lobe golden for thor card

• Adds golden env output for rendering orin-lobe over thor to cover cross-card shape composition and budget precedence.

tests/goldens/shapes/orin-lobe__thor.env

orin-small__orin.envUpdate orin-small rendering over orin card +19/-0

Update orin-small rendering over orin card

• Updates the golden env for orin-small over orin to reflect new card-scoped host_env behavior and profile changes.

tests/goldens/shapes/orin-small__orin.env

spark-lobe__orin.envUpdate spark-lobe rendering over orin card +16/-0

Update spark-lobe rendering over orin card

• Updates golden env for spark-lobe over orin to include Orin card host_env and to preserve shape/card purity.

tests/goldens/shapes/spark-lobe__orin.env

thor-lobe__orin.envUpdate thor-lobe rendering over orin card +21/-0

Update thor-lobe rendering over orin card

• Updates golden env to reflect Orin card presence while preserving the documented clobber behavior that orin-lobe is introduced to avoid.

tests/goldens/shapes/thor-lobe__orin.env

thor-muse__orin.envUpdate thor-muse rendering over orin card +16/-0

Update thor-muse rendering over orin card

• Updates golden env for thor-muse over orin to include card-scoped host_env propagation and shape composition invariants.

tests/goldens/shapes/thor-muse__orin.env

thor-worker__orin.envUpdate thor-worker rendering over orin card +16/-0

Update thor-worker rendering over orin card

• Updates golden env for thor-worker over orin to reflect Orin card host_env propagation and profile schema changes.

tests/goldens/shapes/thor-worker__orin.env

test_catalog.pyStrengthen drift guard and test QAT gear contract +105/-1

Strengthen drift guard and test QAT gear contract

• Pins the parameterized speculative-config default as it appears in templates and adds tests asserting the new QAT gear’s fields, modalities, and non-hijacking of the multimodal tier default.

tests/test_catalog.py

test_cli_fleet.pyAdjust CLI fleet tests for new overlays/behavior +13/-6

Adjust CLI fleet tests for new overlays/behavior

• Updates fleet CLI tests to match new compose chain behavior when additional overlays (e.g., GPU overrides) are present.

tests/test_cli_fleet.py

test_cli_logs.pyMake logs tests host-independent of hardware detection +26/-1

Make logs tests host-independent of hardware detection

• Pins detection in tests to avoid depending on the runner’s physical GPU/card, preventing failures on recognized cards like orin.

tests/test_cli_logs.py

test_compose_chain.pyCover compose chain with gpu overlay dimension +67/-16

Cover compose chain with gpu overlay dimension

• Expands compose-chain tests to include the gpu overlay presence and ensure file ordering rules (paired overrides per base/audio overlay) are enforced.

tests/test_compose_chain.py

test_detect.pyAdd Orin detection tests +49/-0

Add Orin detection tests

• Adds tests for Orin detection via nvidia-smi compute capability, device-tree model fallback, and hostname markers, ensuring no widening of UNKNOWN resolution.

tests/test_detect.py

test_init.pyPin detection with autouse fixture and prove host-independence +62/-13

Pin detection with autouse fixture and prove host-independence

• Makes init tests independent from host hardware by defaulting detection to a known card and adding a parametrized test proving injected detection drives outcomes.

tests/test_init.py

test_init_gpu_access.pyAdd end-to-end tests for gpu_access override generation +274/-0

Add end-to-end tests for gpu_access override generation

• Adds tests verifying gpu_access schema validation, generated docker-compose.gpu*.yml contents, re-render durability, scrubbing on non-csv cards, and dry-run planning payloads.

tests/test_init_gpu_access.py

test_init_shape.pyUpdate init-shape tests for new shape availability +2/-1

Update init-shape tests for new shape availability

• Adjusts init shape coverage to account for orin-lobe being a built-in shape and for derived shape name listings.

tests/test_init_shape.py

test_machines.pyExtend machine registry tests for orin +50/-2

Extend machine registry tests for orin

• Updates machines tests to include the orin strategy and validate registry behavior/precedence remains correct.

tests/test_machines.py

test_orin_lobe_shape.pyAdd orin-lobe and Tegra iowait host_env contract tests +247/-0

Add orin-lobe and Tegra iowait host_env contract tests

• Adds tests proving orin-lobe prevents senses budget clobber, enforces lockstep with orin profile budgets, drops audio services, and ensures Orin host_env fixes gateway shedding at phantom iowait levels.

tests/test_orin_lobe_shape.py

test_profile_render.pyUpdate profile render tests for host_env inclusion +36/-0

Update profile render tests for host_env inclusion

• Adjusts render tests to account for card-scoped host_env keys being emitted before role keys without affecting other cards.

tests/test_profile_render.py

test_profile_schema.pyTest host_env and orin built-in profile semantics +170/-3

Test host_env and orin built-in profile semantics

• Adds schema tests for host_env validation/immutability and asserts orin built-in behavior (QAT senses, infeasible NVFP4 roles, MEASURED-PENDING markers, and machine-registry-derived pooling overrides).

tests/test_profile_schema.py

test_senses_speculative_config.pyAdd tests for speculative-config off-switch semantics +232/-0

Add tests for speculative-config off-switch semantics

• Adds offline tests simulating compose substitution + shell lexing to prove unset renders byte-identical argv and empty value omits the speculative-config flag entirely.

tests/test_senses_speculative_config.py

test_shape_contract_matrix.pyUpdate shape/card contract matrix for new shape +7/-0

Update shape/card contract matrix for new shape

• Extends the shape contract matrix to include orin-lobe combinations and maintain shape×card coverage guarantees.

tests/test_shape_contract_matrix.py

test_shapes.pyUpdate built-in shapes enumeration expectations +17/-1

Update built-in shapes enumeration expectations

• Updates shape enumeration/behavior tests to include orin-lobe and ensure shape resolution remains deterministic.

tests/test_shapes.py

test_smoke_duo.pyAdjust duo smoke tests for new defaults/overlays +6/-2

Adjust duo smoke tests for new defaults/overlays

• Updates smoke tests to remain correct with the new profile schema and compose chain behaviors.

tests/test_smoke_duo.py

test_tool_parser_plugin.pyUpdate plugin tests for template changes +13/-1

Update plugin tests for template changes

• Updates tool parser plugin tests to account for template/argv changes introduced by parameterizing the senses speculative-config token.

tests/test_tool_parser_plugin.py

Documentation (16) +2671 / -37
currentAdvance current devague pointer +1/-1

Advance current devague pointer

• Updates the devague 'current' marker to reference this Orin variation work.

.devague/current

current_planAdvance current devague plan pointer +1/-1

Advance current devague plan pointer

• Updates the devague 'current_plan' marker to the latest exported plan for this delivery.

.devague/current_plan

unsloth-qat-senses-first-class-orin-variation.jsonAdd delivery export JSON +98/-0

Add delivery export JSON

• Adds the exported delivery record for the Orin/QAT senses variation, capturing plan-vs-actual status and artifacts.

.devague/deliveries/unsloth-qat-senses-first-class-orin-variation.json

unsloth-qat-senses-first-class-orin-variation.jsonAdd spec frame export JSON +848/-0

Add spec frame export JSON

• Adds the exported framing/spec JSON used to derive the plan and delivery artifacts.

.devague/frames/unsloth-qat-senses-first-class-orin-variation.json

unsloth-qat-senses-first-class-orin-variation.jsonAdd plan export JSON +652/-0

Add plan export JSON

• Adds the exported plan JSON enumerating tasks, waves, risks, and validation status for the Orin variation.

.devague/plans/unsloth-qat-senses-first-class-orin-variation.json

CHANGELOG.mdRelease notes for Orin first-class card (0.55.0) +43/-0

Release notes for Orin first-class card (0.55.0)

• Documents Orin detection, built-in orin profile, orin-lobe shape, host_env and gpu_access semantics, speculative-config off-switch, and host-independent test fixes with explicit UNVALIDATED disclaimers.

CHANGELOG.md

2026-08-04-unsloth-qat-senses-first-class-orin-variation.mdDelivery record and unvalidated claims ledger +148/-0

Delivery record and unvalidated claims ledger

• Adds a delivery write-up including plan-vs-actual accounting and explicit marking of unverified claims (UNVALIDATED / MEASURED-PENDING).

docs/deliveries/2026-08-04-unsloth-qat-senses-first-class-orin-variation.md

deployment-shapes.mdDocument orin-lobe shape and support table updates +17/-18

Document orin-lobe shape and support table updates

• Adds orin-lobe to the mesh-brain family, updates the support table and selection examples, and clarifies audio-hosting exceptions and validation status.

docs/deployment-shapes.md

2026-08-04-accept-senses-unsloth-orin.txtAdd evidence transcript for QAT senses acceptance attempt +268/-0

Add evidence transcript for QAT senses acceptance attempt

• Adds the acceptance/evidence transcript capturing what was attempted and what was blocked by the execution environment, supporting the UNVALIDATED status.

docs/evidence/2026-08-04-accept-senses-unsloth-orin.txt

gemma-4-12b-qat-w4a16.mdPer-model doc for unsloth/gemma-4-12B-it-qat-w4a16 +160/-0

Per-model doc for unsloth/gemma-4-12B-it-qat-w4a16

• Documents config.json-derived facts (context, modalities, quantization), and marks capability claims as pending live probe with known vLLM risks called out.

docs/gemma-4-12b-qat-w4a16.md

machine-profiles.mdDocument Profile.host_env and gpu_access +89/-0

Document Profile.host_env and gpu_access

• Explains card-level host_env keys, validation rules, ordering/precedence, and the gpu_access modes driving generated compose overrides for csv-mode boards.

docs/machine-profiles.md

orin-profiles.mdUpdate Orin operator guide with new built-ins +49/-6

Update Orin operator guide with new built-ins

• Notes orin-lobe as the successor to prior hand-patching, documents in-tree gpu_access override generation and the Tegra iowait host_env fix, and updates operational examples.

docs/orin-profiles.md

2026-08-04-unsloth-qat-senses-first-class-orin-variation.mdAdd published plan document +100/-0

Add published plan document

• Adds a human-readable plan capturing tasks, sequencing, and validation gates for the Orin/QAT senses variation.

docs/plans/2026-08-04-unsloth-qat-senses-first-class-orin-variation.md

2026-08-04-unsloth-qat-senses-first-class-orin-variation.mdAdd published spec document +154/-0

Add published spec document

• Adds the spec describing the portability/first-class-card goals, constraints, and explicit honesty rules for unvalidated Orin changes.

docs/specs/2026-08-04-unsloth-qat-senses-first-class-orin-variation.md

catalog.pyExplain orin-lobe in built-in shapes section +16/-2

Explain orin-lobe in built-in shapes section

• Updates the catalog explanation docs to include orin-lobe and refreshes shape selection examples accordingly.

lobes/explain/catalog.py

env.exampleDocument MULTIMODAL_SPECULATIVE_CONFIG off-switch and quoting +27/-9

Document MULTIMODAL_SPECULATIVE_CONFIG off-switch and quoting

• Adds guidance for disabling or retargeting speculative decoding via MULTIMODAL_SPECULATIVE_CONFIG, including the unset-vs-empty behavior and quoting rules.

lobes/templates/fleet/env.example

Other (3) +98 / -44
docker-compose.ymlMake senses speculative-config an env-controlled, omittable token +87/-40

Make senses speculative-config an env-controlled, omittable token

• Converts vllm-multimodal command to a shell-lexed string so MULTIMODAL_SPECULATIVE_CONFIG can be empty to omit the flag, while keeping the default rendering byte-identical to prior behavior.

lobes/templates/fleet/docker-compose.yml

pyproject.tomlBump project version for release +1/-1

Bump project version for release

• Updates project metadata version to align with the 0.55.0 release notes.

pyproject.toml

accept-shape.shAllow orin-lobe in acceptance script +10/-3

Allow orin-lobe in acceptance script

• Extends the acceptance script's supported shape set and wiring so orin-lobe can be exercised (while documenting audio constraints).

scripts/accept-shape.sh

Escapes the pipes in <|channel> / <|tool_call> inside table cells (they were
splitting the rows), and stops a line starting with '#108' being read as an
ATX heading.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
@qodo-code-review

qodo-code-review Bot commented Aug 4, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 95 rules

Grey Divider


Remediation recommended

1. Orin marker false positives ✓ Resolved 🐞 Bug ≡ Correctness
Description
The new Orin strategy uses name_markers=("orin",) and card detection only does substring matching on
name/hostname/device-tree strings, ignoring the declared compute_capability/total_memory_gb, so
unrelated hosts or different Orin variants can be incorrectly resolved as the 64GB Orin profile
(applying gpu_access/runtime overrides and Orin-specific knobs). This is a detection correctness
risk because the extra signature fields are present but explicitly not used in matching.
Code

lobes/machines/orin.py[R49-52]

+        signature=DetectionSignature(
+            name_markers=("orin",),
+            compute_capability="sm_87",
+            total_memory_gb=64,
Relevance

●●● Strong

Team previously accepted tightening substring-marker matching to avoid false positives in detection
logic.

PR-#14

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Orin strategy registers a single broad marker string, while the matching implementation
explicitly ignores compute capability and memory fields and only checks substring markers against
GPU/model/hostname text. detect_card then calls machines.detect using only name-ish facts, making
any substring match eligible to resolve as orin.

lobes/machines/orin.py[45-53]
lobes/machines/_strategy.py[50-76]
lobes/runtime/_detect.py[217-220]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`orin` is detected via a very broad substring marker (`"orin"`) while detection matching ignores `compute_capability` and `total_memory_gb`. This can cause false-positive resolution to the Orin profile on hosts whose GPU/model/hostname happens to contain `orin`, or on non-64GB Orin variants.

## Issue Context
- `DetectionSignature.matches()` currently matches only on `name_markers` substrings and explicitly does **not** use `compute_capability` / `total_memory_gb`.
- `detect_card()` already probes `compute_capability` and `total_memory_gb`, but only passes name-ish facts into `machines.detect()`.

## Fix Focus Areas
- lobes/machines/orin.py[45-53]
- lobes/machines/_strategy.py[50-76]
- lobes/runtime/_detect.py[217-220]

## Suggested fix
1. Extend the matching API to accept optional probed facts (compute capability + total memory) and use them as constraints when present.
  - Example behavior: if a strategy declares `compute_capability` and the probed `compute_capability` is non-None, require equality; similarly require `total_memory_gb` to be within a small tolerance (or minimum bound) when probed.
2. Thread those facts from `lobes.runtime._detect.detect_card()` into `lobes.machines.detect()` and into `DetectionSignature.matches()`.
3. Optionally keep the `"orin"` marker but rely on the additional constraints to avoid matching non-sm_87 or non-64GB(-class) devices.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. host_env newline corrupts .env ✓ Resolved 🐞 Bug ☼ Reliability
Description
The new Profile.host_env validation only enforces that values are strings, but lobes init writes
them verbatim into .env, so values containing \n/\r can corrupt the file layout and change how
subsequent env parsing behaves. This is a reliability footgun for operator-authored profiles using
host_env.
Code

lobes/profiles/schema.py[R127-130]

+        if not isinstance(value, str):
+            got = type(value).__name__
+            raise _profile_error(
+                message=(
Relevance

●●● Strong

Repo frequently accepts hardening around .env correctness/parsing to prevent footguns and corrupted
runtime config.

PR-#10
PR-#70

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new host_env parsing logic only checks value type (string) and does not restrict line
terminators, while the existing .env writer writes KEY=value lines with the value inserted
verbatim, so embedded newlines would create additional lines in the output file.

lobes/profiles/schema.py[107-137]
lobes/runtime/_env.py[78-98]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Profile.host_env` values are validated only by type (`str`) and then written verbatim into `.env`. Because `.env` is line-oriented `KEY=VALUE`, a value containing a newline/carriage return can split into multiple physical lines and corrupt the configuration.

## Issue Context
- `_host_env_from_dict()` currently accepts any string content.
- `lobes.runtime._env.set_env()` writes `f"{key}={value}"` and then joins lines with `"\n"`, without escaping.

## Fix Focus Areas
- lobes/profiles/schema.py[107-137]
- lobes/runtime/_env.py[78-98]

## Suggested fix
1. In `_host_env_from_dict()`, reject values containing `"\n"` or `"\r"` (and optionally NUL `"\x00"`).
2. Update the error remediation to instruct users to keep host_env values single-line (or to move complex/multiline content out of `host_env`).
3. (Optional hardening) Add similar validation in `set_env()` as a last-resort guardrail to prevent writing malformed `.env` lines.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread lobes/machines/orin.py
Comment thread lobes/profiles/schema.py
…t11)

0.45/262144 booted FIRST TRY at the full native window: weights 9.07 GiB,
KV 11.81 GiB = 609,266 tokens = 2.32x. csv-mode runtime:nvidia proven at
container CREATE (t8's mechanism, live). orin-lobe is now VALIDATED.

Capability matrix vs the incumbent, same box/engine/day: VIDEO now PASSES the
reversed-motion control the incumbent FAILS; image/reasoning/tools hold; audio
still unusable (#101, vLLM-side). Decode +8% medium, +79% long; the
incumbent's long-generation decay does not reproduce.

Three blockers hit and recorded: operator profile shadowing (#175), --force
destroying operator .env lines (#174), and the checkpoint shipping without
vision_config.num_soft_tokens (#177, patched locally to boot).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
@OriNachum OriNachum changed the title feat: Jetson AGX Orin becomes a first-class card — senses on unsloth/gemma-4-12B-it-qat-w4a16 (0.55.0) feat: Jetson AGX Orin becomes a first-class card — senses on unsloth/gemma-4-12B-it-qat-w4a16, VALIDATED live (0.55.0) Aug 4, 2026
@OriNachum

Copy link
Copy Markdown
Contributor Author

✅ Live-validated on the physical Jetson AGX Orin — the UNVALIDATED caveat in the description is now superseded

The re-render, boot, and probe matrix all ran on the real board (2026-08-04). orin-lobe is validated, and the budget knobs are measured, not hypothesised. Transcript: docs/evidence/2026-08-04-accept-senses-unsloth-orin.txt.

The hypothesis booted first try

gpu_mem_util = 0.45 at the full native 262144 — no trim, nothing refused (contrast thor-muse, whose 0.40 was refused and became 0.55), with senses booted first into an empty device:

model weights loaded          9.07 GiB
Available KV cache memory    11.81 GiB
GPU KV cache size           609,266 tokens
max concurrency @ 262,144     2.32x       (a KV-pool ceiling, not throughput)
init engine                   284.92 s
csv-mode GPU access           worked at container CREATE

That last line is the live proof t8 could not get from docker compose config — the generated runtime: nvidia override is what let the container start at all on this csv-mode board.

Does it fit the machine better? Mostly yes — with one honest exception

incumbent coolthor new unsloth QAT
weights loaded 9.25 GiB 9.07 GiB
context window 131,072 262,144 (2×)
KV per token 24.6 KiB 20.3 KiB (−17%)
decode, medium 9.99 tok/s 10.81 (+8%)
decode, long (512 tok) 6.39 tok/s 11.45 (+79%)
TTFT warm 0.50–0.65 s 0.38–0.47 s
concurrency @ full ctx 6.12× @131,072 2.32× @262,144

Lighter, cheaper per KV token, faster — and the incumbent's decode decay on long generations (9.99 → 6.39) simply does not reproduce (10.81 → 11.45). The exception is concurrency headroom: ~6.7 GiB of the budget goes to peak activation that vLLM profiles "with 1 video items of the maximum feature size". That is the price of the ViT actually working — see below.

Capability matrix, same box and engine as the baseline

probe incumbent new
image PASS PASS
video FAIL (both clips → RIGHT-TO-LEFT) PASS (LEFT-TO-RIGHT / RIGHT-TO-LEFT)
audio FAIL FAIL
reasoning PASS (needs enable_thinking) PASS (same)
tools PASS PASS

Video is the headline. The forward clip and its reversed control now get different, correct answers — a single-frame read cannot pass that. The incumbent answered RIGHT-TO-LEFT for both, i.e. it was guessing, which only the negative control exposed.

Audio remains unusable and is honestly reported as such. Tokens scale with duration (~25 tok/s, so the audio is encoded), but the discrimination control fails 0/3 — the model answers NOISE for both a 200 Hz and a 3000 Hz tone, and denies hearing anything at all for white noise. This is the vLLM path gap (#101), not a checkpoint gap; stt stays the speech path. Of the four capabilities targeted, three are delivered.

Three blockers hit on the way — all filed

Live fleet state now

senses    unsloth/gemma-4-12B-it-qat-w4a16   ready=True  ctx=262144
embedder  Qwen/Qwen3-Embedding-0.6B          ready=True  ctx=8192
reranker  Qwen/Qwen3-Reranker-0.6B           ready=True  ctx=8192
cortex    unsloth/Qwen3.6-27B-NVFP4          ready=True  proxied=True

Still outstanding: the gateway image is cached at 0.45.0, so worker isn't wired yet — it needs a rebuild (d5), which is the remaining piece of t10.

…lease

cortex->Spark verified proxied=True after the --force .env rewrite. worker->Thor
wiring is in .env (keyless, peer verified live) but the gateway image installs
the PUBLISHED wheel, so 0.55.0 is undeployable until #176 merges (d8). The
failed build left the running fleet healthy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
1. Orin marker false positives (lobes/machines/orin.py) — the 'orin' marker
   matches every Jetson Orin variant, and DetectionSignature.matches ignored
   the declared compute_capability/total_memory_gb, so an Orin Nano/NX would
   resolve to the 64GB card and inherit a senses budget (util 0.45 @ 256K) it
   cannot hold. Declared traits are now CONSTRAINTS when the caller supplies
   the corresponding probed fact; unprobed facts constrain nothing, so the
   legacy name-only behaviour and every existing call site are unchanged.
   Memory is a band, not equality — a '64GB' board reports 61.3.

   This also caught three tests asserting a physically impossible board: they
   described an Orin while passing the helper's 128 GB default. Corrected to
   the real 61.3, plus a new Orin-Nano regression test.

2. host_env newline corrupts .env (lobes/profiles/schema.py) — values are
   written verbatim into a line-oriented file, so an embedded newline SPLITS
   the entry rather than escaping. Rejected at profile load (where the operator
   can see the cause) with a last-resort guardrail in _env.set_env so no code
   path can write a malformed .env.

Live re-verified on the physical box: detect_card() still resolves 'orin'
(sm_87, 61.3 GB). Suite 2866 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
The live boot the classifier initially blocked was operator-approved and ran:
t9/t11/t12 move blocked -> delivered, and the four unverified capability
claims are re-stated with evidence (video PASS is now a high-confidence claim;
audio is marked DISPROVEN rather than merely unverified).

t10 stays partial and honest: cortex verified, worker wired but gated on
releasing 0.55.0 (d8). Adds d8 and the #177 checkpoint-config finding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
S5799 (init.py) — implicit string concatenation that read as a missing comma;
merged into one literal.
S3776 (init.py) — _emit_dry_run cognitive complexity 17 > 15; split its two
independent jobs into _dry_run_payload / _dry_run_lines / _dry_run_scope.
Behaviour unchanged (106 init tests green).
S5958 (test_init_gpu_access.py) — pytest.raises(Exception) would have passed on
an incidental TypeError; narrowed to ModelGearError + EXIT_USER_ERROR + message.

S6353 (schema.py) — PUSHBACK, not fixed. Python's \w is Unicode-aware, so the
suggested [A-Za-z_]\w* also accepts CAFÉ_VAR and Aπ (verified). These names get
written verbatim into .env, which must stay ASCII. Documented at the regex and
pinned by a new test so the 'concise' refactor cannot land silently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzZKukvUXJooAZSBn2XD
@sonarqubecloud

sonarqubecloud Bot commented Aug 4, 2026

Copy link
Copy Markdown

@OriNachum
OriNachum merged commit 8339439 into main Aug 4, 2026
10 checks passed
@OriNachum
OriNachum deleted the senses-unsloth-qat-orin-variation branch August 4, 2026 11:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant