{"slug": "pi-asynchronous-exit-summaries", "title": "pi: asynchronous exit summaries", "summary": "Pi, a terminal-based AI assistant, now exits instantly on Ctrl+D instead of waiting several seconds for a session summary, after developer Thiago Perrotta forked the pi-memory package into pi-memory-async. The fix writes a durable JSON job synchronously during shutdown and processes it asynchronously in the next session, eliminating the blocking LLM request and qmd update. The change preserves the original daily log entry while keeping the upstream package intact.", "body_md": "♠ **Problem statement**: `ctrl+d`\n\nin [pi](https://github.com/earendil-works/pi)\ntook several seconds to quit.\n\nThe culprit was [ pi-memory](https://www.npmjs.com/package/pi-memory)’s\n\n`session_shutdown`\n\nhandler. It sent one last LLM request to summarize the\nsession, wrote its answer to the daily log, then ran `qmd update`\n\n. Pi awaits\nall shutdown handlers, correctly, so quitting waited for everything:\n\n``` js\npi.on(\"session_shutdown\", async (event, ctx) => {\n  const result = await generateExitSummary(ctx);\n  fs.writeFileSync(filePath, entry, \"utf-8\");\n  await runQmdUpdateNow();\n});\n```\n\nMaking that promise fire-and-forget would not work: Pi exits immediately after shutdown, and either the in-flight request keeps Node alive or gets killed before it can write anything.\n\nInstead, I made the shutdown path write a durable job, synchronously:\n\n``` js\nconst job: ExitSummaryJob = {\n  version: 1,\n  id: randomUUID(),\n  createdAt: new Date().toISOString(),\n  date: todayStr(),\n  reason,\n  sessionId: shortSessionId(ctx.sessionManager.getSessionId()),\n  conversationText: truncated.text,\n  truncated: truncated.truncated,\n  totalChars: truncated.totalChars,\n};\n\nconst jobPath = path.join(\n  EXIT_SUMMARY_JOBS_DIR,\n  `${job.createdAt.replace(/[:.]/g, \"-\")}-${job.id}.json`,\n);\nfs.writeFileSync(jobPath, `${JSON.stringify(job)}\\n`, { encoding: \"utf-8\", flag: \"wx\" });\n```\n\nThe next interactive session starts an unref’ed worker. It atomically renames one job to claim it, generates summary with active model, writes it back to original day’s daily log, and deletes job. Failed jobs return to queue. A stale claimed job retries after ten minutes.\n\nI kept upstream package intact by replacing it with a local package, effectively forking it:\n\n```\n% pi remove npm:pi-memory\nRemoved npm:pi-memory\n% pi install ~/.pi/agent/local/pi-memory-async\nInstalled /Users/thiago.perrotta/.pi/agent/local/pi-memory-async\n% pi list\nUser packages:\n  local/pi-memory-async\n    /Users/thiago.perrotta/.pi/agent/local/pi-memory-async\n```\n\nJobs live in `~/.pi/agent/memory/exit-summary-jobs/`\n\n. `ctrl+d`\n\nonly writes one\nJSON file now; summary happens during next session, without blocking exits.\n\n**Source code**:\n[pi-memory-async](https://github.com/thiagowfx/.dotfiles/tree/1f175b77a960a460b4e5cc677d6d6390e0cc0530/pi/.pi/agent/local/pi-memory-async).\n\n🤖 *Drafted with *\n\n`/bloggify`\n\n.— § —\n\nReply via [email](mailto:serendipity@perrotta.dev?subject=Reply to: pi: asynchronous exit summaries)", "url": "https://wpnews.pro/news/pi-asynchronous-exit-summaries", "canonical_source": "https://perrotta.dev/2026/07/pi-asynchronous-exit-summaries/", "published_at": "2026-07-27 16:18:38+00:00", "updated_at": "2026-08-04 09:04:20.955173+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools"], "entities": ["Pi", "pi-memory", "pi-memory-async", "Thiago Perrotta"], "alternates": {"html": "https://wpnews.pro/news/pi-asynchronous-exit-summaries", "markdown": "https://wpnews.pro/news/pi-asynchronous-exit-summaries.md", "text": "https://wpnews.pro/news/pi-asynchronous-exit-summaries.txt", "jsonld": "https://wpnews.pro/news/pi-asynchronous-exit-summaries.jsonld"}}