Type: Enhancement opportunity (ENH-PATCH)
Split out from #547 — Fix axis #2 ("reload-confirmation Telegram message"), filed as a standalone enhancement so it can land independently of the agent-preamble update and the drain-loop self-restart heuristic.
Observation
When an agent or user edits ~/.untether/untether.toml and config_watch.handle_reload() successfully applies the change, Untether currently logs a single [info] config.reload.applied path=… line and emits nothing visible to the chat that triggered the edit. The agent and the user both have to trust that the edit took effect — there's no positive confirmation in the conversation thread.
This silence has two empirically-observed consequences:
- Agents reach for
systemctl --user restart untether as a safety blanket (#547 tax-incident, 2026-05-16 15:27 AEST). The restart is unnecessary, drain hangs 120s on the agent's own session, and the agent's final answer message is dropped via outbox.fail_pending. The pattern recurs anywhere agents edit untether.toml.
- Users have no in-conversation evidence the change took effect — they have to switch context to
journalctl --user -u untether or cat ~/.untether/untether.toml | grep <key> to verify. Friction adds up across daily config tweaks.
Opportunity
When handle_reload() succeeds:
- Send a brief Telegram message to the chat that triggered the edit (sourced from
last_handle.incoming.chat_id or a similar discoverable handle), AND
- Make the "no restart needed" framing the headline of that message, not an afterthought. Agents read these messages in next-turn context and the framing materially changes their next action.
Suggested message shapes
For a successful per-key hot-reload (most common case):
♻️ Hot-reloaded untether.toml — change took effect immediately.
No systemctl restart needed. Untether reloaded the file automatically.
Changed section: [triggers.crons.bc-daily-triage] (1 key updated)
For a restart-only key hit (rare but real):
⚠️ Restart required for untether.toml change — the key you edited (<key>) is in the restart-only set.
Run systemctl --user restart untether to apply, OR revert and use the hot-reloadable equivalent (see help).
For a partial-reload failure (some keys hot-reloaded, some require restart):
♻️ Partial reload of untether.toml — 2 of 3 keys applied immediately. No restart needed for those.
1 key requires restart: <key> (was changed but is currently still using the old value).
Run systemctl --user restart untether when ready, OR revert that one key and re-edit.
The headline framing "No restart needed" (bold, explicit, present in every reload-success message) is the critical UX choice — that's the phrase agents will tokenise and remember.
Why it matters
- User impact: Closes the "did my edit work?" feedback loop in-conversation. Eliminates the need to bounce to
journalctl for verification.
- Agent impact: Explicit "no restart needed" framing directly addresses the trained-in reflex "after editing config, restart the service". Agents see the message in next-turn context, parse it, and skip the restart.
- Frequency: Triggers any time
untether.toml is edited — including programmatically by agents, manually by users, and via setup-wizard/automation flows. Likely fires several times per day on lba-1.
- Friction reduction: Major — directly mitigates the #547 failure mode (agent restart → drain timeout → lost outbox message).
Suggested approach
- Chat-id discovery: In
config_watch.handle_reload(), accept an optional triggered_by_chat_id parameter sourced from the most recent handle.incoming event that resulted in the edit. Best-effort — if the source isn't discoverable (manual edit via SSH, scripted change), fall back to broadcasting to the admin DM(s) configured in [transports.telegram] chat_id.
- Message construction: New helper
format_reload_notification(path, changed_sections, restart_required_keys) → str in a new src/untether/config_reload_notification.py module. Returns the message shape per the three cases above.
- Outbox delivery: Route via existing
TelegramOutbox.send() so pacing/rate-limit rules apply uniformly. Tag the message as ephemeral (auto-delete on next run-completion in that chat) so it doesn't clutter the conversation history.
- Diff summary: Optionally include a concise diff summary (changed sections + key names, not values) so the agent/user can confirm at a glance which part of the config changed.
- Tests: Mock the outbox and assert the right message shape fires for hot-reload-only, restart-only, and partial-reload paths. Confirm the headline contains the literal string "No restart needed" (or "Restart required" for the restart-only case).
Related code/files
src/untether/config_watch.py (or wherever handle_reload() lives) — main wiring
src/untether/runner_bridge.py — last_handle.incoming.chat_id source for triggered_by_chat_id
src/untether/telegram/outbox.py — delivery path
src/untether/telegram/bridge.py — ephemeral message machinery
docs/reference/transports/telegram.md — document the new notification format
docs/faq/faq.md — new Q: "Do I need to restart after editing untether.toml?" → "No (with the documented exceptions)"
Related issues / prior conversations
Effort estimate
M — config-watch wiring + new notification module + outbox plumbing + 3 test cases + FAQ + docs update. Roughly half a day.
Milestone
v0.35.4 (next_patch) — well-scoped, no API breaks, directly mitigates the restart-induced outbox-message-loss failure mode.
— filed at user request following /monitor untether-staging synthesis pass (run 20260516T032233Z) discussion
Type: Enhancement opportunity (
ENH-PATCH)Split out from #547 — Fix axis #2 ("reload-confirmation Telegram message"), filed as a standalone enhancement so it can land independently of the agent-preamble update and the drain-loop self-restart heuristic.
Observation
When an agent or user edits
~/.untether/untether.tomlandconfig_watch.handle_reload()successfully applies the change, Untether currently logs a single[info] config.reload.applied path=…line and emits nothing visible to the chat that triggered the edit. The agent and the user both have to trust that the edit took effect — there's no positive confirmation in the conversation thread.This silence has two empirically-observed consequences:
systemctl --user restart untetheras a safety blanket (#547 tax-incident, 2026-05-16 15:27 AEST). The restart is unnecessary, drain hangs 120s on the agent's own session, and the agent's final answer message is dropped viaoutbox.fail_pending. The pattern recurs anywhere agents edituntether.toml.journalctl --user -u untetherorcat ~/.untether/untether.toml | grep <key>to verify. Friction adds up across daily config tweaks.Opportunity
When
handle_reload()succeeds:last_handle.incoming.chat_idor a similar discoverable handle), ANDSuggested message shapes
For a successful per-key hot-reload (most common case):
For a restart-only key hit (rare but real):
For a partial-reload failure (some keys hot-reloaded, some require restart):
The headline framing "No restart needed" (bold, explicit, present in every reload-success message) is the critical UX choice — that's the phrase agents will tokenise and remember.
Why it matters
journalctlfor verification.untether.tomlis edited — including programmatically by agents, manually by users, and via setup-wizard/automation flows. Likely fires several times per day on lba-1.Suggested approach
config_watch.handle_reload(), accept an optionaltriggered_by_chat_idparameter sourced from the most recenthandle.incomingevent that resulted in the edit. Best-effort — if the source isn't discoverable (manual edit via SSH, scripted change), fall back to broadcasting to the admin DM(s) configured in[transports.telegram] chat_id.format_reload_notification(path, changed_sections, restart_required_keys) → strin a newsrc/untether/config_reload_notification.pymodule. Returns the message shape per the three cases above.TelegramOutbox.send()so pacing/rate-limit rules apply uniformly. Tag the message as ephemeral (auto-delete on next run-completion in that chat) so it doesn't clutter the conversation history.Related code/files
src/untether/config_watch.py(or whereverhandle_reload()lives) — main wiringsrc/untether/runner_bridge.py—last_handle.incoming.chat_idsource fortriggered_by_chat_idsrc/untether/telegram/outbox.py— delivery pathsrc/untether/telegram/bridge.py— ephemeral message machinerydocs/reference/transports/telegram.md— document the new notification formatdocs/faq/faq.md— new Q: "Do I need to restart after editinguntether.toml?" → "No (with the documented exceptions)"Related issues / prior conversations
Effort estimate
M — config-watch wiring + new notification module + outbox plumbing + 3 test cases + FAQ + docs update. Roughly half a day.
Milestone
v0.35.4(next_patch) — well-scoped, no API breaks, directly mitigates the restart-induced outbox-message-loss failure mode.— filed at user request following /monitor untether-staging synthesis pass (run
20260516T032233Z) discussion