PicoBot/AGENTS.md
2026-07-14 14:31:16 +08:00

8.0 KiB

PicoBot

This file is the operational contract for coding agents working in this repository. Read docs/ARCHITECTURE.md before making architectural or lifecycle changes. Code and tests are the final source of truth; update the document when an architectural invariant changes.

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 (Config::load_default in src/config/mod.rs)
  • .env (cwd) is loaded with a custom parser, not via dotenv crate; env var placeholders <VAR_NAME> in config JSON are substituted
  • Config example: resources/templates/config.example.json (released to ~/.picobot/ on first run)
  • CLI TUI identity is stored in ~/.picobot/tui_client_id; it is a non-secret stable chat scope used to restore dialogs across reconnects

Tests

  • cargo test --lib — run unit tests (runs all #[test] in src/)
  • cargo clippy --all-targets --all-features -- -D warnings — required for Rust changes
  • cargo test --test test_scheduler and cargo test --test test_request_format — offline integration/protocol tests
  • cargo test --test test_integration -- --ignored and cargo test --test test_tool_calling -- --ignored — model API integration tests
  • API-backed tests require tests/test.env with real API keys; copy from tests/test.env.example and fill in keys
  • API-backed tests are #[ignore] by default; use -- --ignored to run them

Reference

  • reference/ — third-party reference implementations; 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.inbound → Gateway processor → SessionManager → per-session worker → AgentLoop
   ↑                                                                           │
   └── Channel ← OutboundDispatcher ← per-conversation lane ← MessageBus.outbound

WebSocket/Channel → MessageBus.control → Gateway processor → SessionManager (dialog operations)
Scheduler → SessionManager.handle_cron_message → AgentLoop → send_message tool

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 Bounded async queues and ordered outbound delivery lanes MessageBus, OutboundDispatcher, InboundMessage, OutboundMessage, ControlMessage
session Conversation lifecycle, dialog operations, per-session serialization, persistence coordination 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, HistoryMessage
config Config loading, env substitution, path resolution Config, LLMProviderConfig
logging Tracing initialization with file rotation init_logging(), init_logging_console_only()
task_supervisor Owns, cancels, and boundedly joins gateway background tasks TaskSupervisor

Functional Boundaries

  • Channels only send/receive messages via MessageBus; they know nothing about sessions or LLM
  • MessageBus owns bounded inbound/outbound/control queues; outbound routing and retries belong to OutboundDispatcher, not the queue
  • SessionManager owns session state, dialog operations, context construction, per-session work queues, and persistence coordination
  • AgentLoop is stateless across turns; it receives prepared history, calls LLM providers, executes tools, and returns one result
  • Providers are pure HTTP clients; no bus/session/channel awareness
  • Tools are executed by AgentLoop; they receive raw arguments and return string results

Concurrency and Lifecycle Invariants

  • Messages in one session are processed serially through a bounded queue; different sessions may run concurrently
  • Outbound messages are ordered per (channel, chat_id); a slow destination must not block unrelated destinations
  • Never hold a Session mutex across model, network, or database I/O unless a documented invariant requires it
  • Slow work derived from session state must validate worker_generation/state_version before committing results
  • Related durable mutations use Storage transaction APIs; persistence failure must not leave silent memory/database divergence
  • Long-lived gateway tasks must be owned by TaskSupervisor; connection-local tasks must be explicitly joined or aborted by their owner
  • Connection, retry sleep, queue wait, and shutdown join paths must observe cancellation and have hard time bounds
  • Retry only errors explicitly classified as transient; permanent failures must surface immediately
  • Never log secrets, authorization headers, or full connection URLs containing temporary credentials
  • The workspace cwd is not a filesystem sandbox: default file tools accept absolute paths and Bash inherits process permissions; add explicit canonical-path/process isolation when a hard boundary is required

Key Constraints

  • Gateway changes working directory to workspace in GatewayState::new (src/gateway/mod.rs)
  • Session/message persistence uses SQLite via sqlx; DB stored in workspace as picobot.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

Change Workflow

  1. Inspect the relevant implementation, tests, and docs/ARCHITECTURE.md; do not rely on names or old reports alone.
  2. Search reference/ only for comparison. Never edit it or copy behavior without checking PicoBot's boundaries.
  3. Preserve unrelated user changes in a dirty worktree. Use rg for search and apply_patch for edits.
  4. Add regression tests for bugs, especially cancellation, timeout, queue saturation, stale state, persistence failure, and retry classification.
  5. For Rust changes run targeted tests, cargo test --lib, Clippy with warnings denied, and cargo build. Integration tests require real credentials.
  6. For documentation-only changes verify links, commands, paths, and git diff --check.
  7. Update README, this file, and the architecture document together when public behavior or an architectural invariant changes.

Documentation Roles

  • README.md — user-facing overview, setup, capabilities, and navigation
  • docs/ARCHITECTURE.md — maintainer-facing runtime design, invariants, lifecycle, and extension guidance
  • AGENTS.md — concise operational rules for repository agents
  • resources/skills/about-picobot/references/ — runtime knowledge shipped to PicoBot; update it only when the assistant's built-in product knowledge must change