a digital entity named phi that roams bsky phi.zzstoatzz.io
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

phi — a bluesky bot. python + pydantic-ai + atproto + turbopuffer + cosmik/semble. fastapi for the small web surface (status pages, memory graph).

development#

  • just run / just dev (hot-reload) / just deploy (manual fly.io) / just release X (tag vX, CI deploys)
  • just check — lint + typecheck + test
  • just evals — behavioral tests (llm-as-judge)
  • just loq-relax <file> — relax line limit for a file. never edit loq.toml manually or compress code to fit
  • work from repo root

python style#

  • 3.10+ typing (T | None, list[T])
  • prefer functional over OOP
  • imports at the top — no deferred imports unless circular
  • never use pytest.mark.asyncio
  • tool params: Annotated[T, Field(description=...)] so the LLM sees what each param does

project structure#

src/bot/
├── agent.py               # pydantic-ai agent + dynamic system prompts
├── config.py              # settings (env vars)
├── main.py                # fastapi app, status pages, memory graph
├── status.py              # runtime metrics
├── core/                  # atproto client, profile, mentionable, goals, self_state
├── memory/                # turbopuffer namespaces + extraction/reconciliation/review pipelines
├── services/              # notification polling + message handler
├── tools/                 # native pydantic-ai tools (posting, search, memory, goals, blog, etc.)
└── utils/                 # thread context, text formatting

personalities/             # personality definitions (public; phi.md is the live one)
evals/                     # behavioral tests
scripts/                   # proven utility scripts
sandbox/                   # experiments (graduate to scripts/ once proven)
.eggs/                     # cloned reference projects

key architecture#

  • one agent loop, many entry points (notifications batch, scheduled musing, daily reflection, relay check). all end in agent.run() with different PhiDeps.
  • actions happen as tool calls inside the run, not via structured output. the agent's return value is a brief summary string for logging.
  • personality is separate from operational rules. tool docstrings carry per-tool guidance, not the system prompt.
  • memory: turbopuffer namespaces (phi-users-{handle}, phi-episodic). intent state on PDS under io.zzstoatzz.phi.* (goals, mention consent, legacy queue).
  • owner-gated mutations (follow_user, propose_goal_change, manage_mentionable, create_feed) flow through a like-as-approval mechanism: phi posts an authorization request, owner likes it, next batch lets the action through.
  • MCP servers: pdsx (atproto record CRUD), pub-search (publication search). connected via MCPServerStreamableHTTP, fresh per agent.run().
  • web grounding via tavily for recency claims (web_search).

documentation#

deeper reference in docs/:

  • architecture.md — entry points, scheduling, why this shape
  • memory.md — the four kinds of state and how they compose
  • system-prompt.md — block-by-block reference for what's in phi's context
  • mcp.md — MCP integration
  • testing.md — testing philosophy

deployment#

fly.io app zzstoatzz-phi. CI deploys on v* tag push (tangled .tangled/workflows/deploy.yml). just release X.Y.Z tags + pushes; just deploy runs fly deploy directly when CI is backed up. push to both origin (tangled) and github mirror.

secrets via fly secrets set or fly secrets import (pipe grep ^KEY .env into it to keep values off the terminal).