A `systemctl --user` unit runs under the per-user manager, which systemd
starts at first login and stops when the user's last session ends — so
closing the SSH session that started Skald killed it, and it never came
up at boot. No crash and nothing in the journal: the whole cgroup is
simply torn down. Both installers now enable lingering after installing
the unit, and update.sh carries the same helper so an installation
predating this fix is healed by an ordinary update. A failure to enable
it only ever warns, with the manual command — it must not abort an
install.
Two things found on the way there:
update.sh matched `case "$OS" in Linux) ... Darwin)`, but $OS had already
been normalized to lowercase at the top of the file, so stop_service and
start_service were both silent no-ops. None of the ordering the file
documents at its head was executing: the tarball went over the running
binary (ETXTBSY, aborting the update mid-way) and the safety-net restart
in cleanup() was a no-op too, leaving the box down.
Neither workflow published install.sh / install-nightly.sh to the web
root, so the scripts served by builds.skaldagent.net were hand-copied and
drifting from the repo — an installer fix would reach every existing box
through update.sh but never a new one. Nightly publishes the nightly
installer, release publishes the release one, both with the same atomic
temp-and-rename the tarballs use.
Also on the unit: dropped `After=docker.service`, which a user manager
silently ignores rather than honouring advisorily, and moved
`Restart=on-failure` to `always` — run.sh exits 0 on any graceful
shutdown, including one nobody asked for, which on-failure reads as a
clean stop. That is also what absorbs the boot race against Docker now
that lingering makes us start at boot.
scripts/ held the pre-marketplace MCP servers (gmail, gcal, gmaps, ssh, weather,
google_trends, whatsapp, serpapi_flights). Nothing referenced them any more:
connectors are admin-curated and installed into connectors/ from the
marketplace, and ci/package.sh never shipped scripts/ in the first place — so on
every installed box requirements.txt was pulling google-auth, googlemaps,
paramiko, trendspyg and friends for files that did not exist there.
requirements.txt now states what it is actually for: the two TTS plugins, which
spawn a bare `python3` on an embedded server script and so have no dependency
reconciler of their own. A connector's deps stay with the connector —
`ensure_installed` puts them in .pydeps/node_modules inside the user's
container, `ensure_installed_host` beside the files for a global one.
The venv itself stays load-bearing for those two plugins and for the host pip
that installs a global connector's deps, so the run/install/update scripts keep
creating it — but their "Python MCP servers will be unavailable" warning was
naming the one thing that no longer depends on it, and now says what really
breaks.
CONNECTOR_MANIFEST_GUIDE.md moves to the repo root: it was the one thing in
scripts/ still referenced (CLAUDE.md), and being under a gitignored directory it
had never been committed at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TIC said nothing about what the agent does, and named the wrong thing: the
tick belongs to the scheduler, which is generic and lives outside it. The
agent's only decision is whether an incoming event deserves an interruption
— it sorts, it never acts — so it is now event-triage, matching the
functional naming of the two memory lints.
- agents/tic/ -> agents/event-triage/, module tic/ -> event_triage/,
TicManager -> EventTriageManager, TicConfig -> EventTriageConfig
- agent id and chat source: "tic" -> "event-triage"
- config keys: tic.* -> event_triage.*, and the config.yml section tic: ->
event_triage: (greenfield: previously set values fall back to defaults)
- i18n en/it/fr: Event triage / Triage eventi / Tri des evenements; dropped
the stale "TIC sessions" mention from the debug-pages description
- docs/system-agents.md, docs/index.md, docs/settings.md, CLAUDE.md, SKALD.md
homes/ directory can contain files owned by other UIDs (root, etc.)
because Docker containers run as different users. Normal rm -rf fails
with Permission denied. Now falls back to sudo rm -rf automatically.