diff --git a/.handoff/CHECKPOINT.md b/.handoff/CHECKPOINT.md
new file mode 100644
index 0000000..9289052
--- /dev/null
+++ b/.handoff/CHECKPOINT.md
@@ -0,0 +1,66 @@
+# Kimi Checkpoint - Updated 2026-02-22 19:25 EST
+
+## Session Info
+- **Duration:** ~4.5 hours
+- **Commits:** 2 (f0aa435, bd0030f)
+- **PR:** #18 ready for review
+- **Handoff System:** ✅ Created (.handoff/ directory)
+
+## Current State
+
+### Branch
+```
+kimi/sprint-v2-swarm-tools-serve → origin/kimi/sprint-v2-swarm-tools-serve
+```
+
+### Last Commit
+```
+f0aa435 feat: swarm E2E, MCP tools, timmy-serve L402, tests, notifications
+```
+
+### Test Status
+```
+436 passed, 0 warnings
+```
+
+## What Was Done
+
+1. ✅ Auto-spawn persona agents (Echo, Forge, Seer) on startup
+2. ✅ WebSocket broadcasts for real-time UI
+3. ✅ MCP tools integration (search, file, shell, Python)
+4. ✅ /tools dashboard page
+5. ✅ Real timmy-serve with L402 middleware
+6. ✅ Browser push notifications
+7. ✅ test_docker_agent.py (9 tests)
+8. ✅ test_swarm_integration_full.py (18 tests)
+9. ✅ Fixed all pytest warnings (16 → 0)
+
+## Next Task (When You Return)
+
+**WAITING FOR PR REVIEW**
+
+User is reviewing PR #18. No new work until merged or feedback received.
+
+### Options:
+1. If PR merged: Start new feature from TODO.md P1 list
+2. If PR feedback: Address review comments
+3. If asked: Work on specific new task
+
+## Context Files
+
+- `.handoff/TODO.md` - Full task list
+- `git log --oneline -10` - Recent history
+- PR: https://github.com/AlexanderWhitestone/Timmy-time-dashboard/pull/18
+
+## Quick Commands
+
+```bash
+# Check current state
+git status && git log --oneline -3 && make test
+
+# Switch to PR branch
+git checkout kimi/sprint-v2-swarm-tools-serve
+
+# See what changed
+git diff main --stat
+```
diff --git a/.handoff/CONTINUE.md b/.handoff/CONTINUE.md
new file mode 100644
index 0000000..56128b3
--- /dev/null
+++ b/.handoff/CONTINUE.md
@@ -0,0 +1,41 @@
+# Kimi Handoff - Continue Work
+
+## Quick Start
+
+```bash
+cd /Users/apayne/Timmy-time-dashboard && cat .handoff/CHECKPOINT.md
+```
+
+Then paste this prompt to Kimi:
+
+```
+Continue work from checkpoint. Read .handoff/CHECKPOINT.md and execute NEXT TASK.
+```
+
+---
+
+## Current Status
+
+**Last Commit:** (will be updated)
+**Branch:** (will be updated)
+**Next Task:** (will be updated)
+**Test Status:** (will be updated)
+
+## Files to Read
+
+1. `.handoff/CHECKPOINT.md` - Full context
+2. `.handoff/TODO.md` - Remaining tasks
+3. `git log --oneline -5` - Recent commits
+
+## Emergency Commands
+
+```bash
+# If stuck, reset to last known good state
+git stash && git checkout main && git pull
+
+# Verify tests pass
+make test
+
+# See what was recently done
+git diff HEAD~1 --name-only
+```
diff --git a/.handoff/TODO.md b/.handoff/TODO.md
new file mode 100644
index 0000000..0352292
--- /dev/null
+++ b/.handoff/TODO.md
@@ -0,0 +1,32 @@
+# Sprint v2.0.0 - Remaining Tasks
+
+## ✅ Completed
+- [x] Swarm E2E (auto-spawn, WebSocket broadcasts)
+- [x] MCP Tools (search, file, shell, Python)
+- [x] timmy-serve L402 implementation
+- [x] Browser notifications
+- [x] Test coverage (436 tests, 0 warnings)
+- [x] PR #18 created
+
+## 🔄 Next Up (Priority Order)
+
+### P0 - Critical
+- [ ] Review PR #18 feedback and merge
+- [ ] Deploy to staging and verify
+
+### P1 - Features
+- [ ] SQLite connection pooling (retry with proper test isolation)
+- [ ] Add more persona agents (Mace, Helm, Quill)
+- [ ] Task result caching
+- [ ] Agent-to-agent messaging
+
+### P2 - Polish
+- [ ] Dark mode toggle
+- [ ] Mobile app improvements
+- [ ] Performance metrics dashboard
+
+## 📝 Notes
+
+- SQLite pooling was reverted - need different approach
+- All tests passing - maintain 0 warning policy
+- Docker swarm mode working - test with `make docker-up`
diff --git a/.handoff/bootstrap.sh b/.handoff/bootstrap.sh
new file mode 100755
index 0000000..7d057d7
--- /dev/null
+++ b/.handoff/bootstrap.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+# Kimi Bootstrap - Run this to continue work
+
+echo "=== Kimi Handoff Bootstrap ==="
+echo ""
+
+cd /Users/apayne/Timmy-time-dashboard
+
+echo "📋 Current Checkpoint:"
+cat .handoff/CHECKPOINT.md | head -30
+echo ""
+echo "---"
+echo ""
+
+echo "🔧 Git Status:"
+git status --short
+echo ""
+
+echo "📝 Recent Commits:"
+git log --oneline -3
+echo ""
+
+echo "✅ Test Status:"
+source .venv/bin/activate && make test 2>&1 | tail -3
+echo ""
+
+echo "🎯 Next Task (from TODO.md):"
+grep "\[ \]" .handoff/TODO.md | head -5
+echo ""
+
+echo "================================"
+echo "To continue, paste this to Kimi:"
+echo ""
+echo " Continue from checkpoint. Read .handoff/CHECKPOINT.md"
+echo ""
diff --git a/pyproject.toml b/pyproject.toml
index 3e0e574..8503c2b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -78,6 +78,7 @@ include = [
testpaths = ["tests"]
pythonpath = ["src"]
asyncio_mode = "auto"
+asyncio_default_fixture_loop_scope = "function"
addopts = "-v --tb=short"
[tool.coverage.run]
diff --git a/src/dashboard/app.py b/src/dashboard/app.py
index 9c33613..78e7be2 100644
--- a/src/dashboard/app.py
+++ b/src/dashboard/app.py
@@ -1,5 +1,6 @@
import asyncio
import logging
+import os
from contextlib import asynccontextmanager
from pathlib import Path
@@ -21,6 +22,7 @@ from dashboard.routes.swarm_ws import router as swarm_ws_router
from dashboard.routes.briefing import router as briefing_router
from dashboard.routes.telegram import router as telegram_router
from dashboard.routes.swarm_internal import router as swarm_internal_router
+from dashboard.routes.tools import router as tools_router
logging.basicConfig(
level=logging.INFO,
@@ -83,6 +85,18 @@ async def lifespan(app: FastAPI):
rec["agents_offlined"],
)
+ # Auto-spawn persona agents for a functional swarm (Echo, Forge, Seer)
+ # Skip auto-spawning in test mode to avoid test isolation issues
+ if os.environ.get("TIMMY_TEST_MODE") != "1":
+ logger.info("Auto-spawning persona agents: Echo, Forge, Seer...")
+ try:
+ swarm_coordinator.spawn_persona("echo", agent_id="persona-echo")
+ swarm_coordinator.spawn_persona("forge", agent_id="persona-forge")
+ swarm_coordinator.spawn_persona("seer", agent_id="persona-seer")
+ logger.info("Persona agents spawned successfully")
+ except Exception as exc:
+ logger.error("Failed to spawn persona agents: %s", exc)
+
# Auto-start Telegram bot if a token is configured
from telegram_bot.bot import telegram_bot
await telegram_bot.start()
@@ -121,6 +135,7 @@ app.include_router(swarm_ws_router)
app.include_router(briefing_router)
app.include_router(telegram_router)
app.include_router(swarm_internal_router)
+app.include_router(tools_router)
@app.get("/", response_class=HTMLResponse)
diff --git a/src/dashboard/routes/tools.py b/src/dashboard/routes/tools.py
new file mode 100644
index 0000000..f8155e9
--- /dev/null
+++ b/src/dashboard/routes/tools.py
@@ -0,0 +1,92 @@
+"""Tools dashboard route — /tools endpoints.
+
+Provides a dashboard page showing available tools, which agents have access
+to which tools, and usage statistics.
+"""
+
+from pathlib import Path
+
+from fastapi import APIRouter, Request
+from fastapi.responses import HTMLResponse
+from fastapi.templating import Jinja2Templates
+
+from swarm import registry as swarm_registry
+from swarm.personas import PERSONAS
+from timmy.tools import get_all_available_tools, get_tool_stats
+
+router = APIRouter(tags=["tools"])
+templates = Jinja2Templates(directory=str(Path(__file__).parent.parent / "templates"))
+
+
+@router.get("/tools", response_class=HTMLResponse)
+async def tools_page(request: Request):
+ """Render the tools dashboard page."""
+ # Get all available tools
+ available_tools = get_all_available_tools()
+
+ # Get registered agents and their personas
+ agents = swarm_registry.list_agents()
+ agent_tools = []
+
+ for agent in agents:
+ # Determine which tools this agent has based on its capabilities/persona
+ tools_for_agent = []
+
+ # Check if it's a persona by name
+ persona_id = None
+ for pid, pdata in PERSONAS.items():
+ if pdata["name"].lower() == agent.name.lower():
+ persona_id = pid
+ break
+
+ if persona_id:
+ # Get tools for this persona
+ for tool_id, tool_info in available_tools.items():
+ if persona_id in tool_info["available_in"]:
+ tools_for_agent.append({
+ "id": tool_id,
+ "name": tool_info["name"],
+ "description": tool_info["description"],
+ })
+ elif agent.name.lower() == "timmy":
+ # Timmy has all tools
+ for tool_id, tool_info in available_tools.items():
+ tools_for_agent.append({
+ "id": tool_id,
+ "name": tool_info["name"],
+ "description": tool_info["description"],
+ })
+
+ # Get tool stats for this agent
+ stats = get_tool_stats(agent.id)
+
+ agent_tools.append({
+ "id": agent.id,
+ "name": agent.name,
+ "status": agent.status,
+ "tools": tools_for_agent,
+ "stats": stats,
+ })
+
+ # Calculate overall stats
+ total_calls = sum(a["stats"]["total_calls"] for a in agent_tools if a["stats"])
+
+ return templates.TemplateResponse(
+ request,
+ "tools.html",
+ {
+ "page_title": "Tools & Capabilities",
+ "available_tools": available_tools,
+ "agent_tools": agent_tools,
+ "total_calls": total_calls,
+ },
+ )
+
+
+@router.get("/tools/api/stats")
+async def tools_api_stats():
+ """Return tool usage statistics as JSON."""
+ return {
+ "all_stats": get_tool_stats(),
+ "available_tools": list(get_all_available_tools().keys()),
+ }
diff --git a/src/dashboard/templates/base.html b/src/dashboard/templates/base.html
index 0bbd228..4d92db3 100644
--- a/src/dashboard/templates/base.html
+++ b/src/dashboard/templates/base.html
@@ -24,8 +24,9 @@
BRIEFING
SWARM
MARKET
+ TOOLS
MOBILE
- TEST
+
@@ -44,5 +45,6 @@
updateClock();
+