From ff6b32316cf4eb4c9e52fc4bc417c63854d5cf1c Mon Sep 17 00:00:00 2001 From: markov Date: Fri, 27 Feb 2026 11:55:14 +0100 Subject: [PATCH] Tool log: persist agent tool calls in messages - Message model: tool_log JSON column (array of {name, args, result, error}) - chat.send WS handler accepts tool_log - MessageOut schema includes tool_log - Converters pass tool_log through --- src/tracker/api/converters.py | 1 + src/tracker/api/schemas.py | 1 + src/tracker/models/chat.py | 5 ++++- src/tracker/ws/handler.py | 3 +++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/tracker/api/converters.py b/src/tracker/api/converters.py index 1bc6a60..69efd8c 100644 --- a/src/tracker/api/converters.py +++ b/src/tracker/api/converters.py @@ -84,6 +84,7 @@ def message_out(m: Message) -> MessageOut: author=member_brief(m.author) if m.author else None, content=m.content, thinking=m.thinking, + tool_log=m.tool_log, mentions=_resolve_mentions(m), actor=member_brief(m.actor) if hasattr(m, 'actor') and m.actor else None, voice_url=m.voice_url, diff --git a/src/tracker/api/schemas.py b/src/tracker/api/schemas.py index 0163079..cf2c049 100644 --- a/src/tracker/api/schemas.py +++ b/src/tracker/api/schemas.py @@ -61,6 +61,7 @@ class MessageOut(BaseModel): author: MemberBrief | None = None content: str thinking: str | None = None + tool_log: list[dict] | None = None mentions: list[MemberBrief] = [] actor: MemberBrief | None = None voice_url: str | None = None diff --git a/src/tracker/models/chat.py b/src/tracker/models/chat.py index d9744f1..c7349c8 100644 --- a/src/tracker/models/chat.py +++ b/src/tracker/models/chat.py @@ -3,7 +3,7 @@ import uuid from typing import TYPE_CHECKING -from sqlalchemy import ForeignKey, Integer, String, Text +from sqlalchemy import ForeignKey, Integer, JSON, String, Text from sqlalchemy.dialects.postgresql import ARRAY, UUID from sqlalchemy.orm import Mapped, mapped_column, relationship @@ -47,6 +47,9 @@ class Message(Base): # Actor — who initiated the action (for system messages: who created/moved/assigned the task) actor_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("members.id"), nullable=True) + # Tool log — JSON array of tool calls [{name, args, result, error?}] + tool_log: Mapped[dict | None] = mapped_column(JSON, nullable=True) + # Content content: Mapped[str] = mapped_column(Text, nullable=False) thinking: Mapped[str | None] = mapped_column(Text, nullable=True) # LLM reasoning/thinking block diff --git a/src/tracker/ws/handler.py b/src/tracker/ws/handler.py index 8dd40f4..9ce72d8 100644 --- a/src/tracker/ws/handler.py +++ b/src/tracker/ws/handler.py @@ -42,6 +42,7 @@ def _to_message_out(msg: Message, author: Member | None = None) -> MessageOut: author=_to_member_brief(author) if author else None, content=msg.content, thinking=msg.thinking, + tool_log=msg.tool_log, mentions=mention_briefs, actor=_to_member_brief(msg.actor) if hasattr(msg, 'actor') and msg.actor else None, voice_url=msg.voice_url, @@ -287,6 +288,7 @@ async def _handle_chat_send(session_id: str, data: dict): task_id = data.get("task_id") content = data.get("content", "") thinking = data.get("thinking") + tool_log = data.get("tool_log") mentions = data.get("mentions", []) if not content: @@ -306,6 +308,7 @@ async def _handle_chat_send(session_id: str, data: dict): author_id=member.id, content=content, thinking=thinking, + tool_log=tool_log, mentions=mentions, ) db.add(msg)