PicoBot/AGENTS.md

4.6 KiB

PicoBot

Build & Run

  • cargo build — build the binary
  • cargo run -- gateway — start gateway server (binds 127.0.0.1:19876 by default)
  • cargo run -- chat — connect to gateway as CLI client (default ws://127.0.0.1:19876/ws)

Config

  • Config load order: ~/.picobot/config.json then fallback to ./config.json (src/config/mod.rs:237-267)
  • .env (cwd) is loaded with a custom parser, not via dotenv crate; env var placeholders <VAR_NAME> in config JSON are substituted
  • Config example: config.example.json
  • session_ttl_hours defaults to 4 in code when absent (src/gateway/mod.rs:44); config.example.json shows 168 as a suggestion

Tests

  • cargo test --lib — run unit tests (runs all #[test] in src/)
  • cargo test --test test_integration -- --ignored — run integration tests (also test_tool_calling, test_request_format)
  • All integration tests require tests/test.env with real API keys; copy from tests/test.env.example and fill in keys
  • Integration tests are #[ignore] by default; use -- --ignored to run them

Reference

  • reference/ — third-party reference implementations (nanobot, Mini-Agent, zeroclaw); not part of this project; do not modify

Architecture

Modes

  • Gateway mode (cargo run -- gateway): HTTP/WebSocket server; owns GatewayState which holds all services
  • Client mode (cargo run -- chat): TUI chat client; connects to gateway via WebSocket, purely for user interaction

Core Data Flow

Channel → MessageBus → SessionManager → AgentLoop → (tools) → SessionManager → MessageBus → OutboundDispatcher → Channel
                    ↑
              ControlChannel ──→ SessionManager (dialog ops: create/switch/archive/delete)

Modules

Module Responsibility Key Types
gateway Server lifecycle, HTTP/WS endpoints, owns GatewayState GatewayState, run()
client TUI rendering, WebSocket client for CLI chat App, run()
channels External integrations (Feishu, CLI chat) ChannelManager, Channel trait
bus Async message queue (inbound/outbound/control channels) MessageBus, InboundMessage, OutboundMessage, ControlMessage
session Conversation session lifecycle, dialog operations SessionManager, Session
agent LLM call loop, tool execution, context compression AgentLoop
providers LLM API clients (OpenAI-compatible, Anthropic) LLMProvider trait, factory create_provider()
tools Agent tools (bash, file ops, http, web, get_skill) ToolRegistry, Tool trait
skills Skills loading, management, and prompt building SkillsLoader, Skill
storage SQLite persistence for sessions and messages Storage, SessionMeta, MessageMeta
scheduler Cron-based job scheduling, next-run computation Scheduler, Schedule, next_run_for_schedule()
observability Observer pattern for agent/tool telemetry events Observer trait, ObserverEvent, MultiObserver
protocol WebSocket protocol message types WsInbound, WsOutbound, SessionSummary
config Config loading, env substitution, path resolution Config, LLMProviderConfig
logging Tracing initialization with file rotation init_logging(), init_logging_console_only()

Functional Boundaries

  • Channels only send/receive messages via MessageBus; they know nothing about sessions or LLM
  • MessageBus is a pure async queue; it routes nothing, just passes messages
  • SessionManager owns session state and dialog operations; it does NOT call LLM directly
    • SessionManager is responsible for injecting skills prompt into conversation history
  • AgentLoop receives dialog events from SessionManager, calls LLM via providers, executes tools, returns text responses
    • AgentLoop is stateless; all state is managed by Session/SessionManager
  • Providers are pure HTTP clients; no bus/session/channel awareness
  • Tools are executed by AgentLoop; they receive raw arguments and return string results

Key Constraints

  • Gateway changes working directory to workspace on startup (src/gateway/mod.rs:31)
  • Session/message persistence uses SQLite via sqlx; DB stored in workspace as .picobot_sessions.db by default
  • ChannelManager owns the MessageBus and all channel instances
  • OutboundDispatcher routes outbound messages to the correct channel via ChannelManager
  • Config .env loading uses unsafe { env::set_var(...) } — don't refactor to safer patterns without understanding side effects