memory#
phi has four kinds of state it draws on. they differ in visibility, trust, who curates them, and where they live.
1. thread context (chronological)#
source: ATProto network · storage: none — fetched on demand · visibility: public
@alice: I love birds
@phi: me too! what's your favorite?
@alice: especially crows
fetched via client.get_thread(uri, depth=100) per batch (~200ms). provides what was said in this thread. not cached — the network is always current.
2. private memory (TurboPuffer)#
source: extraction agent + phi's note tool · storage: TurboPuffer vector DB (OpenAI text-embedding-3-small) · visibility: private to phi
namespaces#
| namespace | contents |
|---|---|
phi-users-{handle} |
per-user observations, raw interaction logs, exploration notes, summaries |
phi-episodic |
phi's own notes about the world (not tied to a specific user) |
within a user namespace, rows have a kind:
observation— extracted facts about the user ("likes rust", "name is nate")interaction— verbatim log of an exchange ("user: X / bot: Y")exploration_note— background research phi did on a person's public activity (lower trust)summary— compacted relationship summary (generated by external prefect flow)
supersession (not deletion)#
observations carry a status field (active | superseded) and a supersedes field linking to the prior row when an observation is updated. only active rows appear in context injection. superseded rows stay in the namespace as provenance — you can trace what phi believed and when it changed.
the extraction pipeline#
after every reply, after_interaction stores the verbatim exchange. periodically, the extraction agent reads the recent exchanges and proposes new observations. for each proposal:
- find the 3 most similar active observations (vector search)
- send the new + best-match to a haiku reconciliation agent
- it returns ADD / UPDATE / DELETE / NOOP — execute accordingly
reconciliation runs blind on the new exchange (no existing observations in the prompt) so the extraction model can't pattern-match off potentially-bad prior observations. only the reconciliation step sees both.
dream/distill#
a separate process_review pass evaluates recent observations across user namespaces — keep, supersede, promote to public cosmik card. operator-triggered; not on a cron yet.
3. public memory (cosmik / semble)#
source: phi's save_url, note, create_connection tools · storage: phi's PDS as network.cosmik.* records, indexed by semble · visibility: public
three record types:
network.cosmik.card(NOTE) — text notesnetwork.cosmik.card(URL) — bookmarks with title/descriptionnetwork.cosmik.connection— typed semantic links between cards
phi searches public memory via search_network (semble's semantic search). the note tool dual-writes to both turbopuffer (private fast recall) and cosmik (public discoverable). save_url writes only to cosmik.
4. intent state (PDS)#
source: phi via owner-gated tools · storage: phi's PDS under io.zzstoatzz.phi.* · visibility: public
durable intent that phi acts against:
io.zzstoatzz.phi.goal— phi's anchors (e.g. "make 3 friends" with a concrete progress signal). mutated viapropose_goal_change, owner-gated by the like-as-approval mechanism. injected as[GOALS]every tick.io.zzstoatzz.phi.mentionConsent— handles opted in to be tagged by phi.
context injection#
when phi processes a notification batch, the system prompt assembles blocks from each kind of state:
[GOALS] ← intent (PDS)
[INNER CRITIC] ← haiku critique of recent posts vs goals, first person
[SELF STATE] ← last-follow age, queue depth
[NEW NOTIFICATIONS] ← the batch itself
[PHI'S SYNTHESIZED IMPRESSION] ← per-author relationship summary (low trust)
[OBSERVATIONS ABOUT @alice] ← per-author observations (active only)
[BACKGROUND RESEARCH] ← per-author exploration notes (lowest trust)
[PAST EXCHANGES WITH @alice] ← per-author interaction logs (high trust)
[RELEVANT MEMORIES — synthesized for this query] ← episodic top-K → haiku synthesis
[SEMBLE] ← one-line cosmik state
each section is labeled with its trust level. operational instructions tell phi to trust current user messages over stored observations.
see system-prompt.md for the full block-by-block reference (sources, refresh cadences, purposes).
why episodic gets synthesized, observations don't#
episodic memory was getting raw top-K from the vector store dumped into the prompt — stale "pending X" notes appeared next to fresh ones with equal weight, no reconciliation against current PDS state. now inject_episodic fetches top-K, then a haiku pass takes phi's goals + the current query as context and produces a coherent block (deduped, recency-aware, contradictions flagged). same shape as [INNER CRITIC] does for posts.
per-author observation blocks aren't synthesized because they're already curated by reconciliation on write — by the time they hit the prompt they're an active set with no near-duplicates by design.
the graph (/memory)#
a visualization at /memory shows phi + user nodes positioned by semantic similarity of their observation vectors (PCA projection). only active observations contribute to positioning.
summary table#
| thread context | private memory | public memory | intent state | |
|---|---|---|---|---|
| what | this conversation | patterns across conversations | knowledge worth sharing | what phi is for / pending |
| storage | network (atproto) | TurboPuffer | PDS (cosmik) + semble | PDS (io.zzstoatzz.phi.*) |
| visibility | public | private to phi | public | public |
| curation | network handles it | extraction + reconciliation (append-only supersession) | phi's intentional writes | owner-approved (like-gate) |
| trust | high (verbatim) | medium (extracted) | higher (intentional) | highest (gated) |