One clean swap needed — "Pete" in the commit message, replaced with "a contact." Everything else stays verbatim.
TLDR: Every iMessage turn spawns a fresh
claude -pprocess. "Session memory" doesn't exist. If your agent doesn't write to disk before it exits, the learning is gone.
The Setup
I built Apollo (my personal AI agent, living in Claude Code) a bridge into iMessage.
The idea was simple: I text a "Note to Self" message, a 5-second poller picks it up from chat.db (Apple's local iMessage database), spawns a claude -p subprocess, and Apollo replies.
Beautiful. Except for one thing I completely missed.
The Wall
Each turn is a completely fresh process.
claude -p launches, reads the prompt, does the work, prints the reply, and exits.
There is NO session. No context window carrying forward. Nothing.
So the first time I texted Apollo something like "remember how this person likes their updates" and he replied "Got it!" — he didn't got it. The process exited. Gone.
I actually shipped fix(apollo): dedup a contact's memory + load /apollo-boot context every iMessage turn before I fully accepted what was happening. I thought injecting the boot context — loading user_soul.md, identity files, all the Core Behavior Rules — would make the agent feel stateful across turns.
It didn't. Boot context loads identity, not episodic memory. Not the same thing.
What I Tried First
My first instinct: make memory-saving conditional. Apollo would decide if something was worth persisting and save it if so.
The failure mode showed up immediately. The model would reply "I'll remember in this session" — which is a MEANINGLESS promise when there is no session. Next iMessage, new process, fresh slate. Even when it did write memory files, it was inconsistent enough to matter.
So what's the fix? Make the save non-optional.
The Fix That Worked
Two moves, in order.
1. Make the save imperative before the reply goes out.
I wrote memory/feedback_imessage_auto_save.md — a feedback memory (my term for a rule born from a mistake):
When Apollo learns durable signal from any source, it MUST save the memory file in the same turn, before replying. Not conditional on me asking. Not "I'll do it next time."
The synthesis act is the save trigger. If Apollo just told me something it didn't know coming in, it captures that in memory/ first, then replies.
2. Add a lightweight staging inbox for terminal sessions.
Even where there is a context window, I was losing learnings by forgetting to run /apollo-save (my memory consolidation skill) before ending the session.
Fix: memory/_inbox.md. Apollo appends a single self-contained line at the end of any turn that produced a durable learning — no dedup, no reindex, no heavyweight pipeline. When I eventually do run /apollo-save, it flushes the inbox into proper structured memory files and cleans up.
Most turns are a no-op. The ones that aren't don't get lost.
Why This Matters to Me
The iMessage bridge forced me to think clearly about something every agent builder hits eventually: process memory and durable state are not the same thing.
Long context feels like memory. But the moment you go stateless — subprocess per turn, serverless function, async job — that illusion collapses fast.
The lesson isn't "use a database." It's simpler: if your agent can die between steps, decide upfront what must survive and make the write happen before the exit. File, DB, queue — doesn't matter. What matters is the discipline.
The save is not optional. The save happens before the reply.
Apollo knows that now. The process still exits after every message.
But the learning sticks.