feat(core,backends,frontends,storage): conversations, inject queue, session pool, gateway mcp tools, api frontend
This commit is contained in:
1 parent
50b7057fa4
commit
33ccc78fec
28 files changed
+3543
-345
No files matched your search
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user