commits
periodic musing system: process_musing() on agent, original_thought() on
handler, scheduled via thought_post_hours config, triggerable via
POST /api/control/post. agent can decline (action=ignore) if nothing's
interesting.
typecheck fixes: nullable client.me guards, cache typing, global narrowing,
Any vs any, CosmikConnection populate_by_name, conftest import path.
test fix: skip on ModelHTTPError for flaky API tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- notification_poller: dispatch notifications as background tasks with
semaphore(3), mark as read immediately, non-blocking daily post
- agent: make save_url title required so activity feed always has labels
- main: activity feed icons, domain display, titles, linkified URLs,
batched avatar fetches (chunks of 25)
- namespace_memory: replace random projection with PCA (numpy SVD) for
2D memory graph layout
- trim logfire extras, add numpy dep
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
parse_mentions() now accepts allowed_handles — only handles in the set
get notification-sending mention facets. all other @handles render as
plain text (visible but silent). the allowlist is {owner, bot, current
conversation participant} at every create_post call site.
also adds mention consent guidance to phi's operational instructions
so the LLM avoids @mentioning third parties in the first place.
fixes the boris incident where phi auto-resolved a handle in its reply
text and tagged someone who never interacted with the bot.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
JavaScript's new Date() interprets ISO strings without timezone as
browser-local time. Since the server runs in UTC on Fly.io, CT users
saw cards as ~5h in the future → negative "seconds ago" on the status page.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bluesky truncates long URLs in post display text (e.g. "example.com/long...")
but stores the actual URI in the facet. phi was only reading post.record.text,
so it never saw the real link — causing it to guess slugs in a loop.
add resolve_facet_links() that splices facet URIs back into the text at
their byte offsets. used in both _handle_post (mention text) and
describe_post (thread context).
reverts all agent.py changes from v0.0.13/v0.0.14 — those were treating
downstream symptoms, not the root cause.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
the actual problem: phi was brute-force guessing blog post slugs (100+
different URLs across 40 check_urls calls) when given a truncated URL
it couldn't resolve. it's not retrying the same URL — it's hallucinating
new slugs and checking each one.
fix: check_urls tracks URLs checked per agent run. after 10 unique URLs,
it returns a message telling the model to stop guessing and respond with
what it found. reverts the request_limit=15 cap — that was a blunt
instrument that doesn't address the root cause.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
phi was hitting the default 50-request limit calling check_urls in a loop
when asked to read a web page (a task it can't actually do). logfire trace
019d5004 shows 30 consecutive check_urls calls.
three fixes:
- usage_limits=UsageLimits(request_limit=15) on both agent.run() calls
- check_urls docstring explicitly says HEAD-only, no retries
- system prompt: don't verify URLs the user gave you, accept results,
say so if you can't fulfill a request with available tools
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- delete 9 orphaned NOTE cards + recreate URL card with correct semble format
- add thematically distinct SVG favicons (pulse for home, clock for status, graph for memory)
- update test_types to match new to_record() output ($type, createdAt, metadata nesting)
- add tests for CosmikCollection, CosmikCollectionLink, StrongRef, parentCard
- fix pre-existing ruff lint issues
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
URL cards: add $type discriminators on content and metadata, nest
title/description under content.metadata, add createdAt timestamp.
NOTE cards: stop writing standalone notes to PDS — semble requires a
parentCard (strongRef to a URL card) and silently drops notes without one.
the note tool now writes to tpuf only (private recall).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
types.py: add StrongRef, CosmikCollection, CosmikCollectionLink with to_record()
namespace_memory.py: get_graph_data() reads phi-tag-relationships namespace to
inject tag-to-tag edges into the graph visualization
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
add explicit negative instruction ("do NOT call this when someone asks
if you're online") to both operational instructions and tool docstring.
previous wording wasn't strong enough to override pattern from episodic
memory.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
phi was calling check_services when asked "you online?" because it
conflated its own status with operator service health. tightened the
operational instructions and tool docstring to make the distinction
explicit.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
use pydantic-ai's @agent.system_prompt(dynamic=True) to inject
date, thread, memory, episodic, and reflection context as system
prompts instead of stuffing everything into the user prompt.
simplifies process_mention and process_reflection significantly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
check_services tool hits the evergreen proxy to batch-check 13 services.
integrated into daily reflection so phi sees service status and can alert
nate when something's down.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
phi kept looping on list_records (paginating its own posts via pdsx MCP),
exhausting the 50-request limit with no response. get_own_posts wraps
BotClient.get_own_posts() in a single call. operational instructions now
explicitly warn against repeated list_records pagination.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- read_timeline, read_feed, follow_user tools in agent.py
- owner-gate create_feed and follow_user (via settings.owner_handle)
- BotClient methods for timeline, feed, follow, get_following
- 6 new evals for feed consumption + permission checks
- fix test_graze_tools_registered (deprecated pydantic-ai API)
- include previously uncommitted graze client + feed creation evals
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fetch phi's most recent top-level post and include it in the
reflection prompt so the agent can skip if it would just rehash
the same themes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
instrument pydantic-ai agent runs, anthropic/openai LLM calls, and
fastapi requests. each instrumentation call is individually wrapped
so a missing dep degrades to a warning instead of crashing the app.
custom spans on notification handling and daily reflection. suppress
noisy loggers (httpx polls, asyncio selectors, uvicorn access, health
checks) to keep span costs down.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sandbox is for experiments and should never have been committed.
no sensitive content — just design notes, one-off scripts, and an
empty sqlite archive. files remain on disk, just untracked.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- loq.toml: 500-line default, relaxed limits for agent.py (629) and
namespace_memory.py (718). sandbox/ and .eggs/ excluded.
- pre-commit: loq + ruff (check + format)
- extract extraction pipeline (models, prompts, agent factories) into
memory/extraction.py — clean boundary between observation logic and
memory storage/retrieval
- justfile: add loq-relax recipe
- CLAUDE.md: document loq workflow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
README, memory, mcp, and architecture docs now reflect the
public/private memory split, dual-write architecture, and MCP
server changes. tool lists replaced with pointers to agent.py
so docs don't drift as tools change.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
semble returns {urls: [...], pagination: {...}} not a flat array.
fields are metadata.title, metadata.description, urlLibraryCount.
also remove unnecessary @pytest.mark.asyncio (asyncio_mode=auto).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- search_network: semantic search over cosmik/semble network cards
- reframe recall as private memory, remove about="self" mode
- update OPERATIONAL_INSTRUCTIONS with clear search tool landscape
- cosmik types: CosmikNoteCard, CosmikUrlCard, CosmikConnection
- dual-write: note + save_url write to both tpuf and PDS
- create_connection tool for semantic links
- daily reflection via scheduled task + get_recent_interactions
- tests for search_network formatting and cosmik types
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
cross-references turbopuffer record timestamps against fly.io
deployment history and git tags to show which bot version created
each record. useful for auditing extraction quality over time.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
instead of blindly appending new observations, each candidate is now
embedded and compared against existing observations in turbopuffer.
an LLM reconciler decides ADD/UPDATE/DELETE/NOOP per observation,
preventing duplicates and resolving contradictions at write time.
inspired by mem0's consolidation pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
phi hallucinated a user's name because synthesized memory summaries were
treated with the same weight as verbatim exchanges. this adds principled
trust levels — grounded in anthropic's constitution — to the system prompt,
personality doc, context labels, and extraction prompt so phi hedges on
low-trust data and treats user corrections as authoritative.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
relationship summaries are now framed as "phi's synthesized impression"
with an explicit warning they may contain errors. observations are labeled
as extracted from user's own words. interactions labeled as verbatim logs.
gives phi a visible trust hierarchy to calibrate against.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
the extraction agent was storing facts from phi's own responses as user
observations (e.g. phi hallucinated "you're zoë" and extraction stored
"name is zoë"). added explicit rule and example to never extract identity
claims from bot output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
POST /api/control/pause and /api/control/resume toggle notification
processing. when paused, the poller keeps running but skips processing
and doesn't mark notifications as read — they accumulate and get
processed on resume. authenticated via CONTROL_TOKEN bearer token.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fresh instances per run alone isn't enough — pydantic-ai runs parallel
tool calls via asyncio.create_task, and each does async with self: on
the MCP server. the first task opens the connection (anyio task group),
the last to finish closes it in a different task → RuntimeError.
fix: enter MCP servers in the calling task via AsyncExitStack before
agent.run(). parallel tool calls then only bump the reference count
(1→2→...→1) without ever hitting 0, so the connection open/close
both happen in the same task.
ref: https://github.com/pydantic/pydantic-ai/issues/2818
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
shared MCPServerStreamableHTTP instances get entered/exited across
different async tasks when concurrent notifications arrive, triggering
"Attempted to exit cancel scope in a different task than it was entered in".
fix: create fresh MCP server instances per agent.run() call via the
toolsets parameter instead of sharing singletons on the agent.
ref: https://github.com/pydantic/pydantic-ai/issues/2818
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- slowapi: 60/min global, 10/min on /api/memory/graph with 60s response cache
- per-user 30/hour notification rate limit (limits library, moving window)
- check_urls blocks private/loopback IPs before making requests
- fix stale model ID in test_tool_usage (claude-3-5-haiku-latest → claude-haiku-4-5)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
just release <version> to tag and deploy. just deploy for manual.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
notes deploy-on-every-push concern — consider tag-based triggers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
likes, reposts, and follows now go through the full agent pipeline.
phi decides what's worth responding to, not the handler.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
quotes now go through the full agent pipeline (same as mentions).
likes, reposts, and follows are logged for awareness.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
adds get_relationship_summary() to NamespaceMemory — queries kind=summary
rows by attribute rank (no dummy vector). injected into build_user_context
between core identity and known facts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
register_webhook.py: one-off wisp.place webhook tool
test_memory_smoke.py: integration tests needing live credentials
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
splits text exceeding 300 graphemes into a self-reply thread, preferring
paragraph breaks > sentence boundaries > word boundaries. includes
regression tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rolling deploy health-checks a stopped machine, which always times out.
now CI checks machine state first — if stopped, uses immediate strategy
to update config without waiting for health checks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
phi.md is now pure identity/voice — removed capabilities list (redundant
with tool docstrings), response format (moved to Response model fields),
tool discipline (folded into engagement section), and platform constraints
(handled by code). operational guardrails live in agent.py as a separate
constant appended to the system prompt.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- add /memory page with D3 force-directed graph (phi, users, tags, episodic)
- add /api/memory/graph JSON endpoint
- restyle all pages: monospace font, consistent nav, lowercase aesthetic
- persist status counters to /data/status.json on fly volume
- rewrite extraction prompt: examples-based approach fixes misattribution
of bot statements to users (3/3 regression tests pass)
- add phi.zzstoatzz.io custom domain (fly cert + DNS)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
passes image URLs as ImageUrl to the agent for multimodal vision,
not just alt text descriptions. phi can now literally look at images.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
profile_manager: refactored into composable functions that read,
build, and write profiles while preserving all existing fields
including labels. bot label is set once at startup if missing,
not blindly overwritten on every bio update.
agent: added manage_labels tool (list/add/remove self-labels)
and post tool (top-level posts). phi can now manage its own
labels and post unprompted.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sets the "bot" self-label on every profile update so bluesky
displays the automation badge on phi's profile and posts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- add Dockerfile, fly.toml, .dockerignore for Fly.io deployment
- add CI workflow to deploy on push to main
- add deploy recipe to justfile
- rewrite personality doc: replace prescriptive rules with
dispositional identity, add memory philosophy, add interests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- inject current date into agent prompt so phi knows what day it is
- add tool discipline section to personality (finish research before replying)
- match bare domain URLs (e.g. cnbc.com/path) in rich_text and linkify them
- annotate search_posts results with relative age (e.g. "2y 1mo ago")
- add regression tests for URL facet parsing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- phi-episodic namespace in turbopuffer for world knowledge (remember + search_my_memory tools)
- episodic context auto-injected into conversation prompts
- replace naked dict deps with PhiDeps dataclass
- check_urls tool for link verification before sharing
- suppress logfire/otel warnings in pytest config
- memory_inspect.py --episodic flag
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- add search_posts tool: authenticated bluesky post search via bot_client
- fix extraction prompt: distinguish user-expressed interests from
bot-retrieved content (trending topics, search results)
- add scripts/memory_inspect.py: list namespaces, dump user memories,
delete rows, purge observations
- update bio and personality with search capability
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- swap MCPServerStdio (atproto_mcp subprocess) for two MCPServerStreamableHTTP
connections: pdsx (generic atproto CRUD) and pub-search (publication search)
- add get_trending tool: combines coral entity graph with official bluesky
trending topics so phi can answer "what's going on?"
- rewrite personality as minimal deltas on top of base claude character
- show capabilities in bio when online, strip when offline
- fix extraction model name (claude-haiku-4-5-20251001)
- silence MCP protocol logger noise
- simplify memory module (flatten MemoryType enum, rename methods)
- update evals/tests to match new memory API
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
replace rich logging with logfire — auto-instruments pydantic-ai agents,
fastapi, and httpx. bridge stdlib logging into logfire's OTel pipeline
via LogfireLoggingHandler. strip emojis and enforce lowercase log style.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
removed 164 lines of orphaned code from database.py that was never
integrated with the current MCP-based architecture. the approval system
was designed for conditional self-modification (phi proposes changes,
operator approves), but was left behind during the pydanticai refactor.
documented the original design and future integration options in
sandbox/APPROVAL_SYSTEM.md for when we want to reintroduce this capability.
this change makes phi fully stateless - all dynamic state now lives in
remote services (turbopuffer for memories, atproto for threads). the only
local files are ephemeral caches (.session) and configuration.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
replaces deprecated [tool.uv] dev-dependencies with standardized [dependency-groups] dev
- removes deprecation warning
- follows PEP 735 standard for dependency groups
- alphabetized dev dependencies for consistency
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
removes httpx and websockets from pyproject.toml as they are not directly imported and are provided transitively by atproto/fastmcp.
dependencies reduced from 11 to 9:
- anthropic, atproto, fastapi, fastmcp, openai, pydantic-ai, pydantic-settings, rich, turbopuffer, uvicorn
verified: bot still runs correctly, all HTTP operations working via transitive deps
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
periodic musing system: process_musing() on agent, original_thought() on
handler, scheduled via thought_post_hours config, triggerable via
POST /api/control/post. agent can decline (action=ignore) if nothing's
interesting.
typecheck fixes: nullable client.me guards, cache typing, global narrowing,
Any vs any, CosmikConnection populate_by_name, conftest import path.
test fix: skip on ModelHTTPError for flaky API tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- notification_poller: dispatch notifications as background tasks with
semaphore(3), mark as read immediately, non-blocking daily post
- agent: make save_url title required so activity feed always has labels
- main: activity feed icons, domain display, titles, linkified URLs,
batched avatar fetches (chunks of 25)
- namespace_memory: replace random projection with PCA (numpy SVD) for
2D memory graph layout
- trim logfire extras, add numpy dep
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
parse_mentions() now accepts allowed_handles — only handles in the set
get notification-sending mention facets. all other @handles render as
plain text (visible but silent). the allowlist is {owner, bot, current
conversation participant} at every create_post call site.
also adds mention consent guidance to phi's operational instructions
so the LLM avoids @mentioning third parties in the first place.
fixes the boris incident where phi auto-resolved a handle in its reply
text and tagged someone who never interacted with the bot.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bluesky truncates long URLs in post display text (e.g. "example.com/long...")
but stores the actual URI in the facet. phi was only reading post.record.text,
so it never saw the real link — causing it to guess slugs in a loop.
add resolve_facet_links() that splices facet URIs back into the text at
their byte offsets. used in both _handle_post (mention text) and
describe_post (thread context).
reverts all agent.py changes from v0.0.13/v0.0.14 — those were treating
downstream symptoms, not the root cause.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
the actual problem: phi was brute-force guessing blog post slugs (100+
different URLs across 40 check_urls calls) when given a truncated URL
it couldn't resolve. it's not retrying the same URL — it's hallucinating
new slugs and checking each one.
fix: check_urls tracks URLs checked per agent run. after 10 unique URLs,
it returns a message telling the model to stop guessing and respond with
what it found. reverts the request_limit=15 cap — that was a blunt
instrument that doesn't address the root cause.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
phi was hitting the default 50-request limit calling check_urls in a loop
when asked to read a web page (a task it can't actually do). logfire trace
019d5004 shows 30 consecutive check_urls calls.
three fixes:
- usage_limits=UsageLimits(request_limit=15) on both agent.run() calls
- check_urls docstring explicitly says HEAD-only, no retries
- system prompt: don't verify URLs the user gave you, accept results,
say so if you can't fulfill a request with available tools
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- delete 9 orphaned NOTE cards + recreate URL card with correct semble format
- add thematically distinct SVG favicons (pulse for home, clock for status, graph for memory)
- update test_types to match new to_record() output ($type, createdAt, metadata nesting)
- add tests for CosmikCollection, CosmikCollectionLink, StrongRef, parentCard
- fix pre-existing ruff lint issues
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
URL cards: add $type discriminators on content and metadata, nest
title/description under content.metadata, add createdAt timestamp.
NOTE cards: stop writing standalone notes to PDS — semble requires a
parentCard (strongRef to a URL card) and silently drops notes without one.
the note tool now writes to tpuf only (private recall).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
use pydantic-ai's @agent.system_prompt(dynamic=True) to inject
date, thread, memory, episodic, and reflection context as system
prompts instead of stuffing everything into the user prompt.
simplifies process_mention and process_reflection significantly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
phi kept looping on list_records (paginating its own posts via pdsx MCP),
exhausting the 50-request limit with no response. get_own_posts wraps
BotClient.get_own_posts() in a single call. operational instructions now
explicitly warn against repeated list_records pagination.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- read_timeline, read_feed, follow_user tools in agent.py
- owner-gate create_feed and follow_user (via settings.owner_handle)
- BotClient methods for timeline, feed, follow, get_following
- 6 new evals for feed consumption + permission checks
- fix test_graze_tools_registered (deprecated pydantic-ai API)
- include previously uncommitted graze client + feed creation evals
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
instrument pydantic-ai agent runs, anthropic/openai LLM calls, and
fastapi requests. each instrumentation call is individually wrapped
so a missing dep degrades to a warning instead of crashing the app.
custom spans on notification handling and daily reflection. suppress
noisy loggers (httpx polls, asyncio selectors, uvicorn access, health
checks) to keep span costs down.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- loq.toml: 500-line default, relaxed limits for agent.py (629) and
namespace_memory.py (718). sandbox/ and .eggs/ excluded.
- pre-commit: loq + ruff (check + format)
- extract extraction pipeline (models, prompts, agent factories) into
memory/extraction.py — clean boundary between observation logic and
memory storage/retrieval
- justfile: add loq-relax recipe
- CLAUDE.md: document loq workflow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- search_network: semantic search over cosmik/semble network cards
- reframe recall as private memory, remove about="self" mode
- update OPERATIONAL_INSTRUCTIONS with clear search tool landscape
- cosmik types: CosmikNoteCard, CosmikUrlCard, CosmikConnection
- dual-write: note + save_url write to both tpuf and PDS
- create_connection tool for semantic links
- daily reflection via scheduled task + get_recent_interactions
- tests for search_network formatting and cosmik types
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
instead of blindly appending new observations, each candidate is now
embedded and compared against existing observations in turbopuffer.
an LLM reconciler decides ADD/UPDATE/DELETE/NOOP per observation,
preventing duplicates and resolving contradictions at write time.
inspired by mem0's consolidation pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
phi hallucinated a user's name because synthesized memory summaries were
treated with the same weight as verbatim exchanges. this adds principled
trust levels — grounded in anthropic's constitution — to the system prompt,
personality doc, context labels, and extraction prompt so phi hedges on
low-trust data and treats user corrections as authoritative.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
relationship summaries are now framed as "phi's synthesized impression"
with an explicit warning they may contain errors. observations are labeled
as extracted from user's own words. interactions labeled as verbatim logs.
gives phi a visible trust hierarchy to calibrate against.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
POST /api/control/pause and /api/control/resume toggle notification
processing. when paused, the poller keeps running but skips processing
and doesn't mark notifications as read — they accumulate and get
processed on resume. authenticated via CONTROL_TOKEN bearer token.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fresh instances per run alone isn't enough — pydantic-ai runs parallel
tool calls via asyncio.create_task, and each does async with self: on
the MCP server. the first task opens the connection (anyio task group),
the last to finish closes it in a different task → RuntimeError.
fix: enter MCP servers in the calling task via AsyncExitStack before
agent.run(). parallel tool calls then only bump the reference count
(1→2→...→1) without ever hitting 0, so the connection open/close
both happen in the same task.
ref: https://github.com/pydantic/pydantic-ai/issues/2818
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
shared MCPServerStreamableHTTP instances get entered/exited across
different async tasks when concurrent notifications arrive, triggering
"Attempted to exit cancel scope in a different task than it was entered in".
fix: create fresh MCP server instances per agent.run() call via the
toolsets parameter instead of sharing singletons on the agent.
ref: https://github.com/pydantic/pydantic-ai/issues/2818
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- slowapi: 60/min global, 10/min on /api/memory/graph with 60s response cache
- per-user 30/hour notification rate limit (limits library, moving window)
- check_urls blocks private/loopback IPs before making requests
- fix stale model ID in test_tool_usage (claude-3-5-haiku-latest → claude-haiku-4-5)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
phi.md is now pure identity/voice — removed capabilities list (redundant
with tool docstrings), response format (moved to Response model fields),
tool discipline (folded into engagement section), and platform constraints
(handled by code). operational guardrails live in agent.py as a separate
constant appended to the system prompt.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- add /memory page with D3 force-directed graph (phi, users, tags, episodic)
- add /api/memory/graph JSON endpoint
- restyle all pages: monospace font, consistent nav, lowercase aesthetic
- persist status counters to /data/status.json on fly volume
- rewrite extraction prompt: examples-based approach fixes misattribution
of bot statements to users (3/3 regression tests pass)
- add phi.zzstoatzz.io custom domain (fly cert + DNS)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
profile_manager: refactored into composable functions that read,
build, and write profiles while preserving all existing fields
including labels. bot label is set once at startup if missing,
not blindly overwritten on every bio update.
agent: added manage_labels tool (list/add/remove self-labels)
and post tool (top-level posts). phi can now manage its own
labels and post unprompted.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- add Dockerfile, fly.toml, .dockerignore for Fly.io deployment
- add CI workflow to deploy on push to main
- add deploy recipe to justfile
- rewrite personality doc: replace prescriptive rules with
dispositional identity, add memory philosophy, add interests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- inject current date into agent prompt so phi knows what day it is
- add tool discipline section to personality (finish research before replying)
- match bare domain URLs (e.g. cnbc.com/path) in rich_text and linkify them
- annotate search_posts results with relative age (e.g. "2y 1mo ago")
- add regression tests for URL facet parsing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- phi-episodic namespace in turbopuffer for world knowledge (remember + search_my_memory tools)
- episodic context auto-injected into conversation prompts
- replace naked dict deps with PhiDeps dataclass
- check_urls tool for link verification before sharing
- suppress logfire/otel warnings in pytest config
- memory_inspect.py --episodic flag
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- add search_posts tool: authenticated bluesky post search via bot_client
- fix extraction prompt: distinguish user-expressed interests from
bot-retrieved content (trending topics, search results)
- add scripts/memory_inspect.py: list namespaces, dump user memories,
delete rows, purge observations
- update bio and personality with search capability
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- swap MCPServerStdio (atproto_mcp subprocess) for two MCPServerStreamableHTTP
connections: pdsx (generic atproto CRUD) and pub-search (publication search)
- add get_trending tool: combines coral entity graph with official bluesky
trending topics so phi can answer "what's going on?"
- rewrite personality as minimal deltas on top of base claude character
- show capabilities in bio when online, strip when offline
- fix extraction model name (claude-haiku-4-5-20251001)
- silence MCP protocol logger noise
- simplify memory module (flatten MemoryType enum, rename methods)
- update evals/tests to match new memory API
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
removed 164 lines of orphaned code from database.py that was never
integrated with the current MCP-based architecture. the approval system
was designed for conditional self-modification (phi proposes changes,
operator approves), but was left behind during the pydanticai refactor.
documented the original design and future integration options in
sandbox/APPROVAL_SYSTEM.md for when we want to reintroduce this capability.
this change makes phi fully stateless - all dynamic state now lives in
remote services (turbopuffer for memories, atproto for threads). the only
local files are ephemeral caches (.session) and configuration.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
replaces deprecated [tool.uv] dev-dependencies with standardized [dependency-groups] dev
- removes deprecation warning
- follows PEP 735 standard for dependency groups
- alphabetized dev dependencies for consistency
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
removes httpx and websockets from pyproject.toml as they are not directly imported and are provided transitively by atproto/fastmcp.
dependencies reduced from 11 to 9:
- anthropic, atproto, fastapi, fastmcp, openai, pydantic-ai, pydantic-settings, rich, turbopuffer, uvicorn
verified: bot still runs correctly, all HTTP operations working via transitive deps
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>