"""Tools for interacting with the Notebook Engine and Git version control.
These tools allow agents (primarily the Librarian subagent) to read
instance and global tables of contents, retrieve specific state pages,
and examine git version history for the notebook.
"""
from __future__ import annotations
from typing import Annotated
from pydantic import Field
from plugins.notebook_engine import (
get_notebook_state_manager,
read_global_toc,
read_state_page,
read_toc,
)
from tool_context import ToolContext
[docs]
async def get_notebook_toc(
ctx: ToolContext,
instance_uid: Annotated[
str | None,
Field(
description="Instance UID ({platform}:{channel_id}). Defaults to current context."
),
] = None,
page: Annotated[
int, Field(description="Page number to read (-1 for latest).")
] = -1,
page_size: Annotated[int, Field(description="Entries per page.")] = 10,
) -> dict:
"""Read a paginated slice of one notebook instance's table of contents.
Resolves the target instance UID (falling back to the current context's
``{platform}:{channel_id}`` and normalizing a bare channel id by prefixing
the platform), then delegates to :func:`plugins.notebook_engine.read_toc` to
fetch that page of TOC entries from the notebook store on the filesystem.
This is the agent's way of browsing what state pages exist for a channel
before fetching one in full.
Registered in this module's ``TOOLS`` list as ``get_notebook_toc`` and
invoked through the inference worker's tool dispatch (used primarily by the
Librarian subagent); no direct Python callers were found outside the test
suite.
Args:
ctx: The tool context, used for ``platform``/``channel_id`` defaults.
instance_uid: Explicit ``{platform}:{channel_id}`` UID, or ``None`` to
use the current context.
page: Page number to read, with ``-1`` meaning the latest page.
page_size: Number of TOC entries per page.
Returns:
dict: The paginated table-of-contents payload returned by the notebook
engine.
"""
uid = instance_uid or f"{ctx.platform}:{ctx.channel_id}"
if ":" not in uid:
uid = f"{ctx.platform or 'discord'}:{uid}"
return await read_toc(uid, page, page_size)
[docs]
async def get_global_notebook_toc(
ctx: ToolContext,
page: Annotated[
int, Field(description="Page number to read (-1 for latest).")
] = -1,
page_size: Annotated[int, Field(description="Entries per page.")] = 10,
) -> dict:
"""Read a paginated slice of the global, cross-instance table of contents.
Unlike :func:`get_notebook_toc`, this is not scoped to one channel: it
delegates straight to :func:`plugins.notebook_engine.read_global_toc` to page
through TOC entries spanning every notebook instance, letting the agent
survey notebook activity across the whole deployment.
Registered in this module's ``TOOLS`` list as ``get_global_notebook_toc`` and
invoked through the inference worker's tool dispatch (used primarily by the
Librarian subagent); no direct Python callers were found outside the test
suite.
Args:
ctx: The tool context (accepted for dispatch uniformity; not used here).
page: Page number to read, with ``-1`` meaning the latest page.
page_size: Number of TOC entries per page.
Returns:
dict: The paginated global table-of-contents payload returned by the
notebook engine.
"""
return await read_global_toc(page, page_size)
[docs]
async def get_notebook_page(
ctx: Annotated[ToolContext, Field(description="Tool context.")],
instance_uid: Annotated[
str | None,
Field(description="Instance UID. Defaults to current context."),
] = None,
page_num: Annotated[
int, Field(description="Page number to read (-1 for active).")
] = -1,
) -> dict:
"""Read the full content of one notebook state page for an instance.
Where :func:`get_notebook_toc` lists pages, this fetches the body of a single
page. It resolves the instance UID the same way (context default plus
platform-prefix normalization) and delegates to
:func:`plugins.notebook_engine.read_state_page` to load the page from the
notebook store on the filesystem, returning a structured ``error`` dict when
no such page exists rather than raising.
Registered in this module's ``TOOLS`` list as ``get_notebook_page`` and
invoked through the inference worker's tool dispatch (used primarily by the
Librarian subagent); no direct Python callers were found outside the test
suite.
Args:
ctx: The tool context, used for ``platform``/``channel_id`` defaults.
instance_uid: Explicit ``{platform}:{channel_id}`` UID, or ``None`` to
use the current context.
page_num: Page number to read, with ``-1`` meaning the active page.
Returns:
dict: The page contents from the notebook engine, or a dict with an
``error`` key when the page is not found.
"""
uid = instance_uid or f"{ctx.platform}:{ctx.channel_id}"
if ":" not in uid:
uid = f"{ctx.platform or 'discord'}:{uid}"
res = await read_state_page(uid, page_num)
if not res:
return {"error": f"Notebook page {page_num} not found for {uid}."}
return res
[docs]
async def get_notebook_git_log(
ctx: ToolContext,
instance_uid: Annotated[
str | None,
Field(description="Filter by instance UID. If None, show global logs."),
] = None,
count: Annotated[int, Field(description="Number of commits to return.")] = 10,
) -> list[dict[str, str]]:
"""Retrieve recent git commit history for the notebook repository.
Gives the agent visibility into how the notebook has changed over time by
reading the underlying git log. It grabs the shared
:class:`NotebookStateManager` via
:func:`plugins.notebook_engine.get_notebook_state_manager` and calls its
``_git`` manager, choosing ``get_instance_logs`` when an instance UID is
supplied (normalized with a platform prefix) or ``get_recent_logs`` for a
global view. The blocking git calls are wrapped in
:func:`asyncio.to_thread` so they do not stall the event loop, and they read
the notebook git repository on the filesystem.
Registered in this module's ``TOOLS`` list as ``get_notebook_git_log`` and
invoked through the inference worker's tool dispatch (used primarily by the
Librarian subagent); no direct Python callers were found outside the test
suite.
Args:
ctx: The tool context, used for the ``platform`` default when
normalizing a bare instance UID.
instance_uid: Optional ``{platform}:{channel_id}`` UID to filter the log
to one instance; ``None`` returns global recent logs.
count: Maximum number of commits to return.
Returns:
list[dict[str, str]]: One mapping per commit (hash, message, and related
metadata) as produced by the notebook git manager.
"""
import asyncio
mgr = get_notebook_state_manager()
if instance_uid:
uid = instance_uid
if ":" not in uid:
uid = f"{ctx.platform or 'discord'}:{uid}"
return await asyncio.to_thread(mgr._git.get_instance_logs, uid, count)
return await asyncio.to_thread(mgr._git.get_recent_logs, count)
[docs]
async def get_notebook_git_diff(
ctx: ToolContext,
commit_hash: Annotated[str, Field(description="GitHub/Git commit hash.")],
) -> dict[str, str]:
"""Retrieve the unified diff for a specific notebook commit.
Lets the agent inspect exactly what a given commit (surfaced by
:func:`get_notebook_git_log`) changed in the notebook. It obtains the shared
:class:`NotebookStateManager` via
:func:`plugins.notebook_engine.get_notebook_state_manager` and calls its
``_git`` manager's ``get_diff`` for the commit, running that blocking git
operation in :func:`asyncio.to_thread` so the event loop stays responsive;
the diff is read from the notebook git repository on the filesystem.
Registered in this module's ``TOOLS`` list as ``get_notebook_git_diff`` and
invoked through the inference worker's tool dispatch (used primarily by the
Librarian subagent); no direct Python callers were found outside the test
suite.
Args:
ctx: The tool context (accepted for dispatch uniformity; not used here).
commit_hash: The git commit hash to diff.
Returns:
dict[str, str]: A mapping with the requested ``commit`` hash and the
unified ``diff`` text.
"""
import asyncio
mgr = get_notebook_state_manager()
diff = await asyncio.to_thread(mgr._git.get_diff, commit_hash)
return {"commit": commit_hash, "diff": diff}
[docs]
async def write_notebook_entry(
ctx: ToolContext,
entry: Annotated[str, Field(description="The note content to append.")],
instance_uid: Annotated[
str | None,
Field(description="Instance UID. Defaults to current context."),
] = None,
commit_message: Annotated[
str | None,
Field(description="Optional custom git commit message."),
] = None,
) -> dict:
"""Append a new entry to an instance's notebook and commit it to git.
This is the only write tool in the module: it resolves the instance UID the
usual way (context default plus platform-prefix normalization) and delegates
to :func:`plugins.notebook_engine.write_entry`, which appends the note to the
notebook store and records a git commit (optionally with the supplied
message). It therefore mutates the notebook state and the notebook git
repository on the filesystem.
Registered in this module's ``TOOLS`` list as ``write_notebook_entry`` and
invoked through the inference worker's tool dispatch (used primarily by the
Librarian subagent); no direct Python callers were found outside the test
suite.
Args:
ctx: The tool context, used for ``platform``/``channel_id`` defaults.
entry: The note content to append.
instance_uid: Explicit ``{platform}:{channel_id}`` UID, or ``None`` to
use the current context.
commit_message: Optional custom git commit message; the engine supplies a
default when omitted.
Returns:
dict: The result payload from the notebook engine describing the written
entry and resulting commit.
"""
uid = instance_uid or f"{ctx.platform}:{ctx.channel_id}"
if ":" not in uid:
uid = f"{ctx.platform or 'discord'}:{uid}"
from plugins.notebook_engine import write_entry
return await write_entry(uid, entry, commit_message)
[docs]
async def get_instance_conversation_history(
ctx: ToolContext,
instance_uid: Annotated[
str | None,
Field(description="Instance UID. Defaults to current context."),
] = None,
limit: Annotated[int, Field(description="Max messages to retrieve.")] = 50,
) -> dict:
"""Retrieve recent conversation history for a specific instance.
Unlike the notebook-state tools above, this reads live chat rather than
saved notes: it splits the resolved instance UID into platform and channel,
constructs a :class:`message_cache.MessageCache` over the context's Redis
connection, and fetches the most recent messages from the Redis-backed
message cache. It then flattens them into ``[role]: content`` lines so the
Librarian can review what was actually said before summarizing it into the
notebook.
Registered in this module's ``TOOLS`` list as
``get_instance_conversation_history`` and invoked through the inference
worker's tool dispatch (used primarily by the Librarian subagent); no direct
Python callers were found outside the test suite.
Args:
ctx: The tool context, used for the ``platform`` default and the Redis
connection.
instance_uid: Explicit ``{platform}:{channel_id}`` UID, or ``None`` to
use the current context.
limit: Maximum number of recent messages to retrieve.
Returns:
dict: A mapping with the resolved ``instance_uid``, a newline-joined
``history`` string of formatted messages, and their ``count``.
"""
uid = instance_uid or f"{ctx.platform}:{ctx.channel_id}"
if ":" in uid:
platform, channel_id = uid.split(":", 1)
else:
platform = ctx.platform or "discord"
channel_id = uid
uid = f"{platform}:{channel_id}"
from message_cache import MessageCache
cache = MessageCache(ctx.redis)
messages = await cache.get_messages(platform, channel_id, limit=limit)
# Format for easy reading
formatted = []
for msg in messages:
role = msg.get("role", "unknown")
content = msg.get("content", "")
formatted.append(f"[{role}]: {content}")
return {
"instance_uid": uid,
"history": "\n".join(formatted),
"count": len(formatted),
}
# ------------------------------------------------------------------
# Tool Registry Definitions
# ------------------------------------------------------------------
TOOLS = [
{
"name": "get_notebook_toc",
"handler": get_notebook_toc,
"description": "Read the table of contents for a specific notebook instance.",
"parameters": {
"type": "object",
"properties": {
"instance_uid": {"type": "string", "description": "Filtered UID."},
"page": {
"type": "integer",
"description": "Page number (-1 for latest).",
},
"page_size": {"type": "integer", "description": "Entries per page."},
},
},
},
{
"name": "get_global_notebook_toc",
"handler": get_global_notebook_toc,
"description": "Read the global table of contents across all instances.",
"parameters": {
"type": "object",
"properties": {
"page": {"type": "integer", "description": "Page number."},
"page_size": {"type": "integer", "description": "Entries per page."},
},
},
},
{
"name": "get_notebook_page",
"handler": get_notebook_page,
"description": "Read the contents of a specific notebook page.",
"parameters": {
"type": "object",
"properties": {
"instance_uid": {"type": "string", "description": "Instance UID."},
"page_num": {
"type": "integer",
"description": "Page number (-1 for active).",
},
},
},
},
{
"name": "write_notebook_entry",
"handler": write_notebook_entry,
"description": "Append a new entry to the notebook and commit to git.",
"parameters": {
"type": "object",
"properties": {
"entry": {"type": "string", "description": "The content to note down."},
"instance_uid": {"type": "string", "description": "Instance UID."},
"commit_message": {
"type": "string",
"description": "Git commit message.",
},
},
"required": ["entry"],
},
},
{
"name": "get_instance_conversation_history",
"handler": get_instance_conversation_history,
"description": "Retrieve the conversation history for a specific instance.",
"parameters": {
"type": "object",
"properties": {
"instance_uid": {"type": "string", "description": "Instance UID."},
"limit": {"type": "integer", "description": "Max messages."},
},
},
},
{
"name": "get_notebook_git_log",
"handler": get_notebook_git_log,
"description": "View git commit history for the notebook system.",
"parameters": {
"type": "object",
"properties": {
"instance_uid": {"type": "string", "description": "Filter by UID."},
"count": {"type": "integer", "description": "Commit count."},
},
},
},
{
"name": "get_notebook_git_diff",
"handler": get_notebook_git_diff,
"description": "View the changes made in a specific notebook commit.",
"parameters": {
"type": "object",
"properties": {
"commit_hash": {"type": "string", "description": "Commit hash."},
},
"required": ["commit_hash"],
},
},
]