Skip to content

ENH-PATCH: hot-reload success Telegram notification with explicit "no restart needed" framing #548

@nathanschram

Description

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:

  1. 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.
  2. 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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.pylast_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

Metadata

Metadata

Assignees

No one assigned

    Labels

    auto:monitor-auditAuto-filed by /monitor command audit loop (bugs + enhancements, perf and otherwise)enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions