Module Map¶
picobot/
cli/commands.py — typer app: init, wipe, clean, agent, status, cron, daemon, kb
agent/
loop.py — ReAct loop with budget management, error recovery, retry
context.py — system prompt assembly (identity + workspace tree + builtin tools + KB + skills + TODOs)
events.py — streaming event types (TextDelta, ReasoningDelta, ToolCall*)
knowledge.py — progressive KB (topics, YAML frontmatter, search, git sync)
skills.py — skill loading from workspace (seeded from builtins on init)
subagent.py — background sub-agent execution with depth tracking
registry.py — persistent sub-agent registry (spawn/complete/fail/deliver)
budget.py — token budget estimation, history compression, result truncation
trace.py — structured tracing (per-turn timing, tokens, cost)
tools/
base.py — abstract Tool base class with JSON schema validation
registry.py — dynamic tool registration and dispatch
pathguard.py — centralized path access control (allow/block lists)
filesystem.py — read/write/edit/listdir
shell.py — exec with deny patterns and path guard
web.py — web search (Brave) + readability fetch
firecrawl.py — Firecrawl scrape + search (optional)
browser.py — Playwright browser automation (9 actions)
knowledge.py — kb tool (search, read, store, topics, sync)
cron.py — schedule/list/remove cron jobs
spawn.py — spawn subagent tool (depth-aware)
subagent_status.py — check/list sub-agent status
channels/
json_channel.py — NDJSON stdin/stdout protocol for programmatic access
daemon/
service.py — OS service file generation (launchd plist, systemd unit)
logging.py — rotating file handler for daemon logs
dashboard/
app.py — Textual TUI dashboard (6 live-updating panels)
data.py — read-only data readers for dashboard panels
widgets.py — Textual widget components
web/
app.py — FastAPI + HTMX web dashboard with SSE
templates/ — Jinja2 templates (index + 6 partials)
static/ — CSS styles
cron/
types.py — CronSchedule, CronJob, CronStore dataclasses
service.py — scheduling engine (at/every/cron, persistence, async timer)
providers/
base.py — LLMProvider ABC with token/cost tracking
litellm_provider.py — LiteLLM wrapper (any OpenAI-compatible or Anthropic model)
session/manager.py — JSONL session persistence with history trimming
config/schema.py — Pydantic config models
workspace/tracker.py — tool call JSONL logger + workspace git wrapper
skills/ — 14 builtin skill templates
Agent Loop¶
The core is process_direct() in loop.py — a ReAct (Reason + Act) loop:
- Build system prompt (identity → workspace tree → env → AGENTS.md → builtin tools → KB → tool groups → TODO → skills)
- Send messages to LLM via LiteLLM provider
- If LLM returns tool calls, execute them and feed results back
- If LLM returns text, that's the response
- Repeat until done or budget exhausted
Budget management compresses history when token estimate exceeds 80% of budget. Error recovery retries transient LLM errors (rate limits, timeouts) with backoff. Stuck detection catches repeated identical tool calls.
Sub-agents¶
The agent can spawn background sub-agents via the spawn tool. Each sub-agent gets:
- Lean core tool set (read/write/edit/list/exec) — extras opt-in via tools param
- Efficiency-focused system prompt (combine shell ops, minimize LLM round-trips)
- Depth tracking (configurable max, default 2) to prevent infinite recursion
- TODO.md anchoring so spawned tasks survive context truncation
Supervision model:
- Result delivery: In-loop message queue (drained each ReAct iteration) + cross-session registry
- Stop hook: Prevents premature loop exit when sub-agents are running
- Waiting: wait_for_subagents blocks via asyncio.wait at zero token cost
- Monitoring: check_subagents shows live state (iteration, tool calls, files written)
- Intervention: check_subagents(action="cancel") kills stuck sub-agents
- Timers: expected_duration triggers overdue nudge, timeout triggers hard cancel
- Token tracking: Sub-agent tokens accumulated and included in AgentResult.total_tokens
Knowledge Base¶
Structured topic tree (self/, user/, projects/, domains/, journal/) with:
- YAML frontmatter (title, topic, tags, created, updated)
- Two-phase search: frontmatter tags/title first, full-text fallback
- Duplicate detection (title >60% overlap or content >70% overlap)
- Git sync with sharing.yaml for topic-level privacy control
- LWW conflict resolution on updated timestamp
- Obsidian-compatible (point Obsidian at ~/.picobot/workspace/knowledge/)
Tools¶
| Tool | Description |
|---|---|
read_file |
Read file contents (PathGuard enforced) |
write_file |
Write content to file |
edit_file |
Replace exact text in file |
list_dir |
List directory contents |
exec |
Shell command with deny patterns and timeout |
web_search |
Brave Search API |
web_fetch |
URL fetch with Readability extraction |
firecrawl_scrape |
Firecrawl URL scrape (optional) |
firecrawl_search |
Firecrawl web search (optional) |
browser |
Playwright: navigate, click, type, screenshot, get_text, evaluate, select, wait_for, get_elements |
kb |
Knowledge base: search, read, store, topics, sync |
cron |
Schedule: add, list, remove |
spawn |
Launch background sub-agent (depth-aware) |
check_subagents |
List/check sub-agent status |
Workspace-Relative Paths¶
Filesystem tools resolve relative paths against the workspace directory instead of os.getcwd(). This means the agent can use data/config.json instead of the full absolute path, saving tokens and reducing errors when the workspace is a long temp directory (e.g. during eval runs).
The resolution logic lives in _resolve_path() in filesystem.py:
- Relative path + workspace set → resolve against workspace
- Absolute path (/...) → use as-is
- Tilde path (~/...) → expand then resolve
The workspace is passed to all four filesystem tool constructors in both loop.py (_register_default_tools) and subagent.py (_run_subagent). The system prompt (context.py) and sub-agent preambles tell the LLM to prefer relative paths.
The shell tool's _guard_command() also resolves relative path tokens in commands against the working directory and checks them via PathGuard, preventing path traversal escapes like ../../etc/passwd in workspace-restricted mode.
Dashboards¶
TUI (picobot dashboard): Textual app with 6 panels — status header, sub-agent table, cron jobs, activity log, KB tree, TODO display. Polls data files every 2-10s.
Web (picobot serve): FastAPI + HTMX with the same 6 panels as HTML partials. JSON API at /api/*. SSE at /events for real-time updates.
Both dashboards are read-only — they read the same data files the agent writes.
Daemon¶
picobot daemon run runs cron + heartbeat in foreground. picobot daemon install generates an OS service file:
- macOS: launchd plist at ~/Library/LaunchAgents/com.picobot.agent.plist
- Linux: systemd unit at ~/.config/systemd/user/picobot.service
PID file locking via fcntl.flock ensures single instance. Signal handling for graceful shutdown.
JSON Channel¶
picobot agent --json activates NDJSON protocol on stdin/stdout for programmatic integration. Message types: message, ping, shutdown (input); ready, response, progress, error, pong (output). Progress events stream tool calls and text deltas in real time.