Jetson Production Setup
She runs because you are logged in
The Jetson is doing everything it was bought to do. It transcribes on the GPU, thinks with a model chosen against a memory budget instead of a parameter count, drives the servos through the same 40-pin header the Pi drove, and answers about six times faster than the board it replaced. All of that is true only while an SSH session is open and you are the one typing. Close the laptop and she is still running. Pull the barrel jack out of the wall and plug it back in, and there is nothing: no model loaded, no capture stream, no light in the eye, and no error anywhere, because nobody started anything.
Three habits are keeping her alive right now, and every one of them is you. You run the
voice loop by hand from the project directory. You set the power mode with
nvpmodel after any experiment that touched it. You watch the temperatures
climb during a long conversation and decide whether to open the cabinet door. None of
the three announces itself when it stops happening. A throttled Jetson does not raise
an error, it answers slower. A voice loop that never started does not crash, it simply
fails to be there when somebody says her name at seven in the morning.
So the last chapter of this volume writes a setup script, and the important thing about it is what it refuses to do. A production script does not perform the work at every boot. It configures the machine to perform the work by itself, and then proves the configuration is still there after a reboot nobody supervised.
One Jetson Orin Nano Super developer kit on JetPack 6.2.2, root filesystem on the
500 GB NVMe drive, hostname glados-jetson, user glados, and
an x86 laptop with none of the Jetson's files on it. Every mode name, temperature and
timestamp below came off that one board on one warm evening in a room with the door
shut, and yours will differ in all of them. The commands and the code are what carry
over. Anything printed by the board is labelled where it appears.
Three ways a step can fail on a machine you cannot assume
# labs/jetson_production.py
import subprocess
from dataclasses import dataclass
@dataclass(frozen=True)
class Cmd:
"""One attempt to run a command, with all of its outcomes flattened into data."""
argv: list[str]
found: bool
returncode: int | None
stdout: str
stderr: str
@property
def ok(self) -> bool:
return self.found and self.returncode == 0
def why(self) -> str:
if not self.found:
return f"{self.argv[0]}: not on PATH"
if self.returncode != 0:
msg = self.stderr.strip() or self.stdout.strip() or "no message"
return f"{self.argv[0]}: exit {self.returncode}: {msg}"
return f"{self.argv[0]}: ok"
def run(argv: list[str]) -> Cmd:
"""Never raises. A missing binary is an answer, not an accident."""
try:
proc = subprocess.run(argv, text=True, capture_output=True, check=False)
except FileNotFoundError:
return Cmd(argv, False, None, "", "")
return Cmd(argv, True, proc.returncode, proc.stdout, proc.stderr)
if __name__ == "__main__":
for argv in (["nvpmodel", "-q"], ["nvpmodel", "-m", "9"]):
print(run(argv).why())
$ ssh glados-jetson python3 -m labs.jetson_production # on the board
nvpmodel: ok
nvpmodel: exit 255: Error: Invalid power mode 9
$ uv run python -m labs.jetson_production # the same file, on the laptop
nvpmodel: not on PATH
nvpmodel: not on PATH
Those two transcripts are the whole reason this wrapper exists.
subprocess.run has two entirely separate ways of reporting trouble, and
only one of them is an exit code. When the binary exists and complains, you get a
CompletedProcess with a non-zero returncode, and
check=False is what stops that from raising. When the binary is not on
PATH at all, no process was ever created, so there is no exit code to
check and subprocess raises FileNotFoundError before your
code sees anything. Setting check=False does nothing about the second
case, and a setup script that only handles the first dies on the laptop at the first
Jetson-only command.
A third failure is coming later in the chapter and it does not involve
subprocess at all: writing into /etc as an ordinary user
raises PermissionError from the filesystem. Missing command, failing
command, refusing filesystem. Those are the three, and each one wants its own
handler, because a script that catches Exception around all of them can
no longer tell you which of the three happened.
# labs/jetson_production.py — continued
import re
from pathlib import Path
NVPMODEL_CONF = Path("/etc/nvpmodel.conf")
NVPMODEL_STATUS = Path("/var/lib/nvpmodel/status")
WANTED_MODE = "MAXN_SUPER"
MODE_LINE = re.compile(r"<\s*POWER_MODEL\s+ID\s*=\s*(\d+)\s+NAME\s*=\s*(\S+)\s*>")
def mode_table(conf: Path = NVPMODEL_CONF) -> dict[str, int]:
"""Every power mode this board declares, by name, read off the board itself."""
try:
text = conf.read_text(errors="replace")
except OSError:
return {}
return {name: int(ident) for ident, name in MODE_LINE.findall(text)}
def recorded_mode(status: Path = NVPMODEL_STATUS) -> int | None:
"""The mode nvpmodel wrote down, which is the one the next boot restores."""
try:
fields = dict(f.split(":", 1) for f in status.read_text().split())
except (OSError, ValueError):
return None
return int(fields["pmode"]) if "pmode" in fields else None
def set_power_mode(name: str = WANTED_MODE) -> str:
table = mode_table()
if not table:
return "no nvpmodel.conf here, so this is not a Jetson: skipped"
if name not in table:
return f"{name} is not a mode this board declares: {sorted(table)}"
wanted = table[name]
if recorded_mode() == wanted:
return f"already {name} (mode {wanted}), recorded for the next boot"
cmd = run(["nvpmodel", "-m", str(wanted)])
if not cmd.ok:
return cmd.why()
return f"set to {name} (mode {wanted}); recorded mode is now {recorded_mode()}"
if __name__ == "__main__":
print(f"modes on this board: {mode_table()}")
print(f"power mode: {set_power_mode()}")
$ ssh glados-jetson sudo python3 -m labs.jetson_production # first run
modes on this board: {'15W': 0, '7W': 1, 'MAXN_SUPER': 2}
power mode: set to MAXN_SUPER (mode 2); recorded mode is now 2
$ ssh glados-jetson sudo python3 -m labs.jetson_production # again, after a reboot
modes on this board: {'15W': 0, '7W': 1, 'MAXN_SUPER': 2}
power mode: already MAXN_SUPER (mode 2), recorded for the next boot
The number in nvpmodel -m 2 is the part everybody copies from a forum
post and the part that is not portable. Mode identifiers are assigned per board and
per JetPack release, so the same 2 means MAXN_SUPER here, something else on an AGX,
and nothing at all on a board whose configuration file lists three modes in a
different order. The name is stable and the file that maps names to identifiers is
already on the disk, so the script reads it. That is the same instinct as chapter
82's detect_platform: ask the machine what it is instead of telling it.
The second run is the one that matters for production. nvpmodel writes
the chosen mode into /var/lib/nvpmodel/status, and a service that ships
with JetPack reads that file during boot and re-applies it, so the mode survives a
power cut without any help from you. That makes this step a one-time configuration
and not a boot chore, and it also makes the mode something you can quietly break:
one nvpmodel -m 1 typed during an experiment in June is still in effect
in August. Reading the recorded value back is how the script tells the difference
between a board set correctly and a board that merely was, once.
Heat you asked for, and heat the board imposes
# labs/jetson_production.py — continued
THERMAL = Path("/sys/devices/virtual/thermal")
def temperatures() -> dict[str, float]:
"""Every thermal zone the kernel exposes, in degrees Celsius."""
zones: dict[str, float] = {}
for zone in sorted(THERMAL.glob("thermal_zone*")):
try:
name = (zone / "type").read_text().strip()
milli = int((zone / "temp").read_text().strip())
except (OSError, ValueError):
continue
zones[name] = milli / 1000
return zones
def trip_c(zone_name: str) -> float | None:
"""The lowest trip point the kernel declares for a zone, in degrees Celsius."""
for zone in THERMAL.glob("thermal_zone*"):
try:
if (zone / "type").read_text().strip() != zone_name:
continue
trips = [int(p.read_text()) / 1000 for p in zone.glob("trip_point_*_temp")]
except (OSError, ValueError):
continue
return min(trips) if trips else None
return None
def thermal_line() -> str:
zones = temperatures()
if not zones:
return "no thermal zones here: skipped"
name, temp = max(zones.items(), key=lambda kv: kv[1])
trip = trip_c(name)
if trip is None:
return f"hottest {name} {temp:.1f} C, no trip point declared"
return f"hottest {name} {temp:.1f} C, {trip - temp:.1f} C below the {trip:.1f} C trip point"
if __name__ == "__main__":
for zone_name, celsius in temperatures().items():
print(f"{zone_name:<13} {celsius:.1f} C")
print(f"thermals: {thermal_line()}")
$ ssh glados-jetson python3 -m labs.jetson_production # measured on the bench, yours will vary
cpu-thermal 46.5 C
gpu-thermal 45.2 C
cv0-thermal 44.9 C
soc2-thermal 47.8 C
tj-thermal 48.1 C
thermals: hottest tj-thermal 48.1 C, 43.9 C below the 92.0 C trip point
The power mode and the temperature answer two different questions and people run them together. MAXN_SUPER is a ceiling you asked for: it tells the board how much power it may draw and how high it may clock. The trip point is a ceiling the board imposes: cross it and the kernel drops clocks until the junction cools, with no log line in your journal and no exception in your Python. On the open bench with a fan over it, this board sat forty degrees clear of that limit. Inside a closed cabinet in a warm room, the margin is the number that changed, and the only symptom you would ever notice is that she started answering more slowly than the benchmark in chapter 80 said she should. Recording the margin the day you install the box gives you something to compare against on the day she feels sluggish.
# labs/jetson_production.py — continued
DECLARED_STORE = Path("/mnt/nvme/ollama/models") # what the config says
OLLAMA_STORE = Path("/usr/share/ollama/.ollama/models") # where Ollama really writes
def link_model_store(declared: Path = DECLARED_STORE,
store: Path = OLLAMA_STORE) -> str:
"""Make the path the config declares resolve to the directory Ollama uses."""
if not store.is_dir():
return f"{store} is missing: is Ollama installed?"
if declared.is_symlink():
if declared.resolve() == store.resolve():
return f"{declared} already resolves to {store}"
return f"{declared} points at {declared.resolve()} instead: left alone"
if declared.exists():
return f"{declared} exists and is not a link: left alone, move it yourself"
declared.parent.mkdir(parents=True, exist_ok=True)
declared.symlink_to(store)
return f"{declared} -> {store}"
if __name__ == "__main__":
print(f"model store: {link_model_store()}")
$ ssh glados-jetson sudo python3 -m labs.jetson_production # first run, then immediately again
model store: /mnt/nvme/ollama/models -> /usr/share/ollama/.ollama/models
model store: /mnt/nvme/ollama/models already resolves to /usr/share/ollama/.ollama/models
Chapter 82's override table declares model_storage as
/mnt/nvme/ollama/models for this board, and until now no such path
existed. Ollama installs as a system user and keeps weights under
/usr/share, which on this board is already the NVMe drive JetPack was
flashed onto in chapter 76, so nothing here is about speed. It is about there being
one path in the project that names where models live. The day a second drive goes
into the carrier, one link moves and no Python changes.
Read the guards in order, because their order is the design. is_symlink
comes before exists on purpose: exists follows a link and
reports on the target, so a link that dangles answers False to
exists while still being a link you must not silently overwrite. A real
directory sitting at that path means somebody put files there, so the function
refuses and says so instead of deleting anything. Running the script five times in a
row leaves the machine in the same state as running it once, and no run of it can
destroy data. That is the whole meaning of a setup script being safe to re-run.
Handing the loop to systemd without touching the generated unit
Chapter 54 already built the unit. render_unit reads the component
registry, works out which units she has to start after, fills in this machine's user,
working directory and interpreter, and can diff what it would generate against what is
installed. That unit is correct on the Jetson exactly as it is, and editing it here
would be the worst possible move: the next run of --check would report a
difference and you would have no way to tell a Jetson addition from a stale deployment.
systemd reads /etc/systemd/system/glados.service, then every
.conf file in /etc/systemd/system/glados.service.d/, in
filename order. Directives that hold a list, including ExecStartPre,
After and Environment, get appended to. Directives that
hold a single value, such as RestartSec, get replaced. The exception
that catches everyone is ExecStart: it is a list, so a drop-in adds a
second one and the unit refuses to load with Service has more than one ExecStart
setting, which is only allowed for Type=oneshot services. Clearing a list takes
an empty assignment first, ExecStart= on its own line. This drop-in never
needs that, because it only adds.
# labs/jetson_production.py — continued
import socket
import sys
from labs.service_unit import PROJECT_ROOT
DROPIN_DIR = Path("/etc/systemd/system/glados.service.d")
DROPIN_PATH = DROPIN_DIR / "jetson.conf"
ASSERT_MODE = [sys.executable, "-m", "labs.jetson_production", "--assert-mode"]
ON_FAILURE = PROJECT_ROOT / "glados" / "on_failure.py"
DROPIN_TEMPLATE = """# Generated by labs/jetson_production.py on {host}. Do not edit by hand.
[Unit]
StartLimitIntervalSec=120
StartLimitBurst=10
OnFailure=glados-alert.service
[Service]
ExecStartPre={assert_mode}
RestartSec=2
WatchdogSec={watchdog_sec}
NotifyAccess=main
Environment=OLLAMA_MODELS={declared}
"""
def render_dropin(watchdog_sec: int = 90) -> str:
return DROPIN_TEMPLATE.format(
host=socket.gethostname(),
assert_mode=" ".join(ASSERT_MODE),
watchdog_sec=watchdog_sec,
declared=DECLARED_STORE,
)
def install_dropin() -> str:
content = render_dropin()
try:
DROPIN_DIR.mkdir(parents=True, exist_ok=True)
DROPIN_PATH.write_text(content)
except PermissionError:
print(f"\n--- write this to {DROPIN_PATH} yourself ---\n{content}---")
return "no permission: re-run with sudo, or copy the block above"
return f"wrote {DROPIN_PATH} ({len(content.splitlines())} lines)"
$ ssh glados-jetson cat /etc/systemd/system/glados.service.d/jetson.conf
# Generated by labs/jetson_production.py on glados-jetson. Do not edit by hand.
[Unit]
StartLimitIntervalSec=120
StartLimitBurst=10
OnFailure=glados-alert.service
[Service]
ExecStartPre=/home/glados/GladOS/.venv/bin/python -m labs.jetson_production --assert-mode
RestartSec=2
WatchdogSec=90
NotifyAccess=main
Environment=OLLAMA_MODELS=/mnt/nvme/ollama/models
Twelve lines, eight of them directives, and every directive is a claim that is only true on this board. The
appended ExecStartPre runs after chapter 33's health_check
and refuses to let her start on a board that came up in the wrong power mode, so a
throttled machine announces itself at boot instead of hiding behind a slow reply.
WatchdogSec=90 asks systemd to kill and restart the process if it stops
sending heartbeats for a minute and a half. OnFailure names a unit to
run when this one gives up, and StartLimitIntervalSec with
StartLimitBurst is the pair the worked failure below is about. The
PermissionError branch is the third failure kind from stage 1, handled
the way a setup script should handle it: print the exact file and its exact contents,
so a reader who forgot the sudo can finish by hand in ten seconds.
# glados/on_failure.py — systemd runs this when glados.service gives up
import socket
from labs.alerts import alert
alert("CRITICAL", f"glados.service failed on {socket.gethostname()}")
# labs/jetson_production.py — the entry point
def assert_power_mode(name: str = WANTED_MODE) -> int:
table = mode_table()
if not table:
return 0 # not a Jetson: nothing to assert
recorded, wanted = recorded_mode(), table[name]
if recorded == wanted:
return 0
print(f"power mode is {recorded}, expected {wanted} ({name})", file=sys.stderr)
return 1
def main() -> int:
if "--assert-mode" in sys.argv:
return assert_power_mode()
print("=" * 58)
print(" Jetson production setup")
print("=" * 58)
print(f" power mode : {set_power_mode()}")
print(f" thermals : {thermal_line()}")
print(f" model store: {link_model_store()}")
print(f" drop-in : {install_dropin()}")
print("-" * 58)
print(" sudo systemctl daemon-reload")
print(" sudo systemctl enable --now glados")
print(" journalctl -u glados -b -f")
print("=" * 58)
return 0
if __name__ == "__main__":
sys.exit(main())
$ ssh glados-jetson sudo /home/glados/GladOS/.venv/bin/python -m labs.jetson_production
==========================================================
Jetson production setup
==========================================================
power mode : already MAXN_SUPER (mode 2), recorded for the next boot
thermals : hottest tj-thermal 48.1 C, 43.9 C below the 92.0 C trip point
model store: /mnt/nvme/ollama/models already resolves to /usr/share/ollama/.ollama/models
drop-in : wrote /etc/systemd/system/glados.service.d/jetson.conf (12 lines)
----------------------------------------------------------
sudo systemctl daemon-reload
sudo systemctl enable --now glados
journalctl -u glados -b -f
==========================================================
$ ssh glados-jetson sudo reboot; sleep 90; ssh glados-jetson 'systemctl is-enabled glados; systemctl is-active glados; uptime -p'
enabled
active
up 1 minute
The second transcript is the only proof this chapter accepts. Not that the script
ran, not that enable printed a symlink, but that the power was
interrupted and the box came back by itself with the service running. Do it with the
monitor unplugged and the SSH session closed, then walk over and say her name.
Why this works: three supervisors, three lifetimes
Chapter 41 put a watchdog thread inside the process. It watches heartbeats per stage, so when transcription stops answering it can name the stage and fire a recovery without disturbing anything else. It is also the layer with the shortest life: it dies the instant the process it lives in dies, and it cannot see a Python interpreter that hung on a deadlock, because a hung interpreter is not running the watchdog either.
systemd is the supervisor one scope out. It knows two things about her and nothing
else: whether the process exists, and what number it exited with. That is a poor view
of a voice assistant and a perfect view of a service. WatchdogSec closes
the gap the thread cannot cover, because heartbeats now cross the process boundary
through sd_notify, and a process too stuck to send them gets killed by
something that is not stuck. Restart handles a crash; enable
handles a boot; and OnFailure handles the case where systemd itself has
decided to stop trying, which is the case that would otherwise be silent.
The outermost layer survives the power going away, and almost nothing lives out there:
a symlink in /etc that enable created, and a mode identifier
in a status file. Both are one-time writes that later boots read. Generalise the rule
past this board and it decides where any piece of supervision belongs: a mechanism can
only watch what it outlives, so put each check in the innermost layer that survives the
failure it is meant to catch. Stage-level stalls belong in the thread. Crashes and
hangs belong in the service manager. Anything that has to be true after a power cut has
to be written to a file that survives one.
The first version of the drop-in carried RestartSec=2 and nothing else.
Ten seconds felt slow for a box that should be listening by the time you reach the
kitchen, so two seconds it was. It tested perfectly: kill the process, and she is back
before you can run systemctl status. Then the power went out at four in
the morning, came back forty seconds later, and at eight she was dead.
$ ssh glados-jetson journalctl -u glados -b --no-pager | tail -8
Aug 21 04:12:03 glados-jetson systemd[1]: glados.service: Control process exited, code=exited, status=1/FAILURE
Aug 21 04:12:03 glados-jetson systemd[1]: glados.service: Failed with result 'exit-code'.
Aug 21 04:12:05 glados-jetson systemd[1]: glados.service: Scheduled restart job, restart counter is at 4.
Aug 21 04:12:07 glados-jetson systemd[1]: glados.service: Control process exited, code=exited, status=1/FAILURE
Aug 21 04:12:07 glados-jetson systemd[1]: glados.service: Scheduled restart job, restart counter is at 5.
Aug 21 04:12:07 glados-jetson systemd[1]: glados.service: Start request repeated too quickly.
Aug 21 04:12:07 glados-jetson systemd[1]: glados.service: Failed with result 'exit-code'.
Aug 21 04:12:07 glados-jetson systemd[1]: Failed to start GLaDOS voice assistant.
Work through it from the top. A control process is an ExecStartPre, not
the loop itself, and status 1 from the startup probe means it found something wrong:
the USB microphone had not finished enumerating yet, which on this board takes about
fifteen seconds after a cold boot. That is a transient, and transients are what
Restart is for. The counter reaching 5 in four seconds is where the
transient turned permanent. systemd's default allowance is five starts inside ten
seconds, and RestartSec=2 spends all five of them in eight. So it gave
up at 04:12:07, roughly seven seconds before the microphone would have appeared, and
the unit sat in failed for four hours.
Two changes, and each fixes a different half. Widen the allowance so fast retries are not the same thing as a short deadline, and name something to run when the allowance is spent anyway:
# the [Unit] half of the drop-in, corrected
[Unit]
StartLimitIntervalSec=120
StartLimitBurst=10
OnFailure=glados-alert.service
$ ssh glados-jetson journalctl -u glados -b --no-pager | tail -4 # after the change
Aug 21 21:40:12 glados-jetson systemd[1]: glados.service: Scheduled restart job, restart counter is at 7.
Aug 21 21:40:14 glados-jetson glados[1613]: startup check: 6/6 passed
Aug 21 21:40:14 glados-jetson systemd[1]: Started GLaDOS voice assistant.
Aug 21 21:40:26 glados-jetson glados[1613]: listening.
Ten tries across two minutes outlasts a slow USB bus, and the seventh attempt is the
one that worked. Notice that both directives live under [Unit] and not
[Service], which is where people put them and where systemd ignores
them with a warning most readers scroll past. The second half matters more than the
first. Any restart policy has a limit somewhere, and reaching it means the machine has
stopped trying and nothing in the house knows. OnFailure runs
glados/on_failure.py, which calls chapter 16's alert at
CRITICAL and puts a line in the durable log and a message wherever escalation is
armed. A box that fails loudly at 04:12 is a different morning from a box that is
simply not there at eight.
Checkpoint, and a volume that ends with a box on a shelf
- I can name the three ways a setup step fails on a machine whose state it cannot
assume, and say which of them
check=Falsehas any effect on. - I can explain why a power mode identifier copied from a forum post is unsafe, and read the name-to-identifier table off the board instead.
- I can say which file makes a Jetson power mode survive a power cut, and prove the mode came back by reading it rather than by trusting that I set it once.
- Given a thermal zone and its trip points, I can state the margin the board is running with, and predict the symptom of crossing it.
- I can write a setup step that is safe to run five times, including the guard order that keeps a dangling symlink from being mistaken for a missing path.
- I can read a start-limit message in the journal and say how many attempts were made, in what window, and which directive would have bought more of them.
Exercise 1 — an audit that reads the box back. Add
--audit to main(): no writes at all, one line per
configured thing, and a non-zero exit if any of them drifted.
Four questions, all answerable without changing anything: is the recorded mode the
wanted one, does the declared store resolve to Ollama's store, is the drop-in on
disk and identical to render_dropin(), and does
run(["systemctl", "is-enabled", "glados"]) come back
enabled. Collect the failures into a list and exit 1 when it is
non-empty.
$ ssh glados-jetson python3 -m labs.jetson_production --audit; echo "exit $?"
OK power mode MAXN_SUPER (mode 2) recorded
OK model store /mnt/nvme/ollama/models resolves to Ollama's store
DRIFT drop-in on disk, but 2 lines differ from this project
OK boot enabled systemctl is-enabled: enabled
1 of 4 drifted
exit 1
That command belongs in a systemd timer once a week. It answers "is this box still configured the way I left it?" without anybody remembering to ask.
Exercise 2 — make the box tell you it is hot. Extend
thermal_line into a check that warns below a chosen margin and
escalates below a smaller one.
Pick two margins, say 20 C and 8 C, and route them through the levels chapter 16
already defines: above 20 C of headroom print and log nothing, between 20 and 8
call alert("WARNING", ...), below 8 call
alert("CRITICAL", ...). Then close the cabinet door, run a long
generation, and watch which one fires.
$ ssh glados-jetson python3 -m labs.jetson_production --thermal-check # cabinet shut, mid-generation
hottest tj-thermal 76.4 C, 15.6 C below the 92.0 C trip point
WARNING thermal headroom 15.6 C on glados-jetson
exit 0
Sixteen degrees on a warm evening with the door shut is the measurement that tells you whether the enclosure needs a vent, and it is a number nobody can guess from the bench.
Exercise 3 — send the heartbeat systemd is waiting for. The
drop-in asks for WatchdogSec=90, and nothing sends anything yet. Wire
the running loop to the watchdog socket and watch a stuck process get killed.
systemd sets WATCHDOG_USEC and NOTIFY_SOCKET in the
service's environment. Read the interval, halve it, and from the same thread that
already takes heartbeats send WATCHDOG=1 to that socket. The
sdnotify package does it in three lines, and a datagram to an
AF_UNIX socket does it with no dependency at all.
$ ssh glados-jetson 'kill -STOP $(systemctl show -p MainPID --value glados)' # then wait
Aug 21 22:07:41 glados-jetson systemd[1]: glados.service: Watchdog timeout (limit 1min 30s)!
Aug 21 22:07:41 glados-jetson systemd[1]: glados.service: Killing process 1613 (python) with signal SIGABRT.
Aug 21 22:09:12 glados-jetson systemd[1]: Started GLaDOS voice assistant.
SIGSTOP is the honest test, because a stopped process is the one
thing an in-process watchdog can never catch: the watchdog thread is stopped too.
Ninety seconds later the outer layer noticed and she came back.
Look at what this volume did. It started by refusing to describe the new board as a
faster Pi and put both machines into a table whose deciding row was memory: one shared
pool, twice as large, read eight times faster. Then JetPack went onto the NVMe drive and
a verification script read the version files back, because a progress bar that reaches
100 percent is not evidence. First boot happened once at a monitor and never again. CUDA
was checked in two independent layers, which is how a working GPU and a compiler missing
from PATH turned out to be the same afternoon. Ollama was measured instead
of trusted, and the measurement caught the fallback that install logs never mention. Five
runs became one number that could be compared to the Pi's, and the comparison landed on
6.8 times the decode rate, almost exactly what the bandwidth row predicted. The Volume 7
pin map was rechecked pin by pin against a different GPIO chip under an identical header.
The whole stack moved across in four table rows and no rewritten modules. A bigger model
was chosen by memory budget and tokens per second. And today the board became a box: it
boots into her, it holds its power mode, it says where its weights live, and it shouts
when it gives up.
What she still cannot do is anything that needs a sharper sense of the room or of herself. She answers from a model's training and the last few turns of conversation, so the manual for your own furnace might as well not exist. She has an eye that lights up and no camera behind it. She listens for a fixed number of seconds whether you finished your sentence in two or are still talking at six. She waits for the entire reply to be generated before the first word is spoken, which on a longer answer is several seconds of silence while the box has already decided what it is going to say. Nothing outside this house can ask her anything, and nothing inside the house obeys her.
Volume 9 goes after all of it. Retrieval over documents you give her, so she can answer from your own knowledge instead of a stranger's. A camera behind the eye, and a model that describes what is in front of it. Voice activity detection to replace the fixed listening window with one that ends when you stop talking. Streaming, so she begins speaking while the model is still thinking. A REST API so other programs can reach her, device control so she can reach the house, teams of agents that split a request between them, and a health monitor that watches the whole thing the way this chapter's drop-in watches one service. The hardware is finished. From here the work is senses.