fix: address audit low-hanging fruit — docs accuracy, auction timing, stubs, tests
- Docs: "No Cloud" → "No Cloud AI" (frontend uses CDN for Bootstrap/HTMX/fonts) - Docs: "600+" → "640+" tests, "20+" → "58" endpoints (actual counts) - Docs: LND described as "scaffolded" not "gRPC-ready"; remove "agents earn sats" - Fix auction timing: coordinator sleep(0) → sleep(AUCTION_DURATION_SECONDS) - agent_core: implement remember() with dedup/eviction, communicate() via swarm comms - Tests: add CLI tests for chat, think, and backend/model-size forwarding (647 passing) https://claude.ai/code/session_01SZTwAkTg6v4ybv8g9NLxqN
This commit is contained in:
10
README.md
10
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
[](https://github.com/AlexanderWhitestone/Timmy-time-dashboard/actions/workflows/tests.yml)
|
||||
|
||||
A local-first, sovereign AI agent system. Talk to Timmy, watch his swarm, gate API access with Bitcoin Lightning — all from a browser, no cloud required.
|
||||
A local-first, sovereign AI agent system. Talk to Timmy, watch his swarm, gate API access with Bitcoin Lightning — all from a browser, no cloud AI required.
|
||||
|
||||
**[Live Docs →](https://alexanderwhitestone.github.io/Timmy-time-dashboard/)**
|
||||
|
||||
@@ -15,7 +15,7 @@ A local-first, sovereign AI agent system. Talk to Timmy, watch his swarm, gate
|
||||
| **Timmy Agent** | Agno-powered agent (Ollama default, AirLLM optional for 70B/405B) |
|
||||
| **Mission Control** | FastAPI + HTMX dashboard — chat, health, swarm, marketplace |
|
||||
| **Swarm** | Multi-agent coordinator — spawn agents, post tasks, run Lightning auctions |
|
||||
| **L402 / Lightning** | Bitcoin Lightning payment gating for API access |
|
||||
| **L402 / Lightning** | Bitcoin Lightning payment gating for API access (mock backend; LND scaffolded) |
|
||||
| **Spark Intelligence** | Event capture, predictions, memory consolidation, advisory engine |
|
||||
| **Creative Studio** | Multi-persona creative pipeline — image, music, video generation |
|
||||
| **Tools** | Git, image, music, and video tools accessible by persona agents |
|
||||
@@ -25,7 +25,7 @@ A local-first, sovereign AI agent system. Talk to Timmy, watch his swarm, gate
|
||||
| **Telegram** | Bridge Telegram messages to Timmy |
|
||||
| **CLI** | `timmy`, `timmy-serve`, `self-tdd` entry points |
|
||||
|
||||
**600+ tests, 100% passing.**
|
||||
**Full test suite, 100% passing.**
|
||||
|
||||
---
|
||||
|
||||
@@ -161,7 +161,7 @@ cp .env.example .env
|
||||
| `AIRLLM_MODEL_SIZE` | `70b` | `8b` \| `70b` \| `405b` |
|
||||
| `L402_HMAC_SECRET` | *(default — change in prod)* | HMAC signing key for macaroons |
|
||||
| `L402_MACAROON_SECRET` | *(default — change in prod)* | Macaroon secret |
|
||||
| `LIGHTNING_BACKEND` | `mock` | `mock` \| `lnd` |
|
||||
| `LIGHTNING_BACKEND` | `mock` | `mock` (production-ready) \| `lnd` (scaffolded, not yet functional) |
|
||||
|
||||
---
|
||||
|
||||
@@ -217,7 +217,7 @@ src/
|
||||
shortcuts/ # Siri Shortcuts endpoints
|
||||
telegram_bot/ # Telegram bridge
|
||||
self_tdd/ # Continuous test watchdog
|
||||
tests/ # 600+ tests — one file per module, all mocked
|
||||
tests/ # one test file per module, all mocked
|
||||
static/style.css # Dark mission-control theme (JetBrains Mono)
|
||||
docs/ # GitHub Pages landing page
|
||||
AGENTS.md # AI agent development standards ← read this
|
||||
|
||||
@@ -563,13 +563,13 @@
|
||||
<h1>Your agents.<br><em>Your hardware.</em><br>Your sats.</h1>
|
||||
<p class="hero-sub">
|
||||
A local-first AI command center. Talk to Timmy, coordinate your swarm,
|
||||
gate API access with Bitcoin Lightning — no cloud, no telemetry, no compromise.
|
||||
gate API access with Bitcoin Lightning — no cloud AI, no telemetry, no compromise.
|
||||
</p>
|
||||
<div class="hero-badges">
|
||||
<span class="badge green">600+ Tests Passing</span>
|
||||
<span class="badge green">Full Test Suite Passing</span>
|
||||
<span class="badge blue">FastAPI + HTMX</span>
|
||||
<span class="badge amber">Lightning L402</span>
|
||||
<span class="badge">No Cloud</span>
|
||||
<span class="badge">No Cloud AI</span>
|
||||
<span class="badge purple">Multi-Agent Swarm</span>
|
||||
<span class="badge">MIT License</span>
|
||||
</div>
|
||||
@@ -582,11 +582,11 @@
|
||||
<!-- ── Stats ────────────────────────────────────────────────────────────── -->
|
||||
<div class="stats">
|
||||
<div class="stat">
|
||||
<div class="stat-number">600+</div>
|
||||
<div class="stat-number">640+</div>
|
||||
<div class="stat-label">Tests Passing</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-number">20+</div>
|
||||
<div class="stat-number">58</div>
|
||||
<div class="stat-label">API Endpoints</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
@@ -595,7 +595,7 @@
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-number">0</div>
|
||||
<div class="stat-label">Cloud Calls</div>
|
||||
<div class="stat-label">Cloud AI Calls</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -639,7 +639,7 @@
|
||||
<span class="feature-icon">⚡</span>
|
||||
<h3>L402 Lightning Payments</h3>
|
||||
<p>Bitcoin Lightning payment gating via HMAC macaroons. Mock backend for dev,
|
||||
LND gRPC-ready for production. Agents earn and spend sats autonomously.</p>
|
||||
LND backend scaffolded for production. Auction bids priced in sats.</p>
|
||||
<span class="feature-tag">L402 · Macaroon · BOLT11</span>
|
||||
</div>
|
||||
|
||||
@@ -780,7 +780,7 @@ External: Ollama :11434 · optional Redis · optional LND gRPC</span>
|
||||
<div class="step-num">5</div>
|
||||
<div>
|
||||
<h4>Test</h4>
|
||||
<pre class="codeblock"><span class="cmd">make test</span> <span class="cmt"># 600+ tests — no Ollama needed</span>
|
||||
<pre class="codeblock"><span class="cmd">make test</span> <span class="cmt"># full test suite — no Ollama needed</span>
|
||||
<span class="cmd">make test-cov</span> <span class="cmt"># + coverage report</span>
|
||||
<span class="cmd">make watch</span> <span class="cmt"># self-TDD watchdog in background</span></pre>
|
||||
</div>
|
||||
@@ -912,7 +912,7 @@ External: Ollama :11434 · optional Redis · optional LND gRPC</span>
|
||||
<footer>
|
||||
<div class="footer-copy">
|
||||
TIMMY TIME // MISSION CONTROL · MIT License ·
|
||||
No cloud. No telemetry. Sats are sovereignty.
|
||||
No cloud AI. No telemetry. Sats are sovereignty.
|
||||
</div>
|
||||
<div class="footer-links">
|
||||
<a href="https://github.com/AlexanderWhitestone/Timmy-time-dashboard">GitHub</a>
|
||||
|
||||
@@ -172,18 +172,19 @@ Respond naturally and helpfully."""
|
||||
return result
|
||||
|
||||
def remember(self, memory: Memory) -> None:
|
||||
"""Store memory persistently.
|
||||
|
||||
For now, working memory is sufficient. In the future,
|
||||
this would write to SQLite or vector DB for long-term
|
||||
memory across sessions.
|
||||
"""Store memory in working memory.
|
||||
|
||||
Adds the memory to the sliding window and bumps its importance.
|
||||
"""
|
||||
# Mark as accessed to update importance
|
||||
memory.touch()
|
||||
|
||||
# TODO: Persist to SQLite for long-term memory
|
||||
# This would integrate with the existing briefing system
|
||||
pass
|
||||
|
||||
# Deduplicate by id
|
||||
self._working_memory = [m for m in self._working_memory if m.id != memory.id]
|
||||
self._working_memory.append(memory)
|
||||
|
||||
# Evict oldest if over capacity
|
||||
if len(self._working_memory) > self._max_working_memory:
|
||||
self._working_memory.pop(0)
|
||||
|
||||
def recall(self, query: str, limit: int = 5) -> list[Memory]:
|
||||
"""Retrieve relevant memories.
|
||||
@@ -215,13 +216,22 @@ Respond naturally and helpfully."""
|
||||
return [m for _, m in scored[:limit]]
|
||||
|
||||
def communicate(self, message: Communication) -> bool:
|
||||
"""Send message to another agent.
|
||||
|
||||
This would use the swarm comms layer for inter-agent
|
||||
messaging. For now, it's a stub.
|
||||
"""
|
||||
# TODO: Integrate with swarm.comms
|
||||
return True
|
||||
"""Send message to another agent via swarm comms."""
|
||||
try:
|
||||
from swarm.comms import SwarmComms
|
||||
comms = SwarmComms()
|
||||
comms.publish(
|
||||
"agent:messages",
|
||||
"agent_message",
|
||||
{
|
||||
"from": self._identity.name,
|
||||
"to": message.recipient,
|
||||
"content": message.content,
|
||||
},
|
||||
)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _extract_tags(self, perception: Perception) -> list[str]:
|
||||
"""Extract searchable tags from perception."""
|
||||
|
||||
@@ -11,7 +11,7 @@ import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
from swarm.bidder import AuctionManager, Bid
|
||||
from swarm.bidder import AUCTION_DURATION_SECONDS, AuctionManager, Bid
|
||||
from swarm.comms import SwarmComms
|
||||
from swarm import learner as swarm_learner
|
||||
from swarm.manager import SwarmManager
|
||||
@@ -227,7 +227,7 @@ class SwarmCoordinator:
|
||||
All bids are recorded in the learner so agents accumulate outcome
|
||||
history that later feeds back into adaptive bidding.
|
||||
"""
|
||||
await asyncio.sleep(0) # yield to let any pending callbacks fire
|
||||
await asyncio.sleep(AUCTION_DURATION_SECONDS)
|
||||
|
||||
# Snapshot the auction bids before closing (for learner recording)
|
||||
auction = self.auctions.get_auction(task_id)
|
||||
|
||||
@@ -27,3 +27,45 @@ def test_status_does_not_use_inline_string():
|
||||
|
||||
call_args = mock_timmy.print_response.call_args
|
||||
assert call_args[0][0] != "Brief status report — one sentence."
|
||||
|
||||
|
||||
def test_chat_sends_message_to_agent():
|
||||
"""chat command must pass the user message to the agent with streaming."""
|
||||
mock_timmy = MagicMock()
|
||||
|
||||
with patch("timmy.cli.create_timmy", return_value=mock_timmy):
|
||||
runner.invoke(app, ["chat", "Hello Timmy"])
|
||||
|
||||
mock_timmy.print_response.assert_called_once_with("Hello Timmy", stream=True)
|
||||
|
||||
|
||||
def test_think_sends_topic_to_agent():
|
||||
"""think command must pass the topic wrapped in a prompt with streaming."""
|
||||
mock_timmy = MagicMock()
|
||||
|
||||
with patch("timmy.cli.create_timmy", return_value=mock_timmy):
|
||||
runner.invoke(app, ["think", "Bitcoin self-custody"])
|
||||
|
||||
mock_timmy.print_response.assert_called_once_with(
|
||||
"Think carefully about: Bitcoin self-custody", stream=True
|
||||
)
|
||||
|
||||
|
||||
def test_chat_passes_backend_option():
|
||||
"""chat --backend airllm must forward the backend to create_timmy."""
|
||||
mock_timmy = MagicMock()
|
||||
|
||||
with patch("timmy.cli.create_timmy", return_value=mock_timmy) as mock_create:
|
||||
runner.invoke(app, ["chat", "test", "--backend", "airllm"])
|
||||
|
||||
mock_create.assert_called_once_with(backend="airllm", model_size=None)
|
||||
|
||||
|
||||
def test_think_passes_model_size_option():
|
||||
"""think --model-size 70b must forward the model size to create_timmy."""
|
||||
mock_timmy = MagicMock()
|
||||
|
||||
with patch("timmy.cli.create_timmy", return_value=mock_timmy) as mock_create:
|
||||
runner.invoke(app, ["think", "topic", "--model-size", "70b"])
|
||||
|
||||
mock_create.assert_called_once_with(backend=None, model_size="70b")
|
||||
|
||||
@@ -162,8 +162,8 @@ async def test_coordinator_run_auction_no_bids():
|
||||
coord = SwarmCoordinator()
|
||||
task = coord.post_task("No bids task")
|
||||
|
||||
# Patch sleep to avoid 15-second wait
|
||||
with patch("swarm.bidder.asyncio.sleep", new_callable=AsyncMock):
|
||||
# Patch sleep to avoid 15-second wait in tests
|
||||
with patch("swarm.coordinator.asyncio.sleep", new_callable=AsyncMock):
|
||||
winner = await coord.run_auction_and_assign(task.id)
|
||||
|
||||
assert winner is None
|
||||
|
||||
@@ -8,7 +8,7 @@ These tests verify that:
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -16,6 +16,13 @@ from swarm.coordinator import SwarmCoordinator
|
||||
from swarm.tasks import TaskStatus
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _fast_auction():
|
||||
"""Skip the 15-second auction wait in tests."""
|
||||
with patch("swarm.coordinator.AUCTION_DURATION_SECONDS", 0):
|
||||
yield
|
||||
|
||||
|
||||
class TestSwarmInProcessAgents:
|
||||
"""Test the in-process agent spawning and bidding flow."""
|
||||
|
||||
|
||||
@@ -8,6 +8,13 @@ import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _fast_auction():
|
||||
"""Skip the 15-second auction wait in tests."""
|
||||
with patch("swarm.coordinator.AUCTION_DURATION_SECONDS", 0):
|
||||
yield
|
||||
|
||||
|
||||
class TestFullSwarmLifecycle:
|
||||
"""Integration tests for end-to-end swarm task lifecycle."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user