feat: bot and you entity schemas with projector handlers

This commit is contained in:
Joseph Doherty
2026-04-26 11:46:19 -04:00
parent 517fe49aef
commit 5e6bbb586c
4 changed files with 110 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
CREATE TABLE bots (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
persona TEXT NOT NULL,
voice_samples_json TEXT NOT NULL DEFAULT '[]',
traits_json TEXT NOT NULL DEFAULT '[]',
backstory TEXT NOT NULL DEFAULT '',
initial_relationship_to_you TEXT NOT NULL DEFAULT '',
kickoff_prose TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE you_entity (
id INTEGER PRIMARY KEY CHECK (id = 1),
name TEXT NOT NULL,
pronouns TEXT NOT NULL DEFAULT '',
persona TEXT NOT NULL DEFAULT ''
);
View File
+54
View File
@@ -0,0 +1,54 @@
from __future__ import annotations
import json
from sqlite3 import Connection
from chat.eventlog.projector import on
from chat.eventlog.log import Event
@on("bot_authored")
def _apply_bot_authored(conn: Connection, e: Event) -> None:
p = e.payload
conn.execute(
"INSERT OR REPLACE INTO bots "
"(id, name, persona, voice_samples_json, traits_json, backstory, "
" initial_relationship_to_you, kickoff_prose) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
(p["id"], p["name"], p["persona"],
json.dumps(p.get("voice_samples", [])),
json.dumps(p.get("traits", [])),
p.get("backstory", ""),
p.get("initial_relationship_to_you", ""),
p.get("kickoff_prose", "")),
)
@on("you_authored")
def _apply_you_authored(conn: Connection, e: Event) -> None:
p = e.payload
conn.execute(
"INSERT OR REPLACE INTO you_entity (id, name, pronouns, persona) VALUES (1, ?, ?, ?)",
(p["name"], p.get("pronouns", ""), p.get("persona", "")),
)
def get_bot(conn: Connection, bot_id: str) -> dict | None:
row = conn.execute("SELECT * FROM bots WHERE id = ?", (bot_id,)).fetchone()
if not row:
return None
cols = [c[1] for c in conn.execute("PRAGMA table_info(bots)").fetchall()]
d = dict(zip(cols, row))
d["voice_samples"] = json.loads(d.pop("voice_samples_json"))
d["traits"] = json.loads(d.pop("traits_json"))
return d
def list_bots(conn: Connection) -> list[dict]:
cur = conn.execute("SELECT id, name FROM bots ORDER BY name")
return [{"id": r[0], "name": r[1]} for r in cur]
def get_you(conn: Connection) -> dict | None:
row = conn.execute("SELECT name, pronouns, persona FROM you_entity WHERE id = 1").fetchone()
if not row:
return None
return {"name": row[0], "pronouns": row[1], "persona": row[2]}
+38
View File
@@ -0,0 +1,38 @@
from chat.db.migrate import apply_migrations
from chat.db.connection import open_db
from chat.eventlog.log import append_event
from chat.eventlog.projector import project
from chat.state.entities import get_bot, list_bots, get_you
import chat.state.entities # registers handlers
def test_bot_authored_creates_bot_row(tmp_path):
db = tmp_path / "t.db"
apply_migrations(db)
with open_db(db) as conn:
append_event(conn, kind="bot_authored", payload={
"id": "bot_a", "name": "BotA",
"persona": "...", "voice_samples": ["sample"], "traits": ["shy"],
"backstory": "...",
"initial_relationship_to_you": "coworker",
"kickoff_prose": "you stay late",
})
project(conn)
bot = get_bot(conn, "bot_a")
assert bot is not None
assert bot["name"] == "BotA"
assert bot["traits"] == ["shy"]
assert "bot_a" in [b["id"] for b in list_bots(conn)]
def test_you_authored_creates_you_singleton(tmp_path):
db = tmp_path / "t.db"
apply_migrations(db)
with open_db(db) as conn:
append_event(conn, kind="you_authored", payload={
"name": "Me", "pronouns": "they/them", "persona": "engineer",
})
project(conn)
you = get_you(conn)
assert you is not None
assert you["name"] == "Me"