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.

fix cosmik card records to match semble lexicon

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>

zzstoatzz fb69ca62 bad0c75a

+55 -34
+3 -22
src/bot/agent.py
··· 23 23 from bot.memory import NamespaceMemory 24 24 from bot.types import ( 25 25 CosmikConnection, 26 - CosmikNoteCard, 27 26 CosmikUrlCard, 28 - NoteContent, 29 27 UrlContent, 30 28 ) 31 29 ··· 412 410 413 411 @self.agent.tool 414 412 async def note(ctx: RunContext[PhiDeps], content: str, tags: list[str]) -> str: 415 - """Leave a note for your future self. Stored privately (fast recall) and publicly as a cosmik card (visible on the network).""" 416 - parts: list[str] = [] 417 - 418 - # private: turbopuffer for fast vector recall 413 + """Leave a note for your future self. Stored privately for fast vector recall.""" 419 414 if ctx.deps.memory: 420 415 await ctx.deps.memory.store_episodic_memory( 421 416 content, tags, source="tool" 422 417 ) 423 - parts.append("noted privately") 424 - else: 425 - parts.append("private memory not available") 426 - 427 - # public: cosmik NOTE card on PDS 428 - try: 429 - card = CosmikNoteCard(content=NoteContent(text=content)) 430 - uri = await _create_cosmik_record( 431 - "network.cosmik.card", card.to_record() 432 - ) 433 - parts.append(f"card created: {uri}") 434 - except Exception as e: 435 - logger.warning(f"failed to create cosmik note card: {e}") 436 - parts.append("public card failed") 437 - 438 - return f"{' + '.join(parts)} — {content[:100]}" 418 + return f"noted — {content[:100]}" 419 + return "private memory not available" 439 420 440 421 @self.agent.tool 441 422 async def save_url(
+52 -12
src/bot/types.py
··· 1 1 """Validated types for atproto records phi creates.""" 2 2 3 + from datetime import UTC, datetime 3 4 from typing import Annotated, Literal 4 5 5 6 from pydantic import AfterValidator, BaseModel, Field ··· 37 38 text: str = Field(max_length=10000) 38 39 39 40 41 + class UrlMetadata(BaseModel): 42 + """Metadata about a URL (nested under content.metadata per semble lexicon).""" 43 + 44 + title: str | None = None 45 + description: str | None = None 46 + 47 + 40 48 class UrlContent(BaseModel): 41 49 """Content for a URL-type cosmik card.""" 42 50 ··· 45 53 description: str | None = None 46 54 47 55 56 + # --- shared --- 57 + 58 + 59 + class StrongRef(BaseModel): 60 + """AT Protocol strong reference — uri + cid pair.""" 61 + 62 + uri: EntityRef 63 + cid: str 64 + 65 + 48 66 # --- records --- 49 67 50 68 ··· 82 100 class CosmikNoteCard(BaseModel): 83 101 """network.cosmik.card record — NOTE type. 84 102 85 - A text note stored on-protocol as a cosmik card. Indexed by semble. 103 + Semble requires a parentCard (strongRef to an existing URL card) for NOTE 104 + records. Standalone notes are dropped by the firehose handler. 86 105 """ 87 106 88 107 type: Literal["NOTE"] = "NOTE" 89 108 content: NoteContent 109 + parent_card: StrongRef | None = None 90 110 91 111 def to_record(self) -> dict: 92 - return {"type": self.type, "content": {"text": self.content.text}} 112 + record: dict = { 113 + "type": self.type, 114 + "content": { 115 + "$type": "network.cosmik.card#noteContent", 116 + "text": self.content.text, 117 + }, 118 + "createdAt": datetime.now(UTC).isoformat(), 119 + } 120 + if self.parent_card: 121 + record["parentCard"] = { 122 + "uri": self.parent_card.uri, 123 + "cid": self.parent_card.cid, 124 + } 125 + return record 93 126 94 127 95 128 class CosmikUrlCard(BaseModel): 96 129 """network.cosmik.card record — URL type. 97 130 98 131 A bookmarked URL stored on-protocol as a cosmik card. Indexed by semble. 132 + Metadata (title, description) goes under content.metadata per semble lexicon. 99 133 """ 100 134 101 135 type: Literal["URL"] = "URL" 102 136 content: UrlContent 103 137 104 138 def to_record(self) -> dict: 105 - record: dict = {"type": self.type, "content": {"url": self.content.url}} 139 + record: dict = { 140 + "type": self.type, 141 + "content": { 142 + "$type": "network.cosmik.card#urlContent", 143 + "url": self.content.url, 144 + }, 145 + "createdAt": datetime.now(UTC).isoformat(), 146 + } 147 + metadata: dict = {} 106 148 if self.content.title: 107 - record["content"]["title"] = self.content.title 149 + metadata["title"] = self.content.title 108 150 if self.content.description: 109 - record["content"]["description"] = self.content.description 151 + metadata["description"] = self.content.description 152 + if metadata: 153 + record["content"]["metadata"] = { 154 + "$type": "network.cosmik.card#urlMetadata", 155 + **metadata, 156 + } 110 157 return record 111 - 112 - 113 - class StrongRef(BaseModel): 114 - """AT Protocol strong reference — uri + cid pair.""" 115 - 116 - uri: EntityRef 117 - cid: str 118 158 119 159 120 160 class CosmikCollection(BaseModel):