feat: give Timmy hands — artifact tools for conversation (#337)

Co-authored-by: Kimi Agent <kimi@timmy.local>
Co-committed-by: Kimi Agent <kimi@timmy.local>
This commit is contained in:
2026-03-18 20:36:38 -04:00
committed by hermes
parent 22e0d2d4b3
commit 243b1a656f
3 changed files with 93 additions and 0 deletions

View File

@@ -1403,6 +1403,83 @@ def memory_forget(query: str) -> str:
return f"Failed to forget: {exc}"
# ───────────────────────────────────────────────────────────────────────────────
# Artifact Tools — "hands" for producing artifacts during conversation
# ───────────────────────────────────────────────────────────────────────────────
NOTES_DIR = Path.home() / ".timmy" / "notes"
DECISION_LOG = Path.home() / ".timmy" / "decisions.md"
def jot_note(title: str, body: str) -> str:
"""Write a markdown note to Timmy's workspace (~/.timmy/notes/).
Use this tool to capture ideas, drafts, summaries, or any artifact that
should persist beyond the conversation. Each note is saved as a
timestamped markdown file.
Args:
title: Short descriptive title (used as filename slug).
body: Markdown content of the note.
Returns:
Confirmation with the file path of the saved note.
"""
if not title or not title.strip():
return "Cannot jot — title is empty."
if not body or not body.strip():
return "Cannot jot — body is empty."
NOTES_DIR.mkdir(parents=True, exist_ok=True)
slug = re.sub(r"[^a-z0-9]+", "-", title.strip().lower()).strip("-")[:60]
timestamp = datetime.now(UTC).strftime("%Y%m%d-%H%M%S")
filename = f"{timestamp}_{slug}.md"
filepath = NOTES_DIR / filename
content = f"# {title.strip()}\n\n> Created: {datetime.now(UTC).isoformat()}\n\n{body.strip()}\n"
filepath.write_text(content)
logger.info("jot_note: wrote %s", filepath)
return f"Note saved: {filepath}"
def log_decision(decision: str, rationale: str = "") -> str:
"""Append an architectural or design decision to the running decision log.
Use this tool when a significant decision is made during conversation —
technology choices, design trade-offs, scope changes, etc.
Args:
decision: One-line summary of the decision.
rationale: Why this decision was made (optional but encouraged).
Returns:
Confirmation that the decision was logged.
"""
if not decision or not decision.strip():
return "Cannot log — decision is empty."
DECISION_LOG.parent.mkdir(parents=True, exist_ok=True)
# Create file with header if it doesn't exist
if not DECISION_LOG.exists():
DECISION_LOG.write_text(
"# Decision Log\n\nRunning log of architectural and design decisions.\n\n"
)
stamp = datetime.now(UTC).strftime("%Y-%m-%d %H:%M UTC")
entry = f"## {stamp}{decision.strip()}\n\n"
if rationale and rationale.strip():
entry += f"{rationale.strip()}\n\n"
entry += "---\n\n"
with open(DECISION_LOG, "a") as f:
f.write(entry)
logger.info("log_decision: %s", decision.strip()[:80])
return f"Decision logged: {decision.strip()}"
# ───────────────────────────────────────────────────────────────────────────────
# Memory System (Central Coordinator)
# ───────────────────────────────────────────────────────────────────────────────

View File

@@ -48,6 +48,9 @@ SAFE_TOOLS = frozenset(
"check_ollama_health",
"get_memory_status",
"list_swarm_agents",
# Artifact tools
"jot_note",
"log_decision",
# MCP Gitea tools
"issue_write",
"issue_read",

View File

@@ -619,6 +619,18 @@ def _register_gematria_tool(toolkit: Toolkit) -> None:
logger.debug("Gematria tool not available")
def _register_artifact_tools(toolkit: Toolkit) -> None:
"""Register artifact tools — notes and decision logging."""
try:
from timmy.memory_system import jot_note, log_decision
toolkit.register(jot_note, name="jot_note")
toolkit.register(log_decision, name="log_decision")
except (ImportError, AttributeError) as exc:
logger.warning("Tool execution failed (Artifact tools registration): %s", exc)
logger.debug("Artifact tools not available")
def _register_thinking_tools(toolkit: Toolkit) -> None:
"""Register thinking/introspection tools for self-reflection."""
try:
@@ -657,6 +669,7 @@ def create_full_toolkit(base_dir: str | Path | None = None):
_register_introspection_tools(toolkit)
_register_delegation_tools(toolkit)
_register_gematria_tool(toolkit)
_register_artifact_tools(toolkit)
_register_thinking_tools(toolkit)
# Gitea issue management is now provided by the gitea-mcp server