Skip to content

starpod-agent

The orchestrator crate that wires all subsystems together. Provides StarpodAgent — the central type for all chat interactions.

API

rust
let config = load_agent_config(&paths)?;
let agent = StarpodAgent::new(config).await?;

// Non-streaming chat
let response = agent.chat(ChatMessage {
    text: "Hello!".into(),
    user_id: None,
    channel_id: Some("main".into()),
    channel_session_key: Some("session-uuid".into()),
    attachments: vec![],
}).await?;

// Streaming chat
let (stream, session_id, followup_tx, out_attachments) = agent.chat_stream(&message).await?;
// stream is a Query (tokio Stream of Message)
// followup_tx can inject messages into the running agent loop
// out_attachments: Arc<Mutex<Vec<Attachment>>> — files queued by the Attach tool

// Finalize after streaming
agent.finalize_chat(&session_id, &user_text, &result_text, &result, None).await;

// Deliver accumulated attachments to the user's channel
let attachments = out_attachments.lock().await.drain(..).collect::<Vec<_>>();

Chat Pipeline

  1. Snapshot config — take a cheap clone of the current config (supports hot reload)
  2. Resolve channel — map ChatMessage(Channel, key)
  3. Resolve session — find or create session via SessionManager; export closed session transcript to memory if applicable
  4. Bootstrap context — memory files + daily logs
  5. Build system prompt — identity + context + skills + tools + time
  6. Build provider — construct LlmProvider from config.provider (anthropic, openai, gemini, groq, deepseek, openrouter, ollama)
  7. Run agent-sdk query — agentic loop with custom tools via the selected provider + automatic conversation compaction
  8. Drain followup messages — at each iteration boundary, inject any queued user messages (when followup_mode = "inject")
  9. Self-improve reflection — if self_improve is enabled and conditions are met (skill failure or 5+ tool calls), run a follow-up query to create/update skills
  10. Record usage — tokens and cost to session database
  11. Append daily log — conversation summary
  12. Background nudge — if message count hits the nudge interval, spawn a background review (memory + skills when self-improve is on). Un-nudged sessions are also flushed when the user switches to a different session or when a session closes, so short conversations are never lost

Followup Message Handling

When a user sends a message while a stream is active, behavior depends on followup_mode:

  • inject (default) — Messages are sent through a channel and drained at the next agent loop iteration boundary (before the next API call). Multiple rapid messages are batched into a single user message.
  • queue — Messages are buffered. After the current stream finishes, all queued messages are combined and dispatched as a new agent loop.

Conversation compaction is enabled by default with a 160k token context budget. The compaction model is configurable via compaction_model in agent.toml (defaults to the primary model).

Custom Tools (21)

CategoryTools
MemoryMemorySearch, MemoryWrite, MemoryAppendDaily
EnvironmentEnvGet
FilesFileRead, FileWrite, FileList, FileDelete, Attach
SkillsSkillActivate, SkillCreate, SkillUpdate, SkillDelete, SkillList
CronCronAdd, CronList, CronRemove, CronRuns, CronRun, CronUpdate
HeartbeatHeartbeatWake

Scheduler Integration

rust
let agent = Arc::new(agent);
let handle = agent.start_scheduler(Some(notifier));
// Runs in background, executing due cron jobs through agent.chat()

Config Hot Reload

The agent's config is wrapped in RwLock for hot reload support. Each request snapshots the config at the start, so config changes take effect on the next request.

rust
// Reload config (called by the gateway's file watcher)
agent.reload_config(new_config);

// Get current config snapshot
let config = agent.config(); // returns owned StarpodConfig

Component Accessors

rust
agent.memory()      // &Arc<MemoryStore>
agent.session_mgr() // &Arc<SessionManager>
agent.skills()      // &Arc<SkillStore>
agent.cron()        // &Arc<CronStore>
agent.config()      // StarpodConfig (owned snapshot)

Memory Nudging

Memory persistence operates at two levels:

System prompt guidance — The system prompt includes always-on instructions that guide the agent to proactively persist knowledge (user corrections, preferences, environment facts) via MemoryWrite and MemoryAppendDaily tools during normal conversation. This is core agent behavior, not gated behind self_improve.

Background nudge — Every memory.nudge_interval user messages (default: 10), a background tokio::spawn task reviews the conversation and persists important information automatically. This catches details the agent may have missed during inline conversation.

The nudge pipeline:

  1. StarpodAgent tracks a per-session message counter (nudge_counters)
  2. When count % nudge_interval == 0, maybe_nudge_memory() loads the session transcript
  3. A background task calls nudge::run_nudge() which:
    • Converts SessionMessage records into a transcript (truncated to 30K chars)
    • Makes a single non-streaming LLM call with MemoryWrite / MemoryAppendDaily tools
    • Executes any tool calls from the response (reuses flush::execute_flush_tool_calls)
    • Discards the LLM's text output

Model resolution: memory.nudge_modelcompaction.flush_modelcompaction_model → primary model. The nudge is fail-open — provider errors are logged and the chat flow is never interrupted.

Counters are evicted when a session closes (alongside the bootstrap cache).

Self-Improve Mode

When self_improve = true, a SelfImproveTracker is attached to the tool handler. It tracks:

  • Which skill was activated (via SkillActivate)
  • How many tool calls returned errors
  • Total tool call count
  • Whether SkillCreate/SkillUpdate was already called

After the main agent loop, run_self_improve_reflection() checks these metrics and fires a follow-up query if needed. See the skills concept doc for details.

Tests

25+ unit tests covering agent construction, custom tools, attachments (inbound and outbound via Attach), config reload, session export, self-improve tracking, and memory nudging.

Released under the MIT License.