The AI Assistant That Went Silent on Restart: From a Silent Hang to a Session Bridge

Run an always-on AI agent long enough and you meet the worst failure of all: "not dead, but not answering." The process is alive, so your watchdog never fires; to the user it is simply unresponsive. This is the story of a headless conversational AI agent that hung forever on an interactive prompt at restart, and how I bolted on a "session bridge" so a fresh session still inherits the last context.

1. Symptom: alive but unresponsive

I had an AI agent attached to a messaging channel, running 24/7. For continuity it would resume the previous session on restart. At some point, restarts started leaving the channel silent — and it was strange:

  • systemd reported the service active
  • the process was alive and well
  • yet sending a message produced no reply

Neither Restart=always nor a "did the process die?" watchdog catches this. Nothing died.

2. Root cause: an interactive picker in a headless place

Capturing the agent's screen inside the terminal multiplexer revealed it. Once the session grew large (tens of thousands of tokens), the CLI showed an interactive selection prompt on resume:

This session is large and old.
  ❯ 1. Resume from a summary (recommended)
    2. Resume the full session as-is
    3. Don't ask again
  Enter to confirm · Esc to cancel

With a human present you just press Enter. But this is a headless environment with no human. It waits for input forever, and because the process stays alive no auto-recovery fires. Even a restart hits the same picker because the session is still large. Infinite silence.

3. First fix: never resume — always a fresh session

The picker only appears "when resuming a large session." So don't resume. I changed the start script to launch a new session every boot.

# Before: resume the previous session (grows big → picker → headless stall)
exec agent --resume "$PREV_SESSION_ID"

# After: always a fresh session (no room for a picker to appear)
NEW_ID=$(uuidgen)
exec agent --session-id "$NEW_ID"

As a bonus I pre-accepted first-run confirmations like "Do you trust the files in this folder?" via settings. Now any number of restarts reaches idle with no prompt.

There is a cost: starting fresh means the prior conversation context is gone — reliability traded for continuity. Hence the next step.

4. Second fix: a session bridge to carry context forward

"Don't resume, but pass the context." The core idea: summarize the just-ended session and inject it as the new session's starting context.

  1. Generate the summary: just before creating the new session, parse the prior transcript for "the last few user requests + the gist of the final reply." Instead of a heavy LLM call, a fast transcript tail-parse. Guard it with a timeout and exception handling so it never blocks startup.
  2. Inject: start the new session with that summary appended to the system context ("Prior session summary: ..."). Leave the original persona/system prompt intact and only append.
  3. Dedup: mark an injected summary so it isn't re-injected on the next restart.

The safeguards matter. The summary must never contain secrets — tokens, API keys, account numbers, balances. Scrub with regex, and cap the size at 1KB to avoid context pollution. Lock the summary file down to minimal permissions.

5. Make the watchdog check "responsiveness" too

The first fix removed the root cause, but I added one more safety net. The old watchdog only checked "process alive" — which the silent hang passes. So I added two things:

  • Stall detection: if the screen shows a blocking-prompt signature (picker / trust dialog) for consecutive ticks, treat it as stalled and restart — even if no one sent a message.
  • Restart-loop guard: if restarts repeat in a short window (say, more than 3 in an hour), stop auto-restarting and alert the operator directly. This prevents a restart storm.

6. Lessons

  • "Alive" is not "working." Health checks must probe responsiveness, not process liveness.
  • When you run an interactive CLI headless, block every prompt that could appear. Resume pickers, trust dialogs, update prompts — leave one and that's your stall point.
  • Get continuity and stability by different means. Trying to get both from "session resume" produced the silent hang. Splitting them — stability from fresh sessions, continuity from a summary bridge — made both safe.
  • Context injection needs secret-scrubbing and a size cap. The more past content you inject for convenience, the greater the leakage and pollution risk.

FAQ

Q. Can't you just periodically compact the session to keep it small?

It mitigates but doesn't solve. Regardless of compaction timing, "large enough" can still trigger the picker, and at that instant a headless setup stalls. To block it deterministically, not resuming is the sure thing.

Q. Wouldn't an LLM-generated summary per session be better?

Quality improves, but every restart adds cost and latency, and a failed call delays boot. A transcript tail-parse is instant and free, and it was enough to carry "what were we last doing." You can upgrade to an LLM summary later if needed.

Q. What if I want to search context further back?

The session bridge only carries "the immediately prior one." To dig into older history, attach a separate knowledge base (RAG) that embeds and searches past conversations. Bridge for short-term continuity, RAG for long-term memory — split the roles.

This post generalizes stabilization work on a real, in-production personal AI agent. Specific hosts, paths, and credentials have been deliberately omitted.