9.9 KiB
9.9 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 binarycargo run -- gateway— start gateway server (binds127.0.0.1:19876by default)cargo run -- chat— connect to gateway as CLI client (defaultws://127.0.0.1:19876/ws)- WebUI — start Gateway, then open
http://127.0.0.1:19876/; no separate frontend build is required cd webui && npm ci && npm run check && npm run build— validate the Svelte WebUI independently (Node.js 20+); its localdist/is ignoredcargo buildautomatically runs an incremental WebUI production build into CargoOUT_DIR; it runsnpm cionly whenpackage-lock.jsonis not represented by the installed dependency stamppicobot service install|start|stop|status|restart|uninstall— manage the Linux systemd user service (picobot.service)
Config
- Config load order:
~/.picobot/config.jsonthen fallback to./config.json(Config::load_defaultinsrc/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]insrc/)cargo clippy --all-targets --all-features -- -D warnings— required for Rust changescargo test --test test_schedulerandcargo test --test test_request_format— offline integration/protocol testscargo test --test test_integration -- --ignoredandcargo test --test test_tool_calling -- --ignored— model API integration tests- API-backed tests require
tests/test.envwith real API keys; copy fromtests/test.env.exampleand fill in keys - API-backed tests are
#[ignore]by default; use-- --ignoredto 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; ownsGatewayStatewhich 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 scheduled execution → AgentLoop → Scheduler delivery policy → SessionManager/MessageBus
Modules
| Module | Responsibility | Key Types |
|---|---|---|
gateway |
Server lifecycle, HTTP/WS/WebUI 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() |
work |
Session-scoped active plan and concurrently executable checklist items | WorkManager, TaskPlan, TaskItem |
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
- WorkManager owns the single active plan per session, item state transitions, plan versions, and plan-change events; plans are optional and absent from ordinary chat context
- Scheduler supports legacy direct-delivery jobs and managed
task/monitorjobs; managed agents cannot callsend_message, andon_alertsuppresses only healthy informational results - AgentLoop is stateless across turns; it receives prepared history, calls LLM providers, executes tools, and returns one result
- WebUI management APIs only expose allowlisted config/profile/log/storage operations; keep response limits, secret redaction, atomic config writes, and profile path allowlists intact
- WebUI slash completion must consume the existing
get_slash_commandsWebSocket response; do not duplicate the backend command list in frontend source - WebUI chat rendering sanitizes Markdown before inserting HTML; session history must preserve structured tool-call metadata so calls and results remain independently collapsible
- 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_versionbefore 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 aspicobot.dbby default ChannelManagerowns theMessageBusand all channel instancesOutboundDispatcherroutes outbound messages to the correct channel viaChannelManager- Config
.envloading usesunsafe { env::set_var(...) }— don't refactor to safer patterns without understanding side effects
Change Workflow
- Inspect the relevant implementation, tests, and
docs/ARCHITECTURE.md; do not rely on names or old reports alone. - Search
reference/only for comparison. Never edit it or copy behavior without checking PicoBot's boundaries. - Preserve unrelated user changes in a dirty worktree. Use
rgfor search andapply_patchfor edits. - Add regression tests for bugs, especially cancellation, timeout, queue saturation, stale state, persistence failure, and retry classification.
- For WebUI changes run
npm run checkandnpm run buildinwebui/, then runcargo buildto verify theOUT_DIRembedding path. Do not commit generateddist/; verify there is no external runtime dependency and keep browser chat on the existing/wsprotocol. - For Rust changes run targeted tests,
cargo test --lib, Clippy with warnings denied, andcargo build. Integration tests require real credentials. - For documentation-only changes verify links, commands, paths, and
git diff --check. - 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 navigationdocs/ARCHITECTURE.md— maintainer-facing runtime design, invariants, lifecycle, and extension guidanceAGENTS.md— concise operational rules for repository agentsresources/skills/about-picobot/references/— runtime knowledge shipped to PicoBot; update it only when the assistant's built-in product knowledge must change