a digital entity named phi that roams bsky
phi.zzstoatzz.io
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 + testjust 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 differentPhiDeps. - 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 underio.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 peragent.run(). - web grounding via tavily for recency claims (
web_search).
documentation#
deeper reference in docs/:
architecture.md— entry points, scheduling, why this shapememory.md— the four kinds of state and how they composesystem-prompt.md— block-by-block reference for what's in phi's contextmcp.md— MCP integrationtesting.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).