feat(core,backends,frontends,storage): conversations, inject queue, session pool, gateway mcp tools, api frontend

This commit is contained in:
hh committed 2026-08-28 03:08:30 +02:00
1 parent 50b7057fa4
commit 33ccc78fec
28 files changed
+3543 -345

No files matched your search

+97 -9
View File
@@ -1,6 +1,6 @@
"""SQLModel tables.
Four tables, all flat, no FK relationships modelled (``actor`` and
Flat tables, no FK relationships modelled (``actor`` and
``agent_name`` are stored as strings — joining audit→token by name is
fine at this volume; we'll introduce FKs when the admin UI actually
demands them).
@@ -74,15 +74,16 @@ class AuditLog(SQLModel, table=True):
class Conversation(SQLModel, table=True):
"""One chat thread, scoped to a frontend.
"""One conversation (§3.1): a master thread, a branch, a deep chat or a job.
``external_id`` is the identifier the frontend uses to find this
thread again on the next request — for the markdown frontend it's a
uuid we mint and persist into the file's frontmatter, for the
anthropic frontend it'd be the same metadata.conversation_id the
client passes. Unique per ``(frontend, external_id)`` because two
frontends sharing a uuid is fine; the same frontend reusing one is
a bug.
``external_id`` is the public id (uuid) every frontend, the API and the
usage table refer to; ``frontend`` names the frontend that created the
row (``markdown``, ``api``, ``mcp``, ``system``). Where a conversation
is *visible* lives in :class:`ConversationBinding`.
``running_turn`` survives a restart: a non-null value at startup means
the gateway died mid-turn and the transcript needs its open ``tool_use``
closed before the session is resumed (``core/conversations``).
"""
__tablename__ = "conversations"
@@ -95,8 +96,92 @@ class Conversation(SQLModel, table=True):
external_id: str = Field(index=True)
agent_name: str = Field(index=True)
session_id: str | None = Field(default=None, index=True)
kind: str = Field(default="deep", index=True)
parent_id: int | None = Field(default=None, index=True)
title: str | None = Field(default=None)
status: str = Field(default="open", index=True)
running_turn: str | None = Field(default=None)
pending_question: bool = Field(default=False)
flags: dict[str, Any] = Field(
default_factory=dict,
sa_column=Column(
JSON().with_variant(JSONB(), "postgresql"),
nullable=False,
server_default=text("'{}'"),
),
)
created_at: datetime = Field(default_factory=_utcnow)
updated_at: datetime = Field(default_factory=_utcnow)
last_user_activity_at: datetime | None = Field(default=None)
last_activity_at: datetime | None = Field(default=None)
class ConversationBinding(SQLModel, table=True):
"""Where a conversation shows up: a Telegram topic id, a vault-relative path.
Invariant (§3.1): at most one *visible* binding per frontend per
conversation - enforced by the partial unique index. The same external
id may point at several conversations over time (a renamed topic gets a
new branch), only one of them visible.
"""
__tablename__ = "conversation_bindings"
__table_args__ = (
Index(
"uq_binding_visible",
"conversation_id",
"frontend",
unique=True,
postgresql_where=text("visible"),
sqlite_where=text("visible"),
),
Index("ix_binding_lookup", "frontend", "external_id"),
)
id: int | None = Field(default=None, primary_key=True)
conversation_id: int = Field(index=True)
frontend: str
external_id: str
visible: bool = Field(default=True)
created_at: datetime = Field(default_factory=_utcnow)
class InjectQueueItem(SQLModel, table=True):
"""Persisted per-conversation queue (§3.4), priorities ``urgent > user > normal``.
``status`` walks ``queued -> running -> done``; a row still ``running``
at startup was cut by a restart and becomes ``interrupted`` - it is
never re-run, the agent gets an inject saying so instead.
"""
__tablename__ = "inject_queue"
id: int | None = Field(default=None, primary_key=True)
conversation_id: int = Field(index=True)
priority: str = Field(index=True)
origin: str = Field(default="system")
text: str
status: str = Field(default="queued", index=True)
turn_id: str | None = Field(default=None)
created_at: datetime = Field(default_factory=_utcnow)
delivered_at: datetime | None = Field(default=None)
class Schedule(SQLModel, table=True):
"""Deferred inject written by the ``schedule`` tool (§3.6).
M1b only records the promise; the executor (pgqueuer, M3) will move
these into its own job table and this one goes away.
"""
__tablename__ = "schedules"
id: int | None = Field(default=None, primary_key=True)
conversation_id: int = Field(index=True)
execute_at: datetime = Field(index=True)
text: str
created_at: datetime = Field(default_factory=_utcnow)
delivered_at: datetime | None = Field(default=None)
class ConversationMessage(SQLModel, table=True):
@@ -193,7 +278,10 @@ class Usage(SQLModel, table=True):
__all__ = [
"AuditLog",
"Conversation",
"ConversationBinding",
"ConversationMessage",
"InjectQueueItem",
"Schedule",
"Token",
"TranscriptEntry",
"Usage",