copilot

package
v1.18.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 27, 2026 License: MIT Imports: 72 Imported by: 0

Documentation

Overview

Package copilot – abort.go implements abort trigger detection for stopping active agent runs using natural language phrases in multiple languages.

Package copilot – access.go implements the access control system for DevClaw.

The bot does NOT respond to everyone by default. Only explicitly authorized contacts (or groups) can interact with the assistant.

Access levels:

  • owner: Full control, can manage admins, workspaces, and settings
  • admin: Can manage users, create workspaces, and use admin commands
  • user: Can interact with the assistant normally
  • blocked: Explicitly blocked, never receives a response

Default policy: "deny" — unknown contacts are silently ignored.

Package copilot – agent.go implements the agentic loop that orchestrates LLM calls with tool execution. The agent iterates: call LLM → if tool_calls → execute tools → append results → call LLM again, until the LLM produces a final text response with no tool calls.

Architecture:

  • No fixed max turns — the loop runs until the LLM stops calling tools.
  • Single run timeout (default: 600s = 10min) controls the whole run.
  • Per-LLM-call safety timeout (5min) prevents individual hung requests.
  • Reflection nudge every 15 turns for budget awareness.
  • Auto-compaction on context overflow (up to 3 attempts).

Package copilot – agent_router.go implements agent routing based on channel, user, or group. This allows different agents with different models, instructions, and skill sets to handle messages from different sources.

Package copilot – agent_tools.go registers the agent_manage dispatcher tool for creating and managing agents (workspaces) via the AI.

Package copilot – apply_patch.go implements a robust multi-file patch applicator. This is based on the OpenClaw apply_patch tool, allowing the agent to make surgical edits to files without having to rewrite the entire file or use fragile bash/sed commands.

Package copilot implements the main orchestrator for DevClaw. Coordinates channels, skills, scheduler, access control, workspaces, and security to process user messages and generate LLM responses.

Package copilot – block_streamer.go implements progressive message delivery for channels. Instead of waiting for the full LLM response, text is coalesced into blocks and sent as they become available, giving the user near-real-time feedback as blocks become available.

Coalescing rules:

  • Wait until at least MinChars are accumulated.
  • Flush when MaxChars is reached or the idle timer fires.
  • Always try to flush at a natural boundary (newline, sentence end).

Package copilot – browser_act.go implements unified browser actions for browser automation.

Package copilot – browser_snapshot.go implements accessibility tree snapshots and role reference generation for browser automation.

The accessibility tree provides a structured representation of the page that's easier for AI agents to understand and interact with. Role references (e1, e2, ...) provide stable element identifiers across multiple snapshots.

Package copilot – browser_tabs.go implements tab management tools for browser automation.

Package copilot – browser_tool.go implements a browser automation tool using Chrome DevTools Protocol (CDP). This allows the agent to navigate web pages, take screenshots, extract content, click elements, and fill forms.

Architecture:

Agent ──browser_navigate──▶ BrowserManager ──CDP──▶ Chrome/Chromium
Agent ──browser_screenshot──▶ BrowserManager ──CDP──▶ Screenshot → base64
Agent ──browser_content──▶ BrowserManager ──CDP──▶ DOM → text
Agent ──browser_click──▶ BrowserManager ──CDP──▶ Click element

The browser is launched lazily on first use and kept alive for the session. A configurable timeout prevents runaway browser sessions.

Package copilot – builtin_skills.go provides embedded skills that are included in the binary and loaded into the system prompt.

Package copilot – canvas_host.go implements an interactive HTML/JS canvas host. Allows the agent to generate HTML/JS content and serve it via a temporary local HTTP server for the user to interact with.

Use cases:

  • Data visualization (charts, graphs)
  • Interactive prototypes
  • Mini-apps (calculators, forms)
  • Rich output that exceeds chat formatting

Architecture:

Agent ──canvas_create──▶ CanvasHost ──HTTP──▶ user browser
Agent ──canvas_update──▶ CanvasHost (live-reload via SSE)
Agent ──canvas_list──▶ list of active canvases

Package copilot – codebase_tools.go implements codebase analysis tools: file tree indexing, code search (ripgrep), symbol extraction, and Cursor rules generator.

Package copilot – commands.go implements admin commands that can be executed via chat messages (WhatsApp, Discord, etc.).

Commands are prefixed with "/" and only available to admins/owners:

/allow <phone>           - Grant user access
/block <phone>           - Block a user
/unblock <phone>         - Unblock a user
/revoke <phone>          - Revoke user access
/admin <phone>           - Promote user to admin
/users                   - List all authorized users
/ws create <id> <name>   - Create a workspace
/ws delete <id>          - Delete a workspace
/ws assign <phone> <id>  - Assign user to workspace
/ws list                 - List all workspaces
/ws info [id]            - Show workspace details
/ws set <key> <value>    - Update current workspace setting
/group allow             - Allow current group
/group block             - Block current group
/group assign <ws_id>    - Assign current group to workspace
/skills list             - List installed skills
/skills defaults         - List available default skills
/skills install <n|all>  - Install default skills
/status                  - Show bot status
/help                    - Show available commands

Package copilot – compaction_pipeline.go implements a multi-strategy context compaction pipeline that proactively manages context window usage. Instead of waiting for overflow errors, the pipeline monitors context pressure and applies increasingly aggressive compaction strategies as usage grows.

Levels (cheapest → most expensive):

Collapse (70%)     → Truncate oversized tool results in-place
MicroCompact (80%) → Clear old tool result contents with placeholder
AutoCompact (93%)  → LLM-based summarization of conversation history
MemoryCompact (97%)→ Extract memories + summarize (preserves learnings)

Package copilot – compaction_safeguard.go provides safeguards for the context compaction process to ensure critical information is preserved when the conversation is summarized to free context window space.

Package copilot – config.go defines all configuration structures for the DevClaw Copilot assistant.

Package copilot – config_watcher.go polls config.yaml for changes and triggers hot-reload of safe-to-update fields without restarting the daemon.

Package copilot – context_engine.go defines the pluggable ContextEngine interface. A ContextEngine provides additional context layers that are injected into the system prompt alongside the core prompt composition.

The LegacyContextEngine wraps the existing PromptComposer behavior, making it possible to swap in alternative engines (RAG, vector search, code indexing, etc.) without modifying the prompt pipeline.

Package copilot — context_router.go routes incoming messages to the right palace wing based on (channel, external_id) context.

Sprint 1 (v1.18.0), per ADR-001 + ADR-007-v2:

When a message arrives from a channel (telegram, whatsapp, cli, mcp, ...), the context router decides which wing the resulting memory should be associated with. The decision uses a three-tier lookup:

  1. EXPLICIT: the user (or a previous heuristic pass) mapped this (channel, external_id) pair to a wing in the channel_wing_map table. Confidence is whatever was recorded at mapping time (usually 1.0 for manual entries, 0.5-0.8 for heuristic ones).

  2. HEURISTIC: no explicit mapping exists. The router guesses a wing based on user-configured patterns (HierarchyConfig.Heuristics). The binary ships zero defaults — all heuristics are opt-in via YAML. Confidence is 0.7 (constant). The guess is then persisted so subsequent messages skip straight to tier 1.

  3. DEFAULT: neither explicit nor heuristic. Returns an empty wing, which propagates to wing=NULL in storage. This is a first-class citizen per ADR-006: legacy and default-off behavior both live here.

Retrocompat: the router NEVER returns an error. Every resolution path has a graceful fallback. Callers never need to handle "routing failed" — the worst case is a default (empty) wing which matches v1.17.0 behavior.

Feature flag: this router is safe to instantiate regardless of the palace-aware flag state. When the flag is off, callers simply don't invoke Resolve — the router sits idle. When on, Resolve is called at the start of each turn for each incoming message.

Package copilot – context_window_guard.go provides pre-run context window validation to prevent agent runs from starting with models that have insufficient context capacity.

Package copilot – coordinator.go implements a structured multi-agent coordination protocol with four phases:

Research     → parallel read-only workers gather information
Synthesis    → coordinator analyzes findings, creates specs
Implementation → workers execute specs (write operations)
Verification → workers test and validate changes

Each phase restricts worker tools to prevent unintended side effects (e.g., research workers cannot write files). This ensures safe parallel execution and clear separation of concerns.

Implements structured multi-agent coordination for complex tasks.

Package copilot – daemon_manager.go implements a process manager that lets the agent start, monitor, and control long-running background processes (dev servers, watchers, database engines, etc.) with ring-buffer output capture and health checking.

Package copilot – db.go provides the central SQLite database for DevClaw. A single devclaw.db file holds scheduler jobs, session history/meta/facts, and the audit log. The memory.db (FTS5/embeddings) and whatsapp.db (whatsmeow session) remain as separate databases.

Package copilot – db_hub_tools.go implements database hub management tools. These tools use the native Database Hub with Go drivers for better performance than the CLI-based db_tools.go tools.

Package copilot – db_migrate.go handles one-time migration of legacy JSON/JSONL/text data to the central devclaw.db SQLite database. After a successful migration, the old files are renamed to .bak.

Package copilot – db_tools.go implements database tools for querying PostgreSQL, MySQL, and SQLite databases. Uses CLI clients (psql, mysql, sqlite3) to avoid heavy driver dependencies.

Package copilot – destructive_tracker.go implements rate limiting and batch detection for destructive tools to prevent accidental mass deletions.

Package copilot – dev_utils.go implements developer utility tools: JSON formatting, JWT decoding, regex testing, base64 encode/decode, hashing, UUID generation, URL parsing, timestamp conversion, etc.

Package copilot – directives.go parses inline directives embedded in message bodies. Unlike standalone commands (e.g., "/think high"), inline directives can appear alongside regular text: "explain this /model gpt-4o /think high". This aligns with OpenClaw's inline directive system.

Package copilot – docker_tools.go implements native Docker tools for container management, image operations, and compose integration.

Package copilot – dream.go implements the Dream System, a background consolidation process that runs when the daemon is idle. It analyzes accumulated memories, detects contradictions, merges duplicates, and produces consolidated summaries — similar to how sleep consolidates learning in biological systems.

The Dream System uses a 3-gate trigger:

  • Gate 1: Minimum time since last dream (default: 6 hours)
  • Gate 2: Minimum sessions since last dream (default: 2)
  • Gate 3: File lock to prevent concurrent dreams

Phases: Orient → Gather → Consolidate → Apply

Package copilot – env_tools.go implements system information and network diagnostic tools: port scanning, environment info, and process listing.

Package copilot – events.go implements an in-memory pub/sub event bus for agent lifecycle events. Replaces channel-based buffering with fan-out to multiple listeners, each receiving events via direct function call.

Event streams:

  • "lifecycle": run_start, run_end, abort
  • "assistant": delta (text tokens), thinking_start, thinking_delta, thinking_end
  • "tool": tool_use, tool_result
  • "error": agent errors, LLM errors

Package copilot – exec_analysis.go implements command risk analysis for bash/exec tool calls. Commands are categorized by risk level and appropriate actions are taken (allow, log, require approval, deny).

Package copilot – exec_approval.go implements interactive approval for tools that require confirmation before execution (e.g. bash, ssh, write_file).

Package copilot – failover_coordinator.go provides a unified coordinator for profile authentication and model failover. This replaces the fragmented approach of 3 independent systems (profile cooldown, model failover, LLM inline cooldown) with a single consistent error classification and response strategy.

Package copilot – git_tools.go implements native Git tools that provide structured JSON output for the agent, enabling better decision-making without parsing raw text. Uses os/exec to call git directly.

Package copilot – group_chat.go implements enhanced group chat features Activation modes, intro messages, context injection, participant tracking, and quiet hours.

Package copilot – group_policy.go implements group-specific policies including activation modes, quiet hours, and access control for groups.

Package copilot – heartbeat.go implements a periodic heartbeat that checks for pending scheduled jobs, reads HEARTBEAT.md for custom checklists, and triggers proactive agent turns that can send messages to channels.

Package copilot – hooks.go implements a lifecycle hook system. Hooks allow external code to observe and optionally modify agent behavior at well-defined points in the lifecycle.

Hook events include:

SessionStart      — A new session is created or restored.
SessionEnd        — A session is about to be pruned/deleted.
UserPromptSubmit  — User message received, before processing.
PreToolUse        — Before a tool is called (can block/modify).
PostToolUse       — After a tool returns (observe/log).
AgentStart        — Agent loop is about to begin.
AgentStop         — Agent loop finished (normal or error).
SubagentStart     — A subagent has been spawned.
SubagentStop      — A subagent has finished.
PreCompact        — Before session compaction.
PostCompact       — After session compaction.
MemorySave        — A memory was saved.
MemoryRecall      — Memories were recalled for prompt.
Notification      — An outbound notification/message is being sent.
Heartbeat         — Periodic heartbeat tick.
Error             — An unrecoverable error occurred.

Package copilot – ide_extensions.go provides configuration generators for IDE extensions: VSCode, JetBrains, and Neovim. These generate the necessary config files for connecting to DevClaw's MCP server.

Package copilot – identity.go resolves the assistant's identity from multiple sources: agent profile, IDENTITY.md file, config, or defaults.

Package copilot – keyring.go provides secure credential storage using the operating system's native keyring (Linux: Secret Service/GNOME Keyring, macOS: Keychain, Windows: Credential Manager).

Priority for resolving secrets:

  1. Encrypted vault (.devclaw.vault — AES-256-GCM + Argon2, requires master password)
  2. OS keyring (encrypted by the OS, requires user session)
  3. Environment variable (DEVCLAW_API_KEY, OPENAI_API_KEY, etc.)
  4. .env file (loaded by godotenv)
  5. config.yaml value (least secure — plaintext on disk)

Package copilot – lanes.go implements a lane-based concurrency system. Each lane has its own queue and concurrency limit, preventing contention between different types of work (sessions, cron, subagents).

Lane types:

  • session:{id} — one lane per session, maxConcurrent=1 (serialized)
  • global — shared lane for cross-session work, maxConcurrent=3
  • cron — scheduled jobs, maxConcurrent=2
  • subagent — subagent runs, maxConcurrent=8

Package copilot – lcm.go is the coordinator for the Lossless Compaction Module. It ties together store, compactor, assembler, and retrieval into a single engine.

Package copilot – lcm_assembler.go builds the model's context window from the LCM DAG (root summaries + fresh tail messages), with budget-aware eviction.

Package copilot – lcm_compaction.go implements the DAG-based compaction engine for the Lossless Compaction Module. Two passes: leaf (messages → summaries) and condensed (summaries → higher-level summaries), cascading until stable.

Package copilot – lcm_retrieval.go provides the retrieval operations for the LCM tool: grep (FTS + regex), describe (inspect DAG), expand (recover messages).

Package copilot – lcm_store.go provides SQLite CRUD operations for the Lossless Compaction Module (LCM). All message and summary data lives in the central devclaw.db database.

Package copilot – lcm_tools.go registers the `lcm` dispatcher tool with three actions: grep, describe, expand. Follows the same pattern as RegisterSessionsDispatcher in session_tools.go.

Package copilot – legacy_aliases.go registers dispatcher tools that route to individual tools based on the "action" parameter. The memory, vault, and scheduler dispatchers are the PRIMARY visible tools; individual tools are hidden but callable. skill_manage remains hidden.

Package copilot – link_understanding.go extracts URLs from messages and enriches them with readable content before passing to the agent. This aligns with OpenClaw's link understanding pipeline.

Package copilot – llm.go implements the LLM client for chat completions with function calling / tool use support. Uses the OpenAI-compatible API format, which works with OpenAI, Anthropic proxies, GLM (api.z.ai), and any compatible endpoint.

Package copilot – loader.go handles loading configuration from YAML files with secure credential management via environment variables and .env files.

Package copilot – maintenance_manager.go manages maintenance mode state.

Package copilot – markdown.go converts standard Markdown to channel-specific formats. WhatsApp supports a limited subset; other channels may get plain text or passthrough.

Package copilot – mcp_manager.go implements MCP (Model Context Protocol) server management including listing, adding, editing, removing, and testing MCP connections.

Package copilot – mcp_tools.go bridges MCP (Model Context Protocol) servers with the ToolExecutor, allowing the LLM to call tools exposed by external MCP servers as if they were native DevClaw tools.

Workflow:

  1. For each enabled+auto_start MCP server, launch the process (stdio) or connect to the endpoint (SSE/HTTP).
  2. Send tools/list to discover available tools.
  3. Register each tool in the ToolExecutor with a handler that forwards tool calls via MCP's tools/call JSON-RPC.
  4. On shutdown, send a graceful close and terminate processes.

Currently supports stdio transport (most common for MCP).

Package copilot – media_enrichment.go handles extraction of content from documents (PDF, DOCX, TXT) and video frames for enriching agent prompts.

Package copilot – media_registry.go provides a multi-provider registry for media processing (vision and transcription) with priority-based fallback.

Package copilot – media_tools.go registers tools for image understanding (describe_image), audio transcription (transcribe_audio), and the unified send_media tool for sending images, audio, video, and documents.

Package copilot — memory_categorizer.go classifies memory content into categories (event, summary, preference, fact) using keyword patterns. Zero LLM calls — pure regex matching in PT and EN.

Package copilot – memory_extractor.go extracts structured memories from conversation history before context compaction. This preserves valuable information (decisions, preferences, facts, learnings) that would otherwise be lost when messages are summarized or discarded.

Pattern: extract valuable information first, then compact safely knowing nothing important is lost.

Package copilot – memory_hardening.go implements security hardening for memory content that is injected into LLM prompts. Memories are treated as untrusted historical data and sanitized to prevent prompt injection.

Security pattern:

  • Escape HTML entities in memory content
  • Wrap memories in <relevant-memories> tags with untrusted data warning
  • Detect and reject auto-capture of prompt injection patterns
  • Only capture from user-role messages

Package copilot — memory_hierarchy_config.go holds the Sprint 1 (v1.18.0) palace-aware feature flag config and observability scaffolding.

The config lives in its own file so config.go stays focused on core memory configuration. One line in config.go's MemoryConfig struct pulls HierarchyConfig in.

Metric emission follows the devclaw convention of structured slog lines with a known prefix. A log aggregator (fluent-bit, vector, loki) can scrape these into Prometheus time series without DevClaw needing a direct Prometheus dependency.

Metric naming convention (dot-separated, matching ADR-008):

memory.context_router.confidence      histogram bucket counter
memory.search.wing_filter_usage       counter labeled {on, off, fallback}
memory.hierarchy.enabled              gauge (0 or 1)
memory.layer_tokens                   histogram labeled {layer=L0|L1|L2}

All metrics are emitted via slog at INFO level with a "metric" attribute equal to the metric name and a "value" attribute equal to the data point. Zero external dependencies.

Package copilot — memory_hierarchy_tools.go registers the palace-aware memory tools introduced in Sprint 1 (v1.18.0).

These tools are ADDITIVE: they extend the existing memory toolset (memory_save, memory_search, memory_list, memory_index) with five new focused tools that expose the wing/room hierarchy and the context router.

The tools are registered separately from the core memory tools so that:

  • The core set continues to work when hierarchy is disabled
  • Enabling/disabling hierarchy is a single call site change
  • Tests can register one set without the other

Feature flag: each tool checks cfg.Enabled before doing any work. When disabled, the tool is still registered (so the LLM discovers it), but returns a helpful error explaining that the feature is off. This is better UX than hiding the tool entirely — the LLM learns the capability exists and the user can opt in.

New tools added in this file:

memory_list_wings       — enumerate wings with counts
memory_list_rooms       — enumerate rooms inside a wing
memory_get_taxonomy     — full wing → rooms tree
memory_wing_pin         — pin (channel, chatID) → wing mapping
memory_wing_unpin       — remove a pinned mapping
memory_wing_status      — show current wing for a channel/chat

NOT added in this PR (deferred):

  • memory_save wing/room params — requires rerouting markdown file path into wing subdirectories, which is a larger change. Sprint 1 PR #4+.
  • memory_search wing filter — requires wing boost in hybrid fusion, which affects score semantics. Sprint 2.
  • memory_wing_merge / memory_room_merge — bot commands first, see PR #4.

The deferral is intentional: PR #3 adds the taxonomy/routing primitives without touching the hot path of save/search. This keeps retrocompat airtight while delivering user-visible functionality.

Package copilot – memory_indexer.go provides background memory indexing.

Package copilot — memory_stack.go implements Sprint 2 Room 2.4's MemoryStack: the composer that assembles the layered memory system (L0 Identity + L1 Essential + L2 OnDemand) into a single prompt prefix that buildMemoryLayer prepends to the legacy L3 output.

Design principles (do not revise without an ADR):

  1. Stack-over-legacy: the stack NEVER rewrites buildMemoryLayer. It renders a prefix that is prepended. When the stack is nil or all layers return empty, buildMemoryLayer is byte-identical to v1.18.0. This is the retrocompat gate — enforced by the golden fixture test in prompt_layers_golden_test.go.

  2. L0-never-trimmed: the Identity layer is the user's anchor. It is ALWAYS rendered in full, even when it exceeds the total byte budget. Rationale: a 50-byte identity blurb that the user curated themselves is never the reason a prompt is over-budget, and losing it silently breaks persona continuity across turns. If the caller provides an unusually large identity file, the stack logs a WARN but still includes it.

  3. Trim priority L2 > L1 > L0 (trim L2 first, L1 second, L0 never). L2 is ephemeral per-turn context; L1 is the per-wing story summary; L0 is the user's persona anchor.

  4. Panic isolation: a layer that panics is caught, logged, and its contribution becomes the empty string. The remaining layers still render and the prompt is still produced. No single bad layer can prevent the assistant from answering.

  5. Context cancellation short-circuits: a pre-cancelled context returns "" without touching any layer. This keeps the hot path responsive when the caller has already given up.

The stack has zero dependencies on the legacy memory path. It reads the three Room 2.1/2.2/2.3 layers via interfaces so tests can inject mocks that panic, sleep, or return large payloads.

Package copilot – memory_tools.go implements individual memory tools. Each tool has a focused schema with only the parameters it needs, eliminating the ambiguity of the dispatcher pattern.

Package copilot – message_queue.go handles message bursts with debouncing. When a session is already processing, incoming messages are queued and combined after a debounce period.

Package copilot – message_split.go provides splitting of long messages for channels with character limits (e.g. WhatsApp 4096).

Package copilot – metrics_collector.go provides background metrics collection.

Package copilot – model_failover.go implements automatic model failover with cooldowns and reason classification. When the primary LLM returns persistent errors, the system rotates through fallback models automatically.

Package copilot – multiuser.go implements multi-user support with user management, role-based access control, shared memory spaces, and team collaboration features.

Package copilot – ops_tools.go implements operations tools for deploy pipeline execution, server health monitoring, and tunnel management.

Package copilot – pairing.go implements the DM pairing system for secure access onboarding.

The pairing system allows admins to generate shareable tokens that new users can send to the bot to request access. Tokens can be configured for:

  • Auto-approval (immediate access) or manual approval
  • Expiration time
  • Maximum number of uses
  • Role to grant (user or admin)
  • Workspace assignment

Package copilot — palace_bot_commands.go parses and handles slash commands for palace-aware memory operations directly, without invoking the LLM.

This is a pre-agent text parser: the assistant (or channel router) can call HandlePalaceBotCommand at the start of message processing. If the command is handled, the reply is returned directly and the LLM is never invoked. If the input is not a palace command, handled=false and the message continues through the normal LLM path.

Supported commands (all slash-prefixed):

/wing                          Show current wing for this chat
/wing list                     List all wings with counts
/wing set <name>               Pin this chat to <name>
/wing unset                    Remove the pin
/wing merge <from> <to>        Merge one wing into another
/room                          Show rooms in the current chat's wing
/room list [wing]              List rooms (filtered by wing if given)
/tree                          Show full palace taxonomy
/palace help                   Show help text

Design notes:

  • All commands check the HierarchyConfig.Enabled flag. When off, they reply with a "disabled" message that tells the user how to enable.
  • Commands that modify state (set, unset, merge) call through the ContextRouter and SQLiteStore. Read-only commands go directly to the store.
  • Responses are plain text suitable for Telegram/WhatsApp rendering. No markdown escaping needed because channels re-escape on send.
  • Bot commands never wait for LLM provider I/O, so they're cheap and fast — good for rapid iteration on wing config.

Per Sprint 0.5 critic feedback MAJOR-5: the `/wing merge` command MUST ship in Sprint 1 (was originally planned for Sprint 4 WebUI) because users in Telegram/WhatsApp have no other way to fix wing duplicates.

Package copilot – product_tools.go implements product management tools: project management API integration (Jira, Linear), sprint reporting, documentation sync (Notion/Confluence), and DORA metrics calculation.

Package copilot – project.go implements the ProjectManager for managing development projects. Provides project registration, activation, auto-detection of language/framework, and per-session project context.

A "project" maps to a filesystem directory (typically a git repo root) with associated metadata like language, framework, build/test/lint commands, and MCP server configurations.

Package copilot – project_adapter.go provides an adapter that bridges copilot.ProjectManager to skills.ProjectProvider, breaking the import cycle between the two packages.

The adapter converts between copilot.Project and skills.ProjectInfo structs which have the same shape but live in different packages.

Package copilot – prompt_layers.go implements the layered system prompt Each layer has a priority and contributes to the final prompt that is sent to the LLM as the system message.

Bootstrap files (SOUL.md, AGENTS.md, IDENTITY.md, USER.md, TOOLS.md) are loaded from the workspace root and injected as "Project Context". If SOUL.md is present, the agent is instructed to embody its persona.

Package copilot – provider_discovery.go implements optional, non-blocking discovery of locally-hosted LLM providers (Ollama, vLLM).

During startup, the assistant can probe known endpoints to discover available models and their context window sizes. This information supplements the static getModelContextWindowByName() lookup, allowing DevClaw to use correct settings for custom or fine-tuned local models.

Package copilot – queryloop.go defines the Phase-based query loop architecture. This extracts the conceptual phases from the monolithic agent.go Run() method into composable, testable units.

The existing Run() method continues to work unchanged. This module provides the interfaces and orchestrator for incremental migration to phased execution.

Phases:

Prepare   → build messages, resolve tools, load context
APICall   → call LLM with streaming/non-streaming
ToolExec  → execute tool calls from LLM response
Decision  → decide: tool_use → loop back, text → proceed to stop
StopCheck → verify completion before returning

Package copilot – queue_modes.go implements configurable queue modes that control how the agent handles incoming messages while a session is busy. Supports: collect, steer, followup, interrupt, steer-backlog.

Package copilot – scheduler_tools.go implements scheduler tools for managing scheduled tasks and reminders: scheduler_add, scheduler_list, scheduler_remove, and scheduler_search.

session.go implementa o gerenciamento de sessões isoladas por chat/grupo. Cada grupo ou DM possui sua própria sessão com memória, skills ativas e configurações independentes.

Package copilot – session_lock.go implements cross-process session write locks using advisory file locking. This prevents two DevClaw processes from writing to the same session simultaneously, which could corrupt session state.

Architecture:

  • Lock file: <sessions_dir>/<session_id>.lock
  • Contains JSON: {"pid": <PID>, "acquired_at": "<RFC3339>"}
  • Uses syscall.Flock for advisory locking (safe on Linux/macOS)
  • Watchdog goroutine refreshes the lock file mtime every 60s
  • Stale detection: locks older than 30 min are considered abandoned

session_persistence.go implements disk persistence for sessions using JSONL format.

Package copilot – session_persistence_sqlite.go implements session persistence backed by the central devclaw.db SQLite database. It is a drop-in replacement for the JSONL-based SessionPersistence.

Package copilot – session_reaper.go periodically removes stale persisted sessions that haven't been active within a configurable time window. This prevents unbounded storage growth from long-lived deployments.

Package copilot – session_tools.go implements the sessions dispatcher tool. Uses dispatcher pattern to consolidate 4 session tools into 1.

Package copilot - settings.go manages application settings stored separately from config. This includes tool profiles and other infrequently-changed settings.

Package copilot – skill_creator.go implements tools that allow the agent to create, edit, and manage skills via chat. Skills are created as ClawdHub-compatible SKILL.md files in the workspace skills directory.

The agent can use these tools to:

  • Initialize a new skill with a SKILL.md template
  • Edit an existing skill's instructions
  • Add scripts (Python, Node, Shell) to a skill
  • List installed skills
  • Test a skill by executing it

Package copilot – skill_db.go provides a database system for skills. It allows skills to store structured data in a dedicated SQLite database without requiring users to know SQL or create scripts.

Each skill can have multiple tables. Table names are automatically prefixed with the skill name to ensure isolation (e.g., "crm_contacts" for skill "crm").

Package copilot – skill_db_tools.go registers individual skill database tools that allow the agent to store and retrieve structured data.

Package copilot – skill_watcher.go watches skill directories for SKILL.md changes and increments the PromptComposer's skills version to invalidate the cached skills layer. This replaces TTL-based refresh with event-driven invalidation, matching the OpenClaw chokidar pattern.

Package copilot provides startup verification for DevClaw. Checks vault, database, channels, and system dependencies.

Package copilot – stop_hooks.go implements pre-stop completion verification. Before the agent stops, these hooks analyze the conversation to detect incomplete work patterns (e.g., files edited but tests not run, code written but not compiled). If incomplete work is detected, the hook can inject a continuation message to prompt the agent to finish.

Pattern: verify completion before allowing the agent to stop, preventing premature task completion.

Package copilot – subagent.go implements a subagent system that allows the main agent to spawn independent child agents to handle tasks concurrently.

Architecture:

Main Agent ──spawn_subagent──▶ SubagentManager ──goroutine──▶ Child AgentRun
                                   │                              │
                                   ▼                              ▼
                            SubagentRegistry           (runs with own session,
                            tracks runs + results       limited tools, separate
                                                        prompt, optional model)

Subagents:

  • Run in isolated goroutines with their own session context.
  • Cannot spawn nested subagents (no recursion).
  • Have a configurable subset of tools (deny list applied).
  • Results are collected and can be polled or waited on.
  • Are announced back to the parent session when complete.

Package copilot – techops_commands.go implements administrative commands for remote operations. These commands allow operators to manage the system via chat channels without SSH access.

Package copilot – system_tools.go registers built-in tools that are always available to the agent, independent of skills. These tools provide core capabilities like shell execution, file I/O, memory operations, and scheduling.

Package copilot – techops_types.go provides data structures for TechOps commands.

Package copilot – tailscale.go implements Tailscale Serve/Funnel integration for secure remote access to DevClaw's web services.

Tailscale Serve proxies HTTPS traffic from the Tailscale network to a local port. Tailscale Funnel extends this to the public internet with automatic TLS certificates.

Provides secure remote access without manual port forwarding, dynamic DNS, or certificate management.

Architecture:

Internet ──HTTPS──▶ Tailscale Funnel ──▶ Local DevClaw (e.g. :8085)
Tailnet  ──HTTPS──▶ Tailscale Serve  ──▶ Local DevClaw (e.g. :8085)

Package copilot – testing_tools.go implements a testing engine that wraps common test runners and provides tools for running tests, API testing, and generating test reports.

Package copilot – tool_executor.go manages a registry of callable tools and dispatches tool calls from the LLM to the appropriate handlers. Tools can be registered from skills, system built-ins, or plugins.

Package copilot – tool_guard.go implements a security layer that controls which tools can be used, by whom, and under what conditions.

Security features:

  • Tool-level access control (owner/admin/user)
  • Destructive command detection and blocking
  • Sensitive path protection
  • SSH host allowlist
  • Full audit logging of every tool execution
  • Configurable confirmation for dangerous operations

Package copilot – tool_guard_audit_sqlite.go provides a SQLite-backed audit logger for the ToolGuard. It writes tool execution records to the audit_log table in the central devclaw.db and auto-prunes entries older than 30 days.

Package copilot – tool_loop_detection.go detects when the agent enters a tool call loop (repeating the same call with no progress) and triggers circuit breakers to prevent infinite loops.

Four detectors:

  • Generic repeat: same tool+args hash repeated N times
  • Ping-pong: alternating between two tool calls
  • Known no-progress poll: tools that poll external state without progress
  • Global circuit breaker: total no-progress calls across all patterns

Package copilot – tool_mutation.go classifies tool calls as mutating vs read-only. Aligned with OpenClaw's tool-mutation.ts pattern. Used for: - More granular error reporting (warn only on mutating tool errors) - Action fingerprinting for deduplication

Package copilot – tool_outcomes.go implements a per-turn, thread-safe log of tool execution outcomes used for structural provenance checks.

The memory layer uses this log to reject fact saves whose content echoes a tool call that failed earlier in the same turn AND for which no later successful call on the same subject has been observed. This replaces the keyword-based access-failure filter with a locale-agnostic, content-signal-free mechanism: all decisions derive from the tool error flag, ordering, and generic token overlap.

Package copilot – tool_profiles.go implements predefined tool permission profiles. Profiles simplify tool configuration by providing presets for common use cases.

Package copilot – tool_result_truncation.go provides two-layer tool result truncation to prevent context overflow while preserving important content.

Layer 1 (per-result): Smart head+tail truncation that detects error summaries, JSON closing brackets, and other important content at the end of tool results.

Layer 2 (pre-LLM context guard): Caps individual tool results and compacts oldest tool results when total content exceeds the context budget.

Package copilot – transcript_policy.go defines per-provider transcript sanitization policies. Different LLM providers have different requirements for message format, turn ordering, and content types. This policy system ensures that the conversation transcript is compatible with each provider before sending.

Package copilot – typing_controller.go provides a lifecycle-aware typing indicator controller that replaces the simple goroutine+ticker pattern.

State machine:

Started → Active → RunComplete → DispatchIdle → Sealed
  • Started: Controller created, not yet sending.
  • Active: Ticker running, sending typing indicators at regular intervals.
  • RunComplete: Agent execution finished; if block streamer is still dispatching buffered output we keep typing active for a grace period.
  • DispatchIdle: Block streamer has finished; typing stops but can be restarted if a followup run begins within the grace window.
  • Sealed: Terminal state; no further typing will be sent.

The controller also enforces a TTL so a stuck agent cannot send typing indicators indefinitely.

Package copilot – usage_tracker.go records LLM token usage and estimated costs per session and globally.

Package copilot – vault.go provides encrypted credential storage using AES-256-GCM with Argon2id key derivation. Secrets are stored in a local file (.devclaw.vault) that is unreadable without the master password.

Even if someone has filesystem access, the vault contents remain encrypted. The master password is never stored — only a derived key is used in memory.

Package copilot – vault_tools.go implements individual vault tools. Each tool has a focused schema with only the parameters it needs.

Package copilot – web_fetch_readability.go provides HTML-to-text conversion and an in-memory LRU cache for web_fetch results.

Package copilot – webhooks.go implements external webhook support for hooks. Webhooks allow sending hook events to external HTTP endpoints.

Package copilot – workspace.go implements the multi-tenant workspace system.

Workspaces (also called "profiles") allow multiple people to use the same WhatsApp number with completely isolated contexts:

  • Each workspace has its own system prompt, skills, model, and language
  • Each workspace has its own session store (isolated conversation memory)
  • A workspace can be assigned to specific users (JIDs) or groups
  • One user can belong to one workspace at a time
  • There is always a "main" workspace for unassigned users

Example use cases:

  • Personal workspace: your own assistant with custom instructions
  • Team workspace: shared context for a project team
  • Client workspace: different personality/language per client
  • Testing workspace: experimental settings without affecting production

Package copilot – workspace_containment.go implements workspace path containment and symlink escape protection for file operations.

All file tools (read_file, write_file, edit_file, apply_patch) must call AssertSandboxPath before performing any I/O to ensure the resolved path is within the workspace root.

Index

Constants

View Source
const (
	// DefaultRunTimeout is the maximum duration for an entire agent run.
	// Set to 20 minutes to accommodate coding tasks that invoke Claude Code CLI
	// (which itself can take 5-15 minutes for complex projects).
	// This is the PRIMARY timeout — no per-turn limit.
	DefaultRunTimeout = 1200 * time.Second

	// DefaultLLMCallTimeout is the safety-net timeout for a single LLM API call.
	// This only prevents hung HTTP connections — it should be generous enough
	// that even large contexts complete. 5 minutes covers worst-case scenarios.
	DefaultLLMCallTimeout = 5 * time.Minute

	// DefaultMaxCompactionAttempts is how many times to retry after context overflow compaction.
	DefaultMaxCompactionAttempts = 3
)
View Source
const (
	BeginPatchMarker         = "*** Begin Patch"
	EndPatchMarker           = "*** End Patch"
	AddFileMarker            = "*** Add File: "
	DeleteFileMarker         = "*** Delete File: "
	UpdateFileMarker         = "*** Update File: "
	MoveToMarker             = "*** Move to: "
	EOFMarker                = "*** End of File"
	ChangeContextMarker      = "@@ "
	EmptyChangeContextMarker = "@@"
)
View Source
const (
	ActClick    = "click"
	ActType     = "type"
	ActPress    = "press"
	ActHover    = "hover"
	ActDrag     = "drag"
	ActSelect   = "select"
	ActFill     = "fill"
	ActResize   = "resize"
	ActWait     = "wait"
	ActEvaluate = "evaluate"
)

Act kinds

View Source
const (
	// ContextWindowHardMinTokens is the absolute minimum context window required
	// for a useful agent run. Below this, the run is blocked.
	ContextWindowHardMinTokens = 16_000

	// ContextWindowWarnBelowTokens triggers a warning when the context window
	// is usable but small. This helps users understand potential issues with
	// compaction, long tool outputs, and multi-turn conversations.
	ContextWindowWarnBelowTokens = 32_000
)
View Source
const (
	TokenNoReply     = "NO_REPLY"
	TokenHeartbeatOK = "HEARTBEAT_OK"
)

Silent token constants — used by the LLM to signal special behavior. These must be stripped from user-visible output.

View Source
const (
	// DefaultDebounceMs is the debounce delay for followup messages (session busy).
	// Kept short so followups are grouped without adding perceptible lag.
	DefaultDebounceMs = 200
	// DefaultMaxPending is the default max queued messages per session.
	DefaultMaxPending = 20
	// DedupWindowSec is the window for deduplication (skip same content).
	DedupWindowSec = 5
	// FollowupDebounceMs is used when the session is already processing.
	// Slightly longer to allow burst followup messages to be collected.
	FollowupDebounceMs = 500
)
View Source
const (
	// MaxMessageWhatsApp is WhatsApp's character limit.
	MaxMessageWhatsApp = 4096

	// MaxMessageDefault is the default max length for general channels.
	MaxMessageDefault = 4000
)
View Source
const (
	// MaxToolResultContextShare is the maximum fraction of context window that
	// a single tool result should occupy.
	MaxToolResultContextShare = 0.3

	// MinKeepChars is the minimum number of characters to keep in a truncated
	// tool result, even when aggressive truncation is applied.
	MinKeepChars = 2000

	// TailBudgetRatio is the fraction of the truncation budget allocated to
	// the tail portion when important tail content is detected.
	TailBudgetRatio = 0.3

	// MaxTailChars is the absolute maximum characters to keep from the tail.
	MaxTailChars = 4000

	// TailDetectionWindow is how many characters from the end to scan for
	// important tail content.
	TailDetectionWindow = 2000

	// ContextBudgetRatio is the fraction of context window (in chars) that
	// all tool results combined should not exceed.
	ContextBudgetRatio = 0.75

	// PerResultContextCap is the fraction of context window chars that a single
	// tool result should not exceed in the pre-LLM guard.
	PerResultContextCap = 0.5

	// CompactedPlaceholder replaces tool results that are compacted to free context.
	CompactedPlaceholder = "[compacted: tool output removed to free context]"
)
View Source
const (
	// ApprovalTimeout is how long to wait for user approval before giving up.
	// 120s gives ample time for users to read and respond via chat.
	ApprovalTimeout = 120 * time.Second
)
View Source
const DefaultImagePruneAfterTurns = 5

DefaultImagePruneAfterTurns is how many turns back to keep images. Images older than this are replaced with a text placeholder to prevent token accumulation from multimodal content.

View Source
const DefaultMaxHistory = 100

DefaultMaxHistory é o limite padrão de entradas no histórico por sessão.

View Source
const DefaultMaxHistoryDM = 100

DefaultMaxHistoryDM is the history limit for direct message sessions. DMs are linear conversations, so a higher limit preserves more context.

View Source
const DefaultMaxHistoryGroup = 50

DefaultMaxHistoryGroup is the history limit for group/channel sessions. Groups are noisier with more participants, so a lower limit saves context.

View Source
const DefaultSessionTTL = 24 * time.Hour

DefaultSessionTTL é o tempo de inatividade antes de uma sessão ser removida.

View Source
const (
	// DefaultToolTimeout is the maximum time a single tool execution can take.
	DefaultToolTimeout = 30 * time.Second
)
View Source
const HardMaxToolResultChars = 400_000

HardMaxToolResultChars is the absolute maximum size for a tool result. Results exceeding this are truncated before entering the conversation to prevent context overflow.

View Source
const (
	// VaultFile is the default vault file name.
	VaultFile = ".devclaw.vault"
)

Variables

AllHookEvents lists every supported hook event for discovery/documentation.

View Source
var BuiltInProfiles = map[string]ToolProfile{
	"minimal": {
		Name:        "minimal",
		Description: "Basic queries only - read-only access, no writes",
		Allow: []string{
			"group:web",
			"group:memory",
			"read_file",
			"list_files",
			"search_files",
			"glob_files",
			"describe_image",
		},
		Deny: []string{
			"group:runtime",
			"write_file",
			"edit_file",
			"group:skills",
			"group:scheduler",
			"group:vault",
			"group:subagents",
			"group:daemon",
			"group:browser",
		},
	},
	"coding": {
		Name:        "coding",
		Description: "Software development - file access, skills, vault, scheduler, browser",
		Allow: []string{
			"group:fs",
			"group:web",
			"group:memory",
			"group:scheduler",
			"group:vault",
			"group:skills",
			"group:sessions",
			"group:subagents",
			"group:daemon",
			"group:media",
			"group:browser",
			"group:skill_db",
			"bash",
			"exec",
			"apply_patch",
			"read_file",
			"write_file",
			"edit_file",
			"list_files",
			"search_files",
			"glob_files",
		},
		Deny: []string{
			"ssh",
			"scp",
		},
	},
	"messaging": {
		Name:        "messaging",
		Description: "Chat channels (WhatsApp, Discord, Telegram, Slack) - nearly full access except ssh/daemon",
		Allow: []string{
			"group:web",
			"group:memory",
			"group:scheduler",
			"group:vault",
			"group:skills",
			"group:sessions",
			"group:media",
			"group:skill_db",
			"group:fs",
			"group:subagents",
			"group:browser",
			"group:daemon",
			"bash",
			"exec",
			"apply_patch",
			"read_file",
			"write_file",
			"edit_file",
			"list_files",
			"search_files",
			"glob_files",
		},
		Deny: []string{
			"ssh",
			"scp",
			"set_env",
		},
	},
	"full": {
		Name:        "full",
		Description: "Full access - all tools available (respect per-tool permissions)",
		Allow:       []string{"*"},
		Deny:        []string{},
	},
}

BuiltInProfiles provides predefined tool profiles for common use cases.

Design rules:

  • Always use "group:xxx" references instead of individual tool names when the intent is to include all tools in a category. This ensures new tools added to a group are automatically picked up by profiles.
  • Individual tool names are used only for tools that don't belong to any group (e.g. "apply_patch") or when only a subset of a group is needed.
  • Dispatcher tools (memory, vault, scheduler, browser) internally call hidden sub-tools via executeByName which bypasses the profile guard. Using the group (e.g. "group:vault") instead of just "vault" ensures the sub-tools are also in the allow set for direct Execute() calls.
View Source
var ContentRoles = map[string]bool{
	"heading":      true,
	"paragraph":    true,
	"cell":         true,
	"rowheader":    true,
	"columnheader": true,
	"listitem":     true,
	"article":      true,
	"figure":       true,
	"img":          true,
}

ContentRoles are roles that represent content elements.

View Source
var DefaultSubagentDeniedTools = []string{

	"memory_save", "memory_search", "memory_list", "memory_index",

	"scheduler_add", "scheduler_list", "scheduler_remove", "scheduler_search",

	"skill_init", "skill_edit", "skill_add_script", "skill_list",
	"skill_test", "skill_install", "skill_defaults_list", "skill_defaults_install", "skill_remove",

	"memory", "scheduler", "skill_manage",
}

DefaultSubagentDeniedTools lists tools subagents should not access. Note: spawn tools (spawn_subagent, list_subagents, wait_subagent, stop_subagent) are intentionally omitted — they are managed by depth-based logic in createChildExecutor.

View Source
var ErrAgentYield = errors.New("agent yielded turn")

ErrAgentYield is returned when the agent's sessions_yield tool is invoked. The caller should collect pending subagent results and re-invoke the agent.

View Source
var ErrInvalidTime = errorString("invalid time format")

ErrInvalidTime is returned when a time string is invalid.

View Source
var InteractiveRoles = map[string]bool{
	"button":           true,
	"link":             true,
	"textbox":          true,
	"checkbox":         true,
	"radio":            true,
	"combobox":         true,
	"menuitem":         true,
	"menuitemcheckbox": true,
	"menuitemradio":    true,
	"tab":              true,
	"spinbutton":       true,
	"slider":           true,
	"switch":           true,
	"searchbox":        true,
	"textarea":         true,
}

InteractiveRoles are roles that represent interactive elements.

View Source
var ProviderKeyNames = map[string]string{
	"openai":      "OPENAI_API_KEY",
	"anthropic":   "ANTHROPIC_API_KEY",
	"google":      "GOOGLE_API_KEY",
	"xai":         "XAI_API_KEY",
	"groq":        "GROQ_API_KEY",
	"zai":         "ZAI_API_KEY",
	"mistral":     "MISTRAL_API_KEY",
	"openrouter":  "OPENROUTER_API_KEY",
	"cerebras":    "CEREBRAS_API_KEY",
	"minimax":     "MINIMAX_API_KEY",
	"huggingface": "HUGGINGFACE_API_KEY",
	"deepseek":    "DEEPSEEK_API_KEY",
	"custom":      "CUSTOM_API_KEY",
}

ProviderKeyNames maps provider IDs to their standard API key variable names. These follow industry conventions (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.)

View Source
var StructuralRoles = map[string]bool{
	"generic":     true,
	"group":       true,
	"list":        true,
	"table":       true,
	"row":         true,
	"region":      true,
	"section":     true,
	"document":    true,
	"webarea":     true,
	"rootWebArea": true,
}

StructuralRoles are roles that represent structural elements.

View Source
var ToolGroups = map[string][]string{
	"group:memory":    {"memory", "memory_save", "memory_search", "memory_list", "memory_index"},
	"group:web":       {"web_search", "web_fetch"},
	"group:fs":        {"read_file", "write_file", "edit_file", "list_files", "search_files", "glob_files"},
	"group:runtime":   {"bash", "exec", "ssh", "scp", "set_env"},
	"group:subagents": {"spawn_subagent", "list_subagents", "wait_subagent", "stop_subagent"},
	"group:skills": {
		"get_skill_instructions", "get_skill_reference",
		"skill_list", "skill_install", "skill_init",
		"skill_edit", "skill_add_script", "skill_test",
		"skill_defaults_list", "skill_defaults_install", "skill_remove",
	},
	"group:scheduler": {"scheduler", "scheduler_add", "scheduler_list", "scheduler_remove", "scheduler_search"},
	"group:vault":     {"vault", "vault_status", "vault_save", "vault_get", "vault_list", "vault_delete"},
	"group:sessions":  {"sessions"},
	"group:daemon":    {"daemon"},
	"group:media":     {"describe_image", "transcribe_audio", "send_media", "image-gen_generate_image"},
	"group:skill_db":  {"skill_db_query", "skill_db_list_tables", "skill_db_insert", "skill_db_update", "skill_db_delete", "skill_db_create_table", "skill_db_describe", "skill_db_drop_table"},
	"group:browser":   {"browser", "browser_navigate", "browser_screenshot", "browser_content", "browser_click", "browser_fill", "browser_snapshot", "browser_tabs", "browser_open_tab", "browser_focus_tab", "browser_close_tab", "browser_act"},
}

ToolGroups maps group names to tool name lists. Allows policy management at a higher level than individual tools.

Functions

func ApplyTranscriptPolicy added in v1.13.0

func ApplyTranscriptPolicy(messages []chatMessage, policy TranscriptPolicy) []chatMessage

ApplyTranscriptPolicy sanitizes messages according to the provider's policy.

func AuditSecrets

func AuditSecrets(cfg *Config, logger *slog.Logger)

AuditSecrets checks for hardcoded secrets and logs warnings. Should be called on startup to alert the user.

func CallerJIDFromContext

func CallerJIDFromContext(ctx context.Context) string

CallerJIDFromContext extracts the caller JID from context.

func CategorizeToolNames added in v1.12.0

func CategorizeToolNames(names []string) map[string][]string

CategorizeToolNames groups tool names by category.

func CategorizeTools added in v1.12.0

func CategorizeTools(tools []ToolDefinition) map[string][]ToolDefinition

CategorizeTools groups tool definitions by category for display purposes.

func CheckSkillSetup added in v1.12.0

func CheckSkillSetup(skill skills.Skill, vault skills.VaultReader) (*skills.SetupStatus, error)

CheckSkillSetup checks if a skill is properly configured. Returns setup status and nil if check was performed, or error if skill doesn't support setup checking.

func CollapseToolResults added in v1.17.0

func CollapseToolResults(messages []chatMessage, maxChars int) int

CollapseToolResults truncates oversized tool results in-place without clearing them. This is the cheapest compaction level — just trims excessively long outputs. Returns the number of results collapsed.

func ContextWithAgentRun added in v1.14.0

func ContextWithAgentRun(ctx context.Context, a *AgentRun) context.Context

ContextWithAgentRun returns a context carrying the AgentRun.

func ContextWithCaller

func ContextWithCaller(ctx context.Context, level AccessLevel, jid string) context.Context

ContextWithCaller returns a new context carrying the caller's access level and JID. This replaces the global SetCallerContext/SetSessionContext pattern, making tool security checks goroutine-safe (context per request).

func ContextWithDelivery

func ContextWithDelivery(ctx context.Context, channel, chatID string) context.Context

ContextWithDelivery returns a new context carrying the delivery target. This is used by tools like the scheduler dispatcher to know where to deliver scheduled messages.

func ContextWithFastMode added in v1.14.0

func ContextWithFastMode(ctx context.Context, fast bool) context.Context

ContextWithFastMode returns a context with fast mode enabled/disabled.

func ContextWithMediaEmitter added in v1.15.0

func ContextWithMediaEmitter(ctx context.Context, fn MediaEmitter) context.Context

ContextWithMediaEmitter returns a new context carrying a media emitter callback.

func ContextWithMessageID added in v1.14.0

func ContextWithMessageID(ctx context.Context, msgID string) context.Context

ContextWithMessageID returns a context carrying the triggering message ID.

func ContextWithProgressSender

func ContextWithProgressSender(ctx context.Context, fn ProgressSender) context.Context

ContextWithProgressSender returns a context carrying a ProgressSender callback.

func ContextWithSession

func ContextWithSession(ctx context.Context, sessionID string) context.Context

ContextWithSession returns a new context carrying the given session ID.

func ContextWithSpawnDepth added in v1.8.0

func ContextWithSpawnDepth(ctx context.Context, depth int) context.Context

ContextWithSpawnDepth returns a new context with the spawn depth set.

func ContextWithToolOutcomeLog added in v1.18.2

func ContextWithToolOutcomeLog(ctx context.Context, log *ToolOutcomeLog) context.Context

ContextWithToolOutcomeLog attaches an outcome log to the context.

func ContextWithToolOverlay added in v1.16.0

func ContextWithToolOverlay(ctx context.Context, overlay *ToolOverlay) context.Context

ContextWithToolOverlay returns a context carrying the tool overlay.

func ContextWithToolProfile

func ContextWithToolProfile(ctx context.Context, profile *ToolProfile) context.Context

ContextWithToolProfile returns a new context carrying a tool profile. The profile is used for CheckWithProfile to apply allow/deny lists.

func ContextWithVaultReader added in v1.12.0

func ContextWithVaultReader(ctx context.Context, vr skills.VaultReader) context.Context

ContextWithVaultReader returns a new context carrying a vault reader.

func ContextWithWorkspaceID added in v1.16.0

func ContextWithWorkspaceID(ctx context.Context, wsID string) context.Context

ContextWithWorkspaceID returns a context carrying the workspace ID.

func DeleteKeyring

func DeleteKeyring(key string) error

DeleteKeyring removes a secret from the OS keyring.

func DetectInjectionPattern

func DetectInjectionPattern(text string) bool

DetectInjectionPattern checks if a text contains known prompt injection patterns. Returns true if any pattern matches (the text should NOT be auto-captured).

func EmitHierarchyEnabled added in v1.18.0

func EmitHierarchyEnabled(logger *slog.Logger, enabled bool)

EmitHierarchyEnabled logs the feature flag state as a gauge. Called once at startup and on any config reload. Log format matches EmitSnapshot.

func EstimateTokens added in v1.14.0

func EstimateTokens(s string) int

EstimateTokens provides a rough token count estimate (len/4).

func ExpandProfileList

func ExpandProfileList(items []string, allTools []string) []string

ExpandProfileList expands a profile's allow/deny lists into tool names. Handles groups ("group:name") and wildcards ("git_*").

func ExpandToolGroups

func ExpandToolGroups(names []string) []string

ExpandToolGroups expands group references (e.g. "group:memory") into individual tool names. Non-group entries are passed through as-is.

func ExtractLinksFromMessage added in v1.13.0

func ExtractLinksFromMessage(body string, maxLinks int) []string

ExtractLinksFromMessage extracts HTTP(S) URLs from a message body. Strips markdown link formatting and limits to maxLinks.

func ExtractReadableText added in v1.13.0

func ExtractReadableText(r io.Reader) (title string, text string)

ExtractReadableText converts raw HTML into clean readable text with minimal markdown formatting (headings, links, lists). It prioritizes <main>, <article>, and <body> content while skipping navigation, footers, scripts, and styles.

func ExtractTokenFromMessage

func ExtractTokenFromMessage(content string) string

ExtractTokenFromMessage attempts to extract a pairing token from message content. Tokens are 48+ hex characters. Returns empty string if not a token attempt.

func FindConfigFile

func FindConfigFile() string

FindConfigFile searches for config files in standard locations.

func FormatAbortReply added in v1.12.0

func FormatAbortReply(stoppedSubagents int) string

FormatAbortReply formats the reply message after an abort.

func FormatCollectedMessages

func FormatCollectedMessages(msgs []*channels.IncomingMessage) string

FormatCollectedMessages combines multiple messages into a single prompt (used by QueueModeCollect).

func FormatForChannel

func FormatForChannel(text, channel string) string

FormatForChannel dispatches to the appropriate formatter based on channel. Reply tags ([[reply_to_current]], [[reply_to:<id>]]) are stripped before formatting so they never reach the user.

func FormatForPlainText

func FormatForPlainText(text string) string

FormatForPlainText strips all Markdown, leaving only plain text.

func FormatForSlack

func FormatForSlack(text string) string

FormatForSlack converts Markdown to Slack's mrkdwn format. Slack uses: *bold*, _italic_, ~strike~, `code`, ```preformatted```.

func FormatForTelegram

func FormatForTelegram(text string) string

FormatForTelegram converts Markdown to Telegram HTML. Telegram supports: <b>, <i>, <code>, <pre>, <a href="">, <s>, <u>.

func FormatForWhatsApp

func FormatForWhatsApp(text string) string

FormatForWhatsApp converts standard Markdown to WhatsApp-compatible formatting. WhatsApp supports: *bold*, _italic_, ~strikethrough~, `monospace`, ```code blocks```. Headers become bold; links are flattened; images become [Image: alt]; lists use •.

func FormatIdentityMd added in v1.16.0

func FormatIdentityMd(id *IdentityConfig) string

FormatIdentityMd converts an IdentityConfig to markdown content.

func FormatIncompleteWorkMessage added in v1.17.0

func FormatIncompleteWorkMessage(items []IncompleteWork) string

FormatIncompleteWorkMessage formats incomplete work items into a single message that can be injected into the conversation to prompt continuation.

func FormatLinkResults added in v1.13.0

func FormatLinkResults(results []LinkResult) string

FormatLinkResults formats link results as context to prepend to the user message.

func FormatMemoriesForStorage added in v1.17.0

func FormatMemoriesForStorage(memories []ExtractedMemory, sessionID string) string

FormatMemoriesForStorage formats extracted memories into a text block suitable for saving to the memory store.

func FormatSetupPrompt added in v1.12.0

func FormatSetupPrompt(skillName string, status *skills.SetupStatus) string

FormatSetupPrompt creates a user-friendly prompt for missing configuration.

func FormatToolNamesForPrompt added in v1.12.0

func FormatToolNamesForPrompt(names []string) string

FormatToolNamesForPrompt formats tool names as a compact list for the system prompt. Groups by category for better readability.

func FormatToolsForPrompt added in v1.12.0

func FormatToolsForPrompt(tools []ToolDefinition, maxDescLen int) string

FormatToolsForPrompt formats tools as a compact list for the system prompt. Groups by category and truncates descriptions to fit within budget.

func GenerateSummaryID added in v1.14.0

func GenerateSummaryID(content string, ts time.Time) string

GenerateSummaryID creates a deterministic summary ID from content and timestamp.

func GetDeliveryTarget added in v1.12.0

func GetDeliveryTarget(ctx context.Context) (channel, chatID string)

GetDeliveryTarget is a convenience function that extracts the delivery target from the context. Tools should use this to get channel/chatID context.

Example:

func myToolHandler(ctx context.Context, args map[string]any) (any, error) {
	channel, chatID := GetDeliveryTarget(ctx)
	if channel == "" {
		return nil, fmt.Errorf("no channel context")
	}
	// Use channel and chatID...
}

func GetKeyring

func GetKeyring(key string) string

GetKeyring retrieves a secret from the OS keyring. Returns empty string if not found.

func GetProviderKeyName added in v1.8.1

func GetProviderKeyName(provider string) string

GetProviderKeyName returns the standard API key variable name for a provider. Falls back to "API_KEY" for unknown providers.

func GuardToolResultContext added in v1.13.0

func GuardToolResultContext(messages []chatMessage, contextWindowTokens int) []chatMessage

GuardToolResultContext applies the pre-LLM context guard to all messages. It caps individual tool results and compacts oldest tool results when the total content exceeds the context budget.

contextWindowTokens is the model's context window in tokens. We estimate 1 token ≈ 4 chars for budget calculation.

func HasAbortPrefix added in v1.12.0

func HasAbortPrefix(text string) bool

HasAbortPrefix checks if text starts with an abort-related prefix. Useful for early detection in message processing.

func HasImportantTail added in v1.13.0

func HasImportantTail(text string) bool

HasImportantTail returns true if the last portion of text contains patterns that suggest important content (errors, JSON closings, summaries, etc.).

func HistoryLimitForType added in v1.13.0

func HistoryLimitForType(isGroup bool) int

HistoryLimitForType returns the appropriate history limit based on whether the conversation is a DM or group/channel.

func HookEventDescription

func HookEventDescription(ev HookEvent) string

HookEventDescription returns a human-readable description for a hook event.

func IncClassifierPass added in v1.18.0

func IncClassifierPass()

IncClassifierPass increments the legacy classifier phase run counter.

func IncContextRouterDefault added in v1.18.0

func IncContextRouterDefault()

IncContextRouterDefault atomically increments the default counter.

func IncContextRouterDisabled added in v1.18.0

func IncContextRouterDisabled()

IncContextRouterDisabled atomically increments the disabled counter.

func IncContextRouterHeuristic added in v1.18.0

func IncContextRouterHeuristic()

IncContextRouterHeuristic atomically increments the heuristic counter.

func IncContextRouterMapped added in v1.18.0

func IncContextRouterMapped()

IncContextRouterMapped atomically increments the mapped counter.

func IncL1CacheHit added in v1.18.0

func IncL1CacheHit()

IncL1CacheHit increments the L1 essential story cache-hit counter.

func IncL1CacheMiss added in v1.18.0

func IncL1CacheMiss()

IncL1CacheMiss increments the L1 essential story cache-miss counter.

func IncLayerTokensL0 added in v1.18.0

func IncLayerTokensL0(n int)

IncLayerTokensL0 adds n to the L0 layer byte counter.

func IncLayerTokensL1 added in v1.18.0

func IncLayerTokensL1(n int)

IncLayerTokensL1 adds n to the L1 layer byte counter.

func IncLayerTokensL2 added in v1.18.0

func IncLayerTokensL2(n int)

IncLayerTokensL2 adds n to the L2 layer byte counter.

func IncSaveWingRouted added in v1.18.0

func IncSaveWingRouted()

IncSaveWingRouted increments the count of memory_save calls that successfully routed a wing via context or explicit argument.

func IncSearchWithWingFilter added in v1.18.0

func IncSearchWithWingFilter()

IncSearchWithWingFilter atomically increments the wing-filtered search counter.

func IncSearchWithoutWingFilter added in v1.18.0

func IncSearchWithoutWingFilter()

IncSearchWithoutWingFilter atomically increments the unfiltered search counter.

func IncToolCall added in v1.18.0

func IncToolCall(name string)

IncToolCall atomically increments the counter for a given palace tool name. Unknown names are ignored (no-op) rather than creating new counters at runtime — this keeps the metric cardinality bounded.

func InferProfileForChannel added in v1.13.0

func InferProfileForChannel(channel string) string

InferProfileForChannel returns the default tool profile name for a channel. Messaging channels (WhatsApp, Discord, Telegram, Slack) get "messaging" to avoid exposing filesystem/runtime tools. WebUI and CLI get "full". Named instances (e.g. "whatsapp:business") are matched by their base type.

func InferToolCategory added in v1.12.0

func InferToolCategory(name string) string

InferToolCategory determines the category of a tool from its name. Used for grouping tools in the system prompt and list_capabilities output.

func IsAbortRequestText added in v1.12.0

func IsAbortRequestText(text string) bool

IsAbortRequestText checks if text is an abort request, handling both /stop command and natural language triggers.

func IsAbortTrigger added in v1.12.0

func IsAbortTrigger(text string) bool

IsAbortTrigger checks if the given text is a standalone abort trigger. It normalizes the text by: - Converting to lowercase - Normalizing unicode (NFKC) - Stripping mentions and structural prefixes - Removing trailing punctuation - Collapsing whitespace

func IsCommand

func IsCommand(content string) bool

IsCommand returns true if the message starts with "/".

func IsEnvReference

func IsEnvReference(s string) bool

IsEnvReference checks if a string is an environment variable reference.

func IsLikelyContextOverflowError added in v1.13.0

func IsLikelyContextOverflowError(errMsg string) bool

IsLikelyContextOverflowError returns true if the error message indicates a context window overflow. Context overflow errors should NOT trigger model failover because rotating to a model with a potentially smaller context window makes things worse. Aligned with OpenClaw's isLikelyContextOverflowError + isContextOverflowError.

func IsMutatingToolCall added in v1.13.0

func IsMutatingToolCall(name string, args map[string]any) bool

IsMutatingToolCall checks if a specific tool call is mutating by examining the tool name and its action parameter (if present).

func IsMutatingToolName added in v1.13.0

func IsMutatingToolName(name string) bool

IsMutatingToolName returns true if the tool is generally a mutating tool.

func IsRecoverableToolError added in v1.13.0

func IsRecoverableToolError(errMsg string) bool

IsRecoverableToolError returns true if the error message suggests a recoverable issue (missing params, invalid input, transient failure) vs a hard failure that the model cannot recover from.

func KeyringAvailable

func KeyringAvailable() bool

KeyringAvailable checks if the OS keyring is accessible.

func ListProfiles

func ListProfiles(customProfiles map[string]ToolProfile) []string

ListProfiles returns all available profile names.

func LooksLikeCredential added in v1.18.0

func LooksLikeCredential(content string) bool

LooksLikeCredential is the exported version for use by the security package.

func MakeSessionID

func MakeSessionID(channel, chatID string) string

MakeSessionID returns a compact hash-based session ID from channel and chatID. For backward compatibility, this is still used as the primary key.

func MatchesPattern

func MatchesPattern(toolName, pattern string) bool

MatchesPattern checks if a tool name matches a pattern. Supports glob-style wildcards: "git_*" matches "git_status", "git_commit", etc.

func MessageIDFromCtx added in v1.14.0

func MessageIDFromCtx(ctx context.Context) string

MessageIDFromCtx extracts the triggering message ID from context.

func MicroCompact added in v1.17.0

func MicroCompact(messages []chatMessage, keepLastN int) ([]chatMessage, int)

MicroCompact replaces old tool result contents with a placeholder to free context tokens without requiring an LLM summarization call. Clears any tool result that is large enough (>200 chars) in the older part of the conversation. Short results (e.g. "file written successfully") are kept as they consume negligible tokens.

keepLastN specifies how many recent messages to protect from clearing. Returns the modified messages and the number of results cleared.

func MicroCompactByRatio added in v1.17.0

func MicroCompactByRatio(messages []chatMessage, ratio float64, cfg ContextPruningConfig) ([]chatMessage, int)

MicroCompactByRatio applies micro-compaction using the context pruning config ratios. Tools older than the protect window are cleared based on the usage ratio:

  • Above softTrimRatio: trim head+tail to SoftTrimMaxChars
  • Above hardClearRatio: replace with placeholder entirely

func MigrateKeyToKeyring

func MigrateKeyToKeyring(apiKey string, logger *slog.Logger) error

MigrateKeyToKeyring moves an API key from config/env to the OS keyring and clears it from the original location.

func MigrateToSQLite

func MigrateToSQLite(db *sql.DB, dataDir string, logger *slog.Logger)

MigrateToSQLite imports legacy JSON/JSONL data into the central database. It is safe to call multiple times: once the .bak file exists, migration is skipped for that component. Called once from assistant.Start().

func NormalizeTelegramID added in v1.16.5

func NormalizeTelegramID(id string) string

NormalizeTelegramID is exported for use by API handlers.

func OpenDatabase

func OpenDatabase(path string) (*sql.DB, error)

OpenDatabase opens (or creates) the central devclaw.db at the given path. It enables WAL mode for concurrent read performance and creates all tables.

func ProvenanceReason added in v1.18.2

func ProvenanceReason(content string, outcomes []ToolOutcome) string

ProvenanceReason returns a non-empty reason string if `content` should be rejected as a fact save because it echoes the subject of a tool call that failed earlier in this turn AND no later successful call on the same subject has been observed.

The comparison uses generic token overlap (identifier-like tokens of length ≥ 3, with a small locale-agnostic stopword set). No tool-specific names, no error-phrase keywords, no PT/EN heuristics.

func ReadPassword

func ReadPassword(prompt string) (string, error)

ReadPassword reads a password from the terminal without echoing. Falls back to regular stdin reading if terminal is not available.

func RedactCredentials added in v1.18.0

func RedactCredentials(content string) string

RedactCredentials is the exported version for use by the security package.

func RegisterAgentTools added in v1.16.0

func RegisterAgentTools(executor *ToolExecutor, wsMgr *WorkspaceManager)

RegisterAgentTools registers the agent_manage dispatcher tool.

func RegisterApplyPatchTool added in v1.12.0

func RegisterApplyPatchTool(executor *ToolExecutor)

RegisterApplyPatchTool registers the apply_patch tool in the executor.

func RegisterBrowserTools

func RegisterBrowserTools(executor *ToolExecutor, browserMgr *BrowserManager, llmClient *LLMClient, mediaCfg MediaConfig, logger *slog.Logger)

RegisterBrowserTools registers browser automation tools in the executor. If llmClient is provided and vision is enabled, browser_screenshot will automatically describe the image using vision instead of returning raw base64.

func RegisterCanvasTools

func RegisterCanvasTools(executor *ToolExecutor, canvasHost *CanvasHost, logger *slog.Logger)

RegisterCanvasTools registers canvas tools in the executor.

func RegisterCodebaseTools

func RegisterCodebaseTools(executor *ToolExecutor)

RegisterCodebaseTools registers codebase analysis tools in the executor.

func RegisterDBHubTools added in v1.8.0

func RegisterDBHubTools(executor *ToolExecutor, hub *database.Hub)

RegisterDBHubTools registers database hub management tools. These tools operate on the internal database hub, not external databases.

func RegisterDBTools

func RegisterDBTools(executor *ToolExecutor)

RegisterDBTools registers database query and management tools.

func RegisterDaemonTools

func RegisterDaemonTools(executor *ToolExecutor, dm *DaemonManager)

RegisterDaemonTools registers a single "daemon" dispatcher tool that consolidates start, logs, list, stop, restart actions.

func RegisterDevUtilTools

func RegisterDevUtilTools(executor *ToolExecutor)

RegisterDevUtilTools registers developer utility tools.

func RegisterDockerTools

func RegisterDockerTools(executor *ToolExecutor)

RegisterDockerTools registers Docker management tools in the executor.

func RegisterEnvTools

func RegisterEnvTools(executor *ToolExecutor)

RegisterEnvTools registers system information and diagnostic tools.

func RegisterGitTools

func RegisterGitTools(executor *ToolExecutor)

RegisterGitTools registers native Git tools in the executor.

func RegisterIDETools

func RegisterIDETools(executor *ToolExecutor)

RegisterIDETools registers IDE extension configuration tools.

func RegisterKGTools added in v1.18.0

func RegisterKGTools(executor *ToolExecutor, sqliteStore *memory.SQLiteStore)

RegisterKGTools registers the 6 knowledge-graph tools.

func RegisterLCMDispatcher added in v1.14.0

func RegisterLCMDispatcher(executor *ToolExecutor, engine *LCMEngine, llm *LLMClient)

RegisterLCMDispatcher registers the `lcm` tool for lossless memory retrieval. The llm parameter is optional; when nil, the expand_query action is unavailable.

func RegisterLegacyAliases added in v1.13.0

func RegisterLegacyAliases(executor *ToolExecutor)

RegisterLegacyAliases registers dispatcher tools. Memory, vault, and scheduler are visible (primary tools); skill_manage is hidden (backward compat only).

func RegisterMediaTools

func RegisterMediaTools(executor *ToolExecutor, llmClient *LLMClient, cfg *Config, logger *slog.Logger)

RegisterMediaTools registers describe_image and transcribe_audio tools when the LLM client and config support them. If VisionProviders or TranscriptionProviders are configured in MediaConfig, a MediaRegistry is used for priority-based fallback across multiple providers.

func RegisterMemoryHierarchyTools added in v1.18.0

func RegisterMemoryHierarchyTools(executor *ToolExecutor, cfg MemoryHierarchyDispatcherConfig)

RegisterMemoryHierarchyTools registers the six palace-aware memory tools on the given ToolExecutor. This is additive — existing memory tools are not touched.

The registration happens unconditionally (even when cfg.Enabled is false) so that the LLM always sees the tools in its schema. At call time, each tool checks the flag and returns errHierarchyDisabled if off.

func RegisterMemoryTools added in v1.12.0

func RegisterMemoryTools(executor *ToolExecutor, cfg MemoryDispatcherConfig)

RegisterMemoryTools registers individual memory tools. Replaces the old dispatcher pattern with focused tools: memory_save, memory_search, memory_list, memory_index.

func RegisterMultiUserTools

func RegisterMultiUserTools(executor *ToolExecutor, um *UserManager)

RegisterMultiUserTools registers multi-user management tools.

func RegisterNativeMediaTools added in v1.8.0

func RegisterNativeMediaTools(executor *ToolExecutor, mediaSvc *media.MediaService, channelMgr *channels.Manager, logger *slog.Logger)

RegisterNativeMediaTools registers the unified send_media tool for the LLM to send media (images, audio, documents, video) to users through the channel manager. mediaSvc may be nil — in that case only file_path is supported (no media_id or url); this ensures the send_media tool is always available when channels are connected.

func RegisterOpsTools

func RegisterOpsTools(executor *ToolExecutor)

RegisterOpsTools registers operations and deployment tools.

func RegisterPluginAgentDelegation added in v1.16.0

func RegisterPluginAgentDelegation(
	executor *ToolExecutor,
	registry *plugins.Registry,
	subagentMgr *SubagentManager,
	llmClient *LLMClient,
)

RegisterPluginAgentDelegation registers the full plugin agent delegation with access to the SubagentManager and LLMClient. Called after Start().

func RegisterPluginManagementTools added in v1.16.0

func RegisterPluginManagementTools(executor *ToolExecutor, registry *plugins.Registry)

RegisterPluginManagementTools registers tools for managing plugins at runtime.

func RegisterProductTools

func RegisterProductTools(executor *ToolExecutor)

RegisterProductTools registers product management tools.

func RegisterSchedulerDispatcher added in v1.13.0

func RegisterSchedulerDispatcher(executor *ToolExecutor, sched *scheduler.Scheduler, skillDB *SkillDB)

RegisterSchedulerDispatcher registers individual scheduler tools: scheduler_add, scheduler_list, scheduler_remove, and scheduler_search.

func RegisterSessionsDispatcher added in v1.13.0

func RegisterSessionsDispatcher(executor *ToolExecutor, wm *WorkspaceManager)

RegisterSessionsDispatcher registers a single "sessions" dispatcher tool that replaces sessions_list, sessions_delete, sessions_export, sessions_send.

func RegisterSessionsYieldTool added in v1.14.0

func RegisterSessionsYieldTool(executor *ToolExecutor)

RegisterSessionsYieldTool registers the sessions_yield tool that allows an agent to end its current turn and receive pending subagent results. The tool signals the agent loop via the AgentRun's yieldRequested flag.

func RegisterSkillCreatorTools

func RegisterSkillCreatorTools(executor *ToolExecutor, registry *skills.Registry, skillsDir string, skillDB *SkillDB, builtinSkills *BuiltinSkills, reloadCb SkillReloadCallback, logger *slog.Logger)

RegisterSkillCreatorTools registers individual skill management tools. Replaces the old dispatcher pattern with focused tools: skill_init, skill_edit, skill_add_script, skill_list, skill_test, skill_install, skill_defaults_list, skill_defaults_install, skill_remove, get_skill_instructions, get_skill_reference.

func RegisterSkillDBTools added in v1.12.0

func RegisterSkillDBTools(executor *ToolExecutor, skillDB *SkillDB)

RegisterSkillDBTools registers the skill database tools in the executor. Each database operation is registered as its own tool with a focused schema.

func RegisterSubagentTools

func RegisterSubagentTools(
	executor *ToolExecutor,
	manager *SubagentManager,
	llmClient *LLMClient,
	promptComposer *PromptComposer,
	logger *slog.Logger,
)

RegisterSubagentTools registers the spawn_subagent, list_subagents, wait_subagent, and stop_subagent tools in the tool executor. These allow the main agent to create and manage child agents.

func RegisterSystemTools

func RegisterSystemTools(executor *ToolExecutor, sandboxRunner *sandbox.Runner, memStore *memory.FileStore, sqliteStore *memory.SQLiteStore, memCfg MemoryConfig, contextRouter *ContextRouter, sched *scheduler.Scheduler, dataDir string, ssrfGuard *security.SSRFGuard, vault *Vault, webSearchCfg WebSearchConfig, skillDB *SkillDB, gatewayCfg GatewayConfig, toolGuardCfg ToolGuardConfig)

RegisterSystemTools registers all built-in system tools in the executor. These are core tools available regardless of which skills are loaded. If ssrfGuard is non-nil, web_fetch will validate URLs against SSRF rules. contextRouter may be nil; when non-nil it is forwarded to the memory tools so that memory_save can route new memories to palace wings (Sprint 2 Room 2.0b).

func RegisterTestingTools

func RegisterTestingTools(executor *ToolExecutor)

RegisterTestingTools registers testing engine tools.

func RegisterVaultTools added in v1.13.0

func RegisterVaultTools(executor *ToolExecutor, vault *Vault)

RegisterVaultTools registers individual vault tools. Replaces the old dispatcher pattern with focused tools: vault_status, vault_save, vault_get, vault_list, vault_delete.

func ReloadEnvFiles

func ReloadEnvFiles() (int, error)

ReloadEnvFiles forces a reload of .env files with override. Returns the number of variables loaded.

func RepairToolUseResultPairing added in v1.13.0

func RepairToolUseResultPairing(messages []chatMessage) []chatMessage

RepairToolUseResultPairing scans messages and removes orphan tool results (tool_result messages whose corresponding tool_use was removed) and orphan tool_use calls (assistant tool calls with no matching tool result). This prevents API rejections from providers that require strict pairing.

func ResolveContextWindowTokens added in v1.13.0

func ResolveContextWindowTokens(configOverride int, modelName string) int

ResolveContextWindowTokens determines the effective context window for a model. Priority: configOverride > discovered (via provider discovery) > static lookup > default.

func ResolveProfile

func ResolveProfile(name string, customProfiles map[string]ToolProfile) (allow, deny []string)

ResolveProfile returns the allow and deny lists for a profile. Checks built-in profiles first, then custom profiles. Returns nil lists if profile not found.

func ResolveSecrets added in v1.16.5

func ResolveSecrets(cfg *Config)

ResolveSecrets fills in config secrets from environment variables when the config value is empty or a placeholder. Called during initial config load and again after vault injection.

func RunAsync added in v1.12.0

func RunAsync(ctx context.Context, config AsyncToolConfig, fn func(ctx context.Context) *ToolResult)

RunAsync executes a function in the background and calls the callback when done. This is a helper for tools that need to run long operations asynchronously.

The function returns immediately with an AsyncResult. The actual work happens in a background goroutine, and the callback is invoked when complete.

The context is propagated to the async function, so cancellation of the parent context will also cancel the async operation.

Example:

func myAsyncTool(ctx context.Context, args map[string]any) (any, error) {
	config := AsyncToolConfig{
		Label: "processing files",
		OnComplete: func(result *ToolResult) {
			// Handle completion - e.g., send to user
		},
	}

	RunAsync(ctx, config, func(ctx context.Context) *ToolResult {
		// Long-running operation
		return &ToolResult{Content: "done"}
	})

	return AsyncResult("Started processing files..."), nil
}

func SanitizeMemoryContent

func SanitizeMemoryContent(content string) string

SanitizeMemoryContent escapes HTML entities and strips dangerous patterns from memory content before injection into prompts.

func SanitizeToolCallID added in v1.13.0

func SanitizeToolCallID(id string, mode ToolCallIDMode) string

SanitizeToolCallID cleans a tool call ID to be safe for the target provider.

func SaveConfigToFile

func SaveConfigToFile(cfg *Config, path string) error

SaveConfigToFile writes a Config as YAML to the specified path. Secrets are replaced with environment variable references. Creates a backup (.bak) of the existing file before overwriting to prevent data loss from crashes or invalid writes.

func ScaffoldWorkspaceDir added in v1.16.0

func ScaffoldWorkspaceDir(wsID string, ws *Workspace) error

ScaffoldWorkspaceDir creates the workspace directory and seeds template files. Files are written only if they don't exist (preserves user edits). If ws has Soul/Identity content, those are written instead of templates.

func ScaledMaxRetries added in v1.13.0

func ScaledMaxRetries(profileCount int) int

ScaledMaxRetries computes a retry budget scaled by the number of available auth profiles. More profiles = more credential rotation capacity, so the system can afford more retries before giving up on a transient failure. Formula: min(160, max(32, 24 + profileCount * 8))

func SessionIDFromContext

func SessionIDFromContext(ctx context.Context) string

SessionIDFromContext extracts the session ID from a context. Returns empty string if not set.

func SetGlobalMediaRegistry added in v1.13.0

func SetGlobalMediaRegistry(r *MediaRegistry)

SetGlobalMediaRegistry sets the global media registry instance.

func SkillNeedsSetup added in v1.12.0

func SkillNeedsSetup(skill skills.Skill, vault skills.VaultReader) bool

SkillNeedsSetup returns true if a skill requires configuration that is missing.

func Slugify added in v1.16.0

func Slugify(name string) string

Slugify converts a name to a URL-friendly slug (lowercase, hyphens).

func SpawnDepthFromContext added in v1.8.0

func SpawnDepthFromContext(ctx context.Context) int

SpawnDepthFromContext retrieves the current spawn depth from context. Returns 0 if not set (main agent level).

func SplitMessage

func SplitMessage(text string, maxLen int) []string

SplitMessage splits a long message into chunks respecting maxLen. Tries to break on paragraph boundaries, then sentence boundaries, then word boundaries. Does not split inside ``` code blocks.

func StartPersistentSessionReaper added in v1.13.0

func StartPersistentSessionReaper(ctx context.Context, storePath string, maxAgeDays int, logger *slog.Logger)

StartPersistentSessionReaper runs a background goroutine that deletes session files older than maxAgeDays. It runs once daily and on startup. storePath is the directory containing session JSONL/SQLite files.

func StoreKeyring

func StoreKeyring(key, value string) error

StoreKeyring saves a secret to the OS keyring.

func StripDangerousTags added in v1.13.0

func StripDangerousTags(s string) string

StripDangerousTags removes XML/HTML-like tags that could confuse the LLM into treating content as structured instructions.

func StripExternalContentBoundaries

func StripExternalContentBoundaries(content string) string

StripExternalContentBoundaries removes the untrusted content wrappers (useful when preparing content for display to the user).

func StripInternalTags

func StripInternalTags(text string) string

StripInternalTags removes all internal control tags and sentinel tokens from LLM output so they never reach the user. Handles:

  • [[reply_to_*]] delivery tags
  • <final>...</final> and <thinking>...</thinking> XML tags
  • [Tools used: ...] annotations (LLM may mimic the history format)
  • [Previous tool calls in this turn:] + tool call lines (history context leak)
  • Fake "System: ..." messages fabricated by the LLM
  • NO_REPLY / HEARTBEAT_OK sentinel tokens

func StripReplyTags

func StripReplyTags(text string) string

StripReplyTags is an alias for StripInternalTags for backward compatibility.

func SynthesizeFindings added in v1.17.0

func SynthesizeFindings(results []WorkerResult) string

SynthesizeFindings combines research results into a summary for the coordinator.

func SynthesizeProgressSummary added in v1.16.0

func SynthesizeProgressSummary(fragments []string) string

SynthesizeProgressSummary builds a concise summary from collected assistant fragments. Used by subagent timeout handling to report partial progress instead of a generic timeout message.

func TruncateToolResult added in v1.13.0

func TruncateToolResult(text string, maxChars int) string

TruncateToolResult applies smart truncation to a single tool result. When important tail content is detected, it uses a head+tail strategy; otherwise it uses head-only truncation.

func ValidateIdentity added in v1.13.0

func ValidateIdentity(id IdentityConfig) []string

ValidateIdentity checks for common issues in an identity configuration. Returns a list of warnings (empty = valid).

func VaultReaderFromContext added in v1.12.0

func VaultReaderFromContext(ctx context.Context) skills.VaultReader

VaultReaderFromContext extracts the vault reader from context. Returns nil if not set.

func WorkspaceIDFromContext added in v1.16.0

func WorkspaceIDFromContext(ctx context.Context) string

WorkspaceIDFromContext extracts the workspace ID from context.

func WrapExternalContent

func WrapExternalContent(source, content string) string

WrapExternalContent wraps content from external sources (web_fetch, browser, web_search) with untrusted content boundaries to prevent prompt injection replay during compaction.

func WrapMemoriesForPrompt

func WrapMemoriesForPrompt(memories []string) string

WrapMemoriesForPrompt wraps sanitized memory entries with the untrusted data boundary so the LLM treats them as historical context, not instructions.

Types

type APIConfig

type APIConfig struct {
	// BaseURL is the API base URL (OpenAI-compatible endpoint).
	// Examples:
	//   https://api.openai.com/v1           (OpenAI)
	//   https://api.z.ai/api/anthropic      (GLM / Anthropic proxy)
	//   https://api.anthropic.com/v1        (Anthropic direct)
	BaseURL string `yaml:"base_url"`

	// APIKey is the authentication key for the provider.
	// Can also be set via the DEVCLAW_API_KEY environment variable.
	APIKey string `yaml:"api_key"`

	// Provider hints which SDK to use ("openai", "anthropic", "glm").
	// Auto-detected from base_url if omitted.
	Provider string `yaml:"provider"`

	// Params holds provider-specific parameters:
	//   context1m: true   — enable Anthropic 1M context beta for Opus/Sonnet
	//   tool_stream: true — enable real-time tool call streaming (Z.AI)
	Params map[string]any `yaml:"params"`
}

APIConfig configures the LLM provider endpoint and credentials.

type AXNode added in v1.13.0

type AXNode struct {
	NodeID      string    `json:"nodeId"`
	Role        AXRole    `json:"role"`
	Name        AXValue   `json:"name,omitempty"`
	Value       AXValue   `json:"value,omitempty"`
	Description AXValue   `json:"description,omitempty"`
	Properties  any       `json:"properties,omitempty"`
	Children    []*AXNode `json:"children,omitempty"`

	// Ref is the generated reference (e1, e2, ...) for interactive elements.
	Ref string `json:"ref,omitempty"`
}

AXNode represents a node in the accessibility tree from CDP.

type AXRole added in v1.13.0

type AXRole struct {
	Value string
}

AXRole is a custom type that can unmarshal from either a string or an object. Chrome CDP sometimes returns role as {"type": "role", "value": "button"} and sometimes as just "button".

func (AXRole) String added in v1.13.0

func (r AXRole) String() string

String returns the role value as string.

func (*AXRole) UnmarshalJSON added in v1.13.0

func (r *AXRole) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler for AXRole.

type AXValue added in v1.13.0

type AXValue struct {
	Value string
}

AXValue is a custom type that can unmarshal from either a string or an object. Chrome CDP sometimes returns name/value as {"type": "string", "value": "text"} and sometimes as just "text".

func (AXValue) String added in v1.13.0

func (v AXValue) String() string

String returns the value as string.

func (*AXValue) UnmarshalJSON added in v1.13.0

func (v *AXValue) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler for AXValue.

type AccessConfig

type AccessConfig struct {
	// DefaultPolicy determines behavior for unknown contacts.
	// "deny" (default) = ignore unknown, "allow" = respond to all, "ask" = request access.
	DefaultPolicy AccessPolicy `yaml:"default_policy"`

	// Owners have full control (phone numbers or JIDs).
	Owners []string `yaml:"owners"`

	// Admins can manage users and workspaces.
	Admins []string `yaml:"admins"`

	// AllowedUsers can interact with the bot.
	AllowedUsers []string `yaml:"allowed_users"`

	// BlockedUsers are explicitly blocked.
	BlockedUsers []string `yaml:"blocked_users"`

	// AllowedGroups are group IDs where the bot responds.
	AllowedGroups []string `yaml:"allowed_groups"`

	// BlockedGroups are group IDs where the bot stays silent.
	BlockedGroups []string `yaml:"blocked_groups"`

	// AllowGroupAdmins grants "user" access to group admins automatically.
	AllowGroupAdmins bool `yaml:"allow_group_admins"`

	// PendingMessage is the message sent to unknown contacts when policy is "ask".
	PendingMessage string `yaml:"pending_message"`
}

AccessConfig holds the access control configuration.

func DefaultAccessConfig

func DefaultAccessConfig() AccessConfig

DefaultAccessConfig returns the default access control config.

type AccessEntry

type AccessEntry struct {
	// JID is the contact identifier (phone@server or group@server).
	JID string

	// Level is the access level.
	Level AccessLevel

	// AddedBy is the JID of the admin/owner who granted access.
	AddedBy string

	// AddedAt is when access was granted.
	AddedAt time.Time

	// Note is an optional admin note about this contact.
	Note string
}

AccessEntry represents a contact in the access list.

type AccessLevel

type AccessLevel string

AccessLevel defines the permission level of a contact.

const (
	AccessOwner   AccessLevel = "owner"
	AccessAdmin   AccessLevel = "admin"
	AccessUser    AccessLevel = "user"
	AccessBlocked AccessLevel = "blocked"
	AccessNone    AccessLevel = "none"
	AccessUnknown AccessLevel = ""
)

func CallerLevelFromContext

func CallerLevelFromContext(ctx context.Context) AccessLevel

CallerLevelFromContext extracts the caller access level from context. Falls back to AccessNone if not set.

type AccessManager

type AccessManager struct {
	// contains filtered or unexported fields
}

AccessManager handles access control for incoming messages.

func NewAccessManager

func NewAccessManager(cfg AccessConfig, logger *slog.Logger) *AccessManager

NewAccessManager creates a new access manager from config.

func (*AccessManager) ApplyConfig

func (am *AccessManager) ApplyConfig(cfg AccessConfig)

ApplyConfig updates access config from hot-reload. Re-seeds config-derived entries (owners, admins, allowed, blocked, groups). Runtime grants (AddedBy != "config") are preserved.

func (*AccessManager) Block

func (am *AccessManager) Block(jid string, blockedBy string)

Block explicitly blocks a contact.

func (*AccessManager) Check

Check evaluates whether an incoming message should be processed. This is the main entry point called before any message handling.

func (*AccessManager) DefaultPolicy added in v1.16.5

func (am *AccessManager) DefaultPolicy() AccessPolicy

DefaultPolicy returns the current default access policy.

func (*AccessManager) GetLevel

func (am *AccessManager) GetLevel(jid string) AccessLevel

GetLevel returns the access level for a JID.

func (*AccessManager) Grant

func (am *AccessManager) Grant(jid string, level AccessLevel, grantedBy string) error

Grant gives access to a contact at the specified level.

func (*AccessManager) GrantGroup

func (am *AccessManager) GrantGroup(groupJID string, level AccessLevel, grantedBy string) error

GrantGroup gives access to a group.

func (*AccessManager) IsAdmin

func (am *AccessManager) IsAdmin(jid string) bool

IsAdmin returns true if the JID is an admin or owner.

func (*AccessManager) IsOwner

func (am *AccessManager) IsOwner(jid string) bool

IsOwner returns true if the JID is an owner.

func (*AccessManager) ListGroups

func (am *AccessManager) ListGroups() []*AccessEntry

ListGroups returns all group access entries.

func (*AccessManager) ListGroupsByChannel added in v1.16.5

func (am *AccessManager) ListGroupsByChannel(suffix string) []*AccessEntry

ListGroupsByChannel returns group access entries filtered by channel suffix.

func (*AccessManager) ListUsers

func (am *AccessManager) ListUsers() []*AccessEntry

ListUsers returns all access entries.

func (*AccessManager) ListUsersByChannel added in v1.16.5

func (am *AccessManager) ListUsersByChannel(suffix string) []*AccessEntry

ListUsersByChannel returns access entries filtered by channel suffix. For Telegram, pass "@telegram"; for WhatsApp, pass "@s.whatsapp.net". An empty suffix returns all entries.

func (*AccessManager) MarkAsked

func (am *AccessManager) MarkAsked(jid string)

MarkAsked records that we sent the "pending" message to a contact.

func (*AccessManager) PendingMessage

func (am *AccessManager) PendingMessage() string

PendingMessage returns the message to send to unknown contacts.

func (*AccessManager) Revoke

func (am *AccessManager) Revoke(jid string, revokedBy string)

Revoke removes access from a contact.

func (*AccessManager) SetDefaultPolicy added in v1.16.5

func (am *AccessManager) SetDefaultPolicy(policy AccessPolicy)

SetDefaultPolicy updates the default access policy at runtime.

func (*AccessManager) Unblock

func (am *AccessManager) Unblock(jid string, unblockedBy string)

Unblock removes a block from a contact.

type AccessPolicy

type AccessPolicy string

AccessPolicy defines how unknown contacts are handled.

const (
	// PolicyDeny silently ignores unknown contacts (default).
	PolicyDeny AccessPolicy = "deny"

	// PolicyAllow responds to everyone not explicitly blocked.
	PolicyAllow AccessPolicy = "allow"

	// PolicyAsk sends a one-time "request access" message to unknown contacts.
	PolicyAsk AccessPolicy = "ask"
)

type ActRequest added in v1.13.0

type ActRequest struct {
	Kind     string `json:"kind"`
	Ref      string `json:"ref,omitempty"`      // Element reference (e1, e2, ...)
	Selector string `json:"selector,omitempty"` // CSS selector (alternative to ref)

	// For type
	Text string `json:"text,omitempty"`

	// For press
	Key string `json:"key,omitempty"`

	// For drag
	StartRef string `json:"start_ref,omitempty"`
	EndRef   string `json:"end_ref,omitempty"`

	// For select
	Values []string `json:"values,omitempty"`

	// For fill (form fields)
	Fields []FormField `json:"fields,omitempty"`

	// For resize
	Width  int `json:"width,omitempty"`
	Height int `json:"height,omitempty"`

	// For wait
	TimeMs   int    `json:"time_ms,omitempty"`
	TextGone string `json:"text_gone,omitempty"`

	// For evaluate
	Function string `json:"fn,omitempty"`

	// Modifiers (for click)
	DoubleClick bool     `json:"double_click,omitempty"`
	Button      string   `json:"button,omitempty"` // left, right, middle
	Modifiers   []string `json:"modifiers,omitempty"`

	// For type
	Submit bool `json:"submit,omitempty"` // Press Enter after typing
	Slowly bool `json:"slowly,omitempty"` // Type character by character
}

ActRequest represents a browser action request.

type ActResult added in v1.13.0

type ActResult struct {
	OK      bool   `json:"ok"`
	Message string `json:"message,omitempty"`
}

ActResult represents the result of a browser action.

type ActivationMode

type ActivationMode string

ActivationMode defines how the bot activates in a group.

const (
	// ActivationAlways responds to all messages.
	ActivationAlways ActivationMode = "always"
	// ActivationMention responds only when mentioned.
	ActivationMention ActivationMode = "mention"
	// ActivationReply responds only when replying to bot's messages.
	ActivationReply ActivationMode = "reply"
	// ActivationKeyword responds when keywords are detected.
	ActivationKeyword ActivationMode = "keyword"
)

type AddFileHunk added in v1.12.0

type AddFileHunk struct {
	Path     string
	Contents string
}

AddFileHunk represents adding a new file.

type AgentConfig

type AgentConfig struct {
	// RunTimeoutSeconds is the max seconds for the entire agent run (default: 600).
	// One timer for the whole run, not per-turn.
	RunTimeoutSeconds int `yaml:"run_timeout_seconds"`

	// LLMCallTimeoutSeconds is the safety-net timeout per individual LLM call
	// (default: 300). Only catches hung connections — not the primary timeout.
	LLMCallTimeoutSeconds int `yaml:"llm_call_timeout_seconds"`

	// MaxTurns is a soft safety limit on LLM round-trips (default: 25).
	// When > 0, the agent will request a summary after this many turns.
	// Set to 0 for unlimited (not recommended in production).
	MaxTurns int `yaml:"max_turns"`

	// MaxContinuations is how many auto-continue rounds are allowed when
	// MaxTurns is hit and the agent is still using tools.
	// Only relevant when MaxTurns > 0. Default: 2.
	MaxContinuations int `yaml:"max_continuations"`

	// ReflectionEnabled enables periodic budget awareness nudges (default: true).
	ReflectionEnabled bool `yaml:"reflection_enabled"`

	// MaxCompactionAttempts is how many times to retry after context overflow (default: 3).
	MaxCompactionAttempts int `yaml:"max_compaction_attempts"`

	// ContextTokens overrides the auto-detected context window size for the model.
	// When > 0, this value is used instead of the built-in model-specific lookup.
	// Set this for custom/fine-tuned models or when using an unusual context window.
	ContextTokens int `yaml:"context_tokens"`

	// ToolVerbose controls whether tool progress messages are sent to the user.
	// Default "off" means only the final response is shown (OpenClaw-aligned).
	ToolVerbose VerboseLevel `yaml:"tool_verbose"`

	// ToolLoop configures tool loop detection thresholds.
	ToolLoop ToolLoopConfig `yaml:"tool_loop"`

	// MemoryFlush configures pre-compaction memory flush behavior.
	MemoryFlush MemoryFlushConfig `yaml:"memory_flush"`

	// Compaction configures how context compaction preserves important information.
	Compaction CompactionConfig `yaml:"compaction"`

	// KG configures Knowledge Graph extraction during compaction and dream cycles.
	KG KGAgentConfig `yaml:"kg"`
}

AgentConfig holds configurable agent loop parameters.

func DefaultAgentConfig

func DefaultAgentConfig() AgentConfig

DefaultAgentConfig returns sensible defaults for agent autonomy.

type AgentEvent

type AgentEvent struct {
	RunID     string    `json:"run_id"`
	SessionID string    `json:"session_id"`
	Seq       int64     `json:"seq"`
	Stream    string    `json:"stream"` // lifecycle, assistant, tool, error
	Type      string    `json:"type"`   // delta, tool_use, tool_result, done, error, run_start, etc.
	Timestamp time.Time `json:"timestamp"`
	Data      any       `json:"data"`
}

AgentEvent represents a single typed event from an agent run.

type AgentProfileConfig

type AgentProfileConfig struct {
	// ID is the unique identifier for this agent profile.
	ID string `yaml:"id"`

	// Model is the LLM model to use (e.g., "gpt-4o", "claude-sonnet-4").
	Model string `yaml:"model"`

	// Instructions override the base system prompt for this agent.
	Instructions string `yaml:"instructions"`

	// Skills are the skill names to enable for this agent.
	Skills []string `yaml:"skills"`

	// Channels route messages from these channels to this agent.
	Channels []string `yaml:"channels"`

	// Users route messages from these users to this agent.
	Users []string `yaml:"users"`

	// Groups route messages from these groups to this agent.
	Groups []string `yaml:"groups"`

	// MaxTurns is the max LLM turns for this agent (0 = unlimited).
	MaxTurns int `yaml:"max_turns"`

	// RunTimeoutSeconds is the max run time for this agent.
	RunTimeoutSeconds int `yaml:"run_timeout_seconds"`

	// Identity overrides the global identity for this agent profile.
	Identity *IdentityConfig `yaml:"identity,omitempty"`
}

AgentProfileConfig defines a specialized agent configuration.

func (*AgentProfileConfig) MergeConfig

func (p *AgentProfileConfig) MergeConfig(baseModel, baseInstructions string, baseSkills []string) (model, instructions string, skills []string)

MergeConfig merges agent profile settings with the base config. Returns the model, instructions, and skills to use.

type AgentRouter

type AgentRouter struct {
	// contains filtered or unexported fields
}

AgentRouter routes messages to the appropriate agent profile.

func NewAgentRouter

func NewAgentRouter(cfg AgentsConfig, logger *slog.Logger) *AgentRouter

NewAgentRouter creates a new agent router from configuration.

func (*AgentRouter) AddProfile added in v1.16.0

func (r *AgentRouter) AddProfile(p *AgentProfileConfig)

AddProfile registers a new agent profile at runtime (e.g. from plugins).

func (*AgentRouter) DefaultProfileID

func (r *AgentRouter) DefaultProfileID() string

DefaultProfileID returns the default profile ID.

func (*AgentRouter) GetProfile

func (r *AgentRouter) GetProfile(id string) *AgentProfileConfig

GetProfile returns a profile by ID.

func (*AgentRouter) ListProfiles

func (r *AgentRouter) ListProfiles() []string

ListProfiles returns all profile IDs.

func (*AgentRouter) Route

func (r *AgentRouter) Route(channel string, userJID string, groupJID string) *AgentProfileConfig

Route determines which agent profile should handle a message. Priority: user > group > channel > default.

type AgentRun

type AgentRun struct {
	// contains filtered or unexported fields
}

AgentRun encapsulates a single agent execution with its dependencies.

func AgentRunFromCtx added in v1.14.0

func AgentRunFromCtx(ctx context.Context) *AgentRun

AgentRunFromCtx extracts the AgentRun from context.

func NewAgentRun

func NewAgentRun(llm *LLMClient, executor *ToolExecutor, logger *slog.Logger) *AgentRun

NewAgentRun creates a new agent runner.

func NewAgentRunWithConfig

func NewAgentRunWithConfig(llm *LLMClient, executor *ToolExecutor, cfg AgentConfig, logger *slog.Logger) *AgentRun

NewAgentRunWithConfig creates a new agent runner with explicit configuration.

func (*AgentRun) CollectedAssistantFragments added in v1.16.0

func (a *AgentRun) CollectedAssistantFragments() []string

CollectedAssistantFragments returns assistant response fragments collected during the agent run, for partial progress synthesis on timeout.

func (*AgentRun) CollectedToolCalls added in v1.13.0

func (a *AgentRun) CollectedToolCalls() []ToolCallRecord

CollectedToolCalls returns the individual tool call records collected during the run.

func (*AgentRun) Run

func (a *AgentRun) Run(ctx context.Context, systemPrompt string, history []ConversationEntry, userMessage string) (string, error)

Run executes the agent loop: builds the initial message list from conversation history, then iterates LLM calls and tool executions until a final response is produced or the turn limit is exhausted.

If auto-continue is enabled and the agent is still using tools when the budget runs out, it will automatically start a continuation round.

func (*AgentRun) RunWithUsage

func (a *AgentRun) RunWithUsage(ctx context.Context, systemPrompt string, history []ConversationEntry, userMessage string) (string, *LLMUsage, error)

RunWithUsage is like Run but also returns aggregated token usage from all LLM calls.

Architecture:

  • The loop runs until the LLM produces a response with no tool calls.
  • A single run-level timeout controls the entire execution (default: 600s).
  • Individual LLM calls have a safety-net timeout (5min) to catch hung connections.
  • No fixed turn limit — the agent keeps going as long as it has tools to call.

func (*AgentRun) SetInterruptChannel

func (a *AgentRun) SetInterruptChannel(ch <-chan string)

SetInterruptChannel sets the channel for receiving follow-up user messages during agent execution. Messages received on this channel are injected into the conversation between agent turns, allowing users to steer the agent mid-run (similar to Claude Code behavior).

func (*AgentRun) SetKG added in v1.18.0

func (a *AgentRun) SetKG(k *kg.KG)

SetKG sets the Knowledge Graph reference for context-residual extraction.

func (*AgentRun) SetLCMEngine added in v1.14.0

func (a *AgentRun) SetLCMEngine(engine *LCMEngine, conversationID string)

SetLCMEngine wires the LCM engine into the agent run.

func (*AgentRun) SetLoopDetector

func (a *AgentRun) SetLoopDetector(d *ToolLoopDetector)

SetLoopDetector sets the tool loop detector for this run.

func (*AgentRun) SetMemoryIndexer added in v1.14.0

func (a *AgentRun) SetMemoryIndexer(m *MemoryIndexer)

SetMemoryIndexer sets the memory indexer for post-compaction sync.

func (*AgentRun) SetModelOverride

func (a *AgentRun) SetModelOverride(model string)

SetModelOverride sets the model to use instead of the default. Empty string means use the LLM client's default.

func (*AgentRun) SetOnBeforeToolExec

func (a *AgentRun) SetOnBeforeToolExec(fn func())

SetOnBeforeToolExec sets a callback fired right before tool execution starts in the agent loop. Used by the block streamer to flush buffered text so the user sees intermediate reasoning before tools run.

func (*AgentRun) SetOnToolResult

func (a *AgentRun) SetOnToolResult(fn func(name string, result ToolResult))

SetOnToolResult sets a callback fired after each tool execution completes. Used to auto-send media (e.g. generated images) to the channel.

func (*AgentRun) SetReactionSender added in v1.14.0

func (a *AgentRun) SetReactionSender(fn func(emoji string, remove bool))

SetReactionSender sets the function used to send/remove emoji reactions.

func (*AgentRun) SetSession added in v1.14.0

func (a *AgentRun) SetSession(s *Session)

SetSession sets the active session for compaction count tracking.

func (*AgentRun) SetSessionPersistence added in v1.13.0

func (a *AgentRun) SetSessionPersistence(p SessionPersister, sessionID string)

SetSessionPersistence wires session persistence for compaction summary storage.

func (*AgentRun) SetStreamCallback

func (a *AgentRun) SetStreamCallback(cb StreamCallback)

SetStreamCallback sets the callback for streaming text deltas. When set, the agent uses CompleteWithToolsStream; only text content is forwarded, tool calls are accumulated silently.

func (*AgentRun) SetUsageRecorder

func (a *AgentRun) SetUsageRecorder(fn func(model string, usage LLMUsage))

SetUsageRecorder sets a callback invoked after each successful LLM response.

func (*AgentRun) ToolSummary added in v1.13.0

func (a *AgentRun) ToolSummary() string

ToolSummary returns a digest of all tools called during this run.

type AgentsConfig

type AgentsConfig struct {
	// Profiles is the list of agent profiles.
	Profiles []AgentProfileConfig `yaml:"profiles"`

	// Routing defines how messages are routed.
	Routing RoutingConfig `yaml:"routing"`
}

AgentsConfig holds all agent profiles and routing configuration.

type AnnounceCallback

type AnnounceCallback func(run *SubagentRun)

AnnounceCallback is called when a subagent completes, allowing push-style notification to the parent session. Receives the completed run so the caller can notify the user/agent.

type ApplyPatchResult added in v1.12.0

type ApplyPatchResult struct {
	Added    []string
	Modified []string
	Deleted  []string
	Text     string
}

ApplyPatchResult holds the summary of the applied patch.

type ApprovalManager

type ApprovalManager struct {
	// contains filtered or unexported fields
}

ApprovalManager manages pending tool approvals and their resolution. It also tracks session-scoped trust: once a user approves a tool in a session, subsequent uses of the same tool are auto-approved (no re-prompting).

func NewApprovalManager

func NewApprovalManager(logger *slog.Logger) *ApprovalManager

NewApprovalManager creates a new approval manager.

func (*ApprovalManager) ClearSessionTrust

func (m *ApprovalManager) ClearSessionTrust(sessionID string)

ClearSessionTrust removes all trusted tools for a session (e.g. on /new or /reset).

func (*ApprovalManager) Create

func (m *ApprovalManager) Create(sessionID, callerJID, toolName string, args map[string]any) (id string, message string)

Create creates a pending approval and returns the ID and message for the user. The caller should send the message to the chat, then call Wait to block for the result.

func (*ApprovalManager) GrantTrust

func (m *ApprovalManager) GrantTrust(sessionID, toolName string)

GrantTrust marks a tool as trusted for the given session. Future calls to Request for this tool+session will be auto-approved.

func (*ApprovalManager) IsTrusted

func (m *ApprovalManager) IsTrusted(sessionID, toolName string) bool

IsTrusted returns true if the tool has been previously approved in this session.

func (*ApprovalManager) LatestPendingForSession

func (m *ApprovalManager) LatestPendingForSession(sessionID string) string

LatestPendingForSession returns the ID of the most recent pending approval for the given session, or empty string if none. This allows "/approve" without specifying the UUID — it resolves the latest pending request.

func (*ApprovalManager) PendingCountForSession

func (m *ApprovalManager) PendingCountForSession(sessionID string) int

PendingCountForSession returns the number of pending approvals for a session.

func (*ApprovalManager) Request

func (m *ApprovalManager) Request(sessionID, callerJID, toolName string, args map[string]any, sendMsg func(msg string)) (bool, error)

Request creates a pending approval, invokes sendMsg with the approval message, then blocks until the user approves, denies, or timeout. sendMsg is called so the user sees the approval request (e.g. send to channel).

If the tool has already been approved in this session (session trust), the request is auto-approved without prompting the user.

func (*ApprovalManager) Resolve

func (m *ApprovalManager) Resolve(id, sessionID, resolverJID string, approved bool, reason string) bool

Resolve resolves a pending approval by ID. Returns true if the approval was found and resolved. resolverJID is the user resolving (must match CallerJID for "own requests only").

func (*ApprovalManager) Wait

func (m *ApprovalManager) Wait(id string) (approved bool, err error)

Wait blocks until the approval is resolved or times out. Must be called after Create. Removes the pending approval when done.

type ApprovalResult

type ApprovalResult struct {
	Approved bool
	Reason   string
}

ApprovalResult holds the outcome of an approval request.

type Assistant

type Assistant struct {
	// contains filtered or unexported fields
}

Assistant is the main orchestrator for DevClaw. Message flow: receive → access check → command check → trigger check → workspace resolve → input validation → context build → agent → output validation → send.

func New

func New(cfg *Config, logger *slog.Logger) *Assistant

New creates a new Assistant with all dependencies.

func (*Assistant) AccessManager

func (a *Assistant) AccessManager() *AccessManager

AccessManager returns the access manager.

func (*Assistant) ApplyConfigUpdate

func (a *Assistant) ApplyConfigUpdate(newCfg *Config)

ApplyConfigUpdate applies hot-reloadable config changes. Updates: access control, instructions, tool guard, heartbeat, token budget. Does NOT update: API, channels, model, plugins (require restart).

func (*Assistant) ChannelManager

func (a *Assistant) ChannelManager() *channels.Manager

ChannelManager returns the channel manager for external registration.

func (*Assistant) ComposePrompt

func (a *Assistant) ComposePrompt(session *Session, input string) string

ComposePrompt builds a system prompt for the given session and input. Convenience method for CLI and external callers.

func (*Assistant) Config

func (a *Assistant) Config() *Config

Config returns the assistant configuration.

func (*Assistant) ExecuteAgent

func (a *Assistant) ExecuteAgent(ctx context.Context, systemPrompt string, session *Session, userMessage string) string

ExecuteAgent runs the agent loop with tools and returns the response text. Public wrapper for CLI and external callers. Uses "default" as workspace ID.

func (*Assistant) ForceCompactSession

func (a *Assistant) ForceCompactSession(session *Session) (oldLen, newLen int)

ForceCompactSession runs compaction immediately, returns old and new history length.

func (*Assistant) ForceDream added in v1.18.2

func (a *Assistant) ForceDream(ctx context.Context)

ForceDream triggers a dream consolidation cycle immediately, bypassing the normal trigger gates (min hours, min sessions). Useful for operator- initiated consolidation (e.g. via SIGUSR1). Returns quickly if dream is disabled or not yet initialized.

func (*Assistant) GetMediaService added in v1.8.0

func (a *Assistant) GetMediaService() *media.MediaService

GetMediaService returns the media service for WebUI adapter wiring. Returns nil if native media is not enabled.

func (*Assistant) HandleCommand

func (a *Assistant) HandleCommand(msg *channels.IncomingMessage) CommandResult

HandleCommand processes an admin command from a chat message. Returns handled=true if it was a valid command (even if permission denied).

func (*Assistant) HookManager

func (a *Assistant) HookManager() *HookManager

HookManager returns the lifecycle hook manager for registering plugin hooks.

func (*Assistant) InjectVaultEnvVars

func (a *Assistant) InjectVaultEnvVars()

InjectVaultEnvVars loads all vault secrets as environment variables. Key names are uppercased and prefixed if not already (e.g. "brave_api_key" → "BRAVE_API_KEY"). Existing env vars are NOT overwritten — vault only fills gaps. This allows skills/scripts to use process.env.BRAVE_API_KEY without .env files.

func (*Assistant) LLMClient

func (a *Assistant) LLMClient() *LLMClient

LLMClient returns the LLM client (for gateway chat completions).

func (*Assistant) MediaConfig

func (a *Assistant) MediaConfig() MediaConfig

MediaConfig returns the current effective media config under read lock.

func (*Assistant) MemoryEnabled

func (a *Assistant) MemoryEnabled() bool

MemoryEnabled returns true if the memory store is available.

func (*Assistant) PluginRegistry added in v1.16.0

func (a *Assistant) PluginRegistry() *plugins.Registry

PluginRegistry returns the plugin registry (may be nil).

func (*Assistant) ProfileManager added in v1.13.0

func (a *Assistant) ProfileManager() profiles.ProfileManager

ProfileManager returns the auth profile manager for OAuth/API key access. Returns nil if the vault is locked or profiles are not configured.

func (*Assistant) ProjectManager

func (a *Assistant) ProjectManager() *ProjectManager

ProjectManager returns the project manager.

func (*Assistant) ProviderDiscovery added in v1.16.0

func (a *Assistant) ProviderDiscovery() *ProviderDiscovery

ProviderDiscovery returns the provider discovery instance (may be nil).

func (*Assistant) ReloadAndInitializeSkills added in v1.13.0

func (a *Assistant) ReloadAndInitializeSkills(ctx context.Context) (int, error)

ReloadAndInitializeSkills reloads skills from disk, initializes new skills with the sandbox runner, and re-registers all skill tools. This is called after installing or updating skills at runtime.

func (*Assistant) SQLiteMemory

func (a *Assistant) SQLiteMemory() *memory.SQLiteStore

SQLiteMemory returns the SQLite memory store (for advanced search), or nil.

func (*Assistant) Scheduler

func (a *Assistant) Scheduler() *scheduler.Scheduler

Scheduler returns the task scheduler (may be nil if not initialized).

func (*Assistant) SchedulerEnabled

func (a *Assistant) SchedulerEnabled() bool

SchedulerEnabled returns true if the scheduler is running.

func (*Assistant) SessionStore

func (a *Assistant) SessionStore() *SessionStore

SessionStore returns the session store (used by CLI chat).

func (*Assistant) SetPluginRegistry added in v1.16.0

func (a *Assistant) SetPluginRegistry(r *plugins.Registry)

SetPluginRegistry sets the unified plugin registry for the assistant.

func (*Assistant) SetScheduler

func (a *Assistant) SetScheduler(s *scheduler.Scheduler)

SetScheduler configures the assistant's scheduler.

func (*Assistant) SetVault

func (a *Assistant) SetVault(v *Vault)

SetVault sets the unlocked vault for the assistant (enables vault tools).

func (*Assistant) SkillRegistry

func (a *Assistant) SkillRegistry() *skills.Registry

SkillRegistry returns the skills registry.

func (*Assistant) Start

func (a *Assistant) Start(ctx context.Context) error

Start initializes and starts all subsystems.

func (*Assistant) Stop

func (a *Assistant) Stop()

Stop gracefully shuts down all subsystems.

func (*Assistant) StopActiveRun

func (a *Assistant) StopActiveRun(workspaceID, sessionID string) bool

StopActiveRun cancels the active agent run for the given workspace and session. It also signals the tool executor to abort all running tools and forces the session out of "processing" state so new messages are handled immediately. Returns true if a run was stopped, false if none was active.

func (*Assistant) ToolExecutor

func (a *Assistant) ToolExecutor() *ToolExecutor

ToolExecutor returns the tool executor for external tool registration.

func (*Assistant) UpdateLLMClient added in v1.13.0

func (a *Assistant) UpdateLLMClient(cfg *Config)

UpdateLLMClient recreates the LLM client with the current config. Call this after changing provider, model, base_url, or api_key at runtime.

func (*Assistant) UpdateMediaConfig

func (a *Assistant) UpdateMediaConfig(media MediaConfig)

UpdateMediaConfig safely updates the media configuration under lock.

func (*Assistant) UsageTracker

func (a *Assistant) UsageTracker() *UsageTracker

UsageTracker returns the usage tracker for token/cost stats.

func (*Assistant) Vault

func (a *Assistant) Vault() *Vault

Vault returns the vault instance (may be nil if unavailable).

func (*Assistant) WorkspaceManager

func (a *Assistant) WorkspaceManager() *WorkspaceManager

WorkspaceManager returns the workspace manager.

type AsyncCompleteCallback added in v1.12.0

type AsyncCompleteCallback func(result *ToolResult)

AsyncCompleteCallback is called when an async tool completes execution. The result contains the final output that should be delivered.

type AsyncToolConfig added in v1.12.0

type AsyncToolConfig struct {
	// Label is a human-readable description of the async task.
	Label string

	// OnComplete is called when the async task finishes.
	OnComplete AsyncCompleteCallback

	// Timeout is the maximum duration for the async task.
	// Default is 5 minutes if not set.
	Timeout time.Duration
}

AsyncToolConfig holds configuration for async tool execution.

type AuditRecord

type AuditRecord struct {
	ID            int64
	Tool          string
	Caller        string
	Level         string
	Allowed       bool
	ArgsSummary   string
	ResultSummary string
	CreatedAt     string
}

AuditRecord is a structured audit log entry.

type AuditRecordShort

type AuditRecordShort struct {
	ID          int64     `json:"id"`
	Tool        string    `json:"tool"`
	Caller      string    `json:"caller"`
	Level       string    `json:"level"`
	Allowed     bool      `json:"allowed"`
	ArgsSummary string    `json:"args_summary"`
	CreatedAt   time.Time `json:"created_at"`
}

AuditRecordShort is a shortened version of AuditRecord for display.

type BlockStreamConfig

type BlockStreamConfig struct {
	// Enabled turns block streaming on/off (default: true).
	Enabled bool `yaml:"enabled"`

	// MinChars is the minimum characters to accumulate before sending a block (default: 20).
	// Kept low for near-instant first-block feedback.
	MinChars int `yaml:"min_chars"`

	// MaxChars is the maximum characters per block before a forced flush (default: 600).
	MaxChars int `yaml:"max_chars"`

	// IdleMs is the idle timeout in milliseconds: if no new tokens arrive within
	// this window, flush whatever is buffered (default: 200).
	IdleMs int `yaml:"idle_ms"`
}

BlockStreamConfig configures the progressive message streaming behavior.

func DefaultBlockStreamConfig

func DefaultBlockStreamConfig() BlockStreamConfig

DefaultBlockStreamConfig returns sensible defaults for block streaming. Tuned for WhatsApp/chat UX: each flush = a new message, so we prioritize sending coherent paragraphs over low-latency fragments. MinChars must be high enough to avoid sending single-line fragments as separate messages.

func (BlockStreamConfig) Effective

func (c BlockStreamConfig) Effective() BlockStreamConfig

Effective returns a copy with defaults filled in for zero values.

type BlockStreamer

type BlockStreamer struct {
	// contains filtered or unexported fields
}

BlockStreamer accumulates LLM stream tokens and sends them progressively to a channel. It is tied to a single message exchange (one user message → one agent response).

func NewBlockStreamer

func NewBlockStreamer(
	cfg BlockStreamConfig,
	channelMgr *channels.Manager,
	channel, chatID, replyTo string,
) *BlockStreamer

NewBlockStreamer creates a streamer that progressively sends blocks to the given channel.

func (*BlockStreamer) Finish

func (bs *BlockStreamer) Finish()

Finish flushes any remaining buffer and marks the streamer as done.

func (*BlockStreamer) FlushNow

func (bs *BlockStreamer) FlushNow()

FlushNow immediately sends any buffered text to the channel, regardless of MinChars threshold. Use this before tool execution to ensure the user sees the LLM's intermediate text (thoughts/reasoning) before tools start running.

func (*BlockStreamer) HasSentBlocks

func (bs *BlockStreamer) HasSentBlocks() bool

HasSentBlocks returns true if at least one block was sent progressively.

func (*BlockStreamer) StreamCallback

func (bs *BlockStreamer) StreamCallback() StreamCallback

StreamCallback returns a StreamCallback function suitable for AgentRun.SetStreamCallback.

type BrowserConfig

type BrowserConfig struct {
	// Enabled turns the browser tool on/off (default: true if Chrome is found).
	Enabled bool `yaml:"enabled"`

	// ChromePath is the path to the Chrome/Chromium binary.
	// Auto-detected if empty.
	ChromePath string `yaml:"chrome_path"`

	// Headless runs the browser without a visible window (default: true).
	Headless bool `yaml:"headless"`

	// TimeoutSeconds is the max time for a single browser operation (default: 30).
	TimeoutSeconds int `yaml:"timeout_seconds"`

	// MaxPages is the max number of simultaneous pages/tabs (default: 3).
	MaxPages int `yaml:"max_pages"`

	// ViewportWidth is the browser viewport width (default: 1280).
	ViewportWidth int `yaml:"viewport_width"`

	// ViewportHeight is the browser viewport height (default: 720).
	ViewportHeight int `yaml:"viewport_height"`

	// DefaultProfile is the default browser profile to use.
	DefaultProfile string `yaml:"default_profile"`

	// Profiles maps profile names to their configurations.
	Profiles map[string]BrowserProfile `yaml:"profiles"`

	// SSRFPolicy configures SSRF protection for browser navigation.
	SSRFPolicy BrowserSSRFPolicy `yaml:"ssrf_policy"`

	// AttachOnly means never launch a browser; only attach if already running.
	AttachOnly bool `yaml:"attach_only"`

	// ExtraArgs are additional command-line arguments for Chrome.
	ExtraArgs []string `yaml:"extra_args"`
}

BrowserConfig configures the browser tool.

func DefaultBrowserConfig

func DefaultBrowserConfig() BrowserConfig

DefaultBrowserConfig returns sensible defaults.

type BrowserManager

type BrowserManager struct {
	// contains filtered or unexported fields
}

BrowserManager manages a Chrome/Chromium process and CDP connections.

func NewBrowserManager

func NewBrowserManager(cfg BrowserConfig, logger *slog.Logger) *BrowserManager

NewBrowserManager creates a new browser manager.

func (*BrowserManager) Act added in v1.13.0

func (bm *BrowserManager) Act(ctx context.Context, req ActRequest) (*ActResult, error)

Act performs a browser action.

func (*BrowserManager) ClickElement

func (bm *BrowserManager) ClickElement(ctx context.Context, selector string) error

ClickElement clicks an element matched by CSS selector.

func (*BrowserManager) CloseTab added in v1.13.0

func (bm *BrowserManager) CloseTab(ctx context.Context, targetID string) error

CloseTab closes a browser tab by target ID.

func (*BrowserManager) FillInput

func (bm *BrowserManager) FillInput(ctx context.Context, selector, value string) error

FillInput fills a text input matched by CSS selector.

func (*BrowserManager) FocusTab added in v1.13.0

func (bm *BrowserManager) FocusTab(ctx context.Context, targetID string) error

FocusTab focuses a browser tab by target ID.

func (*BrowserManager) GetAccessibilityTree added in v1.13.0

func (bm *BrowserManager) GetAccessibilityTree(ctx context.Context) (*AXNode, error)

GetAccessibilityTree fetches the full accessibility tree via CDP.

func (*BrowserManager) GetContent

func (bm *BrowserManager) GetContent(ctx context.Context) (string, error)

GetContent returns the text content of the current page.

func (*BrowserManager) GetCurrentTab added in v1.13.0

func (bm *BrowserManager) GetCurrentTab(ctx context.Context) (*Tab, error)

GetCurrentTab returns the currently active tab.

func (*BrowserManager) ListTabs added in v1.13.0

func (bm *BrowserManager) ListTabs(ctx context.Context) ([]Tab, error)

ListTabs returns all open browser tabs.

func (*BrowserManager) Navigate

func (bm *BrowserManager) Navigate(ctx context.Context, url string) error

Navigate opens a URL in the browser.

func (*BrowserManager) NavigateTab added in v1.13.0

func (bm *BrowserManager) NavigateTab(ctx context.Context, targetID, url string) error

NavigateTab navigates a specific tab to a URL.

func (*BrowserManager) OpenTab added in v1.13.0

func (bm *BrowserManager) OpenTab(ctx context.Context, url string) (*Tab, error)

OpenTab opens a new browser tab and optionally navigates to a URL.

func (*BrowserManager) ResolveRefToSelector added in v1.13.0

func (bm *BrowserManager) ResolveRefToSelector(refID string) (string, error)

ResolveRefToSelector converts a role reference to a CSS selector or JS query.

func (*BrowserManager) Screenshot

func (bm *BrowserManager) Screenshot(ctx context.Context) (string, error)

Screenshot captures the current page as a PNG and returns base64-encoded data.

func (*BrowserManager) Snapshot added in v1.13.0

func (bm *BrowserManager) Snapshot(ctx context.Context, opts SnapshotOptions) (*SnapshotResult, error)

Snapshot creates a text snapshot of the current page with role references.

func (*BrowserManager) Start

func (bm *BrowserManager) Start(ctx context.Context) error

Start launches Chrome with CDP enabled. Called lazily on first tool use.

func (*BrowserManager) Stop

func (bm *BrowserManager) Stop()

Stop kills the Chrome process and closes connections.

func (*BrowserManager) WithSSRFGuard

func (bm *BrowserManager) WithSSRFGuard(guard *security.SSRFGuard) *BrowserManager

WithSSRFGuard attaches an SSRF guard to the browser manager. When set, Navigate() will validate URLs before loading them.

type BrowserProfile added in v1.13.0

type BrowserProfile struct {
	// Name is the profile name.
	Name string `yaml:"name"`

	// CDPUrl is the remote CDP endpoint (e.g., "http://10.0.0.42:9222").
	CDPUrl string `yaml:"cdp_url"`

	// CDPPort is the local CDP port for this profile.
	CDPPort int `yaml:"cdp_port"`

	// Color is the UI tint color for this profile.
	Color string `yaml:"color"`

	// Driver is "devclaw" (managed) or "extension" (relay).
	Driver string `yaml:"driver"`
}

BrowserProfile configures a browser profile.

type BrowserSSRFPolicy added in v1.13.0

type BrowserSSRFPolicy struct {
	// AllowPrivateNetwork allows navigation to private network addresses (default: true).
	AllowPrivateNetwork bool `yaml:"allow_private_network"`

	// AllowedHostnames is a whitelist of allowed hostnames.
	AllowedHostnames []string `yaml:"allowed_hostnames"`
}

BrowserSSRFPolicy configures SSRF protection.

type BudgetConfig

type BudgetConfig struct {
	// MonthlyLimitUSD is the maximum monthly spend (0 = unlimited).
	MonthlyLimitUSD float64 `yaml:"monthly_limit_usd"`

	// WarnAtPercent triggers a warning when this % of budget is reached (default: 80).
	WarnAtPercent int `yaml:"warn_at_percent"`

	// ActionAtLimit defines behavior when limit is reached: "warn", "block", "fallback_local".
	ActionAtLimit string `yaml:"action_at_limit"`
}

BudgetConfig configures monthly cost tracking and limits.

func DefaultBudgetConfig

func DefaultBudgetConfig() BudgetConfig

DefaultBudgetConfig returns sensible defaults for budget tracking.

type BuiltinSkill added in v1.12.0

type BuiltinSkill struct {
	Name        string
	Description string
	Content     string
	Trigger     string // automatic, manual
}

BuiltinSkill represents a built-in skill loaded from the embedded filesystem.

type BuiltinSkills added in v1.12.0

type BuiltinSkills struct {
	// contains filtered or unexported fields
}

BuiltinSkills holds all loaded built-in skills.

func GetBuiltinSkills added in v1.12.0

func GetBuiltinSkills() *BuiltinSkills

GetBuiltinSkills returns the global builtin skills instance. Returns nil if LoadBuiltinSkills hasn't been called yet.

func LoadBuiltinSkills added in v1.12.0

func LoadBuiltinSkills(logger *slog.Logger) *BuiltinSkills

LoadBuiltinSkills loads all built-in skills from the embedded filesystem. Uses singleton pattern to load only once.

func (*BuiltinSkills) All added in v1.12.0

func (bs *BuiltinSkills) All() map[string]*BuiltinSkill

All returns all loaded skills.

func (*BuiltinSkills) FormatForPrompt added in v1.12.0

func (bs *BuiltinSkills) FormatForPrompt() string

FormatForPrompt formats all skills for inclusion in the system prompt. Only includes skills with trigger="automatic".

func (*BuiltinSkills) FormatSkillForPrompt added in v1.12.0

func (bs *BuiltinSkills) FormatSkillForPrompt(name string) string

FormatSkillForPrompt formats a single skill for the prompt.

func (*BuiltinSkills) Get added in v1.12.0

func (bs *BuiltinSkills) Get(name string) *BuiltinSkill

Get returns a skill by name.

func (*BuiltinSkills) Names added in v1.12.0

func (bs *BuiltinSkills) Names() []string

Names returns all skill names.

func (*BuiltinSkills) OnDemandSkills added in v1.13.0

func (bs *BuiltinSkills) OnDemandSkills() []*BuiltinSkill

OnDemandSkills returns builtin skills that should be listed in the XML reference list (trigger="on-demand") instead of injected inline. The LLM loads their full instructions via get_skill_instructions(name).

type CDPTarget added in v1.13.0

type CDPTarget struct {
	TargetID             string `json:"targetId"`
	Type                 string `json:"type"`
	Title                string `json:"title"`
	URL                  string `json:"url"`
	Attached             bool   `json:"attached"`
	WebSocketDebuggerURL string `json:"webSocketDebuggerUrl"`
}

CDPTarget represents a CDP target (page, worker, etc.)

type Canvas

type Canvas struct {
	ID        string    `json:"id"`
	Title     string    `json:"title"`
	HTML      string    `json:"-"`
	Port      int       `json:"port"`
	URL       string    `json:"url"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
	// contains filtered or unexported fields
}

Canvas represents a single hosted HTML page with live-reload support.

type CanvasConfig

type CanvasConfig struct {
	// Enabled turns canvas hosting on/off (default: true).
	Enabled bool `yaml:"enabled"`

	// BasePort is the starting port for canvas servers (default: 9100).
	// Each canvas gets its own port: BasePort, BasePort+1, etc.
	BasePort int `yaml:"base_port"`

	// MaxCanvases is the max number of simultaneous canvas servers (default: 5).
	MaxCanvases int `yaml:"max_canvases"`

	// TTLMinutes is how long a canvas stays alive without updates (default: 30).
	TTLMinutes int `yaml:"ttl_minutes"`
}

CanvasConfig configures the canvas host.

func DefaultCanvasConfig

func DefaultCanvasConfig() CanvasConfig

DefaultCanvasConfig returns sensible defaults.

type CanvasHost

type CanvasHost struct {
	// contains filtered or unexported fields
}

CanvasHost manages multiple canvas servers.

func NewCanvasHost

func NewCanvasHost(cfg CanvasConfig, logger *slog.Logger) *CanvasHost

NewCanvasHost creates a new canvas host.

func (*CanvasHost) CleanupStale

func (ch *CanvasHost) CleanupStale() int

CleanupStale removes canvases that haven't been updated within TTL.

func (*CanvasHost) Create

func (ch *CanvasHost) Create(id, title, html string) (*Canvas, error)

Create creates and starts a new canvas with the given HTML content.

func (*CanvasHost) List

func (ch *CanvasHost) List() []*Canvas

List returns metadata for all active canvases.

func (*CanvasHost) Stop

func (ch *CanvasHost) Stop(id string) error

Stop stops a specific canvas server.

func (*CanvasHost) StopAll

func (ch *CanvasHost) StopAll()

StopAll stops all canvas servers.

func (*CanvasHost) Update

func (ch *CanvasHost) Update(id, html string) error

Update replaces the HTML content of an existing canvas and triggers live-reload.

type ChannelDiagnostic

type ChannelDiagnostic struct {
	Name       string `json:"name"`
	Connected  bool   `json:"connected"`
	TestResult string `json:"test_result"`
	LatencyMs  int64  `json:"latency_ms"`
}

ChannelDiagnostic represents diagnostic result for a single channel.

type ChannelHealth

type ChannelHealth struct {
	Connected     bool           `json:"connected"`
	LastMessageAt time.Time      `json:"last_message_at,omitempty"`
	ErrorCount    int            `json:"error_count"`
	LatencyMs     int64          `json:"latency_ms"`
	Details       map[string]any `json:"details,omitempty"`
}

ChannelHealth represents health status of a single channel.

type ChannelsConfig

type ChannelsConfig struct {
	// WhatsApp is the default WhatsApp channel config (core).
	WhatsApp whatsapp.Config `yaml:"whatsapp"`

	// Telegram is the default Telegram channel config (core).
	Telegram telegram.Config `yaml:"telegram"`

	// Discord is the default Discord channel config (core).
	Discord discord.Config `yaml:"discord"`

	// Slack is the default Slack channel config (core).
	Slack slack.Config `yaml:"slack"`

	// WhatsAppInstances holds additional named WhatsApp instances.
	// Each key is the instance ID (e.g. "business") and the value is
	// the full channel config for that instance.
	WhatsAppInstances map[string]whatsapp.Config `yaml:"whatsapp_instances,omitempty"`

	// TelegramInstances holds additional named Telegram instances.
	TelegramInstances map[string]telegram.Config `yaml:"telegram_instances,omitempty"`

	// DiscordInstances holds additional named Discord instances.
	DiscordInstances map[string]discord.Config `yaml:"discord_instances,omitempty"`

	// SlackInstances holds additional named Slack instances.
	SlackInstances map[string]slack.Config `yaml:"slack_instances,omitempty"`
}

ChannelsConfig holds configuration for all channels.

func (ChannelsConfig) DiscordAll added in v1.16.0

func (c ChannelsConfig) DiscordAll() map[string]discord.Config

DiscordAll returns all Discord configs: the default instance (key "") merged with any named instances from DiscordInstances.

func (ChannelsConfig) SlackAll added in v1.16.0

func (c ChannelsConfig) SlackAll() map[string]slack.Config

SlackAll returns all Slack configs: the default instance (key "") merged with any named instances from SlackInstances.

func (ChannelsConfig) TelegramAll added in v1.16.0

func (c ChannelsConfig) TelegramAll() map[string]telegram.Config

TelegramAll returns all Telegram configs: the default instance (key "") merged with any named instances from TelegramInstances.

func (ChannelsConfig) WhatsAppAll added in v1.16.0

func (c ChannelsConfig) WhatsAppAll() map[string]whatsapp.Config

WhatsAppAll returns all WhatsApp configs: the default instance (key "") merged with any named instances from WhatsAppInstances.

type CheckResult

type CheckResult struct {
	// Allowed is true if the contact can interact.
	Allowed bool

	// Level is the resolved access level.
	Level AccessLevel

	// ShouldAsk is true if we should send a "request access" message.
	ShouldAsk bool

	// Reason explains why access was denied (for logging).
	Reason string
}

CheckResult contains the result of an access check.

type CommandResult

type CommandResult struct {
	// Response is the text to send back.
	Response string

	// Handled is true if the message was a valid command.
	Handled bool
}

CommandResult contains the result of a command execution.

type CompactLevel added in v1.17.0

type CompactLevel int

CompactLevel represents the severity of compaction to apply.

const (
	// CompactNone means no compaction needed.
	CompactNone CompactLevel = -1

	// CompactCollapse truncates oversized tool results in-place.
	// Cheapest operation — no LLM calls. Triggered at ~70% context usage.
	CompactCollapse CompactLevel = 0

	// CompactMicro clears old tool results with a placeholder.
	// Still cheap — no LLM calls. Triggered at ~80% context usage.
	CompactMicro CompactLevel = 1

	// CompactAuto triggers full LLM-based summarization.
	// Expensive — requires LLM call. Triggered at ~93% context usage.
	CompactAuto CompactLevel = 2

	// CompactMemory extracts memories before summarizing.
	// Most expensive — multiple LLM calls. Triggered at ~97% context usage.
	CompactMemory CompactLevel = 3
)

func (CompactLevel) String added in v1.17.0

func (l CompactLevel) String() string

String returns a human-readable name for the compaction level.

type CompactThresholds added in v1.17.0

type CompactThresholds struct {
	// CollapseAt triggers tool result truncation. Default: 0.70.
	CollapseAt float64 `yaml:"collapse_at"`

	// MicroCompactAt triggers old tool result clearing. Default: 0.80.
	MicroCompactAt float64 `yaml:"micro_compact_at"`

	// AutoCompactAt triggers LLM summarization. Default: 0.93.
	AutoCompactAt float64 `yaml:"auto_compact_at"`

	// MemoryCompactAt triggers memory extraction + summarization. Default: 0.97.
	MemoryCompactAt float64 `yaml:"memory_compact_at"`
}

CompactThresholds defines the context usage ratios that trigger each level.

func DefaultCompactThresholds added in v1.17.0

func DefaultCompactThresholds() CompactThresholds

DefaultCompactThresholds returns sensible defaults.

type CompactionCircuitBreaker added in v1.17.0

type CompactionCircuitBreaker struct {
	// contains filtered or unexported fields
}

CompactionCircuitBreaker prevents compaction loops by tracking failures. After maxFailures consecutive failures, compaction is blocked until cooldown expires.

func NewCompactionCircuitBreaker added in v1.17.0

func NewCompactionCircuitBreaker(maxFailures int, cooldown time.Duration) *CompactionCircuitBreaker

NewCompactionCircuitBreaker creates a new circuit breaker.

func (*CompactionCircuitBreaker) Allow added in v1.17.0

func (cb *CompactionCircuitBreaker) Allow() bool

Allow returns true if compaction is allowed (not tripped).

func (*CompactionCircuitBreaker) Failures added in v1.17.0

func (cb *CompactionCircuitBreaker) Failures() int

Failures returns the current failure count (for testing/logging).

func (*CompactionCircuitBreaker) RecordFailure added in v1.17.0

func (cb *CompactionCircuitBreaker) RecordFailure()

RecordFailure increments the failure counter.

func (*CompactionCircuitBreaker) RecordSuccess added in v1.17.0

func (cb *CompactionCircuitBreaker) RecordSuccess()

RecordSuccess resets the failure counter.

type CompactionConfig added in v1.13.0

type CompactionConfig struct {
	// KeepRecentUserTurns is how many recent user turns to preserve verbatim
	// in the compacted output. Default: 3, Max: 12.
	KeepRecentUserTurns int `yaml:"keep_recent_user_turns"`

	// MaxToolFailures is how many tool failure entries to include in the
	// compaction summary. Default: 8.
	MaxToolFailures int `yaml:"max_tool_failures"`

	// ToolFailurePreviewChars is the max chars per tool failure preview.
	// Default: 240.
	ToolFailurePreviewChars int `yaml:"tool_failure_preview_chars"`

	// IdentifierPolicy controls whether the compaction summary instruction
	// includes guidance to preserve identifiers. Default: "preserve".
	// Values: "preserve" (include instruction), "none" (omit instruction).
	IdentifierPolicy string `yaml:"identifier_policy"`

	// CompactionModel overrides the model used for summarization LLM calls.
	// If empty, uses the agent's current model.
	CompactionModel string `yaml:"compaction_model"`

	// QualityGuard configures post-summarization quality auditing.
	QualityGuard QualityGuardConfig `yaml:"quality_guard"`

	// ContextPruning configures ratio-based in-memory context pruning.
	ContextPruning ContextPruningConfig `yaml:"context_pruning"`

	// TimeoutSeconds is the maximum time (in seconds) allowed for the LLM
	// summarization step during compaction. If exceeded, falls back to
	// trim-by-count. Default: 900 (15 minutes).
	TimeoutSeconds int `yaml:"timeout_seconds"`

	// PostIndexSync controls whether a memory indexer sync is triggered after
	// compaction completes. Values: "off" (default), "async" (fire-and-forget),
	// "await" (block until indexing completes, not yet implemented — treated as async).
	PostIndexSync string `yaml:"post_index_sync"`

	// LCMEnabled enables the Lossless Compaction Module (DAG-based memory).
	// When enabled, messages are persisted verbatim and compaction builds a
	// hierarchical summary DAG instead of a flat summary string.
	// Default: true (nil pointer = true).
	LCMEnabled *bool `yaml:"lcm_enabled"`

	// LCM holds configuration for the Lossless Compaction Module.
	LCM LCMConfig `yaml:"lcm"`
}

CompactionConfig controls how the compaction process preserves important context.

func DefaultCompactionConfig added in v1.13.0

func DefaultCompactionConfig() CompactionConfig

DefaultCompactionConfig returns sensible defaults.

type CompactionEntry added in v1.12.0

type CompactionEntry struct {
	Type           string    `json:"type"`            // Always "compaction_summary"
	Summary        string    `json:"summary"`         // The LLM-generated summary
	CompactedAt    time.Time `json:"compacted_at"`    // When compaction occurred
	MessagesBefore int       `json:"messages_before"` // Count before compaction
	MessagesAfter  int       `json:"messages_after"`  // Count after compaction
}

CompactionEntry represents a compaction summary stored in the session.

type CompactionPipeline added in v1.17.0

type CompactionPipeline struct {
	// contains filtered or unexported fields
}

CompactionPipeline coordinates multi-level proactive compaction.

func NewCompactionPipeline added in v1.17.0

func NewCompactionPipeline(thresholds CompactThresholds) *CompactionPipeline

NewCompactionPipeline creates a pipeline with the given thresholds.

func (*CompactionPipeline) Evaluate added in v1.17.0

func (p *CompactionPipeline) Evaluate(tokenCount, contextWindow int) ContextPressure

Evaluate determines which compaction level should be applied based on the current token count and context window size.

func (*CompactionPipeline) LastLevel added in v1.17.0

func (p *CompactionPipeline) LastLevel() CompactLevel

LastLevel returns the last applied compaction level.

func (*CompactionPipeline) RecordFailure added in v1.17.0

func (p *CompactionPipeline) RecordFailure()

RecordFailure records a failed compaction for circuit breaker tracking.

func (*CompactionPipeline) RecordSuccess added in v1.17.0

func (p *CompactionPipeline) RecordSuccess()

RecordSuccess records a successful compaction for circuit breaker tracking.

func (*CompactionPipeline) SetLastLevel added in v1.17.0

func (p *CompactionPipeline) SetLastLevel(level CompactLevel)

SetLastLevel records the last applied compaction level.

func (*CompactionPipeline) ShouldCompact added in v1.17.0

func (p *CompactionPipeline) ShouldCompact(level CompactLevel) bool

ShouldCompact returns true if compaction should be attempted at the given level. Checks the circuit breaker and suppresses repeat application of the same cheap level to avoid wasteful re-runs on consecutive turns at the same pressure.

type CompletionCheck added in v1.17.0

type CompletionCheck struct {
	// Name identifies this check for logging.
	Name string

	// TriggerPatterns are substrings in the conversation that indicate
	// this type of work was started.
	TriggerPatterns []string

	// CompletionPatterns are substrings that indicate the work was completed.
	// If triggers match but completion doesn't, the check fails.
	CompletionPatterns []string

	// Reminder is the message to inject if the check fails.
	Reminder string
}

CompletionCheck represents a single verification pattern.

type Config

type Config struct {
	// Name is the assistant name shown in responses.
	Name string `yaml:"name"`

	// Identity configures the assistant's structured identity (persona, theme, avatar).
	// When set, Identity.Name takes precedence over the top-level Name field.
	Identity IdentityConfig `yaml:"identity"`

	// Trigger is the keyword that activates the bot (e.g. "@devclaw").
	Trigger string `yaml:"trigger"`

	// Model is the LLM model to use (e.g. "glm-4.7-flash").
	Model string `yaml:"model"`

	// API configures the LLM provider endpoint.
	API APIConfig `yaml:"api"`

	// Instructions are the base system prompt instructions.
	Instructions string `yaml:"instructions"`

	// Timezone is the user's timezone (e.g. "America/Sao_Paulo").
	Timezone string `yaml:"timezone"`

	// Language is the preferred response language (e.g. "pt-BR").
	Language string `yaml:"language"`

	// Access configures who can use the bot (allowlist/blocklist).
	Access AccessConfig `yaml:"access"`

	// Workspaces configures isolated profiles/contexts.
	Workspaces WorkspaceConfig `yaml:"workspaces"`

	// Channels configures communication channels.
	Channels ChannelsConfig `yaml:"channels"`

	// Memory configures the memory system.
	Memory MemoryConfig `yaml:"memory"`

	// Security configures security guardrails.
	Security SecurityConfig `yaml:"security"`

	// TokenBudget configures per-layer token limits.
	TokenBudget TokenBudgetConfig `yaml:"token_budget"`

	// Plugins configures the plugin loader.
	Plugins plugins.Config `yaml:"plugins"`

	// Sandbox configures the script sandbox.
	Sandbox sandbox.Config `yaml:"sandbox"`

	// Skills configures which skills are enabled.
	Skills SkillsConfig `yaml:"skills"`

	// Scheduler configures the task scheduler.
	Scheduler SchedulerConfig `yaml:"scheduler"`

	// Heartbeat configures the proactive heartbeat system.
	Heartbeat HeartbeatConfig `yaml:"heartbeat"`

	// Subagents configures the subagent orchestration system.
	Subagents SubagentConfig `yaml:"subagents"`

	// Agent configures the agent loop parameters (turns, timeouts, auto-continue).
	Agent AgentConfig `yaml:"agent"`

	// Fallback configures model fallback with retry and backoff.
	Fallback FallbackConfig `yaml:"fallback"`

	// Budget configures monthly cost tracking and limits.
	Budget BudgetConfig `yaml:"budget"`

	// Media configures vision and audio transcription.
	Media MediaConfig `yaml:"media"`

	// Logging configures log output.
	Logging LoggingConfig `yaml:"logging"`

	// Queue configures message debouncing for bursts.
	Queue QueueConfig `yaml:"queue"`

	// Database configures the central SQLite database (devclaw.db).
	Database DatabaseConfig `yaml:"database"`

	// Gateway configures the HTTP API gateway.
	Gateway GatewayConfig `yaml:"gateway"`

	// BlockStream configures progressive message delivery (stream text to channel
	// in chunks instead of waiting for the complete response).
	BlockStream BlockStreamConfig `yaml:"block_stream"`

	// WebSearch configures the web search tool provider.
	WebSearch WebSearchConfig `yaml:"web_search"`

	// TTS configures text-to-speech synthesis.
	TTS TTSConfig `yaml:"tts"`

	// WebUI configures the web dashboard.
	WebUI webui.Config `yaml:"webui"`

	// Group configures group chat behavior.
	Group GroupConfig `yaml:"group"`

	// Agents configures specialized agent profiles and routing.
	Agents AgentsConfig `yaml:"agents"`

	// Groups configures group-specific policies and activation modes.
	Groups GroupsPolicyConfig `yaml:"groups"`

	// Hooks configures lifecycle hooks and webhooks.
	Hooks HooksConfig `yaml:"hooks"`

	// MCP configures Model Context Protocol servers.
	MCP MCPConfig `yaml:"mcp"`

	// Routines configures background routines (metrics, memory indexer, etc).
	Routines RoutinesConfig `yaml:"routines"`

	// NativeMedia configures the native media handling system.
	NativeMedia NativeMediaConfig `yaml:"native_media"`

	// Links configures the link understanding pipeline (auto-fetch URLs in messages).
	Links LinkConfig `yaml:"links"`

	// Sessions configures session lifecycle management.
	Sessions SessionReaperConfig `yaml:"sessions"`

	// Browser configures browser automation tools.
	Browser BrowserConfig `yaml:"browser"`

	// OAuthHub configures the OAuth Hub proxy for centralized OAuth management.
	OAuthHub OAuthHubConfig `yaml:"oauth_hub"`

	// Update configures auto-update checking and installation.
	Update UpdateConfig `yaml:"update"`

	// ProfileCooldowns configures per-profile cooldown durations for auth failures.
	// Optional: nil/zero values fall back to hardcoded defaults.
	ProfileCooldowns *profiles.ProfileCooldownConfig `yaml:"profile_cooldowns,omitempty"`

	// DevToolsEnabled forces dev tools registration regardless of workspace detection.
	// nil = auto-detect from workspace (default), true = always enable, false = always disable.
	DevToolsEnabled *bool `yaml:"dev_tools_enabled,omitempty"`

	// ProviderDiscovery configures dynamic model discovery for local providers
	// (Ollama, vLLM). When enabled, DevClaw probes endpoints at startup to
	// discover available models and their context window sizes.
	ProviderDiscovery ProviderDiscoveryConfig `yaml:"provider_discovery"`
}

Config holds all assistant configuration.

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns the default assistant configuration.

func LoadConfigFromFile

func LoadConfigFromFile(path string) (*Config, error)

LoadConfigFromFile reads and parses a YAML configuration file. Automatically loads .env files and expands environment variables. Returns an error if any ${VAR:?error} pattern has its variable unset.

func ParseConfig

func ParseConfig(data []byte) (*Config, error)

ParseConfig parses YAML bytes into a Config. Starts with defaults and overlays values from the YAML.

type ConfigHealth

type ConfigHealth struct {
	Valid    bool     `json:"valid"`
	Path     string   `json:"path"`
	Errors   []string `json:"errors,omitempty"`
	Sections []string `json:"sections"`
}

ConfigHealth represents configuration health status.

type ConfigWatcher

type ConfigWatcher struct {
	// contains filtered or unexported fields
}

ConfigWatcher monitors a config file for changes and invokes a callback when the file is modified. Uses polling (mtime + sha256) to avoid platform-specific file watchers.

func NewConfigWatcher

func NewConfigWatcher(path string, interval time.Duration, onChange func(*Config), logger *slog.Logger) *ConfigWatcher

NewConfigWatcher creates a new config watcher. interval is the polling interval (e.g. 5 * time.Second). onChange is called when a valid config change is detected.

func (*ConfigWatcher) Start

func (w *ConfigWatcher) Start(ctx context.Context)

Start begins polling in a goroutine. Exits when ctx is cancelled.

type ConsoleMessage added in v1.13.0

type ConsoleMessage struct {
	Type   string `json:"type"`
	Text   string `json:"text"`
	Level  string `json:"level"`
	URL    string `json:"url,omitempty"`
	Line   int    `json:"line,omitempty"`
	Column int    `json:"column,omitempty"`
}

ConsoleMessage represents a browser console message.

type ContextEngine added in v1.13.0

type ContextEngine interface {
	// Name returns a unique identifier for this engine.
	Name() string

	// Gather returns context text to inject into the prompt for a given
	// session and user input. Returns "" if no relevant context is found.
	// The maxTokens hint tells the engine its approximate token budget.
	Gather(ctx context.Context, session *Session, input string, maxTokens int) string
}

ContextEngine produces extra context to inject into the system prompt. Implementations can source context from any backend: memory stores, vector databases, code indices, knowledge graphs, etc.

type ContextEngineRegistry added in v1.13.0

type ContextEngineRegistry struct {
	// contains filtered or unexported fields
}

ContextEngineRegistry manages multiple context engines and merges their output during prompt composition.

func NewContextEngineRegistry added in v1.13.0

func NewContextEngineRegistry() *ContextEngineRegistry

NewContextEngineRegistry creates a new registry.

func (*ContextEngineRegistry) Engines added in v1.13.0

func (r *ContextEngineRegistry) Engines() []string

Engines returns the names of all registered engines.

func (*ContextEngineRegistry) GatherAll added in v1.13.0

func (r *ContextEngineRegistry) GatherAll(ctx context.Context, session *Session, input string, maxTokensPerEngine int) string

GatherAll calls Gather on every registered engine and returns the concatenated results, separated by newlines. Empty results are skipped.

func (*ContextEngineRegistry) Register added in v1.13.0

func (r *ContextEngineRegistry) Register(engine ContextEngine)

Register adds a context engine to the registry.

type ContextPressure added in v1.17.0

type ContextPressure struct {
	// TokenCount is the estimated token usage.
	TokenCount int

	// ContextWindow is the effective context window size.
	ContextWindow int

	// Ratio is TokenCount / ContextWindow (0.0 to 1.0+).
	Ratio float64

	// RecommendedLevel is the compaction level that should be applied.
	RecommendedLevel CompactLevel

	// TokensUntilBlocking is how many tokens remain before the blocking limit.
	TokensUntilBlocking int
}

ContextPressure represents the current context window usage and recommended action.

type ContextPruningConfig added in v1.14.0

type ContextPruningConfig struct {
	// SoftTrimRatio is the context usage ratio above which tool results are soft-trimmed
	// (head+tail). Default: 0.3.
	SoftTrimRatio float64 `yaml:"soft_trim_ratio"`

	// HardClearRatio is the context usage ratio above which old tool results are
	// replaced with a placeholder. Default: 0.5.
	HardClearRatio float64 `yaml:"hard_clear_ratio"`

	// SoftTrimMaxChars is the max chars for a tool result before soft-trim kicks in.
	// Default: 4096.
	SoftTrimMaxChars int `yaml:"soft_trim_max_chars"`

	// ProtectRecentTurns is how many recent assistant turns to protect from pruning.
	// Default: 3.
	ProtectRecentTurns int `yaml:"protect_recent_turns"`
}

ContextPruningConfig controls ratio-based in-memory pruning of tool results.

type ContextRouter added in v1.18.0

type ContextRouter struct {
	// contains filtered or unexported fields
}

ContextRouter resolves (channel, external_id) pairs to palace wings. It is safe for concurrent use — all state lives in the SQLite store. An in-process cache reduces burst write pressure on the store.

func NewContextRouter added in v1.18.0

func NewContextRouter(store *memory.SQLiteStore, logger *slog.Logger, cfg HierarchyConfig) *ContextRouter

NewContextRouter creates a ContextRouter backed by the given memory store. The store may be nil — in that case, the router is a no-op that always returns a SourceDisabled resolution. This supports startup paths where the memory system failed to initialize but the channels are still running.

cfg is the HierarchyConfig for this router instance. Heuristics are read from cfg.Heuristics — zero heuristics means tier 2 is a no-op.

func (*ContextRouter) Pin added in v1.18.0

func (r *ContextRouter) Pin(channel, externalID, wing string) error

Pin creates or updates an explicit mapping for a (channel, externalID) pair. This is what the `/wing set` bot command and `devclaw wing map` CLI invoke. Confidence is always 1.0 because it's a user decision.

Returns an error if the wing name is invalid or the store rejects the write. Callers (bot handlers, CLI) should surface the error to the user.

func (*ContextRouter) Resolve added in v1.18.0

func (r *ContextRouter) Resolve(ctx context.Context, channel, externalID, hint string) WingResolution

Resolve determines which wing a message from (channel, externalID) should be associated with. This function NEVER returns an error — every failure mode falls through to SourceDefault with an empty wing.

The hint parameter is an optional message preview that heuristics can inspect. Pass an empty string if no hint is available; heuristics will then rely purely on channel+externalID patterns.

Resolve may write to the store: when a heuristic hit occurs, the result is persisted as a confidence<1.0 mapping so the next Resolve call for the same pair is a fast mapped lookup.

func (*ContextRouter) Unpin added in v1.18.0

func (r *ContextRouter) Unpin(channel, externalID string) error

Unpin removes a mapping. Subsequent Resolve calls for the same pair will fall back to the heuristic tier or the default.

type ContextWindowGuardResult added in v1.13.0

type ContextWindowGuardResult struct {
	// Tokens is the resolved context window size.
	Tokens int

	// ShouldBlock is true when the context window is too small for a useful run.
	ShouldBlock bool

	// ShouldWarn is true when the context window is usable but small.
	ShouldWarn bool

	// Message describes the issue (only set when ShouldBlock or ShouldWarn).
	Message string
}

ContextWindowGuardResult holds the outcome of a context window evaluation.

func EvaluateContextWindowGuard added in v1.13.0

func EvaluateContextWindowGuard(tokens int) ContextWindowGuardResult

EvaluateContextWindowGuard checks the context window size and returns whether the agent should block or warn before starting a run.

type ContextualTool added in v1.12.0

type ContextualTool interface {
	SetDeliveryTarget(channel, chatID string)
}

ContextualTool is an interface for tools that need delivery context. Tools can implement this interface to receive channel/chatID context. The executor checks for this interface and calls SetDeliveryTarget before executing the tool handler.

Note: In DevClaw, handlers are functions (not objects), so this interface is typically implemented by a wrapper struct. For simple cases, tools can use GetDeliveryTarget(ctx) directly to extract context from the context.Context.

Example with wrapper:

type contextualHandler struct {
	fn      ToolHandlerFunc
	channel string
	chatID  string
}

func (h *contextualHandler) SetDeliveryTarget(channel, chatID string) {
	h.channel = channel
	h.chatID = chatID
}

func (h *contextualHandler) Call(ctx context.Context, args map[string]any) (any, error) {
	// Use h.channel and h.chatID
	return h.fn(ctx, args)
}

type ConversationEntry

type ConversationEntry struct {
	UserMessage       string
	AssistantResponse string
	Timestamp         time.Time
	// ToolSummary is a comma-separated digest of tools called during this turn.
	// Injected into history so future turns know what was actually verified vs. inferred.
	ToolSummary string `json:"tool_summary,omitempty"`
	// ToolCalls stores individual tool invocations for richer history reconstruction.
	// When present, buildMessages uses these instead of ToolSummary.
	ToolCalls []ToolCallRecord `json:"tool_calls,omitempty"`
}

ConversationEntry representa uma troca de mensagem na sessão.

type CooldownConfig

type CooldownConfig struct {
	BillingBackoffHours   float64 `yaml:"billing_backoff_hours"`   // Default: 5
	BillingMaxHours       float64 `yaml:"billing_max_hours"`       // Default: 24
	FailureWindowHours    float64 `yaml:"failure_window_hours"`    // Default: 24
	InitialBackoffMinutes float64 `yaml:"initial_backoff_minutes"` // Default: 1
	MaxBackoffMinutes     float64 `yaml:"max_backoff_minutes"`     // Default: 60
}

CooldownConfig defines backoff parameters for model cooldowns.

func DefaultCooldownConfig

func DefaultCooldownConfig() CooldownConfig

DefaultCooldownConfig returns sensible defaults.

type Coordinator added in v1.17.0

type Coordinator struct {
	// contains filtered or unexported fields
}

Coordinator orchestrates multi-agent workflows through structured phases.

func NewCoordinator added in v1.17.0

func NewCoordinator(config CoordinatorConfig, spawner WorkerSpawner, logger *slog.Logger) *Coordinator

NewCoordinator creates a new coordinator with the given config and spawner.

func (*Coordinator) GetPhaseTools added in v1.17.0

func (c *Coordinator) GetPhaseTools(phase CoordinatorPhase) []string

GetPhaseTools returns the allowed tools for a given phase.

func (*Coordinator) MaxWorkersForPhase added in v1.17.0

func (c *Coordinator) MaxWorkersForPhase(phase CoordinatorPhase) int

MaxWorkersForPhase returns the concurrency limit for a phase.

func (*Coordinator) RunPhase added in v1.17.0

func (c *Coordinator) RunPhase(ctx context.Context, phase CoordinatorPhase, tasks []WorkerTask) PhaseResult

RunPhase executes all tasks in a phase, respecting concurrency limits. Tasks within a phase run in parallel up to the phase's worker limit. Results are returned in the same order as the input tasks.

type CoordinatorConfig added in v1.17.0

type CoordinatorConfig struct {
	// MaxResearchWorkers is the max parallel workers in research phase. Default: 4.
	MaxResearchWorkers int `yaml:"max_research_workers"`

	// MaxImplWorkers is the max parallel workers in implementation phase. Default: 2.
	MaxImplWorkers int `yaml:"max_impl_workers"`

	// MaxVerifyWorkers is the max parallel workers in verification phase. Default: 2.
	MaxVerifyWorkers int `yaml:"max_verify_workers"`

	// DefaultTimeout is the default timeout per worker task. Default: 5min.
	DefaultTimeout time.Duration `yaml:"default_timeout"`

	// ResearchTools lists tools allowed during research (read-only).
	ResearchTools []string `yaml:"research_tools"`

	// ImplTools lists tools allowed during implementation.
	ImplTools []string `yaml:"impl_tools"`

	// VerifyTools lists tools allowed during verification.
	VerifyTools []string `yaml:"verify_tools"`
}

CoordinatorConfig configures the multi-agent coordinator.

func DefaultCoordinatorConfig added in v1.17.0

func DefaultCoordinatorConfig() CoordinatorConfig

DefaultCoordinatorConfig returns sensible defaults.

type CoordinatorPhase added in v1.17.0

type CoordinatorPhase string

CoordinatorPhase represents a stage in the multi-agent workflow.

const (
	PhaseResearch       CoordinatorPhase = "research"
	PhaseSynthesis      CoordinatorPhase = "synthesis"
	PhaseImplementation CoordinatorPhase = "implementation"
	PhaseVerification   CoordinatorPhase = "verification"
)

func PhaseOrder added in v1.17.0

func PhaseOrder() []CoordinatorPhase

PhaseOrder returns the standard execution order of phases.

type Daemon

type Daemon struct {
	Label     string    `json:"label"`
	Command   string    `json:"command"`
	PID       int       `json:"pid"`
	Port      int       `json:"port,omitempty"`
	Status    string    `json:"status"` // running, stopped, failed
	StartedAt time.Time `json:"started_at"`
	ExitCode  int       `json:"exit_code,omitempty"`
	Error     string    `json:"error,omitempty"`
	// contains filtered or unexported fields
}

Daemon represents a managed background process.

type DaemonManager

type DaemonManager struct {
	// contains filtered or unexported fields
}

DaemonManager manages a set of background daemons.

func NewDaemonManager

func NewDaemonManager() *DaemonManager

NewDaemonManager creates a new daemon manager.

func (*DaemonManager) ClearDaemon added in v1.13.0

func (dm *DaemonManager) ClearDaemon(label string) error

ClearDaemon removes a stopped/failed daemon from the registry.

func (*DaemonManager) GetLogs

func (dm *DaemonManager) GetLogs(label string, n int, filter string) (string, error)

GetLogs returns the last n lines from a daemon's output ring buffer.

func (*DaemonManager) List

func (dm *DaemonManager) List() []Daemon

List returns info about all managed daemons.

func (*DaemonManager) PollDaemon added in v1.13.0

func (dm *DaemonManager) PollDaemon(label string) (string, error)

PollDaemon returns recent output with backoff hints.

func (*DaemonManager) RestartDaemon

func (dm *DaemonManager) RestartDaemon(label string) (*Daemon, error)

RestartDaemon stops and re-starts a daemon with the same config.

func (*DaemonManager) Shutdown

func (dm *DaemonManager) Shutdown()

Shutdown stops all running daemons.

func (*DaemonManager) StartDaemon

func (dm *DaemonManager) StartDaemon(label, command string, port int, readyPattern string) (*Daemon, error)

StartDaemon starts a new background process.

func (*DaemonManager) StopDaemon

func (dm *DaemonManager) StopDaemon(label string, force bool) error

StopDaemon gracefully stops a daemon (SIGTERM). If force is true, uses SIGKILL.

func (*DaemonManager) WriteToDaemon added in v1.13.0

func (dm *DaemonManager) WriteToDaemon(label, input string) error

WriteToDaemon sends input to a daemon's stdin.

type DatabaseConfig

type DatabaseConfig struct {
	// Path is the database file path for SQLite (default: "./data/devclaw.db").
	// Kept for backward compatibility with existing configs.
	Path string `yaml:"path"`

	// Hub enables the new Database Hub system with multi-backend support.
	// When Hub.Backend is not set, falls back to Path for SQLite.
	Hub database.HubConfig `yaml:"hub"`
}

DatabaseConfig configures the central database using the Database Hub. Supports SQLite (default), PostgreSQL, and MySQL backends.

func (DatabaseConfig) Effective added in v1.8.0

func (c DatabaseConfig) Effective() database.HubConfig

Effective returns the effective Hub configuration, applying defaults.

type DatabaseHealth

type DatabaseHealth struct {
	Connected bool           `json:"connected"`
	SizeMB    float64        `json:"size_mb"`
	Tables    map[string]int `json:"tables"`
	Error     string         `json:"error,omitempty"`
}

DatabaseHealth represents database health status.

type DecisionPhase added in v1.17.0

type DecisionPhase struct{}

DecisionPhase decides whether to loop back (tool_use) or stop (text response).

func (*DecisionPhase) Execute added in v1.17.0

func (d *DecisionPhase) Execute(ctx context.Context, state *TurnState) (NextAction, error)

func (*DecisionPhase) Name added in v1.17.0

func (d *DecisionPhase) Name() string

type DedupCache added in v1.13.0

type DedupCache struct {
	// contains filtered or unexported fields
}

DedupCache provides Message-ID based deduplication with automatic TTL expiration. This catches duplicate deliveries from messaging platforms (webhook retries, reconnection replays) that content-match alone misses.

func NewDedupCache added in v1.13.0

func NewDedupCache(ttl time.Duration) *DedupCache

NewDedupCache creates a new dedup cache with the given TTL.

func (*DedupCache) CheckAndRecord added in v1.13.0

func (dc *DedupCache) CheckAndRecord(key string) bool

CheckAndRecord atomically checks if a key is a duplicate and records it if not. Returns true if the key was already seen (duplicate).

func (*DedupCache) IsDuplicate added in v1.13.0

func (dc *DedupCache) IsDuplicate(key string) bool

IsDuplicate returns true if the key was already seen and still within TTL.

func (*DedupCache) Record added in v1.13.0

func (dc *DedupCache) Record(key string)

Record stores a key with its TTL expiry.

func (*DedupCache) Stop added in v1.13.0

func (dc *DedupCache) Stop()

Stop terminates the background cleanup goroutine.

type DeleteFileHunk added in v1.12.0

type DeleteFileHunk struct {
	Path string
}

DeleteFileHunk represents deleting an existing file.

type DeliveryScope added in v1.16.0

type DeliveryScope string

DeliveryScope controls who receives the subagent completion announcement.

const (
	// DeliveryScopeAll delivers to both parent agent and external channel (default).
	DeliveryScopeAll DeliveryScope = "all"
	// DeliveryScopeParent delivers only to the parent agent (no external notification).
	DeliveryScopeParent DeliveryScope = "parent"
	// DeliveryScopeExternal delivers only to the external channel (no parent injection).
	DeliveryScopeExternal DeliveryScope = "external"
)

type DeliveryTarget

type DeliveryTarget struct {
	Channel string
	ChatID  string
}

DeliveryTarget holds the channel and chatID for message delivery.

func DeliveryTargetFromContext

func DeliveryTargetFromContext(ctx context.Context) DeliveryTarget

DeliveryTargetFromContext extracts the delivery target from a context. Returns empty DeliveryTarget if not set.

type DestructiveCheckResult added in v1.12.0

type DestructiveCheckResult struct {
	Allowed           bool
	Reason            string
	RequiresUserInput bool
	BatchWarning      string
	CooldownRemaining time.Duration
}

DestructiveCheckResult holds the result of a destructive tool check.

type DestructiveToolsConfig added in v1.12.0

type DestructiveToolsConfig struct {
	// Enabled turns on destructive tool protection (default: true).
	Enabled bool `yaml:"enabled"`

	// Tools lists tool names that are considered destructive.
	// With dispatcher consolidation, destructive actions are sub-actions
	// within dispatchers; action-level protection is in each dispatcher handler.
	Tools []string `yaml:"tools"`

	// RateLimitPerMinute is the maximum number of calls per tool per minute.
	// Default: 3
	RateLimitPerMinute int `yaml:"rate_limit_per_minute"`

	// BatchThreshold is the number of consecutive calls that trigger a warning.
	// Default: 3
	BatchThreshold int `yaml:"batch_threshold"`

	// RequireInteractiveConfirmation forces the agent to ask user before
	// executing destructive operations, rather than using a confirm parameter.
	// Default: false (uses confirm parameter)
	RequireInteractiveConfirmation bool `yaml:"require_interactive_confirmation"`

	// CooldownSeconds is the minimum time between destructive operations.
	// Default: 5
	CooldownSeconds int `yaml:"cooldown_seconds"`
}

DestructiveToolsConfig configures protection for destructive operations.

func DefaultDestructiveToolsConfig added in v1.12.0

func DefaultDestructiveToolsConfig() DestructiveToolsConfig

DefaultDestructiveToolsConfig returns safe defaults.

type DestructiveTracker added in v1.12.0

type DestructiveTracker struct {
	// contains filtered or unexported fields
}

DestructiveTracker tracks and limits destructive tool calls.

func NewDestructiveTracker added in v1.12.0

func NewDestructiveTracker(cfg DestructiveToolsConfig, logger *slog.Logger) *DestructiveTracker

NewDestructiveTracker creates a new tracker with the given config.

func (*DestructiveTracker) Check added in v1.12.0

Check evaluates whether a destructive tool call should be allowed. It does NOT modify state - call RecordCall() after the tool executes.

func (*DestructiveTracker) IsDestructive added in v1.12.0

func (d *DestructiveTracker) IsDestructive(toolName string) bool

IsDestructive checks if a tool is in the destructive list.

func (*DestructiveTracker) RecordCall added in v1.12.0

func (d *DestructiveTracker) RecordCall(toolName string)

RecordCall records a destructive tool call for rate limiting. This should be called AFTER the tool executes successfully.

func (*DestructiveTracker) Reset added in v1.12.0

func (d *DestructiveTracker) Reset()

Reset resets the tracker state (useful for testing or manual override).

func (*DestructiveTracker) Stats added in v1.12.0

func (d *DestructiveTracker) Stats() map[string]any

Stats returns current statistics for monitoring.

type DiagnosticsResult

type DiagnosticsResult struct {
	Database     DatabaseHealth      `json:"database"`
	Config       ConfigHealth        `json:"config"`
	Channels     []ChannelDiagnostic `json:"channels"`
	RecentErrors []AuditRecordShort  `json:"recent_errors,omitempty"`
	Memory       MemoryStats         `json:"memory"`
	Disk         DiskStats           `json:"disk"`
}

DiagnosticsResult represents comprehensive system diagnostics.

type DiscoveredModel added in v1.13.0

type DiscoveredModel struct {
	// Name is the model identifier (e.g. "llama3:latest").
	Name string `json:"name"`

	// Provider is the source provider ("ollama", "vllm").
	Provider string `json:"provider"`

	// ContextWindow is the context window size in tokens (0 = unknown).
	ContextWindow int `json:"context_window,omitempty"`

	// ParameterSize is a human-readable parameter count (e.g. "7B", "70B").
	ParameterSize string `json:"parameter_size,omitempty"`
}

DiscoveredModel holds metadata about a model found via provider discovery.

type DiskStats

type DiskStats struct {
	TotalGB float64 `json:"total_gb"`
	FreeGB  float64 `json:"free_gb"`
	UsedPct float64 `json:"used_pct"`
}

DiskStats represents disk usage statistics.

type DreamConfig added in v1.17.0

type DreamConfig struct {
	// Enabled controls whether the dream system is active. Default: true.
	Enabled bool `yaml:"enabled"`

	// MinHoursBetween is the minimum hours between dream runs (Gate 1). Default: 6.
	MinHoursBetween int `yaml:"min_hours_between"`

	// MinSessionsBetween is the minimum sessions between dream runs (Gate 2). Default: 2.
	MinSessionsBetween int `yaml:"min_sessions_between"`

	// MaxMemoriesToProcess limits how many memories to analyze per dream. Default: 100.
	MaxMemoriesToProcess int `yaml:"max_memories_to_process"`

	// IdleMinutes is how long the daemon must be idle before triggering. Default: 10.
	IdleMinutes int `yaml:"idle_minutes"`
}

DreamConfig configures the background memory consolidation system.

func DefaultDreamConfig added in v1.17.0

func DefaultDreamConfig() DreamConfig

DefaultDreamConfig returns sensible defaults.

type DreamConsolidator added in v1.17.0

type DreamConsolidator struct {
	// contains filtered or unexported fields
}

DreamConsolidator manages background memory consolidation.

func NewDreamConsolidator added in v1.17.0

func NewDreamConsolidator(config DreamConfig, store memory.Store, stateDir string, logger *slog.Logger) *DreamConsolidator

NewDreamConsolidator creates a new dream consolidator.

func (*DreamConsolidator) ForceRun added in v1.18.2

func (d *DreamConsolidator) ForceRun(ctx context.Context) DreamResult

ForceRun bypasses all trigger gates and runs a dream cycle immediately. Used by the memory(action="dream_force") tool for on-demand consolidation.

func (*DreamConsolidator) RecordCompaction added in v1.18.0

func (d *DreamConsolidator) RecordCompaction()

RecordCompaction increments the compaction counter. For persistent sessions (WhatsApp), compactions serve as the activity signal since sessions never formally end.

func (*DreamConsolidator) RecordSession added in v1.17.0

func (d *DreamConsolidator) RecordSession()

RecordSession increments the session counter (Gate 2). Called by the assistant when a session ends.

func (*DreamConsolidator) Run added in v1.17.0

Run executes a single dream consolidation cycle. Phases: Orient → Gather → Consolidate → Apply

func (*DreamConsolidator) Start added in v1.17.0

func (d *DreamConsolidator) Start(ctx context.Context)

Start begins the background dream loop. It checks gates periodically and runs consolidation when conditions are met.

func (*DreamConsolidator) State added in v1.17.0

func (d *DreamConsolidator) State() DreamState

State returns the current dream state (for observability).

func (*DreamConsolidator) Stop added in v1.17.0

func (d *DreamConsolidator) Stop()

Stop halts the background dream loop.

func (*DreamConsolidator) WithHierarchyConfig added in v1.18.0

func (d *DreamConsolidator) WithHierarchyConfig(cfg HierarchyConfig) *DreamConsolidator

WithHierarchyConfig provides the hierarchy feature flag and keyword map needed to gate and drive the legacy classifier phase.

func (*DreamConsolidator) WithSQLiteStore added in v1.18.0

func (d *DreamConsolidator) WithSQLiteStore(s *memory.SQLiteStore) *DreamConsolidator

WithSQLiteStore wires an optional *SQLiteStore into the consolidator so the classifier phase can run during dream cycles. Keeps the memory.Store interface unbroken (Option A).

type DreamResult added in v1.17.0

type DreamResult struct {
	MemoriesAnalyzed int           `json:"memories_analyzed"`
	Duplicates       int           `json:"duplicates_merged"`
	Contradictions   int           `json:"contradictions_found"`
	Consolidated     int           `json:"consolidated"`
	Duration         time.Duration `json:"duration"`
	Error            error         `json:"-"`
}

DreamResult holds the outcome of a dream consolidation run.

type DreamState added in v1.17.0

type DreamState struct {
	LastDreamAt      time.Time `json:"last_dream_at"`
	SessionsSince    int       `json:"sessions_since"`
	CompactionsSince int       `json:"compactions_since"` // proxy for sessions in persistent channels (WhatsApp)
	TotalDreams      int       `json:"total_dreams"`
	LastResult       string    `json:"last_result"` // "success", "error", "no_changes"
}

DreamState persists the dream system's state between restarts.

type ErrEscalation added in v1.16.0

type ErrEscalation struct {
	Signal *EscalationSignal
}

ErrEscalation is returned when an agent run is terminated due to escalation.

func (*ErrEscalation) Error added in v1.16.0

func (e *ErrEscalation) Error() string

type EscalationSignal added in v1.16.0

type EscalationSignal struct {
	Reason  string // Why the agent is escalating.
	Summary string // Summary of context/work done so far.
}

EscalationSignal indicates that a plugin agent wants to escalate to the main agent.

type EventBus

type EventBus struct {
	// contains filtered or unexported fields
}

EventBus is a thread-safe pub/sub hub for agent events. Subscribers receive events synchronously during Emit — callers should keep listener logic fast or dispatch to goroutines internally.

func NewEventBus

func NewEventBus() *EventBus

NewEventBus creates a new event bus.

func (*EventBus) CleanupRun

func (eb *EventBus) CleanupRun(runID string)

CleanupRun removes the sequence counter for a completed run.

func (*EventBus) Emit

func (eb *EventBus) Emit(event AgentEvent)

Emit sends an event to all registered listeners. The event's Seq field is auto-assigned using an atomic counter scoped to the run ID.

func (*EventBus) EmitDelta

func (eb *EventBus) EmitDelta(runID, sessionID, content string)

EmitDelta is a convenience method for emitting text delta events.

func (*EventBus) EmitDone

func (eb *EventBus) EmitDone(runID, sessionID string, usage any)

EmitDone emits a run completion event.

func (*EventBus) EmitError

func (eb *EventBus) EmitError(runID, sessionID, message string)

EmitError emits an error event.

func (*EventBus) EmitThinkingDelta

func (eb *EventBus) EmitThinkingDelta(runID, sessionID, content string)

EmitThinkingDelta emits a thinking_delta event with a partial reasoning token.

func (*EventBus) EmitThinkingEnd

func (eb *EventBus) EmitThinkingEnd(runID, sessionID string)

EmitThinkingEnd emits a thinking_end event. Callers are responsible for deduplication — this method emits unconditionally. The recommended pattern is to track a thinkingActive bool and only call EmitThinkingEnd once.

func (*EventBus) EmitThinkingStart

func (eb *EventBus) EmitThinkingStart(runID, sessionID string)

EmitThinkingStart emits a thinking_start event signalling the beginning of an extended thinking / reasoning block.

func (*EventBus) EmitToolResult

func (eb *EventBus) EmitToolResult(runID, sessionID, toolName, output string, isError bool)

EmitToolResult emits a tool result event.

func (*EventBus) EmitToolUse

func (eb *EventBus) EmitToolUse(runID, sessionID, toolName string, input any)

EmitToolUse emits a tool invocation event.

func (*EventBus) Subscribe

func (eb *EventBus) Subscribe(fn EventListener) func()

Subscribe registers a listener and returns an unsubscribe function. The listener is called synchronously for every emitted event.

func (*EventBus) SubscribeRun

func (eb *EventBus) SubscribeRun(runID string, fn EventListener) func()

SubscribeRun registers a listener that only receives events for a specific run. Returns an unsubscribe function.

type EventListener

type EventListener func(event AgentEvent)

EventListener is a callback that receives agent events.

type ExecAnalysisConfig

type ExecAnalysisConfig struct {
	// Enabled turns the analysis on/off.
	Enabled bool `yaml:"enabled"`

	// Categories configures each risk category.
	Categories map[RiskLevel]RiskCategoryConfig `yaml:"categories"`

	// SafeBins lists binary paths that are always safe.
	SafeBins []string `yaml:"safe_bins"`

	// Trust configures per-role trust levels.
	Trust TrustConfig `yaml:"trust"`

	// SuspiciousPatterns are regex patterns for suspicious constructs.
	SuspiciousPatterns []string `yaml:"suspicious_patterns"`

	// DefaultAction is the action for commands that don't match any category.
	DefaultAction RiskAction `yaml:"default_action"`
}

ExecAnalysisConfig configures the exec analysis system.

func DefaultExecAnalysisConfig

func DefaultExecAnalysisConfig() ExecAnalysisConfig

DefaultExecAnalysisConfig returns sensible defaults.

type ExecAnalysisResult

type ExecAnalysisResult struct {
	// Risk is the determined risk level.
	Risk RiskLevel

	// Action is the action to take.
	Action RiskAction

	// Reason explains why this risk level was assigned.
	Reason string

	// MatchedPattern is the pattern that matched (if any).
	MatchedPattern string

	// IsSuspicious indicates if suspicious constructs were found.
	IsSuspicious bool

	// SuspiciousMatches lists the suspicious patterns found.
	SuspiciousMatches []string
}

ExecAnalysisResult contains the analysis result for a command.

type ExecAnalyzer

type ExecAnalyzer struct {
	// contains filtered or unexported fields
}

ExecAnalyzer analyzes commands for risk.

func NewExecAnalyzer

func NewExecAnalyzer(cfg ExecAnalysisConfig, logger *slog.Logger) *ExecAnalyzer

NewExecAnalyzer creates a new command analyzer.

func (*ExecAnalyzer) Analyze

func (a *ExecAnalyzer) Analyze(command string) ExecAnalysisResult

Analyze analyzes a command and returns the risk assessment.

func (*ExecAnalyzer) AnalyzeForRole

func (a *ExecAnalyzer) AnalyzeForRole(command string, role string) ExecAnalysisResult

AnalyzeForRole analyzes a command considering the user's role.

type ExecQueueItem

type ExecQueueItem struct {
	ID          string    `json:"id"`
	Tool        string    `json:"tool"`
	Caller      string    `json:"caller"`
	SessionID   string    `json:"session_id"`
	Description string    `json:"description"`
	CreatedAt   time.Time `json:"created_at"`
}

ExecQueueItem represents a pending execution approval.

type ExportedMessage

type ExportedMessage struct {
	User      string    `json:"user"`
	Assistant string    `json:"assistant"`
	Timestamp time.Time `json:"timestamp"`
}

ExportedMessage is a single message in an exported session.

type ExtractedMemory added in v1.17.0

type ExtractedMemory struct {
	// Type classifies the memory for retrieval and organization.
	// Values: "decision", "preference", "fact", "learning", "context"
	Type string `json:"type"`

	// Content is the extracted information in concise, self-contained form.
	Content string `json:"content"`

	// Importance is a 1-5 score indicating how valuable this memory is.
	// 5 = critical decision, 1 = minor observation.
	Importance int `json:"importance"`
}

ExtractedMemory represents a single piece of information extracted from conversation history that should be preserved in long-term memory.

type FailoverCoordinator added in v1.13.0

type FailoverCoordinator struct {
	// contains filtered or unexported fields
}

FailoverCoordinator unifies profile management and model failover into a single system with consistent error classification and cooldown management.

func NewFailoverCoordinator added in v1.13.0

func NewFailoverCoordinator(profileMgr profiles.ProfileManager, modelCfg ModelFallbackConfig, logger *slog.Logger) *FailoverCoordinator

NewFailoverCoordinator creates a coordinator that wraps profile and model managers.

func (*FailoverCoordinator) GetModelManager added in v1.13.0

func (fc *FailoverCoordinator) GetModelManager() *ModelFailoverManager

GetModelManager returns the underlying model failover manager (for direct access when needed).

func (*FailoverCoordinator) HasProfileManager added in v1.13.0

func (fc *FailoverCoordinator) HasProfileManager() bool

HasProfileManager returns true if a profile manager is configured.

func (*FailoverCoordinator) ReportFailure added in v1.13.0

func (fc *FailoverCoordinator) ReportFailure(model string, profileID profiles.ProfileID, statusCode int, errMsg string) FailoverReason

ReportFailure classifies an error ONCE and applies consistent cooldowns to both the model and profile systems. This eliminates the inconsistency where the same error could be classified differently by each system.

func (*FailoverCoordinator) ReportFailureWithCause added in v1.16.0

func (fc *FailoverCoordinator) ReportFailureWithCause(model string, profileID profiles.ProfileID, statusCode int, errMsg string, cause error) FailoverReason

ReportFailureWithCause is like ReportFailure but also traverses the error cause chain for more accurate classification of wrapped errors (e.g., RESOURCE_EXHAUSTED wrapped in AbortError).

func (*FailoverCoordinator) ReportSuccess added in v1.13.0

func (fc *FailoverCoordinator) ReportSuccess(model string, profileID profiles.ProfileID)

ReportSuccess records a successful call for both model and profile.

func (*FailoverCoordinator) SelectModelAndProfile added in v1.13.0

func (fc *FailoverCoordinator) SelectModelAndProfile(provider, modelOverride string, fallbacksOverride ...string) (model string, profileID profiles.ProfileID, apiKey string, err error)

SelectModelAndProfile resolves the best model and profile for a request. Returns the selected model, profile ID, API key, and any error. Optional fallbacksOverride temporarily overrides the fallback chain for this single request, useful for per-channel or per-skill model preferences.

type FailoverReason

type FailoverReason string

FailoverReason classifies why a model failed.

const (
	FailoverBilling        FailoverReason = "billing"         // 402 Payment Required
	FailoverRateLimit      FailoverReason = "rate_limit"      // 429 Too Many Requests, 529 Overloaded
	FailoverAuth           FailoverReason = "auth"            // 401 (transient)
	FailoverAuthPermanent  FailoverReason = "auth_permanent"  // 403, revoked keys, deactivated accounts
	FailoverSessionExpired FailoverReason = "session_expired" // Token expired/revoked
	FailoverTimeout        FailoverReason = "timeout"         // 408, ETIMEDOUT, empty chunks, Cloudflare 521-530
	FailoverFormat         FailoverReason = "format"          // 400 Bad Request
	FailoverServer         FailoverReason = "server"          // 5xx (excluding Cloudflare CDN codes)
	FailoverModelNotFound  FailoverReason = "model_not_found" // Model doesn't exist for this provider
	FailoverUnknown        FailoverReason = "unknown"
)

func ClassifyError

func ClassifyError(statusCode int, errMsg string) FailoverReason

ClassifyError determines the failover reason from an HTTP status code and error message.

func ClassifyErrorFull added in v1.16.0

func ClassifyErrorFull(statusCode int, errMsg string, cause error) FailoverReason

ClassifyErrorFull determines the failover reason by examining the HTTP status code, error message, AND the full error cause chain. It walks inner causes before applying timeout heuristics, so a RESOURCE_EXHAUSTED wrapped in an AbortError is correctly classified as rate_limit, not timeout.

type FallbackConfig

type FallbackConfig struct {
	// Models is the ordered list of fallback models to try on failure.
	// Supports N providers: primary -> fallback1 -> fallback2 -> ... -> local.
	Models []string `yaml:"models"`

	// Chain defines provider-specific fallback with separate base_url/api_key.
	// Each entry is a complete provider config tried in order on failure.
	Chain []ProviderChainEntry `yaml:"chain"`

	// MaxRetries per model before moving to next (default: 2).
	MaxRetries int `yaml:"max_retries"`

	// InitialBackoffMs is the initial retry delay in ms (default: 1000).
	InitialBackoffMs int `yaml:"initial_backoff_ms"`

	// MaxBackoffMs caps the backoff (default: 30000).
	MaxBackoffMs int `yaml:"max_backoff_ms"`

	// RetryOnStatusCodes lists HTTP codes that trigger retry (default: [429, 500, 502, 503, 529]).
	RetryOnStatusCodes []int `yaml:"retry_on_status_codes"`
}

FallbackConfig configures model fallback and retry behavior.

func DefaultFallbackConfig

func DefaultFallbackConfig() FallbackConfig

DefaultFallbackConfig returns sensible defaults for model fallback.

func (FallbackConfig) Effective

func (f FallbackConfig) Effective() FallbackConfig

Effective returns a copy with default values filled in for zero fields.

type FormField added in v1.13.0

type FormField struct {
	Ref   string `json:"ref"`
	Type  string `json:"type"`  // textbox, checkbox, radio, combobox
	Value string `json:"value"` // For text inputs
}

FormField represents a form field for fill action.

type FunctionCall

type FunctionCall struct {
	Name      string `json:"name"`
	Arguments string `json:"arguments"`
}

FunctionCall holds the function name and serialized arguments from the LLM.

type FunctionDef

type FunctionDef struct {
	Name        string          `json:"name"`
	Description string          `json:"description"`
	Parameters  json.RawMessage `json:"parameters"`
}

FunctionDef describes a callable function exposed to the LLM.

type GatewayConfig

type GatewayConfig struct {
	// Enabled turns the gateway on/off (default: false).
	Enabled bool `yaml:"enabled"`

	// Address is the listen address (default: ":8085").
	Address string `yaml:"address"`

	// AuthToken is the Bearer token for /api/* and /v1/* auth (empty = no auth).
	AuthToken string `yaml:"auth_token"`

	// CORSOrigins lists allowed origins for CORS (empty = no CORS).
	CORSOrigins []string `yaml:"cors_origins"`

	// TLS configures HTTPS for the gateway.
	TLS TLSConfig `yaml:"tls"`
}

GatewayConfig configures the HTTP API gateway.

type GroupConfig

type GroupConfig struct {
	// ActivationMode controls when the bot responds in groups:
	//   "always"  — responds to all messages (default)
	//   "mention" — only when mentioned by name/trigger
	//   "reply"   — only when replied to directly
	ActivationMode string `yaml:"activation_mode"`

	// IntroMessage is sent when the bot joins a new group.
	// Empty = no intro. Supports template variables: {{name}}, {{trigger}}.
	IntroMessage string `yaml:"intro_message"`

	// ContextInjection adds group-specific context to the system prompt.
	// Useful for per-group instructions, rules, or personas.
	ContextInjection map[string]string `yaml:"context_injection"`

	// MaxParticipants limits context tracking for group participants.
	// Names of the last N participants are included in the prompt for
	// natural multi-party conversation (default: 20).
	MaxParticipants int `yaml:"max_participants"`

	// QuietHours defines time ranges when the bot won't respond in groups
	// (e.g. "23:00-07:00"). Empty = always active.
	QuietHours string `yaml:"quiet_hours"`

	// IgnorePatterns are regex patterns for messages the bot should ignore
	// even when activated (e.g. forwarded messages, bot commands for other bots).
	IgnorePatterns []string `yaml:"ignore_patterns"`
}

GroupConfig configures group chat behavior.

type GroupManager

type GroupManager struct {
	// contains filtered or unexported fields
}

GroupManager handles group-specific behavior and state.

func NewGroupManager

func NewGroupManager(cfg GroupConfig) *GroupManager

NewGroupManager creates a new group manager with the given config.

func (*GroupManager) BuildGroupPromptContext

func (gm *GroupManager) BuildGroupPromptContext(chatID, botName string) string

BuildGroupPromptContext builds a group-specific section for the system prompt.

func (*GroupManager) GetContextInjection

func (gm *GroupManager) GetContextInjection(chatID string) string

GetContextInjection returns any group-specific context for the given chatID.

func (*GroupManager) GetIntroMessage

func (gm *GroupManager) GetIntroMessage(chatID, botName, trigger string) string

GetIntroMessage returns the intro message for a group, or empty if already sent. Marks the group as introduced so the message is only sent once.

func (*GroupManager) GetParticipants

func (gm *GroupManager) GetParticipants(chatID string) []string

GetParticipants returns the recent participant names for a group.

func (*GroupManager) ShouldRespond

func (gm *GroupManager) ShouldRespond(chatID, senderName, messageText, botName, trigger string) (bool, string)

ShouldRespond determines if the bot should respond to a group message. Returns (respond bool, reason string).

func (*GroupManager) TrackParticipant

func (gm *GroupManager) TrackParticipant(chatID, name string)

TrackParticipant records a participant's activity in a group.

type GroupPolicy

type GroupPolicy string

GroupPolicy defines the access policy for a group.

const (
	// GroupPolicyOpen allows all group members to use the bot.
	GroupPolicyOpen GroupPolicy = "open"
	// GroupPolicyDisabled prevents the bot from responding in this group.
	GroupPolicyDisabled GroupPolicy = "disabled"
	// GroupPolicyAllowlist restricts access to allowed users only.
	GroupPolicyAllowlist GroupPolicy = "allowlist"
)

type GroupPolicyConfig

type GroupPolicyConfig struct {
	// ID is the group JID.
	ID string `yaml:"id"`
	// Name is a human-readable name for the group.
	Name string `yaml:"name"`
	// Policy is the access policy for this group.
	Policy GroupPolicy `yaml:"policy"`
	// Activation is the activation mode.
	Activation ActivationMode `yaml:"activation"`
	// Keywords trigger the bot in keyword mode.
	Keywords []string `yaml:"keywords"`
	// Workspace is the workspace to use for this group.
	Workspace string `yaml:"workspace"`
	// QuietHours defines when the bot should be silent.
	QuietHours *QuietHoursConfig `yaml:"quiet_hours"`
	// MaxParticipants ignores messages in groups larger than this (0 = unlimited).
	MaxParticipants int `yaml:"max_participants"`
	// AllowedUsers is the list of allowed user JIDs for allowlist policy.
	AllowedUsers []string `yaml:"allowed_users"`
}

GroupPolicyConfig holds configuration for a specific group's policy.

type GroupPolicyManager

type GroupPolicyManager struct {
	// contains filtered or unexported fields
}

GroupPolicyManager manages group-specific policies.

func NewGroupPolicyManager

func NewGroupPolicyManager(cfg GroupsPolicyConfig, logger *slog.Logger) *GroupPolicyManager

NewGroupPolicyManager creates a new group policy manager.

func (*GroupPolicyManager) GetGroupConfig

func (m *GroupPolicyManager) GetGroupConfig(groupJID string) *GroupPolicyConfig

GetGroupConfig returns the configuration for a group. Returns a default config if the group is not explicitly configured.

func (*GroupPolicyManager) GetWorkspace

func (m *GroupPolicyManager) GetWorkspace(groupJID string) string

GetWorkspace returns the workspace for a group, or empty string if not set.

func (*GroupPolicyManager) IsBlocked

func (m *GroupPolicyManager) IsBlocked(groupJID string) bool

IsBlocked returns true if the group is blocked.

func (*GroupPolicyManager) IsQuietHours

func (m *GroupPolicyManager) IsQuietHours(cfg *GroupPolicyConfig) bool

IsQuietHours checks if quiet hours are active for a group.

func (*GroupPolicyManager) ListBlocked

func (m *GroupPolicyManager) ListBlocked() []string

ListBlocked returns all blocked group IDs.

func (*GroupPolicyManager) ListGroups

func (m *GroupPolicyManager) ListGroups() []string

ListGroups returns all configured group IDs.

func (*GroupPolicyManager) ShouldRespond

func (m *GroupPolicyManager) ShouldRespond(groupJID, userJID string, content string, isReplyToBot bool, trigger string) bool

ShouldRespond determines if the bot should respond to a message in a group.

type GroupsPolicyConfig

type GroupsPolicyConfig struct {
	// DefaultPolicy is the policy for groups not explicitly configured.
	DefaultPolicy GroupPolicy `yaml:"default_policy"`
	// Groups is the list of group-specific configurations.
	Groups []GroupPolicyConfig `yaml:"groups"`
	// Blocked is the list of blocked group JIDs.
	Blocked []string `yaml:"blocked"`
}

GroupsPolicyConfig holds all group policy configuration.

type HandlerConfig

type HandlerConfig struct {
	// Event is the hook event to listen to.
	Event string `yaml:"event"`

	// Action is the action to take (notify_admins, send_message).
	Action string `yaml:"action"`

	// Template is a Go template for the action output.
	Template string `yaml:"template"`

	// Enabled controls whether this handler is active.
	Enabled bool `yaml:"enabled"`
}

HandlerConfig configures an internal hook handler.

type HealthCheckResult

type HealthCheckResult struct {
	Component string `json:"component"`
	Status    string `json:"status"` // "PASS", "FAIL", "WARN"
	Message   string `json:"message"`
	LatencyMs int64  `json:"latency_ms,omitempty"`
}

HealthCheckResult represents the result of a health check.

type Heartbeat

type Heartbeat struct {
	// contains filtered or unexported fields
}

Heartbeat runs periodic checks and proactive behavior.

func NewHeartbeat

func NewHeartbeat(cfg HeartbeatConfig, assistant *Assistant, logger *slog.Logger) *Heartbeat

NewHeartbeat creates a new heartbeat instance.

func (*Heartbeat) Start

func (h *Heartbeat) Start(ctx context.Context)

Start begins the heartbeat loop in a background goroutine.

func (*Heartbeat) Stop

func (h *Heartbeat) Stop()

Stop shuts down the heartbeat.

func (*Heartbeat) UpdateConfig

func (h *Heartbeat) UpdateConfig(cfg HeartbeatConfig)

UpdateConfig updates the heartbeat config from hot-reload.

type HeartbeatConfig

type HeartbeatConfig struct {
	// Enabled turns the heartbeat on/off.
	Enabled bool `yaml:"enabled"`

	// Interval is the time between heartbeat ticks.
	// Default: 30 minutes.
	Interval time.Duration `yaml:"interval"`

	// ActiveStart is the earliest hour the heartbeat runs (e.g., 9 for 9 AM).
	ActiveStart int `yaml:"active_start"`

	// ActiveEnd is the latest hour the heartbeat runs (e.g., 22 for 10 PM).
	ActiveEnd int `yaml:"active_end"`

	// Channel is the default channel to send proactive messages to.
	Channel string `yaml:"channel"`

	// ChatID is the default chat to send proactive messages to.
	ChatID string `yaml:"chat_id"`

	// WorkspaceDir is the workspace directory where HEARTBEAT.md is located.
	WorkspaceDir string `yaml:"workspace_dir"`

	// IsolateSession creates a fresh session for each heartbeat tick.
	// This prevents the heartbeat session from growing large over time
	// at the cost of losing inter-heartbeat context. Default: false.
	IsolateSession bool `yaml:"isolate_session"`
}

HeartbeatConfig configures the heartbeat system.

func DefaultHeartbeatConfig

func DefaultHeartbeatConfig() HeartbeatConfig

DefaultHeartbeatConfig returns sensible defaults for the heartbeat.

type HierarchyConfig added in v1.18.0

type HierarchyConfig struct {
	// Enabled turns palace-aware memory on or off at the subsystem level.
	//
	// When false:
	//   - The context router is instantiated but Resolve always returns
	//     SourceDisabled with an empty wing.
	//   - The memory_list_wings / memory_list_rooms / memory_get_taxonomy
	//     tools return a "feature disabled" error.
	//   - Search continues to use the v1.17.0 hybrid fusion without any
	//     wing boost. Byte-identical behavior guaranteed.
	//   - Schema additions remain in the DB (harmless: new columns are
	//     nullable, new tables are empty).
	//
	// When true:
	//   - Context router resolves (channel, chatID) to wings.
	//   - Palace tools are fully functional.
	//   - Wing boost is applied in hybrid search (Sprint 2).
	//   - L0/L1/L2 layered memory stack activates (Sprint 2).
	//
	// Default: true (since v1.18.0 / MemPalace).
	Enabled bool `yaml:"enabled"`

	// DefaultWing is the fallback wing assigned when neither explicit
	// mapping nor heuristics match. Empty string (the default) means
	// "wing IS NULL" — the legacy first-class citizen behavior per ADR-006.
	//
	// Setting this to a non-empty value changes semantics: new memories
	// arriving through unmapped channels get assigned to DefaultWing
	// rather than staying as legacy. Use with caution — this effectively
	// "backfills by default" which violates Princípio Zero rule 4 unless
	// the user explicitly opts in.
	DefaultWing string `yaml:"default_wing"`

	// L1Budget is the token budget for Layer 1 (essential story).
	// Sprint 2 Room 2.2 consumes this as a byte-budget approximation
	// (1 token ≈ 4 bytes → 400 tokens ≈ 1600 bytes). Default: 400 tokens.
	L1Budget int `yaml:"l1_budget_tokens,omitempty"`

	// L2Budget is the token budget for Layer 2 (on-demand retrieval).
	// Sprint 2 reads this. Default: 300 tokens.
	L2Budget int `yaml:"l2_budget_tokens"`

	// OnDemandMaxResults caps how many memories the L2 OnDemandLayer returns
	// per Render call. Default: 5.
	OnDemandMaxResults int `yaml:"on_demand_max_results,omitempty"`

	// OnDemandCrossWingEnabled controls whether the L2 OnDemandLayer may
	// return memories from a wing other than the active wing when nothing is
	// found in the active wing. Default: true.
	OnDemandCrossWingEnabled bool `yaml:"on_demand_cross_wing,omitempty"`

	// TopicChangeThreshold is the cosine similarity below which a topic
	// change is detected. Lower = more sensitive. Default: 0.65.
	TopicChangeThreshold float64 `yaml:"topic_change_threshold,omitempty"`

	// TopicChangeEntityOverlap is the entity overlap ratio below which
	// the cosine stage is triggered. Default: 0.3.
	TopicChangeEntityOverlap float64 `yaml:"topic_change_entity_overlap,omitempty"`

	// EssentialStoryStaleAfter is the TTL before the L1 EssentialLayer
	// regenerates a cached per-wing story. Applies to the essential_stories
	// SQLite cache introduced in Sprint 2 Room 2.2. Default: 6 hours.
	EssentialStoryStaleAfter time.Duration `yaml:"essential_story_stale_after,omitempty"`

	// EssentialStoryRoomsPerWing caps how many rooms the L1 template walks
	// when rendering a wing's essential story. Higher values include more
	// context at the cost of hitting the byte budget sooner. Default: 4.
	EssentialStoryRoomsPerWing int `yaml:"essential_story_rooms_per_wing,omitempty"`

	// WingBoostMatch is the multiplier applied to search scores when a
	// document's wing matches the query's wing. Sprint 2 reads this.
	// Default: 1.3. See doc-02-errata.md correction 3 — the value is
	// relative to DevClaw's weighted inverse rank fusion, NOT standard RRF k=60.
	WingBoostMatch float64 `yaml:"wing_boost_match,omitempty"`

	// WingBoostPenalty is the multiplier applied when a document's wing
	// differs from the query's wing. Default: 0.4 (-60% penalty).
	WingBoostPenalty float64 `yaml:"wing_boost_penalty,omitempty"`

	// AutoRoomCap is the maximum number of auto-created rooms per wing.
	// When exceeded, the least recently used auto-only rooms are archived.
	// Default: 30. See ADR-002 Addendum A.
	AutoRoomCap int `yaml:"auto_room_cap"`

	// AutoRoomDedupeDistance is the Levenshtein distance threshold for
	// insert-time room deduplication. If a candidate room has distance
	// <= this value from an existing auto-room in the same wing, the
	// existing room is reused instead of creating a new one. Default: 2.
	AutoRoomDedupeDistance int `yaml:"auto_room_dedupe_distance"`

	// IdentityPath is the filesystem path to the L0 identity markdown file.
	// Sprint 2 reads this. Empty string uses the default:
	// ~/.devclaw/identity.md. Sprint 1 stores it but does not consume it.
	IdentityPath string `yaml:"identity_path"`

	// Heuristics is the user-provided list of channel/group name patterns
	// used by the context router's heuristic tier. The binary ships zero
	// defaults — a fresh install classifies nothing (all memories land with
	// wing=NULL) until the user opts in via YAML. This is intentional:
	// hardcoded domain or locale keywords would be biased for an open-source
	// project with diverse deployments.
	//
	// The router iterates this slice in order; first match wins (confidence 0.7).
	// If nil or empty, the heuristic tier is a no-op and every unmapped
	// message falls through to DefaultWing or wing=NULL.
	Heuristics []WingHeuristic `yaml:"heuristics,omitempty"`

	// LegacyKeywords is the keyword-to-wing mapping used by the legacy
	// content classifier (RunLegacyClassificationPass). The binary ships
	// zero defaults — the classifier is a no-op unless the user provides
	// keywords here. This preserves locale and domain neutrality.
	//
	// Map key: wing identifier (e.g. "work", "family").
	// Map value: list of lowercase substrings that signal that wing.
	LegacyKeywords map[string][]string `yaml:"legacy_keywords,omitempty"`

	// KG configures knowledge graph extraction (Sprint 3 Room 3.4).
	// Default: AutoExtract="off", LLMBudgetPerCycle=20, LLMConsentACK=false.
	KG KGConfig `yaml:"kg,omitempty"`
}

HierarchyConfig configures the palace-aware memory subsystem introduced in Sprint 1. It lives under MemoryConfig.Hierarchy and is opt-in: Enabled=false by default preserves v1.17.0 behavior byte-for-byte.

func DefaultHierarchyConfig added in v1.18.0

func DefaultHierarchyConfig() HierarchyConfig

DefaultHierarchyConfig returns the defaults for HierarchyConfig.

IMPORTANT — Sprint 1 amendment (2026-04-08): Enabled defaults to TRUE. This is a deliberate reversal of the original Sprint 0.5 "default off" stance. The rationale:

  1. "wing IS NULL is a first-class citizen" (ADR-006) means legacy memories continue to work transparently even when the feature is on. The wing boost code path explicitly treats wing="" as NEUTRAL (no boost, no penalty), so a v1.17.0 user's search results remain the same ordering as before until they start classifying memories.

  2. Feature flags that default off get abandoned. Most users never discover them. DevClaw's differentiator is its memory — it must ship active.

  3. Sprint 1 adds only schema and tool infrastructure — it does NOT rewrite file paths or auto-migrate legacy data. A user upgrading from v1.17.0 sees no data change. The only visible surface is new bot commands (/wing, /room, /tree, /palace help) and new LLM tools that the agent can use to organize memories going forward.

  4. Incremental improvement: as the user interacts with the assistant, new memories gradually get routed to wings (full integration in Sprint 2). The palace fills up organically. No config edits, no migrations, no surprises.

Users who need v1.17.0 byte-identical behavior can still opt out by setting memory.hierarchy.enabled: false in their YAML.

type HierarchyMetrics added in v1.18.0

type HierarchyMetrics struct {
	// ContextRouterMapped counts Resolve calls that returned SourceMapped.
	ContextRouterMapped uint64

	// ContextRouterHeuristic counts Resolve calls that returned SourceHeuristic.
	ContextRouterHeuristic uint64

	// ContextRouterDefault counts Resolve calls that returned SourceDefault.
	ContextRouterDefault uint64

	// ContextRouterDisabled counts Resolve calls made while the flag is off.
	ContextRouterDisabled uint64

	// SearchWithWingFilter counts searches that used an explicit wing filter.
	// Sprint 2 activates this counter when wing boost lands.
	SearchWithWingFilter uint64

	// SearchWithoutWingFilter counts searches that omitted the wing filter.
	SearchWithoutWingFilter uint64

	// ToolCallsByName is incremented by the palace tool handlers with the
	// tool name as a label. Sprint 1 exposes the count via EmitSnapshot.
	// Using a fixed set of counters avoids the need for a map in hot paths.
	ListWingsCalls   uint64
	ListRoomsCalls   uint64
	GetTaxonomyCalls uint64
	WingPinCalls     uint64
	WingUnpinCalls   uint64
	WingStatusCalls  uint64
	// contains filtered or unexported fields
}

HierarchyMetrics holds process-level counters for palace-aware features. Each counter is atomic and safe for concurrent use. Counters are exposed via EmitSnapshot which writes structured log lines that a log aggregator can scrape into Prometheus time series.

Sprint 1 scope: scaffolding and a minimal counter set. Sprint 2 adds the layer_tokens histogram. Sprint 3 adds KG-related counters.

func (*HierarchyMetrics) EmitSnapshot added in v1.18.0

func (m *HierarchyMetrics) EmitSnapshot(logger *slog.Logger)

EmitSnapshot logs the current counter values via slog at INFO level. Called periodically (e.g., every 5 minutes from a goroutine) to produce scrape-friendly log lines.

The log format is:

metric=<name> value=<n> component=palace

Log aggregators can parse these lines directly into Prometheus counters.

func (*HierarchyMetrics) Global added in v1.18.0

func (m *HierarchyMetrics) Global() *HierarchyMetrics

Global returns the process-wide metrics instance. Prefer the Inc* helpers for single-counter updates.

type HookAction

type HookAction struct {
	// Block prevents the operation from proceeding (PreToolUse, UserPromptSubmit).
	Block bool

	// Reason explains why the operation was blocked.
	Reason string

	// ModifiedArgs replaces ToolArgs if non-nil (PreToolUse only).
	ModifiedArgs map[string]any

	// ModifiedMessage replaces the user message if non-empty (UserPromptSubmit).
	ModifiedMessage string
}

HookAction is the result returned by a hook handler.

type HookEvent

type HookEvent string

HookEvent identifies the lifecycle point at which a hook fires.

const (
	HookSessionStart      HookEvent = "session_start"
	HookSessionEnd        HookEvent = "session_end"
	HookUserPromptSubmit  HookEvent = "user_prompt_submit"
	HookPreToolUse        HookEvent = "pre_tool_use"
	HookPostToolUse       HookEvent = "post_tool_use"
	HookAgentStart        HookEvent = "agent_start"
	HookAgentStop         HookEvent = "agent_stop"
	HookSubagentStart     HookEvent = "subagent_start"
	HookSubagentStop      HookEvent = "subagent_stop"
	HookPreCompact        HookEvent = "pre_compact"
	HookPostCompact       HookEvent = "post_compact"
	HookMemorySave        HookEvent = "memory_save"
	HookMemoryRecall      HookEvent = "memory_recall"
	HookNotification      HookEvent = "notification"
	HookHeartbeat         HookEvent = "heartbeat"
	HookError             HookEvent = "error"
	HookUserJoin          HookEvent = "user_join"
	HookUserLeave         HookEvent = "user_leave"
	HookChannelConnect    HookEvent = "channel_connect"
	HookChannelDisconnect HookEvent = "channel_disconnect"

	// Advanced hooks inspired by OpenClaw
	HookBeforeModelResolve HookEvent = "before_model_resolve" // Override provider/model selection
	HookBeforePromptBuild  HookEvent = "before_prompt_build"  // Inject/modify system prompt
	HookLLMInput           HookEvent = "llm_input"            // Modify prompt before sending to LLM
	HookLLMOutput          HookEvent = "llm_output"           // Modify LLM response
	HookToolResultPersist  HookEvent = "tool_result_persist"  // Transform tool result before persisting

	// Message pipeline hooks
	HookMessageTranscribed  HookEvent = "message_transcribed"  // Audio/media transcribed to text
	HookMessagePreprocessed HookEvent = "message_preprocessed" // After directives, links, enrichment
	HookBeforeReset         HookEvent = "before_reset"         // Before session/history reset
)

type HookHandler

type HookHandler func(ctx context.Context, payload HookPayload) HookAction

HookHandler processes a hook event and returns an action. Handlers should be fast and non-blocking. For async work, spawn a goroutine.

type HookManager

type HookManager struct {
	// contains filtered or unexported fields
}

HookManager manages lifecycle hook registration and dispatch.

func NewHookManager

func NewHookManager(logger *slog.Logger) *HookManager

NewHookManager creates a new hook manager.

func (*HookManager) Dispatch

func (hm *HookManager) Dispatch(ctx context.Context, payload HookPayload) HookAction

Dispatch fires all hooks for the given event and returns the combined action. For blocking events (PreToolUse, UserPromptSubmit), the first Block=true stops dispatch and returns immediately. For non-blocking events, all hooks run.

func (*HookManager) DispatchAsync

func (hm *HookManager) DispatchAsync(payload HookPayload)

DispatchAsync fires all hooks for the event without waiting for them. Use for non-critical observe-only events (PostToolUse, Notification, etc.).

func (*HookManager) HasHooks

func (hm *HookManager) HasHooks(event HookEvent) bool

HasHooks returns true if any hooks are registered for the given event.

func (*HookManager) HookCount

func (hm *HookManager) HookCount() int

HookCount returns the total number of registered hooks.

func (*HookManager) ListDetailed

func (hm *HookManager) ListDetailed() []HookSummary

ListDetailed returns a deduplicated list of all registered hooks with metadata.

func (*HookManager) ListHooks

func (hm *HookManager) ListHooks() map[HookEvent][]string

ListHooks returns all registered hooks grouped by event.

func (*HookManager) Register

func (hm *HookManager) Register(hook *RegisteredHook) error

Register adds a hook handler for the specified events.

func (*HookManager) SetEnabled

func (hm *HookManager) SetEnabled(name string, enabled bool) bool

SetEnabled enables or disables a hook by name.

func (*HookManager) Unregister

func (hm *HookManager) Unregister(name string) bool

Unregister removes all registrations for a hook by name.

type HookPayload

type HookPayload struct {
	// Event is the hook event type.
	Event HookEvent

	// SessionID is the session this event relates to (if applicable).
	SessionID string

	// Channel is the originating channel (if applicable).
	Channel string

	// ToolName is the tool being called (PreToolUse/PostToolUse).
	ToolName string

	// ToolArgs are the tool arguments (PreToolUse only).
	ToolArgs map[string]any

	// ToolResult is the tool output (PostToolUse only).
	ToolResult string

	// Message is a human-readable description or the user message content.
	Message string

	// Error is set for HookError events.
	Error error

	// Extra holds arbitrary key-value data for extensibility.
	Extra map[string]any

	// Advanced hook fields
	// Model is the LLM model being used (HookBeforeModelResolve).
	Model string

	// SystemPrompt is the system prompt being built (HookBeforePromptBuild).
	SystemPrompt string

	// LLMInput is the input being sent to the LLM (HookLLMInput).
	LLMInput string

	// LLMOutput is the response from the LLM (HookLLMOutput).
	LLMOutput string

	// ToolCallID identifies the tool call for persistence hooks.
	ToolCallID string
}

HookPayload carries contextual data for a hook invocation. Fields are populated based on the event type; unused fields are zero-valued.

type HookRequirements added in v1.13.0

type HookRequirements struct {
	// Binaries that must be in PATH (e.g. ["ffmpeg", "jq"]).
	Bins []string `yaml:"bins,omitempty" json:"bins,omitempty"`

	// Environment variables that must be non-empty (e.g. ["SLACK_WEBHOOK_URL"]).
	Env []string `yaml:"env,omitempty" json:"env,omitempty"`

	// OS restricts the hook to specific operating systems (e.g. ["linux", "darwin"]).
	// Empty = all platforms.
	OS []string `yaml:"os,omitempty" json:"os,omitempty"`
}

HookRequirements declares runtime prerequisites for a hook. If any requirement is not met, the hook is silently skipped during dispatch.

func (*HookRequirements) Met added in v1.13.0

func (r *HookRequirements) Met() bool

Met returns true if all requirements are satisfied on the current system.

type HookSummary

type HookSummary struct {
	Name        string            `json:"name"`
	Description string            `json:"description"`
	Source      string            `json:"source"`
	Events      []HookEvent       `json:"events"`
	Priority    int               `json:"priority"`
	Enabled     bool              `json:"enabled"`
	Requires    *HookRequirements `json:"requires,omitempty"`
}

HookSummary is a serializable representation of a registered hook (no handler).

type HooksConfig

type HooksConfig struct {
	// Enabled turns the hooks system on/off.
	Enabled bool `yaml:"enabled"`

	// Webhooks is the list of external webhook configurations.
	Webhooks []WebhookConfig `yaml:"webhooks"`

	// Handlers is the list of internal hook handlers.
	Handlers []HandlerConfig `yaml:"handlers"`
}

HooksConfig holds all hook configuration.

type Hunk added in v1.12.0

type Hunk struct {
	Kind   HunkKind
	Add    *AddFileHunk
	Delete *DeleteFileHunk
	Update *UpdateFileHunk
}

Hunk encapsulates one of Add, Delete, or Update operations.

type HunkKind added in v1.12.0

type HunkKind string

HunkKind denotes the type of patch operation.

const (
	HunkAdd    HunkKind = "add"
	HunkDelete HunkKind = "delete"
	HunkUpdate HunkKind = "update"
)

type IDEConfig

type IDEConfig struct {
	MCPPort   int    `yaml:"mcp_port" json:"mcp_port"`
	MCPHost   string `yaml:"mcp_host" json:"mcp_host"`
	Transport string `yaml:"transport" json:"transport"` // stdio, sse
}

IDEConfig holds configuration for IDE extension generation.

func DefaultIDEConfig

func DefaultIDEConfig() IDEConfig

DefaultIDEConfig returns sensible defaults.

type IdentityConfig added in v1.13.0

type IdentityConfig struct {
	// Name is the display name (e.g. "Aria", "DevClaw").
	Name string `yaml:"name"`

	// Emoji is the reaction/acknowledgment emoji (e.g. "🦊").
	Emoji string `yaml:"emoji"`

	// Theme is the personality theme (e.g. "helpful hacker", "friendly mentor").
	Theme string `yaml:"theme"`

	// Avatar is a URL or file path to the assistant's avatar image.
	Avatar string `yaml:"avatar"`

	// Vibe is a short phrase describing the assistant's tone/style.
	Vibe string `yaml:"vibe"`

	// Creature is the mascot type (e.g. "fox", "owl", "cat").
	Creature string `yaml:"creature"`
}

IdentityConfig configures the assistant's persona and identity.

func ParseIdentityFile added in v1.13.0

func ParseIdentityFile(content string) IdentityConfig

ParseIdentityFile extracts structured identity fields from an IDENTITY.md file. Supports two formats:

  • YAML-like "key: value" lines (e.g. "name: Aria")
  • Markdown headers followed by content (e.g. "# Name\nAria")

Unrecognized lines are collected as the vibe if no explicit vibe is set.

func ResolveIdentity added in v1.13.0

func ResolveIdentity(cfg *Config, agentProfile *AgentProfileConfig, identityFileContent string) IdentityConfig

ResolveIdentity returns the effective identity by merging sources in priority order:

  1. AgentProfile.Identity (if agent routing matched)
  2. IDENTITY.md content (parsed for structured fields)
  3. Config.Identity
  4. Fallback: Config.Name

Fields are merged individually: a higher-priority source only overrides fields it actually sets (non-empty), so a profile can override just the name while inheriting the theme from config.

func (IdentityConfig) EffectiveName added in v1.13.0

func (ic IdentityConfig) EffectiveName(fallback string) string

EffectiveName returns the identity name if set, otherwise the fallback.

func (IdentityConfig) IsEmpty added in v1.13.0

func (ic IdentityConfig) IsEmpty() bool

IsEmpty returns true if no identity fields are set.

type IncompleteWork added in v1.17.0

type IncompleteWork struct {
	// CheckName identifies which check failed.
	CheckName string

	// Reminder is the suggested action.
	Reminder string
}

IncompleteWork represents a detected incomplete work pattern.

type IndexConfig

type IndexConfig struct {
	// Auto enables automatic re-indexing on file changes (default: true).
	Auto bool `yaml:"auto"`

	// ChunkMaxTokens is the max tokens per chunk (default: 500).
	ChunkMaxTokens int `yaml:"chunk_max_tokens"`
}

IndexConfig configures automatic memory indexing.

type InlineDirectives added in v1.13.0

type InlineDirectives struct {
	Think    string // "off", "low", "medium", "high"
	Model    string // model override for this message
	Verbose  *bool  // nil = not set, true/false = override
	Queue    string // queue mode override
	Language string // response language hint
}

InlineDirectives holds directives extracted from a message body.

func ParseInlineDirectives added in v1.13.0

func ParseInlineDirectives(body string) (InlineDirectives, string)

ParseInlineDirectives extracts inline directives from a message body. Returns the directives found and the cleaned body with directives removed. Directives can appear at the beginning or end of the message.

Examples:

"/think high explain quantum physics" → Think="high", body="explain quantum physics"
"explain this /model gpt-4o" → Model="gpt-4o", body="explain this"
"/think high /verbose on" → Think="high", Verbose=true, body=""

func (InlineDirectives) HasAny added in v1.13.0

func (d InlineDirectives) HasAny() bool

HasAny returns true if any directive was set.

type KGAgentConfig added in v1.18.0

type KGAgentConfig struct {
	AutoExtract       string `yaml:"auto_extract"`
	LLMBudgetPerCycle int    `yaml:"llm_budget_per_cycle"`
	LLMConsentACK     bool   `yaml:"llm_consent_acknowledged"`
	FactsPerInjection int    `yaml:"facts_per_injection"`
}

KGAgentConfig mirrors the relevant KGConfig fields for use in AgentConfig.

type KGConfig added in v1.18.0

type KGConfig struct {
	// AutoExtract controls which extraction modes are active.
	// Values: "off" (default), "pattern", "llm", "both".
	// When "off", no automatic extraction runs during dream cycles.
	AutoExtract string `yaml:"auto_extract" json:"auto_extract"`

	// LLMBudgetPerCycle caps how many memories the LLM extractor processes
	// per dream cycle. Default: 20. The budget is enforced by the dream
	// cycle caller, not the extractor itself.
	LLMBudgetPerCycle int `yaml:"llm_budget_per_cycle" json:"llm_budget_per_cycle"`

	// LLMConsentACK must be true when AutoExtract contains "llm".
	// This is a safety gate — the operator must explicitly acknowledge
	// that memory contents will be sent to an external LLM API for
	// triple extraction. When false and mode is "llm" or "both",
	// NewLLMExtractor returns an error.
	LLMConsentACK bool `yaml:"llm_consent_acknowledged" json:"llm_consent_acknowledged"`

	// FactsPerInjection caps how many KG facts are injected into the prompt
	// after compaction. Default: 5. Facts are ranked by confidence × recency.
	FactsPerInjection int `yaml:"facts_per_injection" json:"facts_per_injection"`
}

KGConfig configures the knowledge graph extraction subsystem. It lives under HierarchyConfig.KG and controls LLM-based triple extraction during dream cycles. Off by default — the operator must explicitly opt in via AutoExtract and acknowledge consent.

type LCMAssembler added in v1.14.0

type LCMAssembler struct {
	// contains filtered or unexported fields
}

LCMAssembler builds []chatMessage from the LCM store.

func NewLCMAssembler added in v1.14.0

func NewLCMAssembler(store *LCMStore, cfg LCMConfig, logger *slog.Logger) *LCMAssembler

NewLCMAssembler creates a new assembler.

func (*LCMAssembler) AssembleContext added in v1.14.0

func (a *LCMAssembler) AssembleContext(convID, systemPrompt, userMessage string, tokenBudget int) ([]chatMessage, error)

AssembleContext builds the message list for the LLM from DAG summaries + fresh tail.

type LCMCompactor added in v1.14.0

type LCMCompactor struct {
	// contains filtered or unexported fields
}

LCMCompactor runs leaf and condensed compaction passes.

func NewLCMCompactor added in v1.14.0

func NewLCMCompactor(store *LCMStore, cfg LCMConfig, logger *slog.Logger) *LCMCompactor

NewLCMCompactor creates a new compactor.

func (*LCMCompactor) CondensedPass added in v1.14.0

func (c *LCMCompactor) CondensedPass(ctx context.Context, convID string, summarizeFn LCMSummarizeFn) ([]*LCMSummary, error)

CondensedPass groups orphan summaries at each depth into higher-level condensed nodes.

func (*LCMCompactor) FullSweep added in v1.14.0

func (c *LCMCompactor) FullSweep(ctx context.Context, convID string, summarizeFn LCMSummarizeFn) ([]*LCMSummary, error)

FullSweep runs leaf pass then cascading condensed passes until stable.

func (*LCMCompactor) LeafPass added in v1.14.0

func (c *LCMCompactor) LeafPass(ctx context.Context, convID string, summarizeFn LCMSummarizeFn) ([]*LCMSummary, error)

LeafPass groups unsummarized messages into chunks and summarizes each into depth-0 leaf summary nodes.

func (*LCMCompactor) ShouldCompact added in v1.14.0

func (c *LCMCompactor) ShouldCompact(convID string, contextWindowTokens int) (bool, string)

ShouldCompact checks whether compaction is needed based on unsummarized token count. Returns (shouldCompact, triggerReason). Skips compaction for sessions with no real conversation (heartbeat-only or system-only sessions).

type LCMConfig added in v1.14.0

type LCMConfig struct {
	// FreshTailCount is how many recent messages to keep unsummarized. Default: 32.
	FreshTailCount int `yaml:"fresh_tail_count"`

	// LeafChunkMaxTokens is the max tokens per leaf chunk. Default: 20000.
	LeafChunkMaxTokens int `yaml:"leaf_chunk_max_tokens"`

	// CondensedMinChildren is the minimum orphan summaries to trigger condensation. Default: 4.
	CondensedMinChildren int `yaml:"condensed_min_children"`

	// CondensedMaxChildren is the max summaries per condensed batch. Default: 8.
	CondensedMaxChildren int `yaml:"condensed_max_children"`

	// SoftTriggerRatio is the context usage fraction for soft compaction trigger. Default: 0.6.
	SoftTriggerRatio float64 `yaml:"soft_trigger_ratio"`

	// HardTriggerRatio is the context usage fraction for hard compaction trigger. Default: 0.85.
	HardTriggerRatio float64 `yaml:"hard_trigger_ratio"`

	// MaxSummaryTokens is the max tokens per individual summary. Default: 2000.
	MaxSummaryTokens int `yaml:"max_summary_tokens"`

	// SummaryModel overrides the model used for LCM summarization calls.
	// Priority: SummaryModel > CompactionConfig.CompactionModel > agent's current model.
	// If empty, falls back to the next available model in the chain.
	SummaryModel string `yaml:"summary_model"`

	// SummaryProvider overrides the provider for LCM summarization calls.
	// If empty, uses the provider from the session's LLM client.
	SummaryProvider string `yaml:"summary_provider"`

	// LargeFileTokenThreshold is the token count above which ingested content
	// is intercepted and stored as a separate file with an exploration summary.
	// Default: 25000. Set to 0 to disable.
	LargeFileTokenThreshold int `yaml:"large_file_token_threshold"`

	// PruneHeartbeatOK removes heartbeat turn cycles (user heartbeat prompt +
	// assistant HEARTBEAT_OK response) from the LCM store during ingest.
	// Default: true (nil pointer = true).
	PruneHeartbeatOK *bool `yaml:"prune_heartbeat_ok"`
}

LCMConfig controls the Lossless Compaction Module behavior.

type LCMContextItem added in v1.14.0

type LCMContextItem struct {
	ID             int64
	ConversationID string
	Ordinal        int
	ItemType       string // "message" | "summary"
	MessageID      *int64
	SummaryID      *string
}

LCMContextItem is an ordered entry in the model's visible context.

type LCMConversation added in v1.14.0

type LCMConversation struct {
	ID            string
	SessionID     string
	CreatedAt     time.Time
	NextSeq       int
	LastCompactAt time.Time
}

LCMConversation is one LCM conversation tied to a session.

type LCMEngine added in v1.14.0

type LCMEngine struct {
	// contains filtered or unexported fields
}

LCMEngine is the top-level coordinator for lossless compaction.

func NewLCMEngine added in v1.14.0

func NewLCMEngine(db *sql.DB, cfg LCMConfig, ccfg CompactionConfig, logger *slog.Logger) *LCMEngine

NewLCMEngine creates a new LCM engine wired to the given database.

func (*LCMEngine) Assemble added in v1.14.0

func (e *LCMEngine) Assemble(ctx context.Context, convID, systemPrompt, userMessage string, tokenBudget int) ([]chatMessage, error)

Assemble builds the model's context window from DAG summaries + fresh tail.

func (*LCMEngine) Bootstrap added in v1.14.0

func (e *LCMEngine) Bootstrap(sessionID string, sessionHistory []ConversationEntry) (string, error)

Bootstrap initializes an LCM conversation for the given session ID. If sessionHistory is provided, reconciles with the LCM store to import any messages that were missed (e.g. from a previous session that wasn't fully ingested). Returns the conversation ID.

func (*LCMEngine) Compact added in v1.14.0

func (e *LCMEngine) Compact(ctx context.Context, convID string, contextWindowTokens int, summarizeFn LCMSummarizeFn) (bool, error)

Compact evaluates whether compaction is needed and runs a full sweep if so. Returns true if compaction was performed.

func (*LCMEngine) Ingest added in v1.14.0

func (e *LCMEngine) Ingest(ctx context.Context, convID string, messages []chatMessage) error

Ingest persists new messages from the agent loop into the LCM store. Messages are deduplicated by tracking the last ingested index externally.

func (*LCMEngine) Retrieval added in v1.14.0

func (e *LCMEngine) Retrieval() *LCMRetrieval

Retrieval returns the retrieval engine for tool use.

func (*LCMEngine) Store added in v1.14.0

func (e *LCMEngine) Store() *LCMStore

Store returns the underlying LCM store.

type LCMFTSResult added in v1.14.0

type LCMFTSResult struct {
	EntityType string // "message" | "summary"
	EntityID   string
	Content    string
	Rank       float64
}

LCMFTSResult is a full-text search hit.

type LCMFile added in v1.16.0

type LCMFile struct {
	ID             string
	ConversationID string
	OriginalTokens int
	OriginalChars  int
	Summary        string
	FilePath       string
	CreatedAt      time.Time
}

LCMFile represents a large file intercepted during ingest.

type LCMMessage added in v1.14.0

type LCMMessage struct {
	ID             int64
	ConversationID string
	Seq            int
	Role           string
	Content        string
	TokenCount     int
	CreatedAt      time.Time
}

LCMMessage is a single persisted message (verbatim).

type LCMQueryFn added in v1.16.0

type LCMQueryFn func(ctx context.Context, systemPrompt, userContent string) (string, error)

LCMQueryFn is a function that sends a scoped query to the LLM and returns the synthesized answer. No tools are provided (prevents recursion).

type LCMRetrieval added in v1.14.0

type LCMRetrieval struct {
	// contains filtered or unexported fields
}

LCMRetrieval provides search and inspection over the LCM DAG.

func NewLCMRetrieval added in v1.14.0

func NewLCMRetrieval(store *LCMStore, logger *slog.Logger) *LCMRetrieval

NewLCMRetrieval creates a new retrieval engine.

func (*LCMRetrieval) Describe added in v1.14.0

func (r *LCMRetrieval) Describe(convID, summaryID string) (string, error)

Describe returns metadata about a summary or the full DAG tree.

func (*LCMRetrieval) DescribeTree added in v1.14.0

func (r *LCMRetrieval) DescribeTree(convID string) (string, error)

DescribeTree returns an overview of the full LCM DAG.

func (*LCMRetrieval) Expand added in v1.14.0

func (r *LCMRetrieval) Expand(convID, summaryID string, depth int) (string, error)

Expand recovers the original messages behind a summary. depth=0 expands to messages (for leaf) or child summaries (for condensed). depth>0 recursively expands children.

func (*LCMRetrieval) ExpandQuery added in v1.16.0

func (r *LCMRetrieval) ExpandQuery(ctx context.Context, convID, query string, queryFn LCMQueryFn) (string, error)

ExpandQuery searches the LCM store for context relevant to the query, expands matching summaries, and sends the expanded context to an LLM for synthesis. Budget: 30k tokens max, 60s timeout.

func (*LCMRetrieval) Grep added in v1.14.0

func (r *LCMRetrieval) Grep(convID, query string, isRegex bool, limit int) (string, error)

Grep searches messages and summaries by text (FTS) or regex.

type LCMStore added in v1.14.0

type LCMStore struct {
	// contains filtered or unexported fields
}

LCMStore provides all LCM database operations.

func NewLCMStore added in v1.14.0

func NewLCMStore(db *sql.DB, logger *slog.Logger) *LCMStore

NewLCMStore creates a new LCM store.

func (*LCMStore) CountUnsummarizedTokens added in v1.14.0

func (s *LCMStore) CountUnsummarizedTokens(convID string, excludeLastN int) (int, error)

CountUnsummarizedTokens returns the total token count of unsummarized messages, excluding the fresh tail.

func (*LCMStore) DeleteMessage added in v1.16.0

func (s *LCMStore) DeleteMessage(id int64) error

DeleteMessage removes a message from the store by ID.

func (*LCMStore) GetAllSummaries added in v1.14.0

func (s *LCMStore) GetAllSummaries(convID string) ([]*LCMSummary, error)

GetAllSummaries returns all summaries for a conversation.

func (*LCMStore) GetContextItems added in v1.14.0

func (s *LCMStore) GetContextItems(convID string) ([]LCMContextItem, error)

GetContextItems returns ordered context items for a conversation.

func (*LCMStore) GetFile added in v1.16.0

func (s *LCMStore) GetFile(id string) (*LCMFile, error)

GetFile retrieves an LCM file by ID.

func (*LCMStore) GetFreshTailMessages added in v1.14.0

func (s *LCMStore) GetFreshTailMessages(convID string, count int) ([]*LCMMessage, error)

GetFreshTailMessages returns the last N messages by seq (reversed to chronological order).

func (*LCMStore) GetMaxDepth added in v1.14.0

func (s *LCMStore) GetMaxDepth(convID string) (int, error)

GetMaxDepth returns the maximum summary depth for a conversation.

func (*LCMStore) GetMessage added in v1.14.0

func (s *LCMStore) GetMessage(id int64) (*LCMMessage, error)

GetMessage returns a single message by ID.

func (*LCMStore) GetMessageRange added in v1.14.0

func (s *LCMStore) GetMessageRange(convID string, fromSeq, toSeq int) ([]*LCMMessage, error)

GetMessageRange returns messages within a seq range (inclusive).

func (*LCMStore) GetOrCreateConversation added in v1.14.0

func (s *LCMStore) GetOrCreateConversation(sessionID string) (*LCMConversation, error)

GetOrCreateConversation returns the LCM conversation for a session, creating it if it doesn't exist (idempotent).

func (*LCMStore) GetOrphanSummaries added in v1.14.0

func (s *LCMStore) GetOrphanSummaries(convID string, depth int) ([]*LCMSummary, error)

GetOrphanSummaries returns summaries at a given depth that have no parent.

func (*LCMStore) GetRecentMessages added in v1.14.0

func (s *LCMStore) GetRecentMessages(convID string, limit int) ([]*LCMMessage, error)

GetRecentMessages returns the most recent N messages (by seq DESC, reversed to chronological). Use this instead of GetMessageRange for grep searches to prioritize recent messages.

func (*LCMStore) GetRootSummaries added in v1.14.0

func (s *LCMStore) GetRootSummaries(convID string) ([]*LCMSummary, error)

GetRootSummaries returns top-level summaries (no parent), ordered by time.

func (*LCMStore) GetSummary added in v1.14.0

func (s *LCMStore) GetSummary(id string) (*LCMSummary, error)

GetSummary returns a single summary by ID.

func (*LCMStore) GetSummaryChildren added in v1.14.0

func (s *LCMStore) GetSummaryChildren(summaryID string) ([]*LCMSummary, error)

GetSummaryChildren returns child summaries of a condensed node.

func (*LCMStore) GetSummaryMessages added in v1.14.0

func (s *LCMStore) GetSummaryMessages(summaryID string) ([]*LCMMessage, error)

GetSummaryMessages returns the source messages linked to a leaf summary.

func (*LCMStore) GetSummaryParents added in v1.14.0

func (s *LCMStore) GetSummaryParents(summaryID string) ([]*LCMSummary, error)

GetSummaryParents returns parent summaries that contain this node.

func (*LCMStore) GetUnsummarizedMessages added in v1.14.0

func (s *LCMStore) GetUnsummarizedMessages(convID string, excludeLastN int) ([]*LCMMessage, error)

GetUnsummarizedMessages returns messages not yet linked to any summary, excluding the most recent excludeLastN messages (the "fresh tail").

func (*LCMStore) IngestMessage added in v1.14.0

func (s *LCMStore) IngestMessage(convID, role, content string, tokenCount int) (*LCMMessage, error)

IngestMessage inserts a new message, indexes it in FTS, and adds a context item.

func (*LCMStore) InsertFile added in v1.16.0

func (s *LCMStore) InsertFile(f *LCMFile) error

InsertFile stores metadata for an intercepted large file.

func (*LCMStore) InsertSummary added in v1.14.0

func (s *LCMStore) InsertSummary(sum *LCMSummary) error

InsertSummary persists a summary node and indexes it in FTS.

func (*LCMStore) LinkSummaryChildren added in v1.14.0

func (s *LCMStore) LinkSummaryChildren(parentID string, childIDs []string) error

LinkSummaryChildren links a condensed summary to its child summaries.

func (*LCMStore) LinkSummaryMessages added in v1.14.0

func (s *LCMStore) LinkSummaryMessages(summaryID string, msgIDs []int64) error

LinkSummaryMessages links a leaf summary to its source messages.

func (*LCMStore) ListFiles added in v1.16.0

func (s *LCMStore) ListFiles(convID string) ([]*LCMFile, error)

ListFiles returns all LCM files for a conversation.

func (*LCMStore) MessageCount added in v1.14.0

func (s *LCMStore) MessageCount(convID string) (int, error)

MessageCount returns the total number of messages in a conversation.

func (*LCMStore) NextSeq added in v1.14.0

func (s *LCMStore) NextSeq(convID string) (int, error)

NextSeq atomically increments and returns the next sequence number.

func (*LCMStore) ReplaceContextItems added in v1.14.0

func (s *LCMStore) ReplaceContextItems(convID string, items []LCMContextItem) error

ReplaceContextItems atomically replaces all context items for a conversation.

func (*LCMStore) SearchFTS added in v1.14.0

func (s *LCMStore) SearchFTS(convID, query string, limit int) ([]LCMFTSResult, error)

SearchFTS performs a full-text search across messages and summaries. Returns nil results (not error) if FTS5 is unavailable.

func (*LCMStore) SummaryCount added in v1.14.0

func (s *LCMStore) SummaryCount(convID string) (leaf, condensed int, err error)

SummaryCount returns summary counts by kind.

func (*LCMStore) UpdateLastCompactAt added in v1.14.0

func (s *LCMStore) UpdateLastCompactAt(convID string) error

UpdateLastCompactAt updates the last compaction timestamp.

type LCMSummarizeFn added in v1.14.0

type LCMSummarizeFn func(ctx context.Context, text string, aggressive bool) (string, error)

LCMSummarizeFn is the function signature for LLM-based summarization. aggressive=true requests a shorter, more compressed output.

type LCMSummary added in v1.14.0

type LCMSummary struct {
	ID                      string
	ConversationID          string
	Kind                    string // "leaf" | "condensed"
	Depth                   int
	Content                 string
	TokenCount              int
	SourceMessageTokenCount int
	DescendantCount         int
	DescendantTokenCount    int
	EarliestAt              time.Time
	LatestAt                time.Time
	CreatedAt               time.Time
}

LCMSummary is a DAG node — leaf (depth 0) or condensed (depth 1+).

type LLMClient

type LLMClient struct {
	// contains filtered or unexported fields
}

LLMClient handles communication with the LLM provider API.

func NewLLMClient

func NewLLMClient(cfg *Config, logger *slog.Logger) *LLMClient

NewLLMClient creates a new LLM client from config.

func (*LLMClient) Complete

func (c *LLMClient) Complete(ctx context.Context, systemPrompt string, history []ConversationEntry, userMessage string) (string, error)

Complete sends a simple chat completion request (no tools) and returns the text. Convenience wrapper around CompleteWithTools for non-agentic use cases.

func (*LLMClient) CompleteWithFallback

func (c *LLMClient) CompleteWithFallback(ctx context.Context, messages []chatMessage, tools []ToolDefinition) (*LLMResponse, error)

CompleteWithFallback tries the primary model, then fallback models, with retry and exponential backoff on retryable errors. Returns the first successful response.

func (*LLMClient) CompleteWithFallbackUsingModel

func (c *LLMClient) CompleteWithFallbackUsingModel(ctx context.Context, modelOverride string, messages []chatMessage, tools []ToolDefinition) (*LLMResponse, error)

CompleteWithFallbackUsingModel is like CompleteWithFallback but uses modelOverride as the primary model when non-empty. Empty = use c.model. Includes auto-recovery: when the primary model hits a rate limit, subsequent calls use fallback models. Near cooldown expiry, a probe is sent to the primary model to check if it recovered. On success, cooldown is cleared.

func (*LLMClient) CompleteWithTools

func (c *LLMClient) CompleteWithTools(ctx context.Context, messages []chatMessage, tools []ToolDefinition) (*LLMResponse, error)

CompleteWithTools sends a chat completion request with optional tool definitions. Delegates to CompleteWithFallback for retry and model fallback. Returns a structured response that may include tool calls the LLM wants to execute.

func (*LLMClient) CompleteWithToolsStream

func (c *LLMClient) CompleteWithToolsStream(ctx context.Context, messages []chatMessage, tools []ToolDefinition, onChunk StreamCallback) (*LLMResponse, error)

CompleteWithToolsStream sends a streaming chat completion request. For each text delta, onChunk is called. Tool calls are accumulated silently. Falls back to non-streaming if the provider does not support streaming or returns an error.

func (*LLMClient) CompleteWithToolsStreamUsingModel

func (c *LLMClient) CompleteWithToolsStreamUsingModel(ctx context.Context, modelOverride string, messages []chatMessage, tools []ToolDefinition, onChunk StreamCallback) (*LLMResponse, error)

CompleteWithToolsStreamUsingModel is like CompleteWithToolsStream but uses modelOverride when non-empty. Empty = use c.model. Includes retry for transient HTTP errors before falling back to non-streaming.

func (*LLMClient) CompleteWithToolsUsingModel

func (c *LLMClient) CompleteWithToolsUsingModel(ctx context.Context, modelOverride string, messages []chatMessage, tools []ToolDefinition) (*LLMResponse, error)

CompleteWithToolsUsingModel is like CompleteWithTools but uses modelOverride as the primary model when non-empty. Empty string means use the default config model.

func (*LLMClient) CompleteWithVision

func (c *LLMClient) CompleteWithVision(ctx context.Context, systemPrompt, imageBase64, mimeType, userPrompt, detail string, visionModel ...string) (string, error)

CompleteWithVision sends an image plus optional text to the LLM vision API and returns the model's description or response. imageBase64 is the raw base64-encoded image bytes (without data URL prefix). mimeType is e.g. "image/jpeg", "image/png". detail is "auto", "low", or "high" (empty defaults to "auto"). CompleteWithVision sends an image plus optional text to a vision-capable model. visionModel overrides the model; if empty, uses the main chat model.

func (*LLMClient) IsOAuthProvider added in v1.12.0

func (c *LLMClient) IsOAuthProvider() bool

IsOAuthProvider returns true if this client is configured for OAuth.

func (*LLMClient) OAuthBaseProvider added in v1.12.0

func (c *LLMClient) OAuthBaseProvider() string

OAuthBaseProvider returns the base provider name for OAuth providers.

func (*LLMClient) Provider

func (c *LLMClient) Provider() string

Provider returns the detected or configured provider name.

func (*LLMClient) SetFailoverCoordinator added in v1.13.0

func (c *LLMClient) SetFailoverCoordinator(fc *FailoverCoordinator)

SetFailoverCoordinator injects the unified failover coordinator for model+profile rotation. When set, CompleteWithFallback uses the coordinator instead of inline cooldown tracking.

func (*LLMClient) SetOAuthTokenManager added in v1.12.0

func (c *LLMClient) SetOAuthTokenManager(tm OAuthTokenManager)

SetOAuthTokenManager sets the OAuth token manager for this client.

func (*LLMClient) TranscribeAudio

func (c *LLMClient) TranscribeAudio(ctx context.Context, audioData []byte, filename, model string, media ...MediaConfig) (string, error)

TranscribeAudio sends audio data to a Whisper-compatible API and returns the transcript. filename is used as the form field name (e.g. "audio.ogg", "voice.mp3"). model defaults to "whisper-1" if empty. media is optional; if provided, it may override the transcription endpoint and API key for providers that don't natively support Whisper (e.g. Z.AI/GLM, Anthropic).

type LLMErrorKind

type LLMErrorKind int

LLMErrorKind classifies API errors for retry/fallback decisions. Granular classification enables smarter retry behavior.

const (
	LLMErrorRetryable  LLMErrorKind = iota // generic retryable (transient 5xx)
	LLMErrorRateLimit                      // 429 — rate limited, should respect Retry-After
	LLMErrorOverloaded                     // 529 or "overloaded" in body
	LLMErrorTimeout                        // request timeout / deadline exceeded
	LLMErrorAuth                           // 401, 403 — invalid/expired API key
	LLMErrorBilling                        // 402 or billing-related in body
	LLMErrorContext                        // context_length_exceeded
	LLMErrorThinking                       // unsupported extended thinking level
	LLMErrorBadRequest                     // 400 — malformed request
	LLMErrorFatal                          // everything else
)

func (LLMErrorKind) IsRetryableKind

func (k LLMErrorKind) IsRetryableKind() bool

IsRetryableKind returns true if the error kind warrants retrying.

func (LLMErrorKind) String

func (k LLMErrorKind) String() string

String returns a human-readable label for the error kind.

type LLMResponse

type LLMResponse struct {
	Content      string
	ToolCalls    []ToolCall
	FinishReason string
	Usage        LLMUsage
	ModelUsed    string // The model that actually produced the response
}

LLMResponse holds the parsed response from a chat completion.

type LLMUsage

type LLMUsage struct {
	PromptTokens     int
	CompletionTokens int
	TotalTokens      int
	CacheReadTokens  int // Tokens read from provider cache (Anthropic: cache_read_input_tokens)
	CacheWriteTokens int // Tokens written to provider cache (Anthropic: cache_creation_input_tokens)
}

LLMUsage holds token usage information from the API response.

type Lane

type Lane struct {
	Name          string
	MaxConcurrent int
	// contains filtered or unexported fields
}

Lane manages a queue with bounded concurrency.

func NewLane

func NewLane(name string, maxConcurrent int) *Lane

NewLane creates a lane with the given concurrency limit.

func (*Lane) ActiveCount

func (l *Lane) ActiveCount() int

ActiveCount returns the number of currently running tasks.

func (*Lane) Close

func (l *Lane) Close()

Close prevents new tasks from being enqueued. Active tasks finish normally.

func (*Lane) Enqueue

func (l *Lane) Enqueue(ctx context.Context, task LaneTask) error

Enqueue adds a task to the lane. If a slot is available, the task starts immediately. Otherwise, it's queued and will run when a slot opens.

func (*Lane) IsBusy

func (l *Lane) IsBusy() bool

IsBusy returns true if the lane is at capacity.

func (*Lane) QueueLen

func (l *Lane) QueueLen() int

QueueLen returns the number of tasks waiting in the queue.

type LaneConfig

type LaneConfig struct {
	SessionMax  int `yaml:"session_max"`  // Default: 1 (one agent run per session)
	GlobalMax   int `yaml:"global_max"`   // Default: 3
	CronMax     int `yaml:"cron_max"`     // Default: 2
	SubagentMax int `yaml:"subagent_max"` // Default: 8
}

LaneConfig configures default concurrency limits per lane type.

func DefaultLaneConfig

func DefaultLaneConfig() LaneConfig

DefaultLaneConfig returns sensible defaults.

type LaneManager

type LaneManager struct {
	// contains filtered or unexported fields
}

LaneManager manages multiple lanes, creating them on demand.

func NewLaneManager

func NewLaneManager(config LaneConfig, logger *slog.Logger) *LaneManager

NewLaneManager creates a lane manager with the given configuration.

func (*LaneManager) CronLane

func (lm *LaneManager) CronLane() *Lane

CronLane returns the lane for scheduled jobs.

func (*LaneManager) GetLane

func (lm *LaneManager) GetLane(name string) *Lane

GetLane returns an existing lane or creates a new one with the appropriate concurrency limit based on the lane name prefix.

func (*LaneManager) GlobalLane

func (lm *LaneManager) GlobalLane() *Lane

GlobalLane returns the shared global lane.

func (*LaneManager) SessionLane

func (lm *LaneManager) SessionLane(sessionID string) *Lane

SessionLane returns the lane for a specific session.

func (*LaneManager) SubagentLane

func (lm *LaneManager) SubagentLane() *Lane

SubagentLane returns the lane for subagent runs.

type LaneTask

type LaneTask struct {
	ID       string
	Fn       func(ctx context.Context) error
	Priority int // Lower = higher priority (0 is highest)
}

LaneTask represents a unit of work to be executed in a lane.

type LegacyContextEngine added in v1.13.0

type LegacyContextEngine struct{}

LegacyContextEngine is a no-op engine that serves as a reference implementation. The existing PromptComposer already builds project context, memory, and skills layers directly. Register custom ContextEngine implementations to inject additional context from external sources (RAG pipelines, code indices, knowledge graphs, etc.).

func NewLegacyContextEngine added in v1.13.0

func NewLegacyContextEngine(_ *PromptComposer) *LegacyContextEngine

NewLegacyContextEngine creates the default no-op context engine.

func (*LegacyContextEngine) Gather added in v1.13.0

func (e *LegacyContextEngine) Gather(_ context.Context, _ *Session, _ string, _ int) string

Gather is a no-op — all legacy context is already built by PromptComposer.

func (*LegacyContextEngine) Name added in v1.13.0

func (e *LegacyContextEngine) Name() string

Name returns the engine identifier.

type LinkConfig added in v1.13.0

type LinkConfig struct {
	Enabled        bool `yaml:"enabled" json:"enabled"`
	MaxLinks       int  `yaml:"max_links" json:"max_links"`
	TimeoutSeconds int  `yaml:"timeout_seconds" json:"timeout_seconds"`
	MaxCharsPerURL int  `yaml:"max_chars_per_url" json:"max_chars_per_url"`
}

LinkConfig configures the link understanding pipeline.

func DefaultLinkConfig added in v1.13.0

func DefaultLinkConfig() LinkConfig

DefaultLinkConfig returns sensible defaults.

type LinkResult added in v1.13.0

type LinkResult struct {
	URL     string
	Title   string
	Content string
	Error   error
}

LinkResult holds the extracted content from a URL.

func RunLinkUnderstanding added in v1.13.0

func RunLinkUnderstanding(ctx context.Context, urls []string, cfg LinkConfig, ssrfGuard *security.SSRFGuard, logger *slog.Logger) []LinkResult

RunLinkUnderstanding fetches and extracts readable content from the given URLs. Uses SSRFGuard when available to validate URLs. Reuses web_fetch_readability.go for HTML-to-text conversion.

type LoggingConfig

type LoggingConfig struct {
	// Level is the log level ("debug", "info", "warn", "error").
	Level string `yaml:"level"`

	// Format is the log format ("json", "text").
	Format string `yaml:"format"`
}

LoggingConfig configures logging.

type LoopDetectionResult

type LoopDetectionResult struct {
	Severity LoopSeverity
	Message  string // Injected into the conversation as a system hint
	Streak   int    // Number of consecutive repeats detected
	Pattern  string // "repeat", "ping-pong", "known_poll", "global_breaker", or ""
}

LoopDetectionResult is the outcome of a loop check.

type LoopSeverity

type LoopSeverity int

LoopSeverity represents the level of loop detection.

const (
	LoopNone     LoopSeverity = iota
	LoopWarning               // Agent should be nudged
	LoopCritical              // Agent should be strongly nudged
	LoopBreaker               // Agent run should be terminated
)

type MCPConfig

type MCPConfig struct {
	// Enabled turns MCP support on/off.
	Enabled bool `yaml:"enabled" json:"enabled"`

	// Servers is the list of configured MCP servers.
	Servers []ManagedMCPServerConfig `yaml:"servers" json:"servers"`
}

MCPConfig holds all MCP configuration.

func DefaultMCPConfig

func DefaultMCPConfig() MCPConfig

DefaultMCPConfig returns sensible defaults.

type MCPManager

type MCPManager struct {
	// contains filtered or unexported fields
}

MCPManager manages MCP server lifecycle and configuration.

func NewMCPManager

func NewMCPManager(cfg MCPConfig, db *sql.DB, logger *slog.Logger) *MCPManager

NewMCPManager creates a new MCP manager.

func (*MCPManager) AddServer

func (m *MCPManager) AddServer(cfg ManagedMCPServerConfig) error

AddServer adds a new MCP server configuration.

func (*MCPManager) GetConfig

func (m *MCPManager) GetConfig() MCPConfig

GetConfig returns the current MCP configuration.

func (*MCPManager) GetServer

func (m *MCPManager) GetServer(name string) *MCPServerInfo

GetServer returns a specific MCP server by name.

func (*MCPManager) ListServers

func (m *MCPManager) ListServers() []MCPServerInfo

ListServers returns all configured MCP servers with their status.

func (*MCPManager) LoadFromDB

func (m *MCPManager) LoadFromDB() error

LoadFromDB loads the MCP configuration from the database.

func (*MCPManager) RefreshStatus

func (m *MCPManager) RefreshStatus(ctx context.Context)

RefreshStatus refreshes the status of all MCP servers.

func (*MCPManager) Reload

func (m *MCPManager) Reload(cfg MCPConfig)

Reload reloads the MCP configuration.

func (*MCPManager) RemoveServer

func (m *MCPManager) RemoveServer(name string) error

RemoveServer removes an MCP server configuration.

func (*MCPManager) SetEnabled

func (m *MCPManager) SetEnabled(name string, enabled bool) error

SetEnabled enables or disables an MCP server.

func (*MCPManager) TestServer

func (m *MCPManager) TestServer(ctx context.Context, name string) (*MCPServerStatus, error)

TestServer tests the connection to an MCP server.

func (*MCPManager) UpdateServer

func (m *MCPManager) UpdateServer(name string, cfg ManagedMCPServerConfig) error

UpdateServer updates an existing MCP server configuration.

type MCPServerConfig

type MCPServerConfig struct {
	Name      string            `yaml:"name"`
	Transport string            `yaml:"transport"` // "stdio", "sse", "streamable-http"
	Command   string            `yaml:"command,omitempty"`
	Args      []string          `yaml:"args,omitempty"`
	URL       string            `yaml:"url,omitempty"`
	Env       map[string]string `yaml:"env,omitempty"`
}

MCPServerConfig holds configuration for an MCP server associated with a project.

type MCPServerInfo

type MCPServerInfo struct {
	Config ManagedMCPServerConfig `json:"config"`
	Status MCPServerStatus        `json:"status"`
}

MCPServerInfo combines config and status for an MCP server.

type MCPServerStatus

type MCPServerStatus struct {
	Name        string    `json:"name"`
	Connected   bool      `json:"connected"`
	LastChecked time.Time `json:"last_checked"`
	Error       string    `json:"error,omitempty"`
	Tools       []string  `json:"tools,omitempty"`
}

MCPServerStatus represents the status of an MCP server.

type MCPToolsBridge added in v1.13.0

type MCPToolsBridge struct {
	// contains filtered or unexported fields
}

MCPToolsBridge connects MCP servers to the ToolExecutor.

func NewMCPToolsBridge added in v1.13.0

func NewMCPToolsBridge(executor *ToolExecutor, logger *slog.Logger) *MCPToolsBridge

NewMCPToolsBridge creates a bridge that will register MCP tools.

func (*MCPToolsBridge) ConnectAll added in v1.13.0

func (b *MCPToolsBridge) ConnectAll(ctx context.Context, servers []ManagedMCPServerConfig)

ConnectAll launches all enabled auto-start MCP servers, discovers their tools, and registers them in the ToolExecutor.

func (*MCPToolsBridge) Shutdown added in v1.13.0

func (b *MCPToolsBridge) Shutdown()

Shutdown gracefully closes all MCP server connections.

type MCPType

type MCPType string

MCPType defines the type of MCP server connection.

const (
	MCPTypeStdio     MCPType = "stdio"
	MCPTypeSSE       MCPType = "sse"
	MCPTypeHTTP      MCPType = "http"
	MCPTypeWebSocket MCPType = "websocket"
)

type MMRConfig added in v1.12.0

type MMRConfig struct {
	// Enabled activates MMR re-ranking (default: false).
	Enabled bool `yaml:"enabled"`

	// Lambda balances relevance vs diversity (default: 0.7).
	// 0 = max diversity, 1 = max relevance.
	Lambda float64 `yaml:"lambda"`
}

MMRConfig configures Maximal Marginal Relevance for search diversification.

type MaintenanceManager

type MaintenanceManager struct {
	// contains filtered or unexported fields
}

MaintenanceManager manages maintenance mode state with database persistence.

func NewMaintenanceManager

func NewMaintenanceManager(db *sql.DB, logger *slog.Logger) *MaintenanceManager

NewMaintenanceManager creates a new maintenance manager.

func (*MaintenanceManager) Get

Get returns the current maintenance mode state.

func (*MaintenanceManager) IsEnabled

func (m *MaintenanceManager) IsEnabled() bool

IsEnabled returns true if maintenance mode is active.

func (*MaintenanceManager) Load

func (m *MaintenanceManager) Load() error

Load restores maintenance mode from database on startup.

func (*MaintenanceManager) Set

func (m *MaintenanceManager) Set(enabled bool, message, setBy string) error

Set enables or disables maintenance mode.

type MaintenanceMode

type MaintenanceMode struct {
	Enabled bool      `json:"enabled"`
	Message string    `json:"message"`
	SetBy   string    `json:"set_by"`
	SetAt   time.Time `json:"set_at"`
}

MaintenanceMode represents the system maintenance state.

type ManagedMCPServerConfig

type ManagedMCPServerConfig struct {
	// Name is the unique identifier for this MCP server.
	Name string `yaml:"name" json:"name"`

	// Type is the connection type (stdio, sse, http, websocket).
	Type MCPType `yaml:"type" json:"type"`

	// Command is the executable for stdio type.
	Command string `yaml:"command" json:"command,omitempty"`

	// Args are command-line arguments for stdio type.
	Args []string `yaml:"args" json:"args,omitempty"`

	// URL is the endpoint for SSE/HTTP/WebSocket types.
	URL string `yaml:"url" json:"url,omitempty"`

	// Headers are custom HTTP headers for SSE/HTTP/WebSocket types.
	Headers map[string]string `yaml:"headers" json:"headers,omitempty"`

	// Env are environment variables for stdio type.
	Env map[string]string `yaml:"env" json:"env,omitempty"`

	// Enabled controls whether this MCP server is active.
	Enabled bool `yaml:"enabled" json:"enabled"`

	// AutoStart controls whether to start on launch.
	AutoStart bool `yaml:"auto_start" json:"auto_start"`

	// Timeout is the connection timeout in seconds.
	Timeout int `yaml:"timeout" json:"timeout"`

	// Description is a human-readable description.
	Description string `yaml:"description" json:"description,omitempty"`
}

ManagedMCPServerConfig configures a managed MCP server.

type MediaCapability added in v1.13.0

type MediaCapability string

MediaCapability represents a type of media processing.

const (
	CapabilityVision        MediaCapability = "vision"
	CapabilityTranscription MediaCapability = "transcription"
)

type MediaConfig

type MediaConfig struct {
	// VisionEnabled enables image understanding via LLM vision (default: true).
	VisionEnabled bool `yaml:"vision_enabled"`

	// VisionModel overrides the model used for image/video understanding.
	// If empty, uses the main chat model. Examples: "glm-4.6v", "gpt-4o", "claude-sonnet-4-20250514".
	VisionModel string `yaml:"vision_model"`

	// VisionDetail controls quality: "auto", "low", "high" (default: "auto").
	VisionDetail string `yaml:"vision_detail"`

	// TranscriptionEnabled enables audio transcription (default: true).
	TranscriptionEnabled bool `yaml:"transcription_enabled"`

	// TranscriptionModel is the model for audio transcription (default: "whisper-1").
	// Examples: "whisper-1", "glm-asr-2512", "gpt-4o-transcribe", "whisper-large-v3".
	TranscriptionModel string `yaml:"transcription_model"`

	// TranscriptionBaseURL is the base URL for the transcription API.
	// Examples:
	//   Z.AI:   "https://api.z.ai/api/paas/v4"
	//   Groq:   "https://api.groq.com/openai/v1"
	//   OpenAI: "https://api.openai.com/v1" (default)
	TranscriptionBaseURL string `yaml:"transcription_base_url"`

	// TranscriptionAPIKey is the API key for the transcription provider.
	// If empty, falls back to the main API key.
	TranscriptionAPIKey string `yaml:"transcription_api_key"`

	// TranscriptionLanguage hints the expected language (ISO 639-1, e.g. "pt", "en", "es").
	// For Whisper: passed as the "language" field.
	// For Z.AI GLM-ASR: used as a prompt hint for auto-detection.
	TranscriptionLanguage string `yaml:"transcription_language"`

	// MaxImageSize is the max image size in bytes to process (default: 20MB).
	MaxImageSize int64 `yaml:"max_image_size"`

	// MaxAudioSize is the max audio size in bytes (default: 25MB).
	MaxAudioSize int64 `yaml:"max_audio_size"`

	// VisionProviders configures multiple vision providers with priority-based fallback.
	// When set, describe_image will try these providers in priority order instead of the main LLM.
	VisionProviders []MediaProviderConfig `yaml:"vision_providers"`

	// TranscriptionProviders configures multiple transcription providers with priority-based fallback.
	// When set, transcribe_audio will try these providers in priority order.
	TranscriptionProviders []MediaProviderConfig `yaml:"transcription_providers"`

	// ConcurrencyLimit limits simultaneous media API calls across all providers (default: 3).
	ConcurrencyLimit int `yaml:"concurrency_limit"`
}

MediaConfig configures vision and audio transcription capabilities.

func DefaultMediaConfig

func DefaultMediaConfig() MediaConfig

DefaultMediaConfig returns sensible defaults for media processing.

func (MediaConfig) Effective

func (m MediaConfig) Effective() MediaConfig

Effective returns a copy with default values filled in for zero fields.

func (*MediaConfig) ResolveForProvider

func (m *MediaConfig) ResolveForProvider(provider, baseURL string)

ResolveForProvider fills in transcription defaults based on the main API provider so users don't have to configure transcription separately when their provider already supports it.

type MediaEmitter added in v1.15.0

type MediaEmitter func(MediaEvent)

MediaEmitter is a callback that pushes media events to the client.

func MediaEmitterFromContext added in v1.15.0

func MediaEmitterFromContext(ctx context.Context) MediaEmitter

MediaEmitterFromContext extracts the media emitter from a context. Returns nil if not set.

type MediaEvent added in v1.15.0

type MediaEvent struct {
	ID       string `json:"id"`
	URL      string `json:"url"`
	Type     string `json:"type"` // image, audio, video, document
	MimeType string `json:"mime_type"`
	Filename string `json:"filename"`
	Size     int64  `json:"size"`
	Caption  string `json:"caption,omitempty"`
}

MediaEvent represents a media attachment emitted during an agent run. Used to push media to the Web UI via SSE or other non-channel delivery paths.

type MediaProviderConfig added in v1.13.0

type MediaProviderConfig struct {
	// Provider is the provider name (e.g. "openai", "anthropic", "gemini").
	Provider string `yaml:"provider"`

	// BaseURL is the API base URL.
	BaseURL string `yaml:"base_url"`

	// APIKey is the API key for this provider.
	APIKey string `yaml:"api_key"`

	// Model is the model to use (e.g. "gpt-4o", "claude-sonnet-4-20250514").
	Model string `yaml:"model"`

	// Priority determines the order (lower = tried first). Default: 0.
	Priority int `yaml:"priority"`
}

MediaProviderConfig configures a single media provider.

type MediaRegistry added in v1.13.0

type MediaRegistry struct {
	// contains filtered or unexported fields
}

MediaRegistry holds multiple providers per capability and tries them in priority order with fallback on failure.

func GetGlobalMediaRegistry added in v1.13.0

func GetGlobalMediaRegistry() *MediaRegistry

GetGlobalMediaRegistry returns the global media registry, if set.

func NewMediaRegistry added in v1.13.0

func NewMediaRegistry(visionProviders, transcriptionProviders []MediaProviderConfig, concurrencyLimit int, logger *slog.Logger) *MediaRegistry

NewMediaRegistry creates a registry from provider configs. concurrencyLimit limits simultaneous media API calls (0 = default 3).

func (*MediaRegistry) DescribeImageWithFallback added in v1.13.0

func (r *MediaRegistry) DescribeImageWithFallback(ctx context.Context, systemPrompt, imageBase64, mimeType, userPrompt, detail string) (string, error)

DescribeImageWithFallback tries vision providers in priority order until one succeeds.

func (*MediaRegistry) HasTranscriptionProviders added in v1.13.0

func (r *MediaRegistry) HasTranscriptionProviders() bool

HasTranscriptionProviders returns true if transcription providers are configured.

func (*MediaRegistry) HasVisionProviders added in v1.13.0

func (r *MediaRegistry) HasVisionProviders() bool

HasVisionProviders returns true if vision providers are configured.

func (*MediaRegistry) TranscribeAudioWithFallback added in v1.13.0

func (r *MediaRegistry) TranscribeAudioWithFallback(ctx context.Context, audioData []byte, filename, model string, mediaCfg MediaConfig) (string, error)

TranscribeAudioWithFallback tries transcription providers in priority order until one succeeds.

type MemoryChunk added in v1.8.0

type MemoryChunk struct {
	Filepath  string
	Content   string
	Hash      string
	CreatedAt time.Time
}

MemoryChunk represents a chunk of memory content for indexing.

type MemoryConfig

type MemoryConfig struct {
	// Type is the storage type ("sqlite", "file").
	// "sqlite" enables FTS5 + vector search; "file" is the legacy fallback.
	Type string `yaml:"type"`

	// Path is the database file path (for sqlite).
	Path string `yaml:"path"`

	// MaxMessages is the max messages kept per session.
	MaxMessages int `yaml:"max_messages"`

	// CompressionStrategy defines memory compression
	// ("summarize", "truncate", "semantic").
	CompressionStrategy string `yaml:"compression_strategy"`

	// Embedding configures the embedding provider for semantic search.
	Embedding memory.EmbeddingConfig `yaml:"embedding"`

	// Search configures hybrid search behavior.
	Search SearchConfig `yaml:"search"`

	// Index configures automatic indexing.
	Index IndexConfig `yaml:"index"`

	// SessionMemory configures automatic session summarization.
	SessionMemory SessionMemoryConfig `yaml:"session_memory"`

	// Hierarchy configures the palace-aware memory subsystem (Sprint 1,
	// v1.18.0). Defaults to Enabled=true: wing IS NULL is treated as a
	// first-class neutral citizen, so existing v1.17.0 databases keep
	// working byte-identically while new memories get routed through
	// wings/rooms. See HierarchyConfig in memory_hierarchy_config.go.
	Hierarchy HierarchyConfig `yaml:"hierarchy"`

	// Dream configures the background memory consolidation system (v1.17.0,
	// now wired in v1.18.0). Defaults to Enabled=true so out-of-the-box
	// installs get idle-cycle consolidation as the release notes promised.
	// Existing YAML without a dream: block inherits defaults (retrocompat).
	Dream DreamConfig `yaml:"dream"`

	// Stack configures the Sprint 2 layered memory stack (v1.19.0+).
	// Default: MemoryStackConfig{} (stack enabled when hierarchy is on).
	// Set force_legacy: true to bypass the stack entirely and fall back
	// to v1.18.0 prompt composition. See docs/memory-system.md for details.
	Stack MemoryStackConfig `yaml:"stack"`
}

MemoryConfig configures the memory and persistence system.

type MemoryDispatcherConfig added in v1.12.0

type MemoryDispatcherConfig struct {
	Store         *memory.FileStore
	SQLiteStore   *memory.SQLiteStore
	Config        MemoryConfig
	ContextRouter *ContextRouter // optional; nil disables wing routing
}

MemoryDispatcherConfig holds configuration for memory tools.

type MemoryExtractor added in v1.17.0

type MemoryExtractor struct {
	// contains filtered or unexported fields
}

MemoryExtractor analyzes conversation history and extracts structured memories before compaction.

func NewMemoryExtractor added in v1.17.0

func NewMemoryExtractor(llm *LLMClient, logger *slog.Logger) *MemoryExtractor

NewMemoryExtractor creates a new extractor.

func (*MemoryExtractor) Extract added in v1.17.0

func (e *MemoryExtractor) Extract(ctx context.Context, messages []chatMessage, modelOverride string) []ExtractedMemory

Extract analyzes messages and returns structured memories. Uses a focused LLM call with a structured extraction prompt. Returns nil (no error) if extraction fails — this is best-effort.

type MemoryFlushConfig added in v1.12.0

type MemoryFlushConfig struct {
	// Enabled activates memory flush before compaction (default: true).
	Enabled bool `yaml:"enabled"`

	// ProactiveEnabled activates proactive flush before each run (default: true).
	// Uses token projection to decide whether flush is needed.
	ProactiveEnabled bool `yaml:"proactive_enabled"`

	// ProjectionThreshold is the fraction of context window that triggers proactive flush (default: 0.85).
	ProjectionThreshold float64 `yaml:"projection_threshold"`

	// ReserveTokensFloor is the minimum token buffer to maintain (default: 20000).
	ReserveTokensFloor int `yaml:"reserve_tokens_floor"`

	// FlushThreshold is the number of tokens before triggering flush (default: 4000).
	// Flush triggers when: tokenEstimate >= contextWindow - reserveFloor - flushThreshold
	FlushThreshold int `yaml:"flush_threshold"`

	// SystemPrompt is an optional custom system prompt for the flush turn.
	SystemPrompt string `yaml:"system_prompt"`

	// Prompt is the user prompt for the flush turn (default: standard prompt).
	Prompt string `yaml:"prompt"`
}

MemoryFlushConfig configures pre-compaction memory flush behavior. This triggers a silent turn before compaction to save important memories.

type MemoryHierarchyDispatcherConfig added in v1.18.0

type MemoryHierarchyDispatcherConfig struct {
	// SQLiteStore is the memory store that exposes the palace operations
	// (UpsertWing, ListRooms, GetChannelWing, ...). Must not be nil when
	// Enabled is true; if nil, the tools return errors uniformly.
	SQLiteStore *memory.SQLiteStore

	// Router resolves (channel, chatID) to wings. May be nil if the caller
	// only wants the read-side tools. When nil, pin/unpin tools return
	// a "router unavailable" error.
	Router *ContextRouter

	// Enabled gates every tool behavior. When false, tools are still
	// registered but return a feature-disabled error on invocation.
	Enabled bool

	// Logger is used for structured logging of tool calls. Defaults to
	// slog.Default() if nil.
	Logger *slog.Logger
}

MemoryHierarchyDispatcherConfig holds configuration for the palace-aware memory tools. Callers construct one with a reference to the SQLite store and a feature flag; the registrar wires it into the ToolExecutor.

type MemoryIndexer added in v1.8.0

type MemoryIndexer struct {
	// contains filtered or unexported fields
}

MemoryIndexer performs incremental indexing of memory files in the background. Uses fsnotify for event-driven re-indexing with the ticker as fallback.

func NewMemoryIndexer added in v1.8.0

func NewMemoryIndexer(cfg MemoryIndexerConfig, logger *slog.Logger) *MemoryIndexer

NewMemoryIndexer creates a new memory indexer.

func (*MemoryIndexer) ForceReindex added in v1.8.0

func (m *MemoryIndexer) ForceReindex()

ForceReindex clears all stored hashes and triggers a full reindex.

func (*MemoryIndexer) IndexNow added in v1.8.0

func (m *MemoryIndexer) IndexNow()

IndexNow triggers an immediate index (useful for manual triggers).

func (*MemoryIndexer) MemoryDir added in v1.17.0

func (m *MemoryIndexer) MemoryDir() string

MemoryDir returns the configured memory directory path.

func (*MemoryIndexer) SetDeleteFileFunc added in v1.8.0

func (m *MemoryIndexer) SetDeleteFileFunc(fn func(filepath string) error)

SetDeleteFileFunc sets the function for deleting file from index.

func (*MemoryIndexer) SetIndexChunkFunc added in v1.8.0

func (m *MemoryIndexer) SetIndexChunkFunc(fn func(chunks []MemoryChunk) error)

SetIndexChunkFunc sets the function for indexing chunks.

func (*MemoryIndexer) SetMemoryDir added in v1.8.0

func (m *MemoryIndexer) SetMemoryDir(dir string)

SetMemoryDir sets the memory directory to index.

func (*MemoryIndexer) SetSQLiteStore added in v1.8.0

func (m *MemoryIndexer) SetSQLiteStore(store SQLiteMemoryStore)

SetSQLiteStore sets the SQLite memory store for indexing.

func (*MemoryIndexer) Start added in v1.8.0

func (m *MemoryIndexer) Start(ctx context.Context) error

Start begins periodic memory indexing.

func (*MemoryIndexer) Stats added in v1.8.0

func (m *MemoryIndexer) Stats() (indexedTotal, indexedLast, deletedTotal int64, lastIndexTime time.Time)

Stats returns current indexer statistics.

func (*MemoryIndexer) Stop added in v1.8.0

func (m *MemoryIndexer) Stop()

Stop stops the memory indexer and its filesystem watcher.

type MemoryIndexerConfig added in v1.8.0

type MemoryIndexerConfig struct {
	Enabled   bool          `yaml:"enabled" json:"enabled"`
	Interval  time.Duration `yaml:"interval" json:"interval"`
	MemoryDir string        `yaml:"memory_dir" json:"memory_dir"`
}

MemoryIndexerConfig configures the memory indexer.

func DefaultMemoryIndexerConfig added in v1.8.0

func DefaultMemoryIndexerConfig() MemoryIndexerConfig

DefaultMemoryIndexerConfig returns default configuration.

type MemoryPromptSectionBuilder added in v1.16.0

type MemoryPromptSectionBuilder interface {
	// BuildMemorySection returns a prompt section string (may be empty).
	// Implementations should be fast (<100ms) to avoid blocking prompt composition.
	BuildMemorySection(session *Session, input string) string
}

MemoryPromptSectionBuilder is implemented by memory plugins that want to contribute additional sections to the memory layer of the system prompt. Each registered builder is called during buildMemoryLayer and its output is concatenated after the core memory sections.

type MemoryStack added in v1.18.0

type MemoryStack struct {
	// contains filtered or unexported fields
}

MemoryStack composes the Sprint 2 layered memory system:

L0 — IdentityLayer   (anchored, never trimmed)
L1 — EssentialLayer  (per-wing story from template cache)
L2 — OnDemandLayer   (per-turn entity detection + retrieval)
L3 — the legacy buildMemoryLayer fallback (handled by the caller)

The stack renders L0, L1, L2 into a single prefix string that prompt_layers.go's buildMemoryLayer prepends to the L3 output. When all three Sprint 2 layers return empty (or the stack is nil or ForceLegacy is on), the stack returns an empty string and buildMemoryLayer produces byte-identical output to v1.18.0. This is the retrocompat gate enforced by the golden fixture test.

Thread safety: all methods are safe for concurrent use. Telemetry counters use sync/atomic.

func NewMemoryStack added in v1.18.0

func NewMemoryStack(
	identity *memory.IdentityLayer,
	essential *memory.EssentialLayer,
	onDemand *memory.OnDemandLayer,
	cfg StackConfig,
	logger *slog.Logger,
) *MemoryStack

NewMemoryStack constructs a stack. Any layer argument may be nil — nil layers are internally replaced with a no-op adapter that always returns the empty string. If all three concrete layers are nil, the stack's Build() method short-circuits to "" and the caller degrades to the legacy L3 path.

If logger is nil, slog.Default() is used. The cfg is normalized via withDefaults() so callers can pass a zero StackConfig for defaults.

func (*MemoryStack) Build added in v1.18.0

func (s *MemoryStack) Build(ctx context.Context, activeWing, turn string) string

Build renders the L0+L1+L2 prefix for the current turn. Returns the empty string when:

  • the stack is nil;
  • cfg.ForceLegacy is true;
  • the context is already cancelled;
  • all three layers render empty strings.

On panic in any layer, the layer is skipped, an error is logged, and the remaining layers still execute. panicTotal is incremented. The prompt is always produced unless ctx is cancelled.

activeWing is the session's current wing ("" = legacy / no wing). turn is the current user message text (passed to L2 for entity detection).

The byte-budget algorithm is documented at the top of this file and enforced in place:

  1. L0 is always rendered in full. If L0 alone exceeds the budget, it is still included and a WARN is logged.
  2. L1 is truncated first when L0 + L1 would exceed the budget.
  3. L2 is trimmed first when the combined stack exceeds the budget (i.e. L2 is the lowest priority: trim L2 to zero before trimming L1).

func (*MemoryStack) Stats added in v1.18.0

func (s *MemoryStack) Stats() StackStats

Stats returns a point-in-time snapshot of the stack's telemetry counters. Safe to call concurrently with Build.

type MemoryStackConfig added in v1.18.0

type MemoryStackConfig struct {
	// ForceLegacy disables the MemoryStack and falls back to the
	// pre-Sprint-2 buildMemoryLayer code path. Default: false.
	// Use this as an emergency escape hatch if the layered stack causes
	// unexpected behavior in production — no config migration or downgrade
	// is required. Set memory.stack.force_legacy: true in devclaw.yaml.
	ForceLegacy bool `yaml:"force_legacy,omitempty"`
}

MemoryStackConfig configures the Sprint 2 layered memory stack (MemoryStack — see memory_stack.go). The only knob exposed today is force_legacy, which bypasses the stack entirely and falls back to the v1.18.0 prompt composer behavior. Users who want the new layered memory simply leave the block empty or omit it entirely.

type MemoryStats

type MemoryStats struct {
	AllocMB      float64 `json:"alloc_mb"`
	TotalAllocMB float64 `json:"total_alloc_mb"`
	SysMB        float64 `json:"sys_mb"`
	NumGC        uint32  `json:"num_gc"`
}

MemoryStats represents runtime memory statistics.

type MessageQueue

type MessageQueue struct {
	// contains filtered or unexported fields
}

MessageQueue handles message bursts with per-session debouncing.

func NewMessageQueue

func NewMessageQueue(debounceMs, maxPending int, onDrain OnDrainFunc, logger *slog.Logger) *MessageQueue

NewMessageQueue creates a new message queue. onDrain is called when the debounce timer fires with drained messages (may be nil).

func (*MessageQueue) CombineMessages

func (q *MessageQueue) CombineMessages(msgs []*channels.IncomingMessage) string

CombineMessages merges multiple messages into one prompt string.

func (*MessageQueue) Drain

func (q *MessageQueue) Drain(sessionID string) []*channels.IncomingMessage

Drain returns and clears pending messages for the session.

func (*MessageQueue) Enqueue

func (q *MessageQueue) Enqueue(sessionID string, msg *channels.IncomingMessage) bool

Enqueue adds a message to the session queue. Returns true if enqueued, false if deduplicated (same content within 5 seconds).

func (*MessageQueue) IsDuplicate added in v1.13.0

func (q *MessageQueue) IsDuplicate(msg *channels.IncomingMessage) bool

IsDuplicate checks if a message is a platform-level duplicate delivery (webhook retries, reconnection replays) using Message-ID deduplication. Safe to call from any goroutine. Records the message so future duplicates are caught. Returns true if the message was already seen.

func (*MessageQueue) IsProcessing

func (q *MessageQueue) IsProcessing(sessionID string) bool

IsProcessing returns true if the session has an active run.

func (*MessageQueue) SetChannelDebounce added in v1.13.0

func (q *MessageQueue) SetChannelDebounce(m map[string]int)

SetChannelDebounce configures per-channel debounce overrides. Channels not in the map use the default debounceMs.

func (*MessageQueue) SetProcessing

func (q *MessageQueue) SetProcessing(sessionID string, active bool)

SetProcessing marks the session as processing or not.

func (*MessageQueue) StuckSessions

func (q *MessageQueue) StuckSessions(maxAge time.Duration) []string

StuckSessions returns session IDs that have been processing longer than maxAge.

func (*MessageQueue) TrySetProcessing

func (q *MessageQueue) TrySetProcessing(sessionID string) bool

TrySetProcessing atomically checks if the session is NOT processing and sets it to processing. Returns true if successful (caller owns the lock), false if the session was already processing (caller should enqueue). This eliminates the race window between IsProcessing() and SetProcessing().

type MetricsCollector added in v1.8.0

type MetricsCollector struct {
	// contains filtered or unexported fields
}

MetricsCollector collects and aggregates system metrics periodically.

func NewMetricsCollector added in v1.8.0

func NewMetricsCollector(cfg MetricsCollectorConfig, logger *slog.Logger) *MetricsCollector

NewMetricsCollector creates a new metrics collector.

func (*MetricsCollector) Latest added in v1.8.0

func (m *MetricsCollector) Latest() *MetricsSnapshot

Latest returns the most recent metrics snapshot.

func (*MetricsCollector) RecordAgentRunComplete added in v1.8.0

func (m *MetricsCollector) RecordAgentRunComplete(success bool, timedOut bool)

RecordAgentRunComplete decrements active runs and records outcome.

func (*MetricsCollector) RecordAgentRunStart added in v1.8.0

func (m *MetricsCollector) RecordAgentRunStart()

RecordAgentRunStart increments active runs.

func (*MetricsCollector) RecordDBQuery added in v1.8.0

func (m *MetricsCollector) RecordDBQuery(slow bool)

RecordDBQuery records a database query.

func (*MetricsCollector) RecordError added in v1.8.0

func (m *MetricsCollector) RecordError()

RecordError increments error counter.

func (*MetricsCollector) RecordLatency added in v1.8.0

func (m *MetricsCollector) RecordLatency(ms int64)

RecordLatency records a latency measurement in milliseconds.

func (*MetricsCollector) RecordMessage added in v1.8.0

func (m *MetricsCollector) RecordMessage()

RecordMessage increments message counter.

func (*MetricsCollector) RecordSessionCreated added in v1.8.0

func (m *MetricsCollector) RecordSessionCreated()

RecordSessionCreated records a new session.

func (*MetricsCollector) RecordSubagentComplete added in v1.8.0

func (m *MetricsCollector) RecordSubagentComplete(success bool)

RecordSubagentComplete records a subagent completion.

func (*MetricsCollector) RecordSubagentSpawn added in v1.8.0

func (m *MetricsCollector) RecordSubagentSpawn()

RecordSubagentSpawn records a subagent creation.

func (*MetricsCollector) RecordTokens added in v1.8.0

func (m *MetricsCollector) RecordTokens(count int64)

RecordTokens increments token counter.

func (*MetricsCollector) RecordToolCall added in v1.8.0

func (m *MetricsCollector) RecordToolCall(success bool)

RecordToolCall records a tool execution.

func (*MetricsCollector) SetDBSizeFunc added in v1.8.0

func (m *MetricsCollector) SetDBSizeFunc(fn func() int64)

SetDBSizeFunc sets the callback for getting database size.

func (*MetricsCollector) SetMessagesQueueFunc added in v1.8.0

func (m *MetricsCollector) SetMessagesQueueFunc(fn func() int64)

SetMessagesQueueFunc sets the callback for getting queued messages count.

func (*MetricsCollector) SetSessionsCountFunc added in v1.8.0

func (m *MetricsCollector) SetSessionsCountFunc(fn func() int64)

SetSessionsCountFunc sets the callback for getting active sessions count.

func (*MetricsCollector) SetSubagentsCountFunc added in v1.8.0

func (m *MetricsCollector) SetSubagentsCountFunc(fn func() int64)

SetSubagentsCountFunc sets the callback for getting active subagents count.

func (*MetricsCollector) Start added in v1.8.0

func (m *MetricsCollector) Start(ctx context.Context) error

Start begins periodic metrics collection.

func (*MetricsCollector) Stop added in v1.8.0

func (m *MetricsCollector) Stop()

Stop stops the metrics collector.

func (*MetricsCollector) Subscribe added in v1.8.0

func (m *MetricsCollector) Subscribe() <-chan MetricsSnapshot

Subscribe returns a channel that receives metrics snapshots.

func (*MetricsCollector) Unsubscribe added in v1.8.0

func (m *MetricsCollector) Unsubscribe(ch <-chan MetricsSnapshot)

Unsubscribe removes a subscriber.

type MetricsCollectorConfig added in v1.8.0

type MetricsCollectorConfig struct {
	Enabled  bool          `yaml:"enabled" json:"enabled"`
	Interval time.Duration `yaml:"interval" json:"interval"`
	Webhook  string        `yaml:"webhook" json:"webhook"`
}

MetricsCollectorConfig configures the metrics collector.

func DefaultMetricsCollectorConfig added in v1.8.0

func DefaultMetricsCollectorConfig() MetricsCollectorConfig

DefaultMetricsCollectorConfig returns default configuration.

type MetricsResult

type MetricsResult struct {
	Period    string                   `json:"period"`
	StartTime time.Time                `json:"start_time"`
	EndTime   time.Time                `json:"end_time"`
	Total     MetricsTotals            `json:"total"`
	ByModel   map[string]MetricsTotals `json:"by_model"`
	TopUsers  []UserUsage              `json:"top_users"`
}

MetricsResult represents usage metrics for a time period.

type MetricsSnapshot added in v1.8.0

type MetricsSnapshot struct {
	Timestamp time.Time `json:"timestamp"`

	// Message metrics
	MessagesTotal     int64 `json:"messages_total"`
	MessagesPerMinute int64 `json:"messages_per_minute"`

	// Token metrics
	TokensTotal     int64 `json:"tokens_total"`
	TokensPerMinute int64 `json:"tokens_per_minute"`

	// Agent metrics
	AgentRunsTotal   int64 `json:"agent_runs_total"`
	AgentRunsActive  int64 `json:"agent_runs_active"`
	AgentRunsSuccess int64 `json:"agent_runs_success"`
	AgentRunsFailed  int64 `json:"agent_runs_failed"`
	AgentRunsTimeout int64 `json:"agent_runs_timeout"`

	// Tool metrics
	ToolCallsTotal   int64 `json:"tool_calls_total"`
	ToolCallsSuccess int64 `json:"tool_calls_success"`
	ToolCallsFailed  int64 `json:"tool_calls_failed"`

	// Subagent metrics
	SubagentsTotal   int64 `json:"subagents_total"`
	SubagentsActive  int64 `json:"subagents_active"`
	SubagentsSuccess int64 `json:"subagents_success"`
	SubagentsFailed  int64 `json:"subagents_failed"`

	// System metrics
	Goroutines    int64 `json:"goroutines"`
	MemoryAllocMB int64 `json:"memory_alloc_mb"`
	MemorySysMB   int64 `json:"memory_sys_mb"`

	// Session metrics
	SessionsActive int64 `json:"sessions_active"`
	SessionsTotal  int64 `json:"sessions_total"`

	// Error metrics
	ErrorsTotal  int64 `json:"errors_total"`
	ErrorsRecent int64 `json:"errors_recent"` // last interval

	// Latency metrics (milliseconds)
	LatencyAvgMs int64 `json:"latency_avg_ms"`
	LatencyP50Ms int64 `json:"latency_p50_ms"`
	LatencyP99Ms int64 `json:"latency_p99_ms"`

	// Database metrics
	DBSizeMB    int64 `json:"db_size_mb"`
	DBQueries   int64 `json:"db_queries"`
	DBSlowQuery int64 `json:"db_slow_query"`

	// Uptime
	UptimeSeconds int64 `json:"uptime_seconds"`
}

MetricsSnapshot represents a point-in-time collection of system metrics.

type MetricsTotals

type MetricsTotals struct {
	PromptTokens     int64   `json:"prompt_tokens"`
	CompletionTokens int64   `json:"completion_tokens"`
	TotalTokens      int64   `json:"total_tokens"`
	Requests         int64   `json:"requests"`
	EstimatedCostUSD float64 `json:"estimated_cost_usd"`
}

MetricsTotals holds aggregated usage totals.

type ModelCooldown

type ModelCooldown struct {
	Model      string
	Until      time.Time
	Reason     FailoverReason
	ErrorCount int
	LastError  time.Time
}

ModelCooldown tracks a model's cooldown state.

type ModelCost

type ModelCost struct {
	InputPer1M  float64 `yaml:"input_per_1m"`  // USD per 1M input tokens
	OutputPer1M float64 `yaml:"output_per_1m"` // USD per 1M output tokens
}

ModelCost holds pricing per 1M tokens for a model.

type ModelFailoverManager

type ModelFailoverManager struct {
	// contains filtered or unexported fields
}

ModelFailoverManager handles automatic model rotation on failures.

func NewModelFailoverManager

func NewModelFailoverManager(config ModelFallbackConfig, logger *slog.Logger) *ModelFailoverManager

NewModelFailoverManager creates a failover manager.

func (*ModelFailoverManager) ApplyClassifiedFailure added in v1.13.0

func (m *ModelFailoverManager) ApplyClassifiedFailure(model string, reason FailoverReason)

ApplyClassifiedFailure applies a pre-classified failure reason to a model. Use this when the error has already been classified (e.g., by the FailoverCoordinator) to avoid redundant re-classification.

func (*ModelFailoverManager) GetCooldownStatus

func (m *ModelFailoverManager) GetCooldownStatus() map[string]*ModelCooldown

GetCooldownStatus returns the current cooldown state of all models.

func (*ModelFailoverManager) MarkProbed added in v1.13.0

func (m *ModelFailoverManager) MarkProbed(model string)

MarkProbed records that a probe was attempted for a model.

func (*ModelFailoverManager) ReportFailure

func (m *ModelFailoverManager) ReportFailure(model string, statusCode int, errMsg string) FailoverReason

ReportFailure records a failure for a model and applies cooldown if needed. Returns the reason classification.

func (*ModelFailoverManager) ReportFailureWithCause added in v1.16.0

func (m *ModelFailoverManager) ReportFailureWithCause(model string, statusCode int, errMsg string, cause error) FailoverReason

ReportFailureWithCause is like ReportFailure but also traverses the error cause chain for more accurate classification of wrapped errors.

func (*ModelFailoverManager) ReportSuccess

func (m *ModelFailoverManager) ReportSuccess(model string)

ReportSuccess resets the cooldown for a model after a successful call.

func (*ModelFailoverManager) SelectFromChain added in v1.13.0

func (m *ModelFailoverManager) SelectFromChain(chain []string) string

SelectFromChain tries models from a custom chain, skipping those in cooldown. Returns the first available model, or empty string if all are in cooldown.

func (*ModelFailoverManager) SelectModel

func (m *ModelFailoverManager) SelectModel() (model string, isPrimary bool)

SelectModel returns the best available model. It checks the primary first, then iterates through fallbacks, skipping any that are in cooldown. Returns the model name and whether it's the primary.

When the primary is in cooldown but near expiry (within probeMargin), the caller can check ShouldProbe() to decide whether to attempt a recovery probe before committing to a fallback model.

func (*ModelFailoverManager) ShouldProbe added in v1.13.0

func (m *ModelFailoverManager) ShouldProbe(model string) bool

ShouldProbe returns true if a model is in cooldown but near expiry (within probeMargin) and hasn't been probed too recently. This enables auto-recovery without waiting for the full cooldown to expire.

type ModelFallbackConfig

type ModelFallbackConfig struct {
	Primary   string   `yaml:"primary"`   // e.g. "claude-sonnet-4-20250514"
	Fallbacks []string `yaml:"fallbacks"` // e.g. ["gpt-4o", "glm-5"]

	// CooldownConfig controls how long a model is disabled after failures.
	Cooldowns CooldownConfig `yaml:"cooldowns"`
}

ModelFallbackConfig defines the primary model and fallback chain.

type NativeMediaConfig added in v1.8.0

type NativeMediaConfig struct {
	// Enabled activates native media features (default: true after setup).
	Enabled bool `yaml:"enabled"`

	// Store configures media storage.
	Store NativeMediaStoreConfig `yaml:"store"`

	// Service configures the media service.
	Service NativeMediaServiceConfig `yaml:"service"`

	// Enrichment configures automatic media enrichment.
	Enrichment NativeMediaEnrichmentConfig `yaml:"enrichment"`
}

NativeMediaConfig configures the native media handling system.

func DefaultNativeMediaConfig added in v1.8.0

func DefaultNativeMediaConfig() NativeMediaConfig

DefaultNativeMediaConfig returns sensible defaults for native media. Note: The enrichment flags (AutoEnrichImages, AutoEnrichAudio) are set to true by default, but they will only work if the corresponding MediaConfig capabilities (VisionEnabled, TranscriptionEnabled) are also enabled. Documents always work as they don't depend on external APIs.

type NativeMediaEnrichmentConfig added in v1.8.0

type NativeMediaEnrichmentConfig struct {
	// AutoEnrichImages runs vision on received images.
	AutoEnrichImages bool `yaml:"auto_enrich_images"`

	// AutoEnrichAudio transcribes received audio.
	AutoEnrichAudio bool `yaml:"auto_enrich_audio"`

	// AutoEnrichDocuments extracts text from documents.
	AutoEnrichDocuments bool `yaml:"auto_enrich_documents"`
}

NativeMediaEnrichmentConfig configures automatic media enrichment.

type NativeMediaServiceConfig added in v1.8.0

type NativeMediaServiceConfig struct {
	// MaxImageSize is the maximum image size in bytes.
	MaxImageSize int64 `yaml:"max_image_size"`

	// MaxAudioSize is the maximum audio size in bytes.
	MaxAudioSize int64 `yaml:"max_audio_size"`

	// MaxDocSize is the maximum document size in bytes.
	MaxDocSize int64 `yaml:"max_doc_size"`

	// TempTTL is the time-to-live for temporary files.
	TempTTL string `yaml:"temp_ttl"`

	// CleanupEnabled enables automatic cleanup of expired files.
	CleanupEnabled bool `yaml:"cleanup_enabled"`

	// CleanupInterval is the interval between cleanup runs.
	CleanupInterval string `yaml:"cleanup_interval"`
}

NativeMediaServiceConfig configures the media service.

type NativeMediaStoreConfig added in v1.8.0

type NativeMediaStoreConfig struct {
	// BaseDir is the permanent storage directory.
	BaseDir string `yaml:"base_dir"`

	// TempDir is the temporary storage directory.
	TempDir string `yaml:"temp_dir"`

	// MaxFileSize is the maximum file size in bytes.
	MaxFileSize int64 `yaml:"max_file_size"`
}

NativeMediaStoreConfig configures media storage.

type NetworkRequest added in v1.13.0

type NetworkRequest struct {
	URL       string            `json:"url"`
	Method    string            `json:"method"`
	Status    int               `json:"status"`
	Type      string            `json:"type"`
	Headers   map[string]string `json:"headers,omitempty"`
	Timestamp int64             `json:"timestamp"`
}

NetworkRequest represents a network request.

type NextAction added in v1.17.0

type NextAction int

NextAction tells the QueryLoop what to do after a phase completes.

const (
	// ActionContinue proceeds to the next phase in sequence.
	ActionContinue NextAction = iota

	// ActionLoopBack returns to the APICall phase (tool_use detected).
	ActionLoopBack

	// ActionStop ends the turn and returns the response.
	ActionStop

	// ActionInject re-injects a message and loops back to APICall.
	ActionInject
)

func (NextAction) String added in v1.17.0

func (a NextAction) String() string

String returns a human-readable name for the action.

type OAuthHubConfig added in v1.13.0

type OAuthHubConfig struct {
	// Mode selects the OAuth strategy:
	//   "local" (default) - use local TokenManager as before
	//   "hub"             - delegate OAuth to an OAuth Hub instance
	Mode string `yaml:"mode"`

	// HubURL is the base URL of the OAuth Hub (e.g. "http://localhost:8443").
	// Required when Mode is "hub".
	HubURL string `yaml:"hub_url"`

	// APIKey is the API key for authenticating with the Hub (dk_xxx).
	// Can also reference a vault key or environment variable.
	APIKey string `yaml:"api_key"`

	// APIKeyEnvVar is the environment variable containing the API key.
	// Defaults to "OAUTH_HUB_API_KEY" if APIKey is empty.
	APIKeyEnvVar string `yaml:"api_key_env_var"`
}

OAuthHubConfig configures the OAuth Hub integration.

type OAuthTokenManager added in v1.12.0

type OAuthTokenManager interface {
	GetValidToken(provider string) (interface{}, error)
}

OAuthTokenManager is the interface for OAuth token management.

type OnDrainFunc

type OnDrainFunc func(sessionID string, msgs []*channels.IncomingMessage)

OnDrainFunc is called when the debounce timer fires with drained messages.

type PageState added in v1.13.0

type PageState struct {
	ConsoleMessages []ConsoleMessage
	NetworkRequests []NetworkRequest
	LastSnapshot    *SnapshotResult
}

PageState tracks per-page state.

type PairingManager

type PairingManager struct {
	// contains filtered or unexported fields
}

PairingManager handles pairing tokens and requests.

func NewPairingManager

func NewPairingManager(db *sql.DB, accessMgr *AccessManager, wsMgr *WorkspaceManager, logger *slog.Logger) *PairingManager

NewPairingManager creates a new pairing manager.

func (*PairingManager) ApproveRequest

func (pm *PairingManager) ApproveRequest(requestID, approvedBy string) error

ApproveRequest approves a pending request and grants access.

func (*PairingManager) CreateRequest

func (pm *PairingManager) CreateRequest(tokenID, userJID, userName string) (*PairingRequest, error)

CreateRequest creates a pending pairing request.

func (*PairingManager) DenyRequest

func (pm *PairingManager) DenyRequest(requestID, deniedBy, reason string) error

DenyRequest denies a pending request.

func (*PairingManager) GenerateToken

func (pm *PairingManager) GenerateToken(createdBy string, opts TokenOptions) (*PairingToken, error)

GenerateToken creates a new pairing token.

func (*PairingManager) GetTokenByIDOrPrefix

func (pm *PairingManager) GetTokenByIDOrPrefix(idOrPrefix string) (*PairingToken, error)

GetTokenByIDOrPrefix finds a token by ID or token prefix.

func (*PairingManager) ListPendingRequests

func (pm *PairingManager) ListPendingRequests() ([]*PairingRequest, error)

ListPendingRequests returns all pending requests.

func (*PairingManager) ListTokens

func (pm *PairingManager) ListTokens(includeRevoked bool) ([]*PairingToken, error)

ListTokens returns all tokens (with optional filter).

func (*PairingManager) Load

func (pm *PairingManager) Load() error

Load restores token cache from database on startup.

func (*PairingManager) ProcessTokenRedemption

func (pm *PairingManager) ProcessTokenRedemption(tokenStr, userJID, userName string) (bool, string, error)

ProcessTokenRedemption handles a user sending a token. Returns: (approved, message, error)

func (*PairingManager) RevokeToken

func (pm *PairingManager) RevokeToken(tokenID, revokedBy string) error

RevokeToken revokes a token.

func (*PairingManager) ValidateToken

func (pm *PairingManager) ValidateToken(token string) (*PairingToken, error)

ValidateToken checks if a token is valid and returns it.

type PairingRequest

type PairingRequest struct {
	ID         string     `json:"id"`
	TokenID    string     `json:"token_id"`
	UserJID    string     `json:"user_jid"`
	UserName   string     `json:"user_name"`
	Status     string     `json:"status"` // pending, approved, denied
	ReviewedBy string     `json:"reviewed_by"`
	ReviewedAt *time.Time `json:"reviewed_at,omitempty"`
	CreatedAt  time.Time  `json:"created_at"`

	// Loaded via join for display
	TokenNote string    `json:"token_note,omitempty"`
	TokenRole TokenRole `json:"token_role,omitempty"`
}

PairingRequest represents a pending access request.

type PairingToken

type PairingToken struct {
	ID          string     `json:"id"`
	Token       string     `json:"token"`
	Role        TokenRole  `json:"role"`
	MaxUses     int        `json:"max_uses"` // 0 = unlimited
	UseCount    int        `json:"use_count"`
	AutoApprove bool       `json:"auto_approve"` // If true, grants access immediately
	WorkspaceID string     `json:"workspace_id"`
	Note        string     `json:"note"`
	CreatedBy   string     `json:"created_by"`
	CreatedAt   time.Time  `json:"created_at"`
	ExpiresAt   *time.Time `json:"expires_at,omitempty"`
	Revoked     bool       `json:"revoked"`
	RevokedAt   *time.Time `json:"revoked_at,omitempty"`
	RevokedBy   string     `json:"revoked_by"`
}

PairingToken represents a shareable invite token.

func (*PairingToken) CanUse

func (t *PairingToken) CanUse() bool

CanUse returns true if the token can still be used.

func (*PairingToken) IsExpired

func (t *PairingToken) IsExpired() bool

IsExpired returns true if the token has expired.

type PalaceBotConfig added in v1.18.0

type PalaceBotConfig struct {
	Store   *memory.SQLiteStore
	Router  *ContextRouter
	Enabled bool
}

PalaceBotConfig bundles the dependencies a command handler needs. Typically constructed once at startup and passed to HandlePalaceBotCommand on every message.

type PalaceBotReply added in v1.18.0

type PalaceBotReply struct {
	Handled bool
	Reply   string
	Err     error
}

PalaceBotReply is the result of handling a command. When Handled is false, Reply should be ignored and the message should continue through the normal LLM path.

func HandlePalaceBotCommand added in v1.18.0

func HandlePalaceBotCommand(ctx context.Context, cfg PalaceBotConfig, channel, chatID, input string) PalaceBotReply

HandlePalaceBotCommand inspects an incoming chat message and, if it is a palace-aware slash command, handles it directly and returns the reply text. Callers invoke this before any LLM processing.

Parameters:

  • ctx: request context
  • cfg: palace bot config (store, router, enabled flag)
  • channel: the channel identifier (e.g., "telegram", "whatsapp")
  • chatID: the external chat ID from the channel
  • input: the raw message text from the user

Returns a PalaceBotReply. If Handled is false, the caller should process the message normally. If Handled is true, the caller should send Reply back to the user via the channel and skip LLM invocation.

type PendingApproval

type PendingApproval struct {
	ID          string
	ToolName    string
	Args        map[string]any
	Description string
	SessionID   string
	CallerJID   string
	CreatedAt   time.Time
	Result      chan ApprovalResult
}

PendingApproval represents a tool call waiting for user approval.

type Phase added in v1.17.0

type Phase interface {
	// Name returns a human-readable identifier for logging and debugging.
	Name() string

	// Execute runs this phase's logic on the given turn state.
	// Returns the next action and any error.
	Execute(ctx context.Context, state *TurnState) (NextAction, error)
}

Phase represents a single stage in the query loop pipeline. Each phase receives the turn state, performs its work, and returns the next action for the loop orchestrator.

type PhaseResult added in v1.17.0

type PhaseResult struct {
	Phase    CoordinatorPhase
	Results  []WorkerResult
	Duration time.Duration
}

PhaseResult holds the outcome of an entire phase.

type PreparePhase added in v1.17.0

type PreparePhase struct {
	// contains filtered or unexported fields
}

PreparePhase handles message building and tool resolution.

func NewPreparePhase added in v1.17.0

func NewPreparePhase(builder func(state *TurnState) error) *PreparePhase

NewPreparePhase creates a prepare phase with a message builder function.

func (*PreparePhase) Execute added in v1.17.0

func (p *PreparePhase) Execute(ctx context.Context, state *TurnState) (NextAction, error)

func (*PreparePhase) Name added in v1.17.0

func (p *PreparePhase) Name() string

type ProfileChecker

type ProfileChecker struct {
	// contains filtered or unexported fields
}

ProfileChecker checks if tools are allowed/denied by a profile.

func NewProfileChecker

func NewProfileChecker(allow, deny []string, allTools []string) *ProfileChecker

NewProfileChecker creates a checker from allow/deny lists.

func (*ProfileChecker) Check

func (pc *ProfileChecker) Check(toolName string) (allowed bool, reason string)

Check returns whether a tool is permitted by the profile. Returns (allowed, reason) where reason explains why if not allowed.

func (*ProfileChecker) IsAllowed

func (pc *ProfileChecker) IsAllowed(toolName string) bool

IsAllowed returns true if the tool is in the allow list. If allow list is empty, all tools are allowed (respecting deny).

func (*ProfileChecker) IsDenied

func (pc *ProfileChecker) IsDenied(toolName string) bool

IsDenied returns true if the tool is in the deny list.

type ProgressSender

type ProgressSender func(ctx context.Context, message string)

ProgressSender sends intermediate progress messages to the user during long-running tool execution (e.g. claude-code). Called by tools that want to give real-time feedback without waiting for the full result.

func ProgressSenderFromContext

func ProgressSenderFromContext(ctx context.Context) ProgressSender

ProgressSenderFromContext extracts the ProgressSender from context. Returns nil if not set.

type Project

type Project struct {
	// ID is the unique project identifier (slug, e.g. "devclaw", "my-saas").
	ID string `yaml:"id"`

	// Name is the human-readable project name.
	Name string `yaml:"name"`

	// RootPath is the absolute path to the project root directory.
	RootPath string `yaml:"root_path"`

	// Language is the primary programming language (auto-detected or manual).
	Language string `yaml:"language"`

	// Framework is the detected/configured framework (e.g. "laravel", "next", "gin").
	Framework string `yaml:"framework"`

	// GitRemote is the primary git remote URL (auto-detected from origin).
	GitRemote string `yaml:"git_remote,omitempty"`

	// BuildCmd is the command to build the project.
	BuildCmd string `yaml:"build_cmd,omitempty"`

	// TestCmd is the command to run tests.
	TestCmd string `yaml:"test_cmd,omitempty"`

	// LintCmd is the command to run the linter.
	LintCmd string `yaml:"lint_cmd,omitempty"`

	// StartCmd is the command to start the dev server.
	StartCmd string `yaml:"start_cmd,omitempty"`

	// DeployCmd is the command to deploy the project.
	DeployCmd string `yaml:"deploy_cmd,omitempty"`

	// DockerCompose is the path to docker-compose.yml (if present).
	DockerCompose string `yaml:"docker_compose,omitempty"`

	// EnvFile is the path to the .env file (if present).
	EnvFile string `yaml:"env_file,omitempty"`

	// MCPServers lists MCP server configurations for this project.
	MCPServers []MCPServerConfig `yaml:"mcp_servers,omitempty"`
}

Project represents a registered development project.

type ProjectManager

type ProjectManager struct {
	// contains filtered or unexported fields
}

ProjectManager manages registered projects and per-session active project.

func NewProjectManager

func NewProjectManager(dataDir string) *ProjectManager

NewProjectManager creates a new ProjectManager, loading from disk if available.

func (*ProjectManager) Activate

func (pm *ProjectManager) Activate(sessionKey, projectID string) error

Activate sets the active project for a session.

func (*ProjectManager) ActiveProject

func (pm *ProjectManager) ActiveProject(sessionKey string) *Project

ActiveProject returns the active project for a session, or nil.

func (*ProjectManager) FindByPath

func (pm *ProjectManager) FindByPath(path string) *Project

FindByPath finds a project whose root matches the given path.

func (*ProjectManager) Get

func (pm *ProjectManager) Get(id string) *Project

Get returns a project by ID.

func (*ProjectManager) List

func (pm *ProjectManager) List() []*Project

List returns all registered projects sorted by name.

func (*ProjectManager) Register

func (pm *ProjectManager) Register(p *Project) error

Register adds or updates a project.

func (*ProjectManager) Remove

func (pm *ProjectManager) Remove(id string) error

Remove removes a project by ID.

func (*ProjectManager) ScanDirectory

func (pm *ProjectManager) ScanDirectory(root string) ([]*Project, error)

ScanDirectory scans a directory for projects (subdirectories with .git or known markers).

type ProjectProviderAdapter

type ProjectProviderAdapter struct {
	// contains filtered or unexported fields
}

ProjectProviderAdapter adapts a copilot.ProjectManager to the skills.ProjectProvider interface so coding skills can access project context without importing the copilot package.

func NewProjectProviderAdapter

func NewProjectProviderAdapter(pm *ProjectManager) *ProjectProviderAdapter

NewProjectProviderAdapter creates a new adapter wrapping the given ProjectManager.

func (*ProjectProviderAdapter) Activate

func (a *ProjectProviderAdapter) Activate(sessionKey, projectID string) error

Activate sets the active project for a session.

func (*ProjectProviderAdapter) ActiveProject

func (a *ProjectProviderAdapter) ActiveProject(sessionKey string) *skills.ProjectInfo

ActiveProject returns the active project for a session.

func (*ProjectProviderAdapter) Get

Get returns a project by ID.

func (*ProjectProviderAdapter) List

List returns all registered projects.

func (*ProjectProviderAdapter) Register

Register adds or updates a project.

func (*ProjectProviderAdapter) Remove

func (a *ProjectProviderAdapter) Remove(id string) error

Remove removes a project by ID.

func (*ProjectProviderAdapter) ScanDirectory

func (a *ProjectProviderAdapter) ScanDirectory(root string) ([]*skills.ProjectInfo, error)

ScanDirectory scans for projects in a directory.

type PromptComposer

type PromptComposer struct {
	// contains filtered or unexported fields
}

PromptComposer assembles the final system prompt from multiple layers.

func NewPromptComposer

func NewPromptComposer(config *Config) *PromptComposer

NewPromptComposer creates a new prompt composer.

func (*PromptComposer) Compose

func (p *PromptComposer) Compose(session *Session, input string) string

Compose builds the complete system prompt for a session and user input. Heavy layers (bootstrap, memory, skills, conversation) are built concurrently to minimize prompt composition latency.

func (*PromptComposer) ComposeForSubagent added in v1.13.0

func (p *PromptComposer) ComposeForSubagent() string

ComposeForSubagent builds a system prompt optimized for subagents. Compared to ComposeMinimal, it strips sections that are irrelevant to subagent execution: Heartbeats, Reply Tags, Messaging, Silent Replies, and Memory Recall. This saves ~30-40% of core-layer tokens while keeping tooling info, safety, epistemic restraint, and workspace context.

func (*PromptComposer) ComposeMinimal

func (p *PromptComposer) ComposeMinimal() string

ComposeMinimal builds a lightweight system prompt for scheduled jobs and other fast-path scenarios. It includes only: Core identity, Safety, Temporal (date/time), and the user's custom instructions. It deliberately skips bootstrap files, memory search, skill instructions, and conversation history to minimize token count and latency.

func (*PromptComposer) ComposeWithMode added in v1.12.0

func (p *PromptComposer) ComposeWithMode(session *Session, input string, mode PromptMode) string

ComposeWithMode assembles the system prompt using the specified mode. Use PromptModeFull for the main agent, PromptModeMinimal for subagents, and PromptModeNone for simple tasks requiring only core identity.

func (*PromptComposer) IncrementSkillsVersion added in v1.13.0

func (p *PromptComposer) IncrementSkillsVersion()

IncrementSkillsVersion bumps the skills snapshot version, causing all sessions to rebuild their skills layer on the next prompt composition. Call after installing, removing, or reloading skills.

func (*PromptComposer) RegisterMemorySectionBuilder added in v1.16.0

func (p *PromptComposer) RegisterMemorySectionBuilder(builder MemoryPromptSectionBuilder)

RegisterMemorySectionBuilder adds a pluggable memory section builder. Builders are called in registration order during buildMemoryLayer. Thread-safe: may be called concurrently with prompt composition.

func (*PromptComposer) SetAgentProfile added in v1.13.0

func (p *PromptComposer) SetAgentProfile(profile *AgentProfileConfig)

SetAgentProfile sets the active agent profile for identity resolution.

func (*PromptComposer) SetBuiltinSkills added in v1.12.0

func (p *PromptComposer) SetBuiltinSkills(skills *BuiltinSkills)

SetBuiltinSkills sets the built-in skills for the prompt composer.

func (*PromptComposer) SetContextEngines added in v1.13.0

func (p *PromptComposer) SetContextEngines(registry *ContextEngineRegistry)

SetContextEngines sets the pluggable context engine registry.

func (*PromptComposer) SetContextRouter added in v1.18.0

func (p *PromptComposer) SetContextRouter(router *ContextRouter)

SetContextRouter wires the context router used by the memory stack to resolve the active wing for a session. Nil means every session uses the legacy wing ("").

func (*PromptComposer) SetLCMStore added in v1.18.2

func (p *PromptComposer) SetLCMStore(store *LCMStore)

SetLCMStore sets the LCM store for conversation-aware memory recall.

func (*PromptComposer) SetMemoryStack added in v1.18.0

func (p *PromptComposer) SetMemoryStack(stack *MemoryStack)

SetMemoryStack wires a Sprint 2 Room 2.4 layered memory stack into the composer. The stack renders L0+L1+L2 as a prefix that buildMemoryLayer prepends to its legacy L3 output. Passing nil detaches any previously configured stack and restores v1.18.0 byte-identical behavior.

func (*PromptComposer) SetMemoryStore

func (p *PromptComposer) SetMemoryStore(store *memory.FileStore)

SetMemoryStore configures the file-based memory store for the prompt composer.

func (*PromptComposer) SetPluginAgentLister added in v1.16.0

func (p *PromptComposer) SetPluginAgentLister(lister func() []pluginAgentInfo)

SetPluginAgentLister sets the function used to list available plugin agents.

func (*PromptComposer) SetSQLiteMemory

func (p *PromptComposer) SetSQLiteMemory(store *memory.SQLiteStore)

SetSQLiteMemory configures the SQLite memory store for hybrid search.

func (*PromptComposer) SetSkillGetter

func (p *PromptComposer) SetSkillGetter(getter func(name string) (interface{ SystemPrompt() string }, bool))

SetSkillGetter sets the function used to retrieve skill system prompts.

func (*PromptComposer) SetSkillLister added in v1.12.0

func (p *PromptComposer) SetSkillLister(lister func() []SkillInfo)

SetSkillLister sets the function used to list all available skills.

func (*PromptComposer) SetSubagentMode

func (p *PromptComposer) SetSubagentMode(isSubagent bool)

SetSubagentMode restricts bootstrap loading to AGENTS.md + TOOLS.md only.

func (*PromptComposer) SetToolExecutor added in v1.12.0

func (p *PromptComposer) SetToolExecutor(executor *ToolExecutor)

SetToolExecutor sets the tool executor for dynamic tool list generation.

func (*PromptComposer) SetWorkspaceContext added in v1.16.0

func (p *PromptComposer) SetWorkspaceContext(wsID string, dirs []string)

SetWorkspaceContext configures workspace-specific bootstrap loading. Call before Compose() for non-main workspaces; call with "" to reset.

type PromptLayer

type PromptLayer int

PromptLayer defines the priority of a prompt layer. Lower values = higher priority (never trimmed first on budget cuts).

const (
	LayerCore           PromptLayer = 0  // Base identity and tooling.
	LayerSafety         PromptLayer = 5  // Safety rules.
	LayerIdentity       PromptLayer = 10 // Custom instructions.
	LayerThinking       PromptLayer = 12 // Extended thinking level hint (from /think).
	LayerBootstrap      PromptLayer = 15 // SOUL.md, AGENTS.md, etc.
	LayerBuiltinSkills  PromptLayer = 18 // Built-in tool guides (memory, agents, etc.)
	LayerPluginAgents   PromptLayer = 19 // Available plugin agents for delegation.
	LayerBusiness       PromptLayer = 20 // User/workspace context.
	LayerProjectContext PromptLayer = 25 // Auto-discovered project context.
	LayerSkills         PromptLayer = 40 // Active skill instructions.
	LayerMemory         PromptLayer = 50 // Long-term memory facts.
	LayerTemporal       PromptLayer = 60 // Date/time context.
	LayerConversation   PromptLayer = 70 // Recent history summary.
	LayerRuntime        PromptLayer = 80 // Runtime info (final line).
)

type PromptMode added in v1.12.0

type PromptMode string

PromptMode controls which prompt layers are included in the final prompt. Used to reduce token usage for subagents and specialized contexts.

const (
	// PromptModeFull includes all layers (default for main agent).
	PromptModeFull PromptMode = "full"

	// PromptModeMinimal omits skills, memory, heartbeats (for subagents).
	PromptModeMinimal PromptMode = "minimal"

	// PromptModeNone includes only core identity (for simple tasks).
	PromptModeNone PromptMode = "none"
)

type ProviderChainEntry

type ProviderChainEntry struct {
	Provider string `yaml:"provider"`          // Provider name (openai, anthropic, ollama, etc.)
	BaseURL  string `yaml:"base_url"`          // API endpoint
	APIKey   string `yaml:"api_key,omitempty"` // API key (can use ${VAR} references)
	Model    string `yaml:"model"`             // Model to use from this provider
}

ProviderChainEntry defines a single provider in the fallback chain.

type ProviderDiscovery added in v1.13.0

type ProviderDiscovery struct {
	// contains filtered or unexported fields
}

ProviderDiscovery probes local LLM providers to discover available models.

func NewProviderDiscovery added in v1.13.0

func NewProviderDiscovery(cfg ProviderDiscoveryConfig, logger *slog.Logger) *ProviderDiscovery

NewProviderDiscovery creates a new discovery instance.

func (*ProviderDiscovery) DiscoverAll added in v1.13.0

func (pd *ProviderDiscovery) DiscoverAll(ctx context.Context)

DiscoverAll probes all configured providers and populates the model cache. Safe to call from a goroutine; does not block on failure.

func (*ProviderDiscovery) GetContextWindow added in v1.13.0

func (pd *ProviderDiscovery) GetContextWindow(model string) int

GetContextWindow returns the discovered context window for a model. Returns 0 if the model was not discovered or context window is unknown.

func (*ProviderDiscovery) ListModels added in v1.13.0

func (pd *ProviderDiscovery) ListModels() []DiscoveredModel

ListModels returns a copy of all discovered models.

type ProviderDiscoveryConfig added in v1.13.0

type ProviderDiscoveryConfig struct {
	// Enabled turns discovery on (default: false).
	Enabled bool `yaml:"enabled"`

	// OllamaURL is the base URL for the Ollama API (default: http://localhost:11434).
	OllamaURL string `yaml:"ollama_url"`

	// VLLMUrl is the base URL for the vLLM API (default: http://localhost:8000).
	VLLMURL string `yaml:"vllm_url"`

	// VLLMAPIKey is the optional API key for vLLM (some deployments require auth).
	VLLMAPIKey string `yaml:"vllm_api_key"`

	// TimeoutSeconds is the per-probe HTTP timeout (default: 5).
	TimeoutSeconds int `yaml:"timeout_seconds"`
}

ProviderDiscoveryConfig configures dynamic model discovery for local providers.

type QualityGuardConfig added in v1.14.0

type QualityGuardConfig struct {
	// Enabled turns on quality guard (audit + retry). Default: true.
	Enabled *bool `yaml:"enabled"`

	// MaxRetries is the maximum number of retry attempts on audit failure. Default: 1, Max: 3.
	MaxRetries int `yaml:"max_retries"`

	// StrictIdentifiers requires that extracted identifiers appear in the summary. Default: false.
	StrictIdentifiers bool `yaml:"strict_identifiers"`
}

QualityGuardConfig controls the post-summarization audit and retry mechanism.

type QueryLoop added in v1.17.0

type QueryLoop struct {
	// contains filtered or unexported fields
}

QueryLoop orchestrates the phase pipeline for a single agent turn. It runs phases in sequence, handling loop-back and injection actions.

func NewQueryLoop added in v1.17.0

func NewQueryLoop(phases ...Phase) *QueryLoop

NewQueryLoop creates a loop with the given phases. Phases are executed in order: [0] → [1] → [2] → [3] → [4] ActionLoopBack returns to phase index 1 (APICall).

func (*QueryLoop) RunTurn added in v1.17.0

func (q *QueryLoop) RunTurn(ctx context.Context, state *TurnState) (*TurnState, error)

RunTurn executes the phase pipeline for a single turn. Returns the final state after all phases complete, or an error.

func (*QueryLoop) SetMaxIterations added in v1.17.0

func (q *QueryLoop) SetMaxIterations(n int)

SetMaxIterations sets the safety limit for loop iterations.

func (*QueryLoop) SetOnPhaseComplete added in v1.17.0

func (q *QueryLoop) SetOnPhaseComplete(fn func(phase string, action NextAction, duration time.Duration))

SetOnPhaseComplete sets a callback for phase completion events.

type QueryOptions added in v1.12.0

type QueryOptions struct {
	Where   map[string]any // Filter conditions
	Limit   int            // Max results (default 100, max 1000)
	Offset  int            // Skip results for pagination
	OrderBy string         // Column to order by (default: created_at)
	Order   string         // "ASC" or "DESC" (default: DESC)
}

QueryOptions contains optional parameters for queries.

type QueueConfig

type QueueConfig struct {
	// DebounceMs is the debounce delay in ms before draining queued messages (default: 200).
	DebounceMs int `yaml:"debounce_ms"`

	// MaxPending is the max queued messages per session before dropping oldest (default: 20).
	MaxPending int `yaml:"max_pending"`

	// DefaultMode is the default queue mode for all channels (default: "collect").
	DefaultMode QueueMode `yaml:"default_mode"`

	// ByChannel overrides the default mode per channel name.
	ByChannel map[string]QueueMode `yaml:"by_channel"`

	// ChannelDebounce overrides debounce delay per channel (in ms).
	// Channels not listed use DebounceMs. Useful for giving WhatsApp a
	// longer debounce (e.g. 1000ms) while keeping WebUI snappy (100ms).
	ChannelDebounce map[string]int `yaml:"channel_debounce"`

	// DropPolicy controls what happens when the queue exceeds MaxPending (default: "old").
	DropPolicy QueueDropPolicy `yaml:"drop_policy"`
}

QueueConfig configures the message queue for handling bursts.

type QueueDirective added in v1.13.0

type QueueDirective struct {
	Mode       QueueMode       // required
	DebounceMs int             // 0 = not set
	Cap        int             // 0 = not set
	Drop       QueueDropPolicy // "" = not set
}

QueueDirective holds the parsed result of a /queue directive with optional parameters. Supports: /queue [mode] [debounce=Xms] [cap=N] [drop=policy]

func ParseQueueDirective added in v1.13.0

func ParseQueueDirective(args string) *QueueDirective

ParseQueueDirective parses an extended /queue directive string. Input format: "mode [debounce=500ms] [cap=10] [drop=old]" Returns nil if the mode is invalid.

type QueueDropPolicy

type QueueDropPolicy string

QueueDropPolicy defines what happens when the queue exceeds max size.

const (
	// DropOld removes the oldest messages to make room.
	DropOld QueueDropPolicy = "old"

	// DropNew rejects new messages when the queue is full.
	DropNew QueueDropPolicy = "new"

	// DropSummarize uses the LLM to summarize dropped messages.
	DropSummarize QueueDropPolicy = "summarize"
)

type QueueMode

type QueueMode string

QueueMode defines how incoming messages are handled when the session is busy.

const (
	// QueueModeCollect groups all queued messages into a single prompt.
	// Processed as one agent run after the current run completes.
	QueueModeCollect QueueMode = "collect"

	// QueueModeSteer injects messages into the active agent run via interruptCh.
	// The agent sees the message between turns and adjusts behavior.
	QueueModeSteer QueueMode = "steer"

	// QueueModeFollowup enqueues each message as a separate agent run.
	// Processed in order after the current run completes.
	QueueModeFollowup QueueMode = "followup"

	// QueueModeInterrupt aborts the current run and processes the new message.
	QueueModeInterrupt QueueMode = "interrupt"

	// QueueModeSteerBacklog tries steer first; if no active run to inject into,
	// falls back to followup.
	QueueModeSteerBacklog QueueMode = "steer-backlog"
)

func EffectiveQueueMode

func EffectiveQueueMode(qc QueueConfig, channelName string) QueueMode

EffectiveQueueMode returns the queue mode for a given channel, falling back to the default mode from QueueConfig (defined in config.go).

func ParseQueueMode

func ParseQueueMode(s string) (QueueMode, bool)

ParseQueueMode parses a string into a QueueMode. Returns (mode, true) on success, ("", false) on unknown mode.

type QuietHoursConfig

type QuietHoursConfig struct {
	// Enabled activates quiet hours.
	Enabled bool `json:"enabled" yaml:"enabled"`
	// Start is the start time in HH:MM format.
	Start string `json:"start" yaml:"start"`
	// End is the end time in HH:MM format.
	End string `json:"end" yaml:"end"`
	// Timezone is the timezone for quiet hours (default: UTC).
	Timezone string `json:"timezone" yaml:"timezone"`
	// Days are the days of week when quiet hours apply (0=Sunday, 6=Saturday).
	Days []int `json:"days,omitempty" yaml:"days,omitempty"`
}

QuietHoursConfig defines quiet hours for a group or notification rule.

type Ref added in v1.13.0

type Ref struct {
	Role string `json:"role"`
	Name string `json:"name,omitempty"`
	Nth  int    `json:"nth,omitempty"` // For duplicate role+name combinations
}

Ref represents a reference to an element in the page.

type RegisteredHook

type RegisteredHook struct {
	// Name identifies this hook for logging.
	Name string

	// Description provides a human-readable summary of this hook.
	Description string

	// Source indicates where the hook came from (e.g. "system", "plugin:github", "skill:monitor").
	Source string

	// Events lists which events this hook subscribes to.
	Events []HookEvent

	// Priority controls execution order (lower = earlier). Default: 100.
	Priority int

	// Enabled controls whether this hook is active. Default: true.
	Enabled bool

	// Requires declares runtime prerequisites. Hook is skipped if unmet.
	Requires *HookRequirements

	// Handler is the callback function.
	Handler HookHandler
}

RegisteredHook associates a handler with metadata.

type ReminderInfo added in v1.12.0

type ReminderInfo struct {
	ID        string `json:"id"`
	JobID     string `json:"job_id"`
	JobType   string `json:"job_type"`
	Schedule  string `json:"schedule"`
	Command   string `json:"command"`
	Channel   string `json:"channel,omitempty"`
	ChatID    string `json:"chat_id,omitempty"`
	Status    string `json:"status"` // active, removed, fired
	RemovedAt string `json:"removed_at,omitempty"`
	CreatedAt string `json:"created_at"`
}

ReminderInfo contains information about a reminder.

type ResolvedWorkspace

type ResolvedWorkspace struct {
	// Workspace is the resolved workspace.
	Workspace *Workspace

	// Session is the workspace-isolated session for this chat.
	Session *Session

	// SessionStore is the workspace session store (for pruning, etc.).
	SessionStore *SessionStore
}

ResolvedWorkspace contains the resolved workspace and session for a message.

type RiskAction

type RiskAction string

RiskAction represents the action to take for a risk level.

const (
	ActionAllow           RiskAction = "allow"
	ActionAllowLog        RiskAction = "allow_log"
	ActionRequireApproval RiskAction = "require_approval"
	ActionDeny            RiskAction = "deny"
)

type RiskCategoryConfig

type RiskCategoryConfig struct {
	// Patterns are glob-like patterns to match commands.
	Patterns []string `yaml:"patterns"`

	// Action is the action to take for this category.
	Action RiskAction `yaml:"action"`

	// Notify lists who to notify (e.g., ["owners"], ["admins"]).
	Notify []string `yaml:"notify"`

	// Message is a custom denial/approval message.
	Message string `yaml:"message"`
}

RiskCategoryConfig configures a risk category.

type RiskLevel

type RiskLevel string

RiskLevel represents the risk category of a command.

const (
	RiskSafe      RiskLevel = "safe"      // Execute immediately
	RiskModerate  RiskLevel = "moderate"  // Log and execute
	RiskDangerous RiskLevel = "dangerous" // Require approval
	RiskBlocked   RiskLevel = "blocked"   // Always deny
)

type RoutinesConfig added in v1.8.0

type RoutinesConfig struct {
	// Metrics configures the metrics collector.
	Metrics MetricsCollectorConfig `yaml:"metrics"`

	// MemoryIndexer configures the background memory indexer.
	MemoryIndexer MemoryIndexerConfig `yaml:"memory_indexer"`
}

RoutinesConfig configures background routines for metrics and memory indexing.

func DefaultRoutinesConfig added in v1.8.0

func DefaultRoutinesConfig() RoutinesConfig

DefaultRoutinesConfig returns sensible defaults for background routines.

type RoutingConfig

type RoutingConfig struct {
	// Default is the default agent ID to use when no match is found.
	Default string `yaml:"default"`
}

RoutingConfig defines how messages are routed to agents.

type SQLiteAuditLogger

type SQLiteAuditLogger struct {
	// contains filtered or unexported fields
}

SQLiteAuditLogger writes tool execution audit records to the devclaw.db audit_log table. It replaces the plain-text append-only file.

func NewSQLiteAuditLogger

func NewSQLiteAuditLogger(db *sql.DB, logger *slog.Logger) *SQLiteAuditLogger

NewSQLiteAuditLogger creates an audit logger backed by SQLite.

func (*SQLiteAuditLogger) Close

func (a *SQLiteAuditLogger) Close()

Close is a no-op; the shared *sql.DB is closed at the application level.

func (*SQLiteAuditLogger) Count

func (a *SQLiteAuditLogger) Count() int

Count returns the total number of audit log entries.

func (*SQLiteAuditLogger) Log

func (a *SQLiteAuditLogger) Log(toolName, caller, level string, allowed bool, argsSummary, resultSummary string)

Log records a tool execution in the audit_log table.

func (*SQLiteAuditLogger) Recent

func (a *SQLiteAuditLogger) Recent(n int) []string

Recent returns the last N audit log entries as formatted strings.

func (*SQLiteAuditLogger) RecentRecords

func (a *SQLiteAuditLogger) RecentRecords(n int) []AuditRecord

RecentRecords returns the last N audit log entries as structured records.

type SQLiteMemoryStore added in v1.8.0

type SQLiteMemoryStore interface {
	IndexChunks(chunks []MemoryChunk) error
	DeleteByFilepath(filepath string) error
	GetIndexedFiles() (map[string]string, error) // filepath -> hash
}

SQLiteMemoryStore is an interface for SQLite memory operations.

type SQLiteSessionPersistence

type SQLiteSessionPersistence struct {
	// contains filtered or unexported fields
}

SQLiteSessionPersistence stores session data in the devclaw.db tables: session_entries, session_meta, session_facts.

func NewSQLiteSessionPersistence

func NewSQLiteSessionPersistence(db *sql.DB, logger *slog.Logger) *SQLiteSessionPersistence

NewSQLiteSessionPersistence creates a SQLite-backed session persistence. The tables must already exist (created by OpenDatabase).

func (*SQLiteSessionPersistence) Close

func (p *SQLiteSessionPersistence) Close() error

Close is a no-op; the shared *sql.DB is closed at the application level.

func (*SQLiteSessionPersistence) DeleteSession

func (p *SQLiteSessionPersistence) DeleteSession(sessionID string) error

DeleteSession removes all data for a session (entries, facts, meta).

func (*SQLiteSessionPersistence) ListSessionsMeta added in v1.13.0

func (p *SQLiteSessionPersistence) ListSessionsMeta() ([]SessionMeta, error)

ListSessionsMeta returns lightweight metadata for all sessions without loading full conversation history. Used by the web UI sidebar to list conversations.

func (*SQLiteSessionPersistence) LoadAll

func (p *SQLiteSessionPersistence) LoadAll() (map[string]*SessionData, error)

LoadAll scans all sessions from the database and returns SessionData for each.

func (*SQLiteSessionPersistence) LoadSession

func (p *SQLiteSessionPersistence) LoadSession(sessionID string) ([]ConversationEntry, []string, error)

LoadSession reads all entries and facts for a session.

func (*SQLiteSessionPersistence) Rotate

func (p *SQLiteSessionPersistence) Rotate(sessionID string, maxLines int) error

Rotate keeps only the latest maxLines entries for a session.

func (*SQLiteSessionPersistence) SaveCompaction added in v1.12.0

func (p *SQLiteSessionPersistence) SaveCompaction(sessionID string, entry CompactionEntry) error

SaveCompaction appends a compaction summary entry for the session.

func (*SQLiteSessionPersistence) SaveEntry

func (p *SQLiteSessionPersistence) SaveEntry(sessionID string, entry ConversationEntry) error

SaveEntry appends a conversation entry for the given session.

func (*SQLiteSessionPersistence) SaveFacts

func (p *SQLiteSessionPersistence) SaveFacts(sessionID string, facts []string) error

SaveFacts replaces all facts for the given session.

func (*SQLiteSessionPersistence) SaveMeta

func (p *SQLiteSessionPersistence) SaveMeta(sessionID, channel, chatID string, config SessionConfig, activeSkills []string) error

SaveMeta persists session metadata (channel, chatID, config, activeSkills).

func (*SQLiteSessionPersistence) TruncateAfterCompaction added in v1.16.0

func (p *SQLiteSessionPersistence) TruncateAfterCompaction(sessionID string, keepRecentEntries int) error

TruncateAfterCompaction removes old entries keeping only the last compaction entry and entries written after it.

type SchedulerConfig

type SchedulerConfig struct {
	// Enabled turns the scheduler on/off.
	Enabled bool `yaml:"enabled"`

	// Storage is the path to the scheduler storage file (legacy/fallback).
	// When devclawDB is available, jobs are stored in the "jobs" table in devclaw.db.
	// This field is only used as a fallback for file-based storage.
	Storage string `yaml:"storage"`
}

SchedulerConfig configures the task scheduler.

type SchedulerStats

type SchedulerStats struct {
	Enabled bool   `json:"enabled"`
	Jobs    int    `json:"jobs"`
	NextRun string `json:"next_run,omitempty"`
	Running int    `json:"running"`
}

SchedulerStats holds scheduler statistics.

type SearchConfig

type SearchConfig struct {
	// HybridWeightVector is the weight for vector search (default: 0.7).
	HybridWeightVector float64 `yaml:"hybrid_weight_vector"`

	// HybridWeightBM25 is the weight for BM25 keyword search (default: 0.3).
	HybridWeightBM25 float64 `yaml:"hybrid_weight_bm25"`

	// MaxResults is the max results returned (default: 6).
	MaxResults int `yaml:"max_results"`

	// MinScore is the minimum score threshold (default: 0.1).
	MinScore float64 `yaml:"min_score"`

	// TemporalDecay configures time-based score decay for memory search.
	TemporalDecay TemporalDecayConfig `yaml:"temporal_decay"`

	// MMR configures Maximal Marginal Relevance for result diversification.
	MMR MMRConfig `yaml:"mmr"`
}

SearchConfig configures hybrid search behavior.

type SecurityAuditToolConfig added in v1.13.0

type SecurityAuditToolConfig struct {
	DataDir       string
	Vault         *Vault
	SSRFGuard     *security.SSRFGuard
	GatewayConfig GatewayConfig
	AllowSudo     bool
	EmbeddingCfg  memory.EmbeddingConfig
}

SecurityAuditToolConfig holds static config for the security_audit tool.

type SecurityConfig

type SecurityConfig struct {
	// MaxInputLength is the max input size in characters.
	MaxInputLength int `yaml:"max_input_length"`

	// RateLimit is max messages per minute per user.
	RateLimit int `yaml:"rate_limit"`

	// EnablePIIDetection enables PII detection in outputs.
	EnablePIIDetection bool `yaml:"enable_pii_detection"`

	// EnableURLValidation enables URL validation in outputs.
	EnableURLValidation bool `yaml:"enable_url_validation"`

	// ToolGuard configures per-tool access control, command safety,
	// path protection, SSH allowlist, and audit logging.
	ToolGuard ToolGuardConfig `yaml:"tool_guard"`

	// ToolExecutor configures parallel tool execution.
	ToolExecutor ToolExecutorConfig `yaml:"tool_executor"`

	// SSRF configures URL validation for web_fetch (private IPs, metadata, etc.).
	SSRF security.SSRFConfig `yaml:"ssrf"`

	// ExecAnalysis configures command risk analysis for bash/exec tools.
	ExecAnalysis ExecAnalysisConfig `yaml:"exec_analysis"`
}

SecurityConfig configures security guardrails.

type Session

type Session struct {
	// ID é o identificador único da sessão (combinação de channel + chatID).
	ID string

	// Channel identifica o canal de origem (ex: "whatsapp", "discord").
	Channel string

	// ChatID é o identificador do grupo ou DM.
	ChatID string

	// CreatedAt é o timestamp de criação da sessão.
	CreatedAt time.Time
	// contains filtered or unexported fields
}

Session representa uma sessão isolada de conversa para um chat/grupo específico.

func (*Session) AddCompactionSummary added in v1.13.0

func (s *Session) AddCompactionSummary(entry CompactionEntry)

AddCompactionSummary appends a compaction entry to the session. Thread-safe.

func (*Session) AddFact

func (s *Session) AddFact(fact string)

AddFact adiciona um fato de longo prazo à sessão. Persiste os fatos em disco se persistence estiver configurada.

func (*Session) AddMessage

func (s *Session) AddMessage(userMsg, assistantResp string)

AddMessage adiciona uma nova entrada de conversa à sessão. Aplica o limite de maxHistory, removendo mensagens antigas quando excedido. Persiste a entrada em disco se persistence estiver configurada.

func (*Session) AddMessageWithToolCalls added in v1.13.0

func (s *Session) AddMessageWithToolCalls(userMsg, assistantResp string, toolCalls []ToolCallRecord)

AddMessageWithToolCalls adds a conversation entry with individual tool call records. Falls back to ToolSummary derived from the records for backward compat.

func (*Session) AddMessageWithTools added in v1.13.0

func (s *Session) AddMessageWithTools(userMsg, assistantResp, toolSummary string)

AddMessageWithTools adds a conversation entry with an optional tool summary.

func (*Session) AddTokenUsage

func (s *Session) AddTokenUsage(promptTokens, completionTokens int)

AddTokenUsage records token usage from an LLM response. Thread-safe.

func (*Session) ClearFacts

func (s *Session) ClearFacts()

ClearFacts removes all session facts. Used by /reset.

func (*Session) ClearHistory

func (s *Session) ClearHistory()

ClearHistory limpa o histórico de conversa mantendo fatos de longo prazo.

func (*Session) CompactHistory

func (s *Session) CompactHistory(summary string, keepRecent int) []ConversationEntry

CompactHistory replaces the full history with a summary entry, keeping only the most recent entries. Returns the old entries for memory extraction.

func (*Session) EstimateHistorySizeBytes added in v1.13.0

func (s *Session) EstimateHistorySizeBytes() int

EstimateHistorySizeBytes estimates the total byte size of conversation history. Thread-safe.

func (*Session) GetActiveSkills

func (s *Session) GetActiveSkills() []string

GetActiveSkills retorna uma cópia thread-safe das skills ativas.

func (*Session) GetCompactionCount added in v1.14.0

func (s *Session) GetCompactionCount() int

GetCompactionCount returns the total number of compactions performed.

func (*Session) GetCompactionSummaries added in v1.13.0

func (s *Session) GetCompactionSummaries() []CompactionEntry

GetCompactionSummaries returns the session's compaction summaries. Thread-safe.

func (*Session) GetConfig

func (s *Session) GetConfig() SessionConfig

GetConfig retorna uma cópia thread-safe da configuração da sessão.

func (*Session) GetFacts

func (s *Session) GetFacts() []string

GetFacts retorna uma cópia thread-safe dos fatos da sessão.

func (*Session) GetFastMode added in v1.14.0

func (s *Session) GetFastMode() bool

GetFastMode returns whether fast mode is enabled for this session.

func (*Session) GetLastCallTokens added in v1.13.0

func (s *Session) GetLastCallTokens() (promptTokens, outputTokens, cacheRead, cacheWrite int)

GetLastCallTokens returns the most recent LLM call's token snapshot. Thread-safe.

func (*Session) GetMaxHistory added in v1.13.0

func (s *Session) GetMaxHistory() int

GetMaxHistory returns the maximum history size for this session (thread-safe).

func (*Session) GetThinkingLevel

func (s *Session) GetThinkingLevel() string

GetThinkingLevel returns the session thinking level. Thread-safe.

func (*Session) GetTokenUsage

func (s *Session) GetTokenUsage() (promptTokens, completionTokens, requests int)

GetTokenUsage returns a copy of the token usage. Thread-safe.

func (*Session) HistoryLen

func (s *Session) HistoryLen() int

HistoryLen returns the number of entries in the session history.

func (*Session) IncrementCompactionCount added in v1.14.0

func (s *Session) IncrementCompactionCount()

IncrementCompactionCount atomically increments the compaction counter.

func (*Session) LastActiveAt

func (s *Session) LastActiveAt() time.Time

LastActiveAt retorna o timestamp da última atividade (thread-safe).

func (*Session) RecentHistory

func (s *Session) RecentHistory(maxEntries int) []ConversationEntry

RecentHistory retorna as últimas N entradas de conversa (cópia thread-safe).

func (*Session) ResetTokenUsage

func (s *Session) ResetTokenUsage()

ResetTokenUsage clears token counters. Thread-safe.

func (*Session) ResetWithPreservation added in v1.16.0

func (s *Session) ResetWithPreservation()

ResetWithPreservation clears conversation history, compaction state, and token counters while preserving session identity and configuration fields: ThinkingLevel, FastMode, Model, Language, Verbose, ToolProfile, BusinessContext, Trigger, activeSkills, and facts. Also syncs the persistence layer so that the next load does not restore stale data. The lastActiveAt timestamp is bumped to prevent immediate pruning after reset.

func (*Session) SetActiveSkills

func (s *Session) SetActiveSkills(skills []string)

SetActiveSkills define as skills ativas da sessão.

func (*Session) SetConfig

func (s *Session) SetConfig(cfg SessionConfig)

SetConfig atualiza a configuração da sessão.

func (*Session) SetMaxHistory added in v1.13.0

func (s *Session) SetMaxHistory(n int)

SetMaxHistory adjusts the history limit for this session. This is used to differentiate DM (higher limit) from group (lower limit) sessions.

func (*Session) SetThinkingLevel

func (s *Session) SetThinkingLevel(level string)

SetThinkingLevel sets the session thinking level. Thread-safe.

func (*Session) UpdateLastCallTokens added in v1.13.0

func (s *Session) UpdateLastCallTokens(promptTokens, outputTokens, cacheRead, cacheWrite int)

UpdateLastCallTokens stores the most recent LLM call's token snapshot. Used by proactive memory flush to project next context size. Thread-safe.

type SessionConfig

type SessionConfig struct {
	// Trigger é a palavra-chave que ativa o copilot nesta sessão.
	Trigger string `yaml:"trigger"`

	// Language é o idioma preferido nesta sessão.
	Language string `yaml:"language"`

	// MaxTokens é o budget máximo de tokens para esta sessão.
	MaxTokens int `yaml:"max_tokens"`

	// Model é o modelo LLM a ser usado nesta sessão (pode ser diferente do padrão).
	Model string `yaml:"model"`

	// BusinessContext é o contexto de negócio/usuário para esta sessão.
	BusinessContext string `yaml:"business_context"`

	// ThinkingLevel controls extended thinking: "", "off", "low", "medium", "high".
	ThinkingLevel string `yaml:"thinking_level"`

	// Verbose enables narration of tool calls and internal steps.
	Verbose bool `yaml:"verbose"`

	// ToolProfile selects which tools are available in this session.
	// Empty = inherit from workspace/global config. Options: "minimal", "coding",
	// "messaging", "team", "full", or a custom profile name.
	ToolProfile string `yaml:"tool_profile"`

	// FastMode enables faster processing with reduced quality.
	// Anthropic: service_tier="auto". OpenAI: service_tier="priority", reasoning_effort="low".
	FastMode bool `yaml:"fast_mode"`
}

SessionConfig contém configurações específicas de uma sessão.

type SessionData

type SessionData struct {
	ID           string
	Channel      string
	ChatID       string
	History      []ConversationEntry
	Facts        []string
	Config       SessionConfig
	ActiveSkills []string
}

SessionData holds all data needed to restore a session from disk.

type SessionExport

type SessionExport struct {
	ID        string            `json:"id"`
	Channel   string            `json:"channel"`
	ChatID    string            `json:"chat_id"`
	Config    SessionConfig     `json:"config"`
	Facts     []string          `json:"facts"`
	CreatedAt time.Time         `json:"created_at"`
	Messages  []ExportedMessage `json:"messages"`
}

SessionExport is a portable representation of a session for backup/export.

type SessionInfo

type SessionInfo struct {
	SessionMeta
	WorkspaceID string
}

SessionInfo holds session metadata with workspace ID for API responses.

type SessionKey

type SessionKey struct {
	Channel string // "whatsapp", "discord", "webui", "subagent", etc.
	ChatID  string // Group JID, user JID, or chat UUID.
	Branch  string // Optional: sub-session branch (e.g. "topic-research", fork ID).
}

sessionKey gera a chave única para uma sessão. MakeSessionID generates a deterministic, opaque session ID from channel and chatID. The ID is a truncated SHA-256 hash, so no PII (phone numbers, etc.) leaks into file names, logs, or persisted job data. SessionKey is a structured session identifier that preserves the original channel, chatID, and optional branch components while providing a compact string form. This enables multi-agent routing: sessions can be found by channel, user, or branch without losing context.

func ParseSessionKey

func ParseSessionKey(s string) SessionKey

ParseSessionKey parses a "channel:chatID" or "channel:chatID:branch" string.

func (SessionKey) Hash

func (sk SessionKey) Hash() string

Hash returns a compact hash suitable for map keys and persistence IDs.

func (SessionKey) String

func (sk SessionKey) String() string

String returns the canonical string form: "channel:chatID" or "channel:chatID:branch".

type SessionLock added in v1.13.0

type SessionLock struct {
	// contains filtered or unexported fields
}

SessionLock represents an active cross-process lock on a session.

func AcquireSessionLock added in v1.13.0

func AcquireSessionLock(sessionsDir, sessionID string, logger *slog.Logger) (*SessionLock, error)

AcquireSessionLock attempts to acquire an advisory file lock for the given session. If the lock is held by another live process, it returns an error. Stale locks (older than sessionLockStaleThreshold) are automatically broken.

func (*SessionLock) Release added in v1.13.0

func (sl *SessionLock) Release()

Release releases the session lock and stops the watchdog.

type SessionMemoryConfig

type SessionMemoryConfig struct {
	// Enabled turns session memory on/off (default: false).
	Enabled bool `yaml:"enabled"`

	// Messages is the number of recent messages to include in summaries (default: 15).
	Messages int `yaml:"messages"`
}

SessionMemoryConfig configures automatic session summarization.

type SessionMeta

type SessionMeta struct {
	ID           string
	Channel      string
	ChatID       string
	Title        string
	MessageCount int
	CreatedAt    time.Time
	LastActiveAt time.Time
}

SessionMeta holds read-only metadata for a session (for listing).

type SessionMetaLister added in v1.13.0

type SessionMetaLister interface {
	ListSessionsMeta() ([]SessionMeta, error)
}

SessionMetaLister is an optional interface that persistence backends can implement to provide a lightweight session listing without loading full history.

type SessionPersistence

type SessionPersistence struct {
	// contains filtered or unexported fields
}

SessionPersistence handles saving and loading sessions to JSONL files.

func NewSessionPersistence

func NewSessionPersistence(dir string, logger *slog.Logger) (*SessionPersistence, error)

NewSessionPersistence creates a SessionPersistence and ensures the directory exists.

func (*SessionPersistence) Close

func (p *SessionPersistence) Close() error

Close flushes any buffers. JSONL writes are unbuffered (direct write), so this is a no-op for now.

func (*SessionPersistence) DeleteSession

func (p *SessionPersistence) DeleteSession(sessionID string) error

DeleteSession removes the session's JSONL and facts files.

func (*SessionPersistence) LoadAll

func (p *SessionPersistence) LoadAll() (map[string]*SessionData, error)

LoadAll scans the directory and restores all sessions from disk.

func (*SessionPersistence) LoadSession

func (p *SessionPersistence) LoadSession(sessionID string) ([]ConversationEntry, []string, error)

LoadSession reads all entries and facts for a session.

func (*SessionPersistence) Rotate

func (p *SessionPersistence) Rotate(sessionID string, maxLines int) error

Rotate creates a .bak of the JSONL file and starts fresh.

func (*SessionPersistence) SaveCompaction added in v1.12.0

func (p *SessionPersistence) SaveCompaction(sessionID string, entry CompactionEntry) error

SaveCompaction appends a compaction summary entry to the session file.

func (*SessionPersistence) SaveEntry

func (p *SessionPersistence) SaveEntry(sessionID string, entry ConversationEntry) error

SaveEntry appends one JSONL line for the given conversation entry.

func (*SessionPersistence) SaveFacts

func (p *SessionPersistence) SaveFacts(sessionID string, facts []string) error

SaveFacts writes facts to {session_id}.facts.json.

func (*SessionPersistence) SaveMeta

func (p *SessionPersistence) SaveMeta(sessionID, channel, chatID string, config SessionConfig, activeSkills []string) error

SaveMeta persists session metadata (channel, chatID, config, activeSkills).

func (*SessionPersistence) TruncateAfterCompaction added in v1.16.0

func (p *SessionPersistence) TruncateAfterCompaction(sessionID string, keepRecentEntries int) error

TruncateAfterCompaction rewrites the JSONL file keeping only the last compaction entry and the most recent conversation entries written after it. This prevents unbounded JSONL growth on long-lived sessions.

type SessionPersister

type SessionPersister interface {
	SaveEntry(sessionID string, entry ConversationEntry) error
	LoadSession(sessionID string) ([]ConversationEntry, []string, error)
	SaveFacts(sessionID string, facts []string) error
	SaveMeta(sessionID, channel, chatID string, config SessionConfig, activeSkills []string) error
	SaveCompaction(sessionID string, entry CompactionEntry) error
	TruncateAfterCompaction(sessionID string, keepRecentEntries int) error
	DeleteSession(sessionID string) error
	Rotate(sessionID string, maxLines int) error
	LoadAll() (map[string]*SessionData, error)
	Close() error
}

SessionPersister is the interface for session persistence backends (JSONL or SQLite).

type SessionReaperConfig added in v1.13.0

type SessionReaperConfig struct {
	Enabled    bool `yaml:"enabled" json:"enabled"`
	MaxAgeDays int  `yaml:"max_age_days" json:"max_age_days"`
}

SessionReaperConfig configures the session reaper.

type SessionStats

type SessionStats struct {
	Active      int `json:"active"`
	Total       int `json:"total"`
	WithHistory int `json:"with_history"`
}

SessionStats holds session-related statistics.

type SessionStore

type SessionStore struct {
	// contains filtered or unexported fields
}

SessionStore gerencia sessões ativas, criando e recuperando por canal e chatID. Implementa pruning automático de sessões inativas.

func NewSessionStore

func NewSessionStore(logger *slog.Logger) *SessionStore

NewSessionStore cria um novo store de sessões.

func (*SessionStore) Count

func (ss *SessionStore) Count() int

Count retorna o número de sessões ativas.

func (*SessionStore) Delete

func (ss *SessionStore) Delete(channel, chatID string) bool

Delete removes a session by channel and chatID.

func (*SessionStore) DeleteByID

func (ss *SessionStore) DeleteByID(id string) bool

DeleteByID removes a session by its hash ID. It removes from both in-memory store and persistence (even if only persisted).

func (*SessionStore) Export

func (ss *SessionStore) Export(id string) *SessionExport

Export returns a portable representation of a session's history and metadata.

func (*SessionStore) Get

func (ss *SessionStore) Get(channel, chatID string) *Session

Get retorna a sessão pelo canal e chatID, ou nil se não existir.

func (*SessionStore) GetByID

func (ss *SessionStore) GetByID(id string) *Session

GetByID returns a session by its raw store key. Returns nil if not found.

func (*SessionStore) GetOrCreate

func (ss *SessionStore) GetOrCreate(channel, chatID string) *Session

GetOrCreate retorna a sessão existente ou cria uma nova para o canal e chatID. Se persistence estiver configurada, tenta carregar do disco antes de criar.

func (*SessionStore) ListAllSessions added in v1.13.0

func (ss *SessionStore) ListAllSessions() []SessionMeta

ListAllSessions returns metadata for all sessions, merging in-memory sessions with persisted sessions from the database. This ensures sessions survive server restarts.

func (*SessionStore) ListSessions

func (ss *SessionStore) ListSessions() []SessionMeta

ListSessions returns metadata for all sessions in the store.

func (*SessionStore) Prune

func (ss *SessionStore) Prune() int

Prune remove sessões inativas há mais tempo que o TTL configurado. Deve ser chamado periodicamente (ex: via goroutine com ticker).

func (*SessionStore) RenameSession

func (ss *SessionStore) RenameSession(oldID, newChannel, newChatID string) bool

RenameSession changes the ChatID of a session (e.g. for aliasing).

func (*SessionStore) SetPersistence

func (ss *SessionStore) SetPersistence(p SessionPersister)

SetPersistence configures disk persistence for sessions.

func (*SessionStore) StartPruner

func (ss *SessionStore) StartPruner(ctx context.Context)

StartPruner inicia uma goroutine que executa Prune periodicamente. Para quando o contexto é cancelado.

type SessionUsage

type SessionUsage struct {
	PromptTokens     int64
	CompletionTokens int64
	TotalTokens      int64
	Requests         int64
	EstimatedCostUSD float64
	FirstRequestAt   time.Time
	LastRequestAt    time.Time
}

SessionUsage holds token and cost stats for a session.

type Settings added in v1.12.0

type Settings struct {
	// ToolProfiles contains custom tool profiles defined by the user.
	// Built-in profiles (minimal, coding, messaging, team, full) are always available.
	ToolProfiles map[string]ToolProfile `yaml:"tool_profiles"`
}

Settings holds application-wide settings loaded from settings.yaml.

type SettingsManager added in v1.12.0

type SettingsManager struct {
	// contains filtered or unexported fields
}

SettingsManager manages loading and saving of settings.

func NewSettingsManager added in v1.12.0

func NewSettingsManager() *SettingsManager

NewSettingsManager creates a new settings manager.

func (*SettingsManager) AddProfile added in v1.12.0

func (sm *SettingsManager) AddProfile(profile ToolProfile) error

AddProfile adds or updates a custom profile.

func (*SettingsManager) DeleteProfile added in v1.12.0

func (sm *SettingsManager) DeleteProfile(name string) error

DeleteProfile removes a custom profile. Returns error if trying to delete a built-in profile.

func (*SettingsManager) GetAllProfiles added in v1.12.0

func (sm *SettingsManager) GetAllProfiles() map[string]ToolProfile

GetAllProfiles returns built-in + custom profiles merged.

func (*SettingsManager) GetSettingsPath added in v1.12.0

func (sm *SettingsManager) GetSettingsPath() string

GetSettingsPath returns the path to the settings file.

func (*SettingsManager) ListProfilesInfo added in v1.12.0

func (sm *SettingsManager) ListProfilesInfo() []ToolProfileInfo

ListProfilesInfo returns all profiles with info about built-in status.

func (*SettingsManager) Load added in v1.12.0

func (sm *SettingsManager) Load() (*Settings, error)

Load reads settings from the YAML file.

func (*SettingsManager) Save added in v1.12.0

func (sm *SettingsManager) Save(settings *Settings) error

Save writes settings to the YAML file.

func (*SettingsManager) UpdateProfile added in v1.12.0

func (sm *SettingsManager) UpdateProfile(profile ToolProfile) error

UpdateProfile updates an existing custom profile.

type SharedMemory

type SharedMemory struct {
	Key       string    `json:"key"`
	Value     string    `json:"value"`
	Author    string    `json:"author"` // User ID who wrote it
	UpdatedAt time.Time `json:"updated_at"`
	Tags      []string  `json:"tags,omitempty"`
}

SharedMemory represents a team-shared memory space.

type SkillDB added in v1.12.0

type SkillDB struct {
	// contains filtered or unexported fields
}

SkillDB manages the skill database, allowing skills to create tables and perform CRUD operations on their data.

func OpenSkillDatabase added in v1.12.0

func OpenSkillDatabase(dataDir string) (*SkillDB, error)

OpenSkillDatabase opens (or creates) the skill database at dataDir/skill_database.db.

func (*SkillDB) Close added in v1.12.0

func (s *SkillDB) Close() error

Close closes the database connection.

func (*SkillDB) CreateTable added in v1.12.0

func (s *SkillDB) CreateTable(skillName, tableName, displayName, description string, columns map[string]string) error

CreateTable creates a new table for a skill with the specified columns. Columns is a map of column name to SQL type definition (e.g., "TEXT NOT NULL"). Automatic columns: id (TEXT PRIMARY KEY), created_at, updated_at.

func (*SkillDB) Delete added in v1.12.0

func (s *SkillDB) Delete(skillName, tableName, rowID string) error

Delete removes a row from a skill's table.

func (*SkillDB) DescribeTable added in v1.12.0

func (s *SkillDB) DescribeTable(skillName, tableName string) (*TableInfo, error)

DescribeTable returns detailed information about a table.

func (*SkillDB) DropTable added in v1.12.0

func (s *SkillDB) DropTable(skillName, tableName string) error

DropTable removes a table and all its data.

func (*SkillDB) GetByID added in v1.12.0

func (s *SkillDB) GetByID(skillName, tableName, rowID string) (map[string]any, error)

GetByID retrieves a single row by ID.

func (*SkillDB) GetReminderByJobID added in v1.12.0

func (s *SkillDB) GetReminderByJobID(jobID string) (*ReminderInfo, error)

GetReminderByJobID gets a reminder by its job ID.

func (*SkillDB) InitRemindersTable added in v1.12.0

func (s *SkillDB) InitRemindersTable() error

InitRemindersTable creates the reminders tracking table if it doesn't exist.

func (*SkillDB) Insert added in v1.12.0

func (s *SkillDB) Insert(skillName, tableName string, data map[string]any) (string, error)

Insert inserts a new row into a skill's table and returns the generated ID.

func (*SkillDB) ListTables added in v1.12.0

func (s *SkillDB) ListTables(skillName string) ([]TableInfo, error)

ListTables returns all tables for a skill. If skillName is empty, returns all tables from all skills.

func (*SkillDB) MarkReminderRemoved added in v1.12.0

func (s *SkillDB) MarkReminderRemoved(jobID string) error

MarkReminderRemoved marks a reminder as removed (soft delete).

func (*SkillDB) Path added in v1.12.0

func (s *SkillDB) Path() string

Path returns the database file path.

func (*SkillDB) Query added in v1.12.0

func (s *SkillDB) Query(skillName, tableName string, filters map[string]any, limit int) ([]map[string]any, error)

Query retrieves rows from a skill's table with optional filters and pagination.

func (*SkillDB) QueryWithOptions added in v1.12.0

func (s *SkillDB) QueryWithOptions(skillName, tableName string, opts QueryOptions) ([]map[string]any, error)

QueryWithOptions retrieves rows with full query options including pagination and ordering.

func (*SkillDB) SaveReminder added in v1.12.0

func (s *SkillDB) SaveReminder(jobID, jobType, schedule, command, channel, chatID string) error

SaveReminder saves a reminder to the tracking table. If a reminder with the same job_id exists and is active, it updates it. If it exists but was removed, it creates a new entry.

func (*SkillDB) SearchReminders added in v1.12.0

func (s *SkillDB) SearchReminders(query string, includeRemoved bool, limit int) ([]ReminderInfo, error)

SearchReminders searches for reminders matching the query. If query is empty, returns all reminders. If includeRemoved is false, only returns active reminders.

func (*SkillDB) Update added in v1.12.0

func (s *SkillDB) Update(skillName, tableName, rowID string, data map[string]any) error

Update modifies a row in a skill's table.

type SkillInfo added in v1.12.0

type SkillInfo struct {
	Name          string
	Description   string
	Location      string // Absolute path to SKILL.md ("" for built-in skills)
	HasReferences bool   // True if the skill has a references/ directory
	Tools         []string
}

SkillInfo holds basic skill information for the Skill Discovery XML. Used by the reference model: skills listed as XML references, LLM reads SKILL.md on demand.

type SkillReloadCallback added in v1.13.0

type SkillReloadCallback func(ctx context.Context) (int, error)

SkillReloadCallback reloads skills from disk, initializes them with the sandbox runner, and re-registers their tools. Returns the total number of skills loaded.

type SkillWatcher added in v1.13.0

type SkillWatcher struct {
	// contains filtered or unexported fields
}

SkillWatcher watches skill directories for SKILL.md changes and increments the PromptComposer's skills version on change.

func NewSkillWatcher added in v1.13.0

func NewSkillWatcher(dirs []string, composer *PromptComposer, logger *slog.Logger) (*SkillWatcher, error)

NewSkillWatcher creates a watcher on the given skill directories. Only reacts to SKILL.md file events (create, write, remove).

func (*SkillWatcher) Stop added in v1.13.0

func (sw *SkillWatcher) Stop()

Stop shuts down the watcher.

type SkillsConfig

type SkillsConfig struct {
	// Builtin lists built-in skills to enable.
	Builtin []string `yaml:"builtin"`

	// Installed lists installed skill names.
	Installed []string `yaml:"installed"`

	// ClawdHubDirs lists directories with ClawdHub SKILL.md skills (TierManaged).
	ClawdHubDirs []string `yaml:"clawdhub_dirs"`

	// PersonalDir is an optional user-global skills directory (TierPersonal).
	// No default — only activated when explicitly set in config.
	PersonalDir string `yaml:"personal_dir"`

	// ProjectDir is an optional project-scoped skills directory (TierProject).
	// No default — only activated when explicitly set in config.
	ProjectDir string `yaml:"project_dir"`

	// Limits configures resource limits for skill loading.
	Limits skills.SkillsLimitsConfig `yaml:"limits"`
}

SkillsConfig configures the skills system.

type SnapshotOptions added in v1.13.0

type SnapshotOptions struct {
	// InteractiveOnly only includes interactive elements (buttons, links, inputs, etc.).
	InteractiveOnly bool

	// Compact removes structural noise (generic containers without names).
	Compact bool

	// MaxDepth limits the tree depth (0 = unlimited).
	MaxDepth int
}

SnapshotOptions configures the snapshot behavior.

type SnapshotResult added in v1.13.0

type SnapshotResult struct {
	// Snapshot is the text representation of the accessibility tree.
	Snapshot string `json:"snapshot"`

	// Refs maps reference IDs (e1, e2, ...) to their element info.
	Refs map[string]Ref `json:"refs"`

	// Stats contains statistics about the snapshot.
	Stats SnapshotStats `json:"stats"`
}

SnapshotResult is the result of a browser snapshot operation.

func (*SnapshotResult) SortedRefs added in v1.13.0

func (s *SnapshotResult) SortedRefs() []string

SortedRefs returns refs sorted by their numeric ID.

type SnapshotStats added in v1.13.0

type SnapshotStats struct {
	Lines       int `json:"lines"`
	Chars       int `json:"chars"`
	Refs        int `json:"refs"`
	Interactive int `json:"interactive"`
}

SnapshotStats contains statistics about a snapshot.

type SpawnParams

type SpawnParams struct {
	Task            string
	Label           string
	Model           string
	ParentSessionID string
	TimeoutSeconds  int

	// SpawnDepth is the nesting level (1 = top-level subagent).
	// If not set, defaults to 1.
	SpawnDepth int

	// OriginChannel, OriginTo, and OriginThreadID identify where to push the
	// completion announcement. When OriginChannel is set the announce callback
	// delivers the result directly to that channel/chat in addition to injecting
	// it into the parent agent loop.
	OriginChannel  string
	OriginTo       string
	OriginThreadID string

	// DeliveryScope controls announcement delivery. Default: "all".
	DeliveryScope DeliveryScope

	// MaxTurns overrides the agent loop turn limit for this spawn.
	MaxTurns int

	// EscalationChecker is called after each turn to check if the agent
	// should escalate to the main agent. Used by plugin agents.
	EscalationChecker func(turn int, lastResponse string) *EscalationSignal
}

SpawnParams holds parameters for spawning a subagent.

type StackConfig added in v1.18.0

type StackConfig struct {
	// TotalBudget caps the combined byte length of L0+L1+L2. A value
	// <= 0 uses defaultStackBudget (3600 bytes ≈ 900 tokens).
	TotalBudget int

	// ForceLegacy, when true, makes Build() short-circuit to "". The
	// caller then falls back to the legacy buildMemoryLayer output.
	// This is the escape hatch for v1.18.0 byte-identical behavior
	// under the stack — exposed in Room 2.5 via memory.stack.force_legacy.
	ForceLegacy bool
}

StackConfig is the subset of HierarchyConfig the stack consumes. A zero-valued StackConfig uses defaults (see DefaultStackConfig).

func DefaultStackConfig added in v1.18.0

func DefaultStackConfig() StackConfig

DefaultStackConfig returns the sensible defaults: 3600-byte total budget, stack-active (not forced to legacy).

type StackStats added in v1.18.0

type StackStats struct {
	// L0Bytes is the cumulative byte count contributed by the identity
	// layer across all Build calls.
	L0Bytes int64

	// L1Bytes is the cumulative byte count contributed by the essential
	// layer across all Build calls (after any truncation).
	L1Bytes int64

	// L2Bytes is the cumulative byte count contributed by the on-demand
	// layer across all Build calls (after any truncation).
	L2Bytes int64

	// TrimmedTotal counts Build calls where at least one layer was
	// truncated to fit the byte budget.
	TrimmedTotal int64

	// PanicTotal counts individual layer panics caught by the stack.
	// A single Build call can contribute up to three panics (one per
	// layer), though in practice a single bug rarely affects more than
	// one layer at a time.
	PanicTotal int64

	// BuildTotal counts Build calls that proceeded past the early-exit
	// checks (ForceLegacy, all-nil, context cancellation).
	BuildTotal int64
}

StackStats is a point-in-time snapshot of the MemoryStack telemetry counters. Returned by (*MemoryStack).Stats.

type StartupCheckResult

type StartupCheckResult struct {
	Name     string // Check name (e.g., "vault", "database", "channels")
	Status   string // "ok", "warning", "error", "skipped"
	Message  string // Human-readable message
	Required bool   // If true, failure blocks startup
}

StartupCheckResult represents the result of a single startup check.

type StartupReport

type StartupReport struct {
	Results []StartupCheckResult
	Healthy bool // true if all required checks pass
}

StartupReport contains all startup check results.

type StartupVerifier

type StartupVerifier struct {
	// contains filtered or unexported fields
}

StartupVerifier performs system checks at initialization.

func NewStartupVerifier

func NewStartupVerifier(cfg *Config, vault *Vault, logger *slog.Logger) *StartupVerifier

NewStartupVerifier creates a new startup verifier.

func (*StartupVerifier) PrintReport

func (sv *StartupVerifier) PrintReport(report *StartupReport)

PrintReport logs a formatted startup report.

func (*StartupVerifier) RunAll

func (sv *StartupVerifier) RunAll() *StartupReport

RunAll executes all startup checks and returns a report.

type StopCheckPhase added in v1.17.0

type StopCheckPhase struct {
	// contains filtered or unexported fields
}

StopCheckPhase runs completion verification before allowing stop.

func NewStopCheckPhase added in v1.17.0

func NewStopCheckPhase(v *StopHookVerifier) *StopCheckPhase

NewStopCheckPhase creates a stop check phase with the given verifier. Limits injection to 3 attempts to prevent infinite loops.

func (*StopCheckPhase) Execute added in v1.17.0

func (s *StopCheckPhase) Execute(ctx context.Context, state *TurnState) (NextAction, error)

func (*StopCheckPhase) Name added in v1.17.0

func (s *StopCheckPhase) Name() string

type StopHookVerifier added in v1.17.0

type StopHookVerifier struct {
	// contains filtered or unexported fields
}

StopHookVerifier checks conversation history for incomplete work.

func NewStopHookVerifier added in v1.17.0

func NewStopHookVerifier() *StopHookVerifier

NewStopHookVerifier creates a verifier with default completion checks.

func (*StopHookVerifier) VerifyCompletion added in v1.17.0

func (v *StopHookVerifier) VerifyCompletion(messages []chatMessage) []IncompleteWork

VerifyCompletion analyzes the conversation for incomplete work patterns. Returns a list of incomplete work items, empty if everything looks complete.

The analysis works by scanning assistant and tool messages for trigger patterns (indicating work was done) and then checking if completion patterns also appear (indicating the work was verified). Only the most recent portion of the conversation is checked to avoid false positives from old, already-resolved work.

type StreamCallback

type StreamCallback func(chunk string)

StreamCallback is called for each token/chunk during streaming.

type StreamSanitizer added in v1.13.0

type StreamSanitizer struct {
	// contains filtered or unexported fields
}

StreamSanitizer wraps a StreamCallback to prevent partial silent tokens from leaking into user-visible output. It buffers short prefixes that could be the beginning of a silent token and flushes them when safe.

func NewStreamSanitizer added in v1.13.0

func NewStreamSanitizer(cb StreamCallback) *StreamSanitizer

NewStreamSanitizer wraps a StreamCallback with token buffering.

func (*StreamSanitizer) Callback added in v1.13.0

func (s *StreamSanitizer) Callback() StreamCallback

Callback returns the StreamCallback function to use in place of the inner callback.

func (*StreamSanitizer) Flush added in v1.13.0

func (s *StreamSanitizer) Flush()

Flush sends any remaining buffered content to the inner callback. Must be called when the stream ends.

func (*StreamSanitizer) Write added in v1.13.0

func (s *StreamSanitizer) Write(chunk string)

Write is the StreamCallback-compatible function that buffers and filters.

type SubagentConfig

type SubagentConfig struct {
	// Enabled turns the subagent system on/off (default: true).
	Enabled bool `yaml:"enabled"`

	// MaxConcurrent is the max number of subagents running at the same time.
	MaxConcurrent int `yaml:"max_concurrent"`

	// MaxTurns is the max agent loop turns for each subagent (default: 15).
	MaxTurns int `yaml:"max_turns"`

	// TimeoutSeconds is the max execution time per subagent (default: 900 = 15min).
	TimeoutSeconds int `yaml:"timeout_seconds"`

	// MaxSpawnDepth controls nested subagent spawning (default: 1 = no nesting).
	// Set to 2 to allow subagents to spawn their own children.
	// Higher values allow deeper nesting (e.g., 3 = sub-sub-subagents).
	MaxSpawnDepth int `yaml:"max_spawn_depth"`

	// MaxChildrenPerAgent limits how many children a single agent can spawn (default: 5).
	MaxChildrenPerAgent int `yaml:"max_children_per_agent"`

	// DeniedTools lists tool names that subagents cannot use.
	// Default: ["spawn_subagent", "list_subagents", "wait_subagent"]
	DeniedTools []string `yaml:"denied_tools"`

	// Model overrides the LLM model for subagents (empty = use parent model).
	Model string `yaml:"model"`
}

SubagentConfig configures the subagent system.

func DefaultSubagentConfig

func DefaultSubagentConfig() SubagentConfig

DefaultSubagentConfig returns safe defaults.

type SubagentManager

type SubagentManager struct {
	// contains filtered or unexported fields
}

SubagentManager orchestrates subagent lifecycle: spawning, tracking, and cleanup.

func NewSubagentManager

func NewSubagentManager(cfg SubagentConfig, logger *slog.Logger) *SubagentManager

NewSubagentManager creates a new subagent manager.

func (*SubagentManager) ActiveCount

func (m *SubagentManager) ActiveCount() int

ActiveCount returns the number of currently running subagents.

func (*SubagentManager) Cleanup

func (m *SubagentManager) Cleanup(maxAge time.Duration) int

Cleanup removes completed/failed runs older than the given duration.

func (*SubagentManager) CreateChildExecutorWithProfile added in v1.16.0

func (m *SubagentManager) CreateChildExecutorWithProfile(parent *ToolExecutor, depth int, allow, deny []string) *ToolExecutor

createChildExecutorWithProfile creates a child ToolExecutor with allow/deny filtering from a plugin agent's tool profile.

func (*SubagentManager) Get

func (m *SubagentManager) Get(runID string) (*SubagentRun, bool)

Get returns a subagent run by ID. Checks in-memory first, then SQLite.

func (*SubagentManager) List

func (m *SubagentManager) List() []*SubagentRun

List returns all subagent runs (active in-memory + recent from SQLite). Merges both sources, deduplicating by ID.

func (*SubagentManager) PruneOldRuns

func (m *SubagentManager) PruneOldRuns(days int) int

PruneOldRuns removes persisted runs older than the given number of days.

func (*SubagentManager) SetAnnounceCallback

func (m *SubagentManager) SetAnnounceCallback(cb AnnounceCallback)

SetAnnounceCallback registers a callback that fires when any subagent completes. This enables push-style announce: the parent is notified immediately instead of having to poll via wait_subagent.

func (*SubagentManager) SetDB

func (m *SubagentManager) SetDB(db *sql.DB)

SetDB wires the central SQLite database for persisting subagent runs. When set, completed/failed runs survive process restarts.

func (*SubagentManager) Spawn

func (m *SubagentManager) Spawn(
	parentCtx context.Context,
	params SpawnParams,
	llmClient *LLMClient,
	parentExecutor *ToolExecutor,
	promptComposer *PromptComposer,
) (*SubagentRun, error)

Spawn creates and starts a new subagent. Returns the run ID immediately. The subagent executes in a background goroutine.

func (*SubagentManager) SpawnWithExecutor added in v1.16.0

func (m *SubagentManager) SpawnWithExecutor(
	parentCtx context.Context,
	params SpawnParams,
	llmClient *LLMClient,
	childExecutor *ToolExecutor,
	customPrompt string,
) (*SubagentRun, error)

SpawnWithExecutor is a variant of Spawn that accepts a pre-built executor and custom system prompt. Used by the plugin system to spawn plugin agents with filtered tools and custom instructions.

func (*SubagentManager) StartPeriodicSweeper added in v1.13.0

func (m *SubagentManager) StartPeriodicSweeper(ctx context.Context, interval time.Duration, maxAgeDays int)

StartPeriodicSweeper launches a background goroutine that prunes old subagent runs every interval. Stops when ctx is cancelled.

func (*SubagentManager) Stop

func (m *SubagentManager) Stop(runID string) error

Stop cancels a running subagent.

func (*SubagentManager) Wait

func (m *SubagentManager) Wait(ctx context.Context, runID string) (*SubagentRun, error)

Wait blocks until the specified subagent run completes or the context is cancelled. Returns the final run state.

type SubagentRun

type SubagentRun struct {
	// ID is a unique identifier for this run.
	ID string `json:"id"`

	// Label is a human-readable label for identification.
	Label string `json:"label"`

	// Task is the original task description given to the subagent.
	Task string `json:"task"`

	// Status is the current execution status.
	Status SubagentStatus `json:"status"`

	// Result is the final text response (set on completion).
	Result string `json:"result,omitempty"`

	// Error holds the error message if the subagent failed.
	Error string `json:"error,omitempty"`

	// Model is the LLM model used for this run.
	Model string `json:"model,omitempty"`

	// ParentSessionID is the session that spawned this subagent.
	ParentSessionID string `json:"parent_session_id"`

	// SpawnDepth is the nesting level (1 = top-level subagent, 2 = child of subagent, etc.).
	SpawnDepth int `json:"spawn_depth"`

	// ChildrenCount tracks how many children this subagent has spawned.
	ChildrenCount int `json:"children_count,omitempty"`

	// OriginChannel is the channel name (e.g. "telegram", "discord") where the
	// spawn was requested. When set, the completion announcement is delivered
	// directly to that channel/chat rather than only injecting into the agent loop.
	OriginChannel string `json:"origin_channel,omitempty"`

	// OriginTo is the chat ID / recipient address in the origin channel.
	// For Telegram this may include a topic suffix (e.g. "12345678:topic:42").
	OriginTo string `json:"origin_to,omitempty"`

	// OriginThreadID is an optional thread or topic ID for threaded delivery
	// within the origin channel (e.g. a Slack thread_ts or Telegram topic ID).
	// TODO: populate from IncomingMessage.Metadata once per-message thread context
	// is propagated through the tool execution context.
	OriginThreadID string `json:"origin_thread_id,omitempty"`

	// StartedAt is when the subagent started.
	StartedAt time.Time `json:"started_at"`

	// CompletedAt is when the subagent finished (zero if still running).
	CompletedAt time.Time `json:"completed_at,omitempty"`

	// Duration is the wall-clock execution time.
	Duration time.Duration `json:"duration,omitempty"`

	// TokensUsed tracks approximate token usage.
	TokensUsed int `json:"tokens_used,omitempty"`

	// DeliveryScope controls who receives the completion announcement.
	// Default: "all" (parent + external). Internal subagents use "parent".
	DeliveryScope DeliveryScope `json:"delivery_scope,omitempty"`

	// RetryCount tracks how many times this run has been retried after interruption.
	RetryCount int `json:"retry_count,omitempty"`
	// contains filtered or unexported fields
}

SubagentRun tracks a single subagent execution.

func (*SubagentRun) Done added in v1.13.0

func (r *SubagentRun) Done() <-chan struct{}

Done returns a channel that is closed when the subagent run completes. For DB-loaded runs with nil done, returns a pre-closed channel.

type SubagentStatus

type SubagentStatus string

SubagentStatus represents the current state of a subagent run.

const (
	SubagentStatusRunning   SubagentStatus = "running"
	SubagentStatusCompleted SubagentStatus = "completed"
	SubagentStatusFailed    SubagentStatus = "failed"
	SubagentStatusTimeout   SubagentStatus = "timeout"
)

type SystemCommands

type SystemCommands struct {
	// contains filtered or unexported fields
}

SystemCommands holds system administration command handlers.

func NewSystemCommands

func NewSystemCommands(a *Assistant, configPath string, maintenanceMgr *MaintenanceManager) *SystemCommands

NewSystemCommands creates a new system command handler.

func (*SystemCommands) ChannelsCommand

func (t *SystemCommands) ChannelsCommand(args []string) string

ChannelsCommand handles /channels [connect|disconnect <name>]

func (*SystemCommands) DiagnosticsCommand

func (t *SystemCommands) DiagnosticsCommand(full bool) string

DiagnosticsCommand handles /diagnostics [--full]

func (*SystemCommands) ExecQueueCommand

func (t *SystemCommands) ExecQueueCommand() string

ExecQueueCommand handles /exec queue

func (*SystemCommands) HealthCommand

func (t *SystemCommands) HealthCommand() string

HealthCommand handles /health

func (*SystemCommands) LogsCommand

func (t *SystemCommands) LogsCommand(args []string) string

LogsCommand handles /logs [level] [lines]

func (*SystemCommands) MaintenanceCommand

func (t *SystemCommands) MaintenanceCommand(args []string, setBy string) string

MaintenanceCommand handles /maintenance [on|off] [message]

func (*SystemCommands) MetricsCommand

func (t *SystemCommands) MetricsCommand(args []string) string

MetricsCommand handles /metrics [period]

func (*SystemCommands) ReloadCommand

func (t *SystemCommands) ReloadCommand(args []string) string

ReloadCommand handles /reload [section]

func (*SystemCommands) StatusCommand

func (t *SystemCommands) StatusCommand(jsonOutput bool) string

StatusCommand handles /status [--json]

type SystemStatus

type SystemStatus struct {
	Version       string                   `json:"version"`
	Uptime        string                   `json:"uptime"`
	UptimeSeconds int64                    `json:"uptime_seconds"`
	MemoryMB      float64                  `json:"memory_mb"`
	GoRoutines    int                      `json:"goroutines"`
	Channels      map[string]ChannelHealth `json:"channels"`
	Sessions      SessionStats             `json:"sessions"`
	Scheduler     SchedulerStats           `json:"scheduler"`
	Skills        int                      `json:"skills"`
	Maintenance   *MaintenanceMode         `json:"maintenance,omitempty"`
}

SystemStatus represents comprehensive system status for TechOps commands.

type TLSConfig added in v1.16.0

type TLSConfig struct {
	// Enabled turns TLS on/off (default: false).
	Enabled bool `yaml:"enabled"`

	// AutoGenerate auto-generates self-signed certificates if they don't exist (default: true).
	AutoGenerate bool `yaml:"auto_generate"`

	// CertPath is the path to the TLS certificate PEM file (default: data/tls/devclaw-cert.pem).
	CertPath string `yaml:"cert_path"`

	// KeyPath is the path to the TLS private key PEM file (default: data/tls/devclaw-key.pem).
	KeyPath string `yaml:"key_path"`
}

TLSConfig configures TLS/HTTPS for servers (WebUI, Gateway).

type TTSConfig

type TTSConfig struct {
	// Enabled activates TTS for assistant responses.
	Enabled bool `yaml:"enabled"`

	// Provider is the TTS provider to use: "openai" (default), "edge", "auto".
	// "auto" tries OpenAI first, falls back to Edge TTS if OpenAI is unavailable.
	Provider string `yaml:"provider"`

	// Voice is the voice to use.
	//   OpenAI: alloy, echo, fable, onyx, nova, shimmer
	//   Edge: pt-BR-FranciscaNeural, en-US-JennyNeural, etc.
	Voice string `yaml:"voice"`

	// EdgeVoice is the voice to use specifically for Edge TTS (when provider is "auto").
	// If empty, falls back to Voice.
	EdgeVoice string `yaml:"edge_voice"`

	// Model is the TTS model: "tts-1" (fast) or "tts-1-hd" (high quality).
	// Only used for OpenAI provider.
	Model string `yaml:"model"`

	// AutoMode controls when TTS is used:
	//   "off"     - disabled (default)
	//   "always"  - always generate audio alongside text
	//   "inbound" - generate audio only when the user sent a voice note
	AutoMode string `yaml:"auto_mode"`
}

TTSConfig configures text-to-speech synthesis.

type Tab added in v1.13.0

type Tab struct {
	TargetID string `json:"targetId"`
	URL      string `json:"url"`
	Title    string `json:"title"`
	Type     string `json:"type"`
}

Tab represents a browser tab.

type TableInfo added in v1.12.0

type TableInfo struct {
	SkillName   string            `json:"skill_name"`
	TableName   string            `json:"table_name"`
	DisplayName string            `json:"display_name,omitempty"`
	Description string            `json:"description,omitempty"`
	Schema      map[string]string `json:"schema,omitempty"`
	RowCount    int               `json:"row_count"`
	CreatedAt   string            `json:"created_at"`
	UpdatedAt   string            `json:"updated_at"`
}

TableInfo contains metadata about a skill's table.

type TailscaleConfig

type TailscaleConfig struct {
	// Enabled turns Tailscale integration on/off.
	Enabled bool `yaml:"enabled"`

	// Serve enables Tailscale Serve (accessible within your Tailnet).
	Serve bool `yaml:"serve"`

	// Funnel enables Tailscale Funnel (accessible from the public internet).
	// Requires Tailscale Funnel to be enabled in your Tailscale admin console.
	Funnel bool `yaml:"funnel"`

	// Port is the local port to proxy (default: 8085).
	Port int `yaml:"port"`

	// Hostname is the Tailscale hostname to use (empty = auto from `tailscale status`).
	Hostname string `yaml:"hostname"`
}

TailscaleConfig configures Tailscale Serve/Funnel integration.

type TailscaleManager

type TailscaleManager struct {
	// contains filtered or unexported fields
}

TailscaleManager manages Tailscale Serve/Funnel lifecycle.

func NewTailscaleManager

func NewTailscaleManager(cfg TailscaleConfig, logger *slog.Logger) *TailscaleManager

NewTailscaleManager creates a new Tailscale manager.

func (*TailscaleManager) Start

func (tm *TailscaleManager) Start(ctx context.Context) error

Start sets up Tailscale Serve and/or Funnel based on config.

func (*TailscaleManager) Status

func (tm *TailscaleManager) Status() map[string]any

Status returns the current Tailscale integration status.

func (*TailscaleManager) Stop

func (tm *TailscaleManager) Stop()

Stop tears down Tailscale Serve/Funnel.

func (*TailscaleManager) URL

func (tm *TailscaleManager) URL() string

URL returns the public-facing URL if available, or empty string.

type TeamUser

type TeamUser struct {
	ID        string    `json:"id"`
	Name      string    `json:"name"`
	Email     string    `json:"email,omitempty"`
	Role      UserRole  `json:"role"`
	CreatedAt time.Time `json:"created_at"`
	LastSeen  time.Time `json:"last_seen"`
	Active    bool      `json:"active"`
}

TeamUser represents a user in the multi-user system.

type TemporalDecayConfig added in v1.12.0

type TemporalDecayConfig struct {
	// Enabled activates temporal decay (default: false).
	Enabled bool `yaml:"enabled"`

	// HalfLifeDays is the number of days for score to halve (default: 30).
	HalfLifeDays float64 `yaml:"half_life_days"`
}

TemporalDecayConfig configures exponential score decay based on memory age.

type TokenBudgetConfig

type TokenBudgetConfig struct {
	Total    int `yaml:"total"`
	Reserved int `yaml:"reserved"`
	System   int `yaml:"system"`
	Skills   int `yaml:"skills"`
	Memory   int `yaml:"memory"`
	History  int `yaml:"history"`
	Tools    int `yaml:"tools"`

	// BootstrapMaxChars is the max total characters for all bootstrap files
	// combined (SOUL.md, IDENTITY.md, etc.). Default: 20000 (~5K tokens).
	BootstrapMaxChars int `yaml:"bootstrap_max_chars"`
}

TokenBudgetConfig configures per-layer token allocation.

type TokenOptions

type TokenOptions struct {
	Role        TokenRole
	MaxUses     int           // 0 = unlimited
	ExpiresIn   time.Duration // 0 = never expires
	AutoApprove bool
	WorkspaceID string
	Note        string
}

TokenOptions configures token generation.

type TokenRole

type TokenRole string

TokenRole defines the access level granted by a pairing token.

const (
	TokenRoleUser  TokenRole = "user"
	TokenRoleAdmin TokenRole = "admin"
)

type ToolCall

type ToolCall struct {
	ID       string       `json:"id"`
	Type     string       `json:"type"`
	Function FunctionCall `json:"function"`
}

ToolCall represents a tool invocation requested by the LLM.

type ToolCallIDMode added in v1.13.0

type ToolCallIDMode int

ToolCallIDMode determines the sanitization rules for tool call IDs.

const (
	// ToolCallIDStrict keeps only [a-zA-Z0-9], max 40 chars.
	// Used for OpenAI, Anthropic, OpenRouter, and most providers.
	ToolCallIDStrict ToolCallIDMode = iota

	// ToolCallIDStrict9 keeps only [a-zA-Z0-9], exactly 9 chars.
	// Used for Mistral which requires short fixed-length IDs.
	ToolCallIDStrict9
)

func ProviderToolCallIDMode added in v1.13.0

func ProviderToolCallIDMode(provider string) ToolCallIDMode

ProviderToolCallIDMode returns the appropriate sanitization mode for a provider.

type ToolCallRecord added in v1.13.0

type ToolCallRecord struct {
	ID     string `json:"id,omitempty"`
	Name   string `json:"name"`
	Args   string `json:"args,omitempty"`   // truncated to 200 chars
	Result string `json:"result,omitempty"` // truncated to 500 chars
}

ToolCallRecord stores a single tool invocation for session history fidelity. Truncated args/result keep storage bounded while preserving context.

type ToolCheckResult

type ToolCheckResult struct {
	Allowed              bool
	Reason               string
	RequiresConfirmation bool // true if tool needs user approval before execution
}

CheckResult holds the result of a tool access check.

type ToolDefinition

type ToolDefinition struct {
	Type     string      `json:"type"`
	Function FunctionDef `json:"function"`
}

ToolDefinition is an OpenAI-compatible tool definition for function calling.

func MakeToolDefinition

func MakeToolDefinition(name, description string, params map[string]any) ToolDefinition

MakeToolDefinition creates a ToolDefinition from name, description, and a parameter schema map (matching JSON Schema format). The name is automatically sanitized to match OpenAI's pattern.

func SkillToolToDefinition

func SkillToolToDefinition(name string, tool skills.Tool) ToolDefinition

SkillToolToDefinition converts a skills.Tool into an OpenAI ToolDefinition.

type ToolExecutor

type ToolExecutor struct {
	// contains filtered or unexported fields
}

ToolExecutor manages tool registration and dispatches tool calls.

func NewToolExecutor

func NewToolExecutor(logger *slog.Logger) *ToolExecutor

NewToolExecutor creates a new empty tool executor.

func (*ToolExecutor) Abort

func (e *ToolExecutor) Abort()

Abort signals all running tools to stop. Safe to call multiple times.

func (*ToolExecutor) AbortCh

func (e *ToolExecutor) AbortCh() <-chan struct{}

AbortCh returns the abort channel for tools to select on.

func (*ToolExecutor) ApplyDefaultConcurrency added in v1.17.0

func (e *ToolExecutor) ApplyDefaultConcurrency()

ApplyDefaultConcurrency marks all registered tools that appear in the defaultConcurrentSafeTools set as ConcurrentSafe. Called once after all tools are registered.

func (*ToolExecutor) Configure

func (e *ToolExecutor) Configure(cfg ToolExecutorConfig)

Configure applies ToolExecutorConfig (parallel, max_parallel, timeouts).

func (*ToolExecutor) Execute

func (e *ToolExecutor) Execute(ctx context.Context, calls []ToolCall) []ToolResult

Execute dispatches a batch of tool calls to their registered handlers. Each tool is executed with a per-tool timeout. When Parallel is true and no sequential tools are in the batch, runs concurrently. Returns results in the same order as the input calls.

func (*ToolExecutor) Guard

func (e *ToolExecutor) Guard() *ToolGuard

Guard returns the configured ToolGuard (may be nil).

func (*ToolExecutor) HasTool

func (e *ToolExecutor) HasTool(name string) bool

HasTool checks if a tool is registered by name.

func (*ToolExecutor) IsAborted

func (e *ToolExecutor) IsAborted() bool

IsAborted returns true if an abort has been signaled.

func (*ToolExecutor) IsConcurrentSafe added in v1.17.0

func (e *ToolExecutor) IsConcurrentSafe(name string) bool

IsConcurrentSafe returns true if the named tool can run in parallel with others. Checks the per-tool annotation first, then falls back to the default set.

func (*ToolExecutor) MarkConcurrentSafe added in v1.17.0

func (e *ToolExecutor) MarkConcurrentSafe(names ...string)

MarkConcurrentSafe marks the named tools as safe for concurrent execution. Tools not explicitly marked and not in the defaultConcurrentSafeTools set are treated as serial (must execute one at a time).

func (*ToolExecutor) ProfileManager added in v1.13.0

func (e *ToolExecutor) ProfileManager() profiles.ProfileManager

ProfileManager returns the configured auth profile manager (may be nil).

func (*ToolExecutor) Register

func (e *ToolExecutor) Register(def ToolDefinition, handler ToolHandlerFunc)

Register adds a tool with its definition and handler. If a tool with the same name already exists, it is overwritten.

func (*ToolExecutor) RegisterHidden added in v1.13.0

func (e *ToolExecutor) RegisterHidden(def ToolDefinition, handler ToolHandlerFunc)

RegisterHidden registers a tool that is callable but excluded from the LLM tool schema. Used for legacy/deprecated aliases that should still work if invoked but shouldn't consume slots in the tool list sent to the model.

func (*ToolExecutor) RegisterHook

func (e *ToolExecutor) RegisterHook(hook *ToolHook)

RegisterHook adds a before/after tool execution hook. Hooks are called in registration order. Multiple hooks can be registered.

func (*ToolExecutor) RegisterPluginTool added in v1.16.0

func (e *ToolExecutor) RegisterPluginTool(reg plugins.ToolRegistration)

RegisterPluginTool registers a tool from the plugin system. Adapts plugins.ToolRegistration to the internal ToolDefinition format.

func (*ToolExecutor) RegisterSkillTools

func (e *ToolExecutor) RegisterSkillTools(skill skills.Skill)

RegisterSkillTools registers all tools exposed by a skill. Tool names are prefixed with the skill name to avoid collisions. Names are sanitized to match OpenAI's pattern: ^[a-zA-Z0-9_-]+$

When the reference model is active (skill has Location != ""), the generic "execute" tool is registered as hidden — the LLM should read SKILL.md via read_file instead of calling the execute tool (which returns "instructions only"). Script-specific tools (run_*) remain visible.

func (*ToolExecutor) ResetAbort

func (e *ToolExecutor) ResetAbort()

ResetAbort creates a fresh abort channel for a new run.

func (*ToolExecutor) SessionContext

func (e *ToolExecutor) SessionContext() string

SessionContext returns the current session ID (format: "channel:chatID").

func (*ToolExecutor) SetCallerContext

func (e *ToolExecutor) SetCallerContext(level AccessLevel, jid string)

SetCallerContext sets the access level and JID for the current caller. Must be called before Execute() in the message handling flow.

func (*ToolExecutor) SetConfirmationRequester

func (e *ToolExecutor) SetConfirmationRequester(fn func(sessionID, callerJID, toolName string, args map[string]any) (bool, error))

SetConfirmationRequester sets the callback for tools requiring user approval. When a tool is in RequireConfirmation list, this callback is invoked.

func (*ToolExecutor) SetGuard

func (e *ToolExecutor) SetGuard(guard *ToolGuard)

SetGuard configures the security guard for tool execution.

func (*ToolExecutor) SetProfileManager added in v1.13.0

func (e *ToolExecutor) SetProfileManager(pm profiles.ProfileManager)

SetProfileManager configures the auth profile manager for OAuth/API key access.

func (*ToolExecutor) SetSessionContext

func (e *ToolExecutor) SetSessionContext(sessionID string)

SetSessionContext sets the session ID for approval matching (channel:chatID). Must be set before Execute() when using approval flow.

func (*ToolExecutor) SetVault added in v1.12.0

func (e *ToolExecutor) SetVault(vault skills.VaultReader)

SetVault sets the vault reader for skill setup checking.

func (*ToolExecutor) ToolNames

func (e *ToolExecutor) ToolNames() []string

ToolNames returns the names of all registered tools.

func (*ToolExecutor) Tools

func (e *ToolExecutor) Tools() []ToolDefinition

Tools returns all registered tool definitions for the LLM. Uses a cached slice that is rebuilt only when tools are added/removed.

func (*ToolExecutor) UnregisterTool added in v1.16.0

func (e *ToolExecutor) UnregisterTool(name string) bool

UnregisterTool removes a tool from the executor by name. Returns true if the tool was found and removed.

func (*ToolExecutor) UpdateGuardConfig

func (e *ToolExecutor) UpdateGuardConfig(cfg ToolGuardConfig)

UpdateGuardConfig updates the tool guard config (for hot-reload).

type ToolExecutorConfig

type ToolExecutorConfig struct {
	// Parallel enables parallel execution of independent tools (default: true).
	Parallel bool `yaml:"parallel"`

	// MaxParallel is the max concurrent tool executions when parallel is enabled (default: 5).
	MaxParallel int `yaml:"max_parallel"`

	// BashTimeoutSeconds is the executor-level timeout for bash/ssh/scp/exec tools (default: 300).
	BashTimeoutSeconds int `yaml:"bash_timeout_seconds"`

	// DefaultTimeoutSeconds is the executor-level timeout for all other tools (default: 30).
	DefaultTimeoutSeconds int `yaml:"default_timeout_seconds"`
}

ToolExecutorConfig configures tool execution behavior.

type ToolGuard

type ToolGuard struct {
	// contains filtered or unexported fields
}

ToolGuard enforces security policies on tool execution.

func NewToolGuard

func NewToolGuard(cfg ToolGuardConfig, logger *slog.Logger) *ToolGuard

NewToolGuard creates and initializes a tool security guard.

func (*ToolGuard) AuditLog

func (g *ToolGuard) AuditLog(toolName string, callerJID string, callerLevel AccessLevel, args map[string]any, allowed bool, result string)

AuditLog records a tool execution to the audit log.

func (*ToolGuard) Check

func (g *ToolGuard) Check(toolName string, callerLevel AccessLevel, args map[string]any) ToolCheckResult

Check evaluates whether a tool call is permitted for the given access level.

func (*ToolGuard) CheckWithProfile

func (g *ToolGuard) CheckWithProfile(toolName string, callerLevel AccessLevel, args map[string]any, profile *ToolProfile, overlay *ToolOverlay) ToolCheckResult

CheckWithProfile evaluates tool access considering a profile's allow/deny lists and an optional workspace overlay. The overlay is applied after the profile check but before standard permission checks. If no profile is provided (nil), delegates directly to Check() (with overlay).

func (*ToolGuard) Close

func (g *ToolGuard) Close()

Close closes the audit log file.

func (*ToolGuard) GetActiveProfile

func (g *ToolGuard) GetActiveProfile() *ToolProfile

GetActiveProfile returns the active profile based on config. Returns nil if no profile is configured or if profile is not found.

func (*ToolGuard) GetAllToolNames

func (g *ToolGuard) GetAllToolNames() []string

GetAllToolNames returns all known tool names from permissions and groups.

func (*ToolGuard) SQLiteAudit

func (g *ToolGuard) SQLiteAudit() *SQLiteAuditLogger

SQLiteAudit returns the SQLite audit logger (may be nil).

func (*ToolGuard) SetSQLiteAudit

func (g *ToolGuard) SetSQLiteAudit(a *SQLiteAuditLogger)

SetSQLiteAudit configures a SQLite-backed audit logger. When set, audit records go to the database instead of the text file.

func (*ToolGuard) UpdateConfig

func (g *ToolGuard) UpdateConfig(cfg ToolGuardConfig)

UpdateConfig updates the tool guard config from hot-reload. Re-compiles dangerous patterns and protected paths. The audit log file is not changed (requires restart to change audit log path).

type ToolGuardConfig

type ToolGuardConfig struct {
	// Enable turns on the tool security guard (default: true).
	Enabled bool `yaml:"enabled"`

	// AuditLog path for recording all tool executions.
	AuditLogPath string `yaml:"audit_log"`

	// Profile selects a predefined tool profile.
	// Options: minimal, coding, messaging, full, or custom profile name.
	// Empty = use ToolPermissions directly (backward compatibility).
	Profile string `yaml:"profile"`

	// CustomProfiles allows defining custom tool profiles.
	CustomProfiles map[string]ToolProfile `yaml:"custom_profiles"`

	// ToolPermissions overrides per-tool permission levels.
	// key = tool name, value = "owner"/"admin"/"user"/"public".
	ToolPermissions map[string]string `yaml:"tool_permissions"`

	// AllowDestructive enables destructive commands (rm -rf /, mkfs, dd, etc)
	// for the owner. When false (default), these are blocked for everyone.
	// When true, owner can run them; non-owners are still blocked.
	AllowDestructive bool `yaml:"allow_destructive"`

	// AllowSudo allows sudo commands. When false (default), sudo is blocked
	// for non-owners. When true, owner and admin can use sudo.
	AllowSudo bool `yaml:"allow_sudo"`

	// AllowReboot allows shutdown/reboot/halt commands. Default: false.
	AllowReboot bool `yaml:"allow_reboot"`

	// DangerousCommands are additional regex patterns for commands that should
	// be blocked. These are added ON TOP of the defaults (not replacing them).
	// To disable all defaults, set allow_destructive: true.
	DangerousCommands []string `yaml:"dangerous_commands"`

	// ProtectedPaths are file paths that cannot be read or written by non-owners.
	// Supports glob patterns. If empty, defaults are used.
	ProtectedPaths []string `yaml:"protected_paths"`

	// SSHAllowedHosts restricts which hosts can be connected via SSH.
	// Empty list = any host allowed (no restriction). Use "*" explicitly to allow all.
	SSHAllowedHosts []string `yaml:"ssh_allowed_hosts"`

	// BlockSudo blocks sudo commands for non-owners (default: true).
	// Deprecated: use AllowSudo instead. Kept for backward compatibility.
	BlockSudo bool `yaml:"block_sudo"`

	// AutoApprove lists tools that can execute without any permission check,
	// even for regular users. Use with caution. Example: ["web_search", "memory"]
	AutoApprove []string `yaml:"auto_approve"`

	// RequireConfirmation lists tools that require the user to confirm via
	// the chat before executing. The agent will ask "Confirm: <action>?" and
	// wait for approval. Example: ["bash", "ssh", "scp", "write_file"]
	RequireConfirmation []string `yaml:"require_confirmation"`

	// DestructiveProtection configures rate limiting and batch detection for
	// destructive tools. With dispatcher consolidation, configure at action level.
	DestructiveProtection DestructiveToolsConfig `yaml:"destructive_protection"`
}

ToolGuardConfig configures the security guard for tools.

func DefaultToolGuardConfig

func DefaultToolGuardConfig() ToolGuardConfig

DefaultToolGuardConfig returns safe defaults for the tool security guard. Owners have full access by default - adjust if you need stricter security.

type ToolHandlerFunc

type ToolHandlerFunc func(ctx context.Context, args map[string]any) (any, error)

ToolHandlerFunc is the signature for tool execution handlers. Receives parsed arguments and returns the result or an error.

type ToolHook

type ToolHook struct {
	// Name identifies this hook for logging and debugging.
	Name string

	// BeforeToolCall is called before the tool handler executes.
	// Return modified args (or original), or an error to block execution.
	// If blocked is true, the tool is not executed and blockReason is returned.
	BeforeToolCall func(toolName string, args map[string]any) (modifiedArgs map[string]any, blocked bool, blockReason string)

	// AfterToolCall is called after the tool handler executes (success or error).
	AfterToolCall func(toolName string, args map[string]any, result string, err error)
}

ToolHook is a callback that runs before or after tool execution. Before hooks can modify args or block execution by returning an error. After hooks can observe/log the result but cannot modify it.

type ToolLoopConfig

type ToolLoopConfig struct {
	// Enabled turns loop detection on (default: true).
	Enabled bool `yaml:"enabled"`

	// HistorySize is how many recent tool calls to track (default: 30).
	HistorySize int `yaml:"history_size"`

	// WarningThreshold triggers a warning injected into the conversation (default: 8).
	WarningThreshold int `yaml:"warning_threshold"`

	// CriticalThreshold triggers a strong nudge to stop (default: 15).
	CriticalThreshold int `yaml:"critical_threshold"`

	// CircuitBreakerThreshold force-stops the agent run (default: 25).
	CircuitBreakerThreshold int `yaml:"circuit_breaker_threshold"`

	// GlobalCircuitBreaker is the max total no-progress calls before hard stop (default: 20).
	GlobalCircuitBreaker int `yaml:"global_circuit_breaker"`

	// ProgressDetection enables content-based progress analysis (default: true).
	ProgressDetection bool `yaml:"progress_detection"`
}

ToolLoopConfig configures tool loop detection thresholds.

func DefaultToolLoopConfig

func DefaultToolLoopConfig() ToolLoopConfig

DefaultToolLoopConfig returns sensible defaults.

type ToolLoopDetector

type ToolLoopDetector struct {
	// contains filtered or unexported fields
}

ToolLoopDetector tracks tool call history and detects loops.

func NewToolLoopDetector

func NewToolLoopDetector(cfg ToolLoopConfig, logger *slog.Logger) *ToolLoopDetector

NewToolLoopDetector creates a new detector with the given config.

func (*ToolLoopDetector) RecordAndCheck

func (d *ToolLoopDetector) RecordAndCheck(toolName string, args map[string]any) LoopDetectionResult

RecordAndCheck records a tool call and checks for loops. Returns a result indicating the severity (if any).

func (*ToolLoopDetector) RecordToolOutcome

func (d *ToolLoopDetector) RecordToolOutcome(output string)

RecordToolOutcome records the result of a tool call for progress tracking. Call this after tool execution with the output to determine if the agent is making progress. An empty or identical output signals no progress.

func (*ToolLoopDetector) Reset

func (d *ToolLoopDetector) Reset()

Reset clears the history (e.g. for a new run).

type ToolOutcome added in v1.18.2

type ToolOutcome struct {
	Name      string    // tool name
	Args      string    // raw tool args (trimmed)
	Error     bool      // true if the tool returned an error
	Content   string    // tool output (truncated)
	Timestamp time.Time // when the outcome was recorded
}

ToolOutcome captures the structured result of a single tool execution.

type ToolOutcomeLog added in v1.18.2

type ToolOutcomeLog struct {
	// contains filtered or unexported fields
}

ToolOutcomeLog is a bounded, thread-safe chronological log of tool outcomes for a single agent turn. Entries are appended in execution order; older entries drop off once the capacity is exceeded.

func NewToolOutcomeLog added in v1.18.2

func NewToolOutcomeLog(capacity int) *ToolOutcomeLog

NewToolOutcomeLog constructs an outcome log with the given capacity. A non-positive capacity defaults to 32 entries, which comfortably covers the 25-turn tool loop with multiple calls per turn.

func ToolOutcomeLogFromContext added in v1.18.2

func ToolOutcomeLogFromContext(ctx context.Context) *ToolOutcomeLog

ToolOutcomeLogFromContext extracts the outcome log, or nil if not set.

func (*ToolOutcomeLog) Record added in v1.18.2

func (l *ToolOutcomeLog) Record(o ToolOutcome)

Record appends an outcome to the log, trimming oldest entries beyond max.

func (*ToolOutcomeLog) Snapshot added in v1.18.2

func (l *ToolOutcomeLog) Snapshot() []ToolOutcome

Snapshot returns a copy of the current outcomes in chronological order.

type ToolOverlay added in v1.16.0

type ToolOverlay struct {
	Allow []string
	Deny  []string
}

ToolOverlay holds workspace-scoped tool allow/deny lists.

func ToolOverlayFromContext added in v1.16.0

func ToolOverlayFromContext(ctx context.Context) *ToolOverlay

ToolOverlayFromContext extracts the tool overlay from context.

type ToolPermission

type ToolPermission string

ToolPermission defines which access level is required for a tool.

const (
	PermOwner  ToolPermission = "owner"  // Only owner can use.
	PermAdmin  ToolPermission = "admin"  // Admin and owner.
	PermUser   ToolPermission = "user"   // Any authorized user.
	PermPublic ToolPermission = "public" // No restriction (used for read-only tools).
)

type ToolProfile

type ToolProfile struct {
	// Name is the profile identifier (e.g., "minimal", "coding", "full").
	Name string `yaml:"name"`

	// Description explains what this profile is for.
	Description string `yaml:"description"`

	// Allow lists tools and groups that are permitted.
	// Supports: tool names, "group:name", wildcards like "git_*"
	// Empty means no allow list (use permission levels).
	Allow []string `yaml:"allow"`

	// Deny lists tools and groups that are always blocked.
	// Takes precedence over Allow.
	Deny []string `yaml:"deny"`
}

ToolProfile defines a preset of allowed and denied tools.

func ExtendProfileWithSkills added in v1.13.0

func ExtendProfileWithSkills(base *ToolProfile, activeSkills []string) *ToolProfile

ExtendProfileWithSkills creates a copy of the profile that also allows tools from the given active skills. Skill tools follow the naming pattern "skillname_toolname", so we add "skillname_*" wildcards to the allow list. If the profile already allows all tools ("*"), returns it unchanged.

func GetProfile

func GetProfile(name string, customProfiles map[string]ToolProfile) *ToolProfile

GetProfile returns a profile by name (built-in or custom).

func ToolProfileFromContext

func ToolProfileFromContext(ctx context.Context) *ToolProfile

ToolProfileFromContext extracts the tool profile from context. Returns nil if no profile is set.

type ToolProfileInfo added in v1.12.0

type ToolProfileInfo struct {
	Name        string   `json:"name"`
	Description string   `json:"description"`
	Allow       []string `json:"allow"`
	Deny        []string `json:"deny"`
	Builtin     bool     `json:"builtin"`
}

ToolProfileInfo contains profile info for API responses.

type ToolResult

type ToolResult struct {
	ToolCallID string
	Name       string
	Content    string // Main content (used for LLM context when ForLLM is empty)
	Error      error

	// Extended fields for dual output (PicoClaw-inspired)
	ForLLM   string // Technical content for LLM reasoning (if empty, Content is used)
	ForUser  string // Friendly message to show user immediately
	IsAsync  bool   // Tool is running in background, result comes later
	IsSilent bool   // Don't notify user about this result
}

ToolResult holds the output of a single tool execution.

func AsyncResult added in v1.12.0

func AsyncResult(message string) *ToolResult

AsyncResult creates a ToolResult indicating the tool is running in background. The actual result will be delivered via callback or follow-up message.

func DualToolResult added in v1.12.0

func DualToolResult(forLLM, forUser string) *ToolResult

DualToolResult creates a ToolResult with separate content for LLM and user. This is the recommended way to return results that have both technical details and user-friendly messages.

func ErrorResult added in v1.12.0

func ErrorResult(err error) *ToolResult

ErrorResult creates a ToolResult from an error.

func SilentResult added in v1.12.0

func SilentResult(content string) *ToolResult

SilentResult creates a ToolResult that doesn't notify the user. Useful for background operations or when the result should only be used for LLM reasoning.

func (*ToolResult) GetForLLM added in v1.12.0

func (r *ToolResult) GetForLLM() string

GetForLLM returns the content to use for LLM context. Returns ForLLM if set, otherwise Content.

func (*ToolResult) GetForUser added in v1.12.0

func (r *ToolResult) GetForUser() string

GetForUser returns the content to show the user. Returns ForUser if set, otherwise GetForLLM().

type TranscriptPolicy added in v1.13.0

type TranscriptPolicy struct {
	// StrictToolCallIDs requires that tool_call IDs are exactly formatted.
	// Mistral requires strict UUID-like IDs (no prefixes, lowercase).
	StrictToolCallIDs bool

	// EnforceTurnOrdering ensures user/assistant turns alternate properly.
	// Google (Gemini) rejects consecutive same-role messages.
	EnforceTurnOrdering bool

	// DropThinkingBlocks removes <thinking> and <reasoning> content blocks.
	// Some providers reject or misinterpret these meta-reasoning blocks.
	DropThinkingBlocks bool

	// MergeConsecutiveSameRole merges consecutive messages from the same role
	// into a single message. Required by some providers that reject duplicates.
	MergeConsecutiveSameRole bool

	// RequireToolResultsAfterToolUse ensures every tool_use has a matching
	// tool_result immediately following it. Anthropic requires this.
	RequireToolResultsAfterToolUse bool

	// StripCacheControl removes cache_control fields from messages.
	// Only Anthropic and compatible proxies support this.
	StripCacheControl bool

	// MaxSystemMessages is the maximum number of system messages allowed.
	// Some providers only support a single system message.
	// 0 = unlimited.
	MaxSystemMessages int

	// CollapseMultipleSystemMessages merges multiple system messages into one.
	CollapseMultipleSystemMessages bool
}

TranscriptPolicy defines the sanitization rules for a provider's API.

func GetTranscriptPolicy added in v1.13.0

func GetTranscriptPolicy(provider string) TranscriptPolicy

GetTranscriptPolicy returns the policy for a given provider.

type TrustConfig

type TrustConfig struct {
	// Owner is the maximum risk level the owner can execute without approval.
	Owner RiskLevel `yaml:"owner"`

	// Admin is the maximum risk level admins can execute without approval.
	Admin RiskLevel `yaml:"admin"`

	// User is the maximum risk level regular users can execute without approval.
	User RiskLevel `yaml:"user"`
}

TrustConfig configures trust levels per user role.

type TurnState added in v1.17.0

type TurnState struct {
	// Messages is the current conversation history.
	Messages []chatMessage

	// Tools is the tool definitions available for this turn.
	Tools []ToolDefinition

	// Response holds the LLM response (set by APICall phase).
	Response *LLMResponse

	// ToolResults holds results from tool execution (set by ToolExec phase).
	ToolResults []ToolResult

	// FinalText is the final response text (set when Decision chooses Stop).
	FinalText string

	// InjectedMessage is a message to inject (set when Decision chooses Inject).
	InjectedMessage string

	// Turn is the current turn number.
	Turn int

	// Usage tracks accumulated token usage.
	Usage LLMUsage

	// Metadata holds arbitrary phase-specific data.
	Metadata map[string]any
}

TurnState carries mutable state through the phase pipeline. Each phase can read and modify this state before passing it to the next.

type TypingController added in v1.13.0

type TypingController struct {
	// contains filtered or unexported fields
}

TypingController manages typing indicator lifecycle with state awareness.

func NewTypingController added in v1.13.0

func NewTypingController(channelMgr *channels.Manager, channel, chatID string) *TypingController

NewTypingController creates a new controller in the Started state.

func (*TypingController) MarkDispatchIdle added in v1.13.0

func (tc *TypingController) MarkDispatchIdle()

MarkDispatchIdle transitions to DispatchIdle, stopping typing indicators.

func (*TypingController) MarkRunComplete added in v1.13.0

func (tc *TypingController) MarkRunComplete()

MarkRunComplete transitions to RunComplete state. Typing continues during a grace period to cover block streamer dispatch.

func (*TypingController) Seal added in v1.13.0

func (tc *TypingController) Seal()

Seal transitions to the terminal Sealed state and stops the typing loop.

func (*TypingController) ShouldSuppress added in v1.13.0

func (tc *TypingController) ShouldSuppress(content string) bool

ShouldSuppress returns true if typing should be suppressed for the given content. Empty or internal-only content (heartbeats, etc.) should not restart typing indicators.

func (*TypingController) Start added in v1.13.0

func (tc *TypingController) Start(ctx context.Context)

Start transitions to Active and begins sending typing indicators. The context is used to cancel the typing loop if the parent context is cancelled.

type TypingState added in v1.13.0

type TypingState int

TypingState represents the lifecycle state of the typing controller.

const (
	TypingStarted      TypingState = iota // Created, not yet active.
	TypingActive                          // Sending typing indicators.
	TypingRunComplete                     // Agent done, grace period for dispatch.
	TypingDispatchIdle                    // Dispatch finished, idle.
	TypingSealed                          // Terminal, no further sends.
)

type UpdateConfig added in v1.13.0

type UpdateConfig struct {
	// Enabled turns auto-update checking on/off (default: true).
	Enabled bool `yaml:"enabled"`

	// AssetsURL is the base URL for release assets.
	AssetsURL string `yaml:"assets_url"`

	// CheckInterval is the duration between automatic update checks (e.g. "1h").
	CheckInterval string `yaml:"check_interval"`
}

UpdateConfig configures auto-update checking and installation.

type UpdateFileChunk added in v1.12.0

type UpdateFileChunk struct {
	ChangeContext string
	OldLines      []string
	NewLines      []string
	IsEndOfFile   bool
}

UpdateFileChunk represents a single chunk of changes within an UpdateFileHunk.

type UpdateFileHunk added in v1.12.0

type UpdateFileHunk struct {
	Path     string
	MovePath string
	Chunks   []UpdateFileChunk
}

UpdateFileHunk represents an update to an existing file, optionally moving it.

type UsageAccumulator added in v1.13.0

type UsageAccumulator struct {
	TotalInput      int // Sum of all PromptTokens across calls
	TotalOutput     int // Sum of all CompletionTokens
	TotalCacheRead  int // Sum of all CacheReadTokens
	TotalCacheWrite int // Sum of all CacheWriteTokens
	LastInput       int // Most recent call's PromptTokens
	LastOutput      int // Most recent call's CompletionTokens
	LastCacheRead   int // Most recent call's CacheReadTokens
	LastCacheWrite  int // Most recent call's CacheWriteTokens
	CallCount       int // Number of LLM calls in this run
}

UsageAccumulator tracks token usage across multiple LLM calls within a run. It maintains both cumulative totals and the most recent call's values. Using last-call values for context size estimation avoids the inflation problem where accumulated cacheRead across N calls = N × context_size.

func (*UsageAccumulator) EffectiveContextTokens added in v1.13.0

func (ua *UsageAccumulator) EffectiveContextTokens() int

EffectiveContextTokens returns the actual context size as reported by the provider in the most recent call. This is more accurate than character-based estimation because it uses real token counts from the API.

func (*UsageAccumulator) Record added in v1.13.0

func (ua *UsageAccumulator) Record(usage LLMUsage)

Record accumulates a single LLM call's usage and updates last-call snapshots.

func (*UsageAccumulator) ToLLMUsage added in v1.13.0

func (ua *UsageAccumulator) ToLLMUsage() LLMUsage

ToLLMUsage converts the accumulator totals to an LLMUsage for backward compat.

type UsageTracker

type UsageTracker struct {
	// contains filtered or unexported fields
}

UsageTracker records usage per session and globally.

func NewUsageTracker

func NewUsageTracker(logger *slog.Logger) *UsageTracker

NewUsageTracker creates a new UsageTracker.

func (*UsageTracker) FormatGlobalUsage

func (u *UsageTracker) FormatGlobalUsage() string

FormatGlobalUsage returns a human-readable global usage report.

func (*UsageTracker) FormatUsage

func (u *UsageTracker) FormatUsage(sessionID string) string

FormatUsage returns a human-readable usage report for a session.

func (*UsageTracker) GetGlobal

func (u *UsageTracker) GetGlobal() *SessionUsage

GetGlobal returns a copy of global usage.

func (*UsageTracker) GetSession

func (u *UsageTracker) GetSession(sessionID string) *SessionUsage

GetSession returns a copy of the session's usage stats, or nil if not found.

func (*UsageTracker) Record

func (u *UsageTracker) Record(sessionID, model string, usage LLMUsage)

Record adds usage for a session and globally.

func (*UsageTracker) ResetSession

func (u *UsageTracker) ResetSession(sessionID string)

ResetSession clears usage for a session.

type UserManager

type UserManager struct {
	// contains filtered or unexported fields
}

UserManager handles multi-user operations.

func NewUserManager

func NewUserManager(config UserManagerConfig) *UserManager

NewUserManager creates a new user manager.

func (*UserManager) AddUser

func (um *UserManager) AddUser(user *TeamUser) error

AddUser adds a new user.

func (*UserManager) CheckPermission

func (um *UserManager) CheckPermission(userID string, required UserRole) bool

CheckPermission verifies a user has the required role.

func (*UserManager) GetSharedMemory

func (um *UserManager) GetSharedMemory(key string) (*SharedMemory, bool)

GetSharedMemory retrieves a value from shared team memory.

func (*UserManager) GetUser

func (um *UserManager) GetUser(id string) (*TeamUser, bool)

GetUser returns a user by ID.

func (*UserManager) ListSharedMemory

func (um *UserManager) ListSharedMemory() []*SharedMemory

ListSharedMemory returns all shared memory entries.

func (*UserManager) ListUsers

func (um *UserManager) ListUsers() []*TeamUser

ListUsers returns all users.

func (*UserManager) RemoveUser

func (um *UserManager) RemoveUser(id string) error

RemoveUser removes a user.

func (*UserManager) SetSharedMemory

func (um *UserManager) SetSharedMemory(key, value, authorID string, tags []string)

SetSharedMemory stores a value in shared team memory.

type UserManagerConfig added in v1.16.0

type UserManagerConfig struct {
	MaxUsers    int    `yaml:"max_users" json:"max_users"`
	DefaultRole string `yaml:"default_role" json:"default_role"`
}

UserManagerConfig holds multi-user configuration.

type UserRole

type UserRole string

UserRole defines permission levels for multi-user access.

const (
	RoleOwner  UserRole = "owner"
	RoleAdmin  UserRole = "admin"
	RoleUser   UserRole = "user"
	RoleViewer UserRole = "viewer"
)

type UserUsage

type UserUsage struct {
	SessionID string  `json:"session_id"`
	Tokens    int64   `json:"tokens"`
	Requests  int64   `json:"requests"`
	CostUSD   float64 `json:"cost_usd"`
}

UserUsage represents usage for a single user/session.

type Vault

type Vault struct {
	// contains filtered or unexported fields
}

Vault provides encrypted secret storage backed by a local file.

func NewVault

func NewVault(path string) *Vault

NewVault creates a vault instance pointing to the given file path. The vault is not yet unlocked — call Unlock() or Create() first.

func ResolveAPIKey

func ResolveAPIKey(cfg *Config, logger *slog.Logger) *Vault

ResolveAPIKey resolves the API key using the priority chain: vault → keyring → env var → config value. Also updates the config in-place with the resolved value. If a vault exists but is locked, it prompts for the master password (or uses DEVCLAW_VAULT_PASSWORD env var for non-interactive mode). Returns the unlocked vault (or nil if unavailable) so it can be reused by the assistant for agent vault tools.

func (*Vault) ChangePassword

func (v *Vault) ChangePassword(newPassword string) error

ChangePassword re-encrypts all entries with a new master password. The vault must be unlocked.

func (*Vault) Create

func (v *Vault) Create(password string) error

Create initializes a new vault with the given master password. If the vault file already exists, it returns an error.

func (*Vault) Delete

func (v *Vault) Delete(name string) error

Delete removes a secret from the vault. The vault must be unlocked.

func (*Vault) Exists

func (v *Vault) Exists() bool

Exists returns true if the vault file exists on disk.

func (*Vault) Get

func (v *Vault) Get(name string) (string, error)

Get retrieves and decrypts a secret from the vault. Returns empty string if the key doesn't exist. The vault must be unlocked.

func (*Vault) Has added in v1.12.0

func (v *Vault) Has(name string) bool

Has returns true if a secret exists in the vault. Returns false if vault is locked or key doesn't exist.

func (*Vault) InjectProviderKeys added in v1.8.1

func (v *Vault) InjectProviderKeys() error

InjectProviderKeys injects all secrets from the vault into environment variables. This allows LLM clients to find their API keys via standard variable names (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) without the config needing to reference them explicitly.

The vault must be unlocked before calling this method.

func (*Vault) IsUnlocked

func (v *Vault) IsUnlocked() bool

IsUnlocked returns true if the vault has been unlocked with a password.

func (*Vault) Keys

func (v *Vault) Keys() ([]string, error)

Keys returns the names of all stored secrets (excluding internal entries).

func (*Vault) List

func (v *Vault) List() []string

List returns the names of all stored secrets (excluding internal entries). Returns an empty slice if the vault is locked or empty.

func (*Vault) Lock

func (v *Vault) Lock()

Lock clears the derived key from memory, locking the vault.

func (*Vault) Path

func (v *Vault) Path() string

Path returns the vault file path.

func (*Vault) Set

func (v *Vault) Set(name, value string) error

Set stores a secret in the vault (encrypted). The vault must be unlocked.

func (*Vault) Unlock

func (v *Vault) Unlock(password string) error

Unlock decrypts and loads the vault using the provided master password. Returns an error if the password is wrong (decryption will fail on the verification entry or the first real entry).

type VaultData

type VaultData struct {
	Version int                   `json:"version"`
	Salt    string                `json:"salt"` // base64-encoded Argon2 salt
	Entries map[string]VaultEntry `json:"entries"`
}

VaultData is the on-disk format of the vault.

type VaultEntry

type VaultEntry struct {
	Nonce      string `json:"nonce"`      // base64-encoded AES-GCM nonce
	Ciphertext string `json:"ciphertext"` // base64-encoded encrypted data
}

VaultEntry holds one encrypted secret.

type VaultReaderAdapter added in v1.12.0

type VaultReaderAdapter struct {
	// contains filtered or unexported fields
}

VaultReaderAdapter adapts a Vault to implement skills.VaultReader.

func NewVaultReaderAdapter added in v1.12.0

func NewVaultReaderAdapter(getKey func(key string) (string, error), hasKey func(key string) bool) *VaultReaderAdapter

NewVaultReaderAdapter creates a VaultReader from getter functions.

func (*VaultReaderAdapter) Get added in v1.12.0

func (v *VaultReaderAdapter) Get(key string) (string, error)

Get returns the value for a key.

func (*VaultReaderAdapter) Has added in v1.12.0

func (v *VaultReaderAdapter) Has(key string) bool

Has returns true if the key exists.

type VerboseLevel added in v1.13.0

type VerboseLevel string

VerboseLevel controls tool progress message visibility, matching OpenClaw's verboseLevel semantics:

  • "off" → no tool progress messages (default)
  • "on" → tool name summaries are shown
  • "full" → tool names + detailed output are shown
const (
	VerboseOff  VerboseLevel = "off"
	VerboseOn   VerboseLevel = "on"
	VerboseFull VerboseLevel = "full"
)

func (VerboseLevel) ShouldEmitToolProgress added in v1.13.0

func (v VerboseLevel) ShouldEmitToolProgress() bool

ShouldEmitToolProgress returns true when tool progress messages should be sent to the user (verbose "on" or "full").

type WebFetchCache added in v1.13.0

type WebFetchCache struct {
	// contains filtered or unexported fields
}

WebFetchCache is a simple in-memory cache with TTL for web fetch results.

func NewWebFetchCache added in v1.13.0

func NewWebFetchCache(ttl time.Duration, maxSize int) *WebFetchCache

NewWebFetchCache creates a cache with the given TTL and max entries.

func (*WebFetchCache) Get added in v1.13.0

func (c *WebFetchCache) Get(url string) (string, bool)

Get returns a cached entry if it exists and is not expired.

func (*WebFetchCache) Set added in v1.13.0

func (c *WebFetchCache) Set(url, content string)

Set stores a result in the cache, evicting the oldest entry if full.

type WebSearchConfig

type WebSearchConfig struct {
	// Provider is the search engine to use: "duckduckgo" (default), "brave", or "perplexity".
	Provider string `yaml:"provider"`

	// BraveAPIKey is the Brave Search API subscription token.
	// Can also be set via BRAVE_API_KEY env var.
	BraveAPIKey string `yaml:"brave_api_key"`

	// PerplexityModel is the Perplexity model to use via OpenRouter (default: "perplexity/sonar").
	// Requires the main API to be configured with OpenRouter as provider.
	PerplexityModel string `yaml:"perplexity_model"`

	// PerplexityAPIKey is the OpenRouter API key for Perplexity queries.
	// Falls back to OPENROUTER_API_KEY env var, then to the main API key.
	PerplexityAPIKey string `yaml:"perplexity_api_key"`

	// MaxResults is the maximum number of results to return (default: 8).
	MaxResults int `yaml:"max_results"`

	// CacheTTLSeconds is how long search results are cached (default: 300 = 5 min, 0 = disabled).
	CacheTTLSeconds int `yaml:"cache_ttl_seconds"`
}

WebSearchConfig configures the web search tool.

type WebhookConfig

type WebhookConfig struct {
	// Name identifies this webhook for logging.
	Name string `yaml:"name"`

	// URL is the webhook endpoint URL.
	URL string `yaml:"url"`

	// Events lists which events to send to this webhook.
	Events []string `yaml:"events"`

	// Secret is used to sign payloads (HMAC-SHA256).
	Secret string `yaml:"secret"`

	// Headers are additional HTTP headers to send.
	Headers map[string]string `yaml:"headers"`

	// Timeout is the request timeout in seconds (default: 10).
	Timeout int `yaml:"timeout"`

	// Enabled controls whether this webhook is active.
	Enabled bool `yaml:"enabled"`

	// RetryCount is the number of retry attempts on failure (default: 3).
	RetryCount int `yaml:"retry_count"`

	// RetryDelayMs is the delay between retries in milliseconds (default: 1000).
	RetryDelayMs int `yaml:"retry_delay_ms"`
}

WebhookConfig configures an external webhook endpoint.

type WebhookManager

type WebhookManager struct {
	// contains filtered or unexported fields
}

WebhookManager manages webhook delivery.

func NewWebhookManager

func NewWebhookManager(cfg WebhooksConfig, hookMgr *HookManager, logger *slog.Logger) *WebhookManager

NewWebhookManager creates a new webhook manager.

func (*WebhookManager) ForwardEvent

func (wm *WebhookManager) ForwardEvent(payload HookPayload)

ForwardEvent sends an event to all matching webhooks.

func (*WebhookManager) ListWebhooks

func (wm *WebhookManager) ListWebhooks() []WebhookConfig

ListWebhooks returns all configured webhooks.

func (*WebhookManager) Reload

func (wm *WebhookManager) Reload(cfg WebhooksConfig)

Reload updates the webhook configuration.

func (*WebhookManager) SetWebhookEnabled

func (wm *WebhookManager) SetWebhookEnabled(name string, enabled bool) bool

SetWebhookEnabled enables or disables a webhook by name.

type WebhookPayload

type WebhookPayload struct {
	// Event is the hook event name.
	Event string `json:"event"`

	// Timestamp is when the event occurred (ISO 8601).
	Timestamp string `json:"timestamp"`

	// SessionID is the session this event relates to (if applicable).
	SessionID string `json:"session_id,omitempty"`

	// Channel is the originating channel (if applicable).
	Channel string `json:"channel,omitempty"`

	// ToolName is the tool being called (for tool events).
	ToolName string `json:"tool_name,omitempty"`

	// Message is a human-readable description.
	Message string `json:"message,omitempty"`

	// Error is the error message (for error events).
	Error string `json:"error,omitempty"`

	// Extra holds arbitrary key-value data.
	Extra map[string]any `json:"extra,omitempty"`
}

WebhookPayload is the JSON payload sent to webhook endpoints.

type WebhooksConfig

type WebhooksConfig struct {
	// Enabled turns the webhook system on/off.
	Enabled bool `yaml:"enabled"`

	// Webhooks is the list of configured webhooks.
	Webhooks []WebhookConfig `yaml:"webhooks"`
}

WebhooksConfig holds all webhook configurations.

type WingHeuristic added in v1.18.0

type WingHeuristic struct {
	// Wing is the canonical wing identifier to assign on match.
	Wing string `yaml:"wing"`
	// MatchChannelName contains lowercase substrings; if the hint (channel
	// name, group name, display name) contains any of them, this wing wins.
	MatchChannelName []string `yaml:"match_channel_name,omitempty"`
	// MatchGroupName is an alias for MatchChannelName for readability
	// when the hint comes from a messaging group name.
	MatchGroupName []string `yaml:"match_group_name,omitempty"`
}

WingHeuristic matches channel or group name patterns to a wing. The binary ships zero defaults — all heuristics are user-provided via YAML config. This is intentional: hardcoded locale or domain-specific keywords would be biased for open-source deployments.

Example YAML snippet under memory.hierarchy.heuristics:

heuristics:
  - wing: work
    match_channel_name: [work, job, office]
  - wing: family
    match_channel_name: [family, home, kids]

type WingResolution added in v1.18.0

type WingResolution struct {
	Wing       string               // normalized wing identifier, "" = none
	Confidence float64              // 0.0 - 1.0; 0 for default/disabled
	Source     WingResolutionSource // where the decision came from
}

WingResolution is what ContextRouter.Resolve returns. An empty Wing string is a valid, first-class result — it means "no wing", which maps to SQL NULL and matches the legacy behavior.

func (WingResolution) IsEmpty added in v1.18.0

func (w WingResolution) IsEmpty() bool

IsEmpty reports whether this resolution yielded no wing. Callers use this to decide whether to pass wing through to downstream memory operations or to leave them unscoped.

type WingResolutionSource added in v1.18.0

type WingResolutionSource string

WingResolutionSource identifies how a wing decision was reached. Useful for telemetry (ADR-008) and debugging.

const (
	// SourceMapped — explicit entry in channel_wing_map.
	SourceMapped WingResolutionSource = "mapped"
	// SourceHeuristic — derived from channel pattern matching.
	SourceHeuristic WingResolutionSource = "heuristic"
	// SourceDefault — no mapping and no heuristic match; wing is empty.
	SourceDefault WingResolutionSource = "default"
	// SourceDisabled — context routing is off by feature flag; wing is empty.
	SourceDisabled WingResolutionSource = "disabled"
)

type WorkerResult added in v1.17.0

type WorkerResult struct {
	TaskID   string
	Label    string
	Phase    CoordinatorPhase
	Result   string
	Error    error
	Duration time.Duration
}

WorkerResult holds the outcome of a single worker task.

type WorkerSpawner added in v1.17.0

type WorkerSpawner func(ctx context.Context, task WorkerTask, allowedTools []string) (string, error)

WorkerSpawner is the function signature for spawning a worker. The coordinator calls this for each task, passing the allowed tools. The implementation should create a subagent with only the specified tools.

type WorkerTask added in v1.17.0

type WorkerTask struct {
	// ID is a unique identifier for this task.
	ID string

	// Label is a human-readable description.
	Label string

	// Prompt is the task instruction for the worker.
	Prompt string

	// Phase determines the tool restrictions applied.
	Phase CoordinatorPhase

	// Timeout is the maximum duration for this task. Zero uses the default.
	Timeout time.Duration
}

WorkerTask describes a unit of work for a subagent within a phase.

type Workspace

type Workspace struct {
	// ID is the unique workspace identifier (slug, e.g. "personal", "work").
	ID string `yaml:"id"`

	// Name is the human-readable workspace name.
	Name string `yaml:"name"`

	// Description explains the workspace purpose.
	Description string `yaml:"description"`

	// Instructions are the custom system prompt for this workspace.
	// Overrides the global instructions when set.
	Instructions string `yaml:"instructions"`

	// Soul is the agent's persona/personality definition.
	// Personality traits, core truths, boundaries, vibe.
	// Injected as persona layer before operational instructions.
	Soul string `yaml:"soul,omitempty" json:"soul,omitempty"`

	// Model overrides the default LLM model.
	// Empty = use global default.
	Model string `yaml:"model"`

	// Language overrides the default language.
	// Empty = use global default.
	Language string `yaml:"language"`

	// Timezone overrides the default timezone.
	// Empty = use global default.
	Timezone string `yaml:"timezone"`

	// Trigger overrides the activation keyword for this workspace.
	// Empty = use global default.
	Trigger string `yaml:"trigger"`

	// Skills lists the skills available in this workspace.
	// Empty = use all globally enabled skills.
	Skills []string `yaml:"skills"`

	// TokenBudget overrides token limits for this workspace.
	// Nil = use global defaults.
	TokenBudget *TokenBudgetConfig `yaml:"token_budget,omitempty"`

	// MaxMessages overrides the session history limit.
	// 0 = use global default.
	MaxMessages int `yaml:"max_messages"`

	// ToolProfile specifies which tool profile to use for this workspace.
	// Options: minimal, coding, messaging, full, or custom profile name.
	// Empty = use global profile from tool_guard config.
	ToolProfile string `yaml:"tool_profile"`

	// Members lists the user JIDs assigned to this workspace.
	Members []string `yaml:"members"`

	// Groups lists the group JIDs assigned to this workspace.
	Groups []string `yaml:"groups"`

	// CreatedBy is the JID of whoever created this workspace.
	CreatedBy string `yaml:"created_by"`

	// CreatedAt is when the workspace was created.
	CreatedAt time.Time `yaml:"created_at"`

	// Active indicates if the workspace is enabled.
	Active bool `yaml:"active"`

	// Identity overrides the global identity for this workspace/agent.
	Identity *IdentityConfig `yaml:"identity,omitempty" json:"identity,omitempty"`

	// Channels routes all messages from these channels to this workspace.
	// E.g. ["slack", "telegram"] means all Slack and Telegram messages go here.
	Channels []string `yaml:"channels,omitempty" json:"channels,omitempty"`

	// MaxTurns overrides the max LLM turns for this workspace (0 = use global).
	MaxTurns int `yaml:"max_turns,omitempty" json:"max_turns,omitempty"`

	// RunTimeout overrides the max run time in seconds (0 = use global).
	RunTimeout int `yaml:"run_timeout,omitempty" json:"run_timeout,omitempty"`

	// Default marks this workspace as the default for unassigned users.
	// Only one workspace should be marked as default.
	Default bool `yaml:"default,omitempty" json:"default,omitempty"`

	// ToolsAllow overrides the allowed tools list for this workspace.
	ToolsAllow []string `yaml:"tools_allow,omitempty" json:"tools_allow,omitempty"`

	// ToolsDeny overrides the denied tools list for this workspace.
	ToolsDeny []string `yaml:"tools_deny,omitempty" json:"tools_deny,omitempty"`

	// Source indicates where this workspace was defined: "config", "api", "plugin".
	Source string `yaml:"source,omitempty" json:"source,omitempty"`
}

Workspace represents an isolated assistant profile. Each workspace has its own instructions, skills, model, and memory.

type WorkspaceConfig

type WorkspaceConfig struct {
	// DefaultWorkspace is the ID of the workspace used for unassigned contacts.
	DefaultWorkspace string `yaml:"default_workspace"`

	// Workspaces is the list of defined workspaces.
	Workspaces []Workspace `yaml:"workspaces"`
}

WorkspaceConfig holds the workspaces configuration.

func DefaultWorkspaceConfig

func DefaultWorkspaceConfig() WorkspaceConfig

DefaultWorkspaceConfig returns a minimal workspace configuration.

type WorkspaceContainment

type WorkspaceContainment struct {
	// Root is the absolute path of the workspace root.
	Root string

	// Enabled toggles containment enforcement (default: true).
	Enabled bool
}

WorkspaceContainment enforces that file operations stay within the workspace root.

func NewWorkspaceContainment

func NewWorkspaceContainment(root string) *WorkspaceContainment

NewWorkspaceContainment creates a containment checker for the given root directory.

func (*WorkspaceContainment) AssertNoSymlinkEscape

func (wc *WorkspaceContainment) AssertNoSymlinkEscape(path string) error

AssertNoSymlinkEscape checks that a path does not traverse through a symlink that points outside the workspace. Unlike AssertSandboxPath, this allows symlinks WITHIN the workspace but blocks those that escape.

func (*WorkspaceContainment) AssertSandboxPath

func (wc *WorkspaceContainment) AssertSandboxPath(path string) (string, error)

AssertSandboxPath validates that the given path resolves to within the workspace root. It resolves symlinks to prevent escape attacks and checks for path traversal. Returns the resolved absolute path or an error if containment is violated.

type WorkspaceManager

type WorkspaceManager struct {
	// contains filtered or unexported fields
}

WorkspaceManager manages workspaces and their member assignments. It provides workspace resolution for incoming messages and maintains isolated session stores per workspace.

func NewWorkspaceManager

func NewWorkspaceManager(globalCfg *Config, wsCfg WorkspaceConfig, logger *slog.Logger) *WorkspaceManager

NewWorkspaceManager creates a new workspace manager.

func (*WorkspaceManager) AssignGroup

func (wm *WorkspaceManager) AssignGroup(groupJID, wsID, assignedBy string) error

AssignGroup assigns a group to a workspace.

func (*WorkspaceManager) AssignUser

func (wm *WorkspaceManager) AssignUser(jid, wsID, assignedBy string) error

AssignUser assigns a user to a workspace.

func (*WorkspaceManager) Count

func (wm *WorkspaceManager) Count() int

Count returns the number of workspaces.

func (*WorkspaceManager) Create

func (wm *WorkspaceManager) Create(ws Workspace, createdBy string) error

Create creates a new workspace.

func (*WorkspaceManager) DefaultID added in v1.16.0

func (wm *WorkspaceManager) DefaultID() string

DefaultID returns the current default workspace ID.

func (*WorkspaceManager) Delete

func (wm *WorkspaceManager) Delete(wsID, deletedBy string) error

Delete removes a workspace (members go back to default).

func (*WorkspaceManager) DeleteSessionByID

func (wm *WorkspaceManager) DeleteSessionByID(sessionID string) bool

DeleteSessionByID removes a session by ID across all workspaces.

func (*WorkspaceManager) ExportSession

func (wm *WorkspaceManager) ExportSession(id string) *SessionExport

ExportSession exports a session's history and metadata by hash ID.

func (*WorkspaceManager) FindSessionByID

func (wm *WorkspaceManager) FindSessionByID(id string) *Session

FindSessionByID searches all workspace session stores for a session by its hash ID.

func (*WorkspaceManager) Get

func (wm *WorkspaceManager) Get(wsID string) (*Workspace, bool)

Get returns a workspace by ID.

func (*WorkspaceManager) GetForUser

func (wm *WorkspaceManager) GetForUser(jid string) (*Workspace, bool)

GetForUser returns the workspace assigned to a user JID.

func (*WorkspaceManager) GetSessionByID

func (wm *WorkspaceManager) GetSessionByID(sessionID string) (*Session, *Workspace)

GetSessionByID finds a session by ID (format "channel:chatID") across all workspaces.

func (*WorkspaceManager) List

func (wm *WorkspaceManager) List() []*Workspace

List returns all workspaces.

func (*WorkspaceManager) ListAllSessions

func (wm *WorkspaceManager) ListAllSessions() []SessionInfo

ListAllSessions returns session metadata from all workspaces.

func (*WorkspaceManager) ListSessionsForWorkspace added in v1.16.0

func (wm *WorkspaceManager) ListSessionsForWorkspace(wsID string) []SessionInfo

ListSessionsForWorkspace returns sessions for a specific workspace only.

func (*WorkspaceManager) RebuildMaps added in v1.16.0

func (wm *WorkspaceManager) RebuildMaps()

RebuildMaps rebuilds the user, group, and channel maps from all workspaces. Call after Update() when member/group/channel assignments may have changed.

func (*WorkspaceManager) Resolve

func (wm *WorkspaceManager) Resolve(channel, chatID, senderJID string, isGroup bool) *ResolvedWorkspace

Resolve determines which workspace a message belongs to and returns the workspace along with its isolated session.

func (*WorkspaceManager) ResolveByNameOrID added in v1.16.0

func (wm *WorkspaceManager) ResolveByNameOrID(input string) (*Workspace, bool)

Resolve returns a workspace by ID first, then falls back to normalized name match. This allows lookups like "agentdev" to find an agent named "AgentDev" even if the ID is different (e.g. "tester"). Returns the workspace and its canonical ID so callers can use the real ID for subsequent operations.

func (*WorkspaceManager) SessionCount

func (wm *WorkspaceManager) SessionCount() int

SessionCount returns the total number of sessions across all workspaces.

func (*WorkspaceManager) SessionCountForWorkspace added in v1.16.0

func (wm *WorkspaceManager) SessionCountForWorkspace(wsID string) int

SessionCountForWorkspace returns the number of active sessions in a workspace.

func (*WorkspaceManager) SetDefault added in v1.16.0

func (wm *WorkspaceManager) SetDefault(wsID string) error

SetDefault marks a workspace as the default and unmarks all others.

func (*WorkspaceManager) SetPersistence

func (wm *WorkspaceManager) SetPersistence(p SessionPersister)

SetPersistence propagates a SessionPersister to all workspace session stores and stores it for newly created workspaces.

func (*WorkspaceManager) StartPruners

func (wm *WorkspaceManager) StartPruners(ctx context.Context)

StartPruners starts session pruning for all workspace session stores.

func (*WorkspaceManager) UnassignUser

func (wm *WorkspaceManager) UnassignUser(jid string)

UnassignUser removes a user from their workspace (goes to default).

func (*WorkspaceManager) Update

func (wm *WorkspaceManager) Update(wsID string, fn func(ws *Workspace)) error

Update modifies a workspace's settings.

Source Files

Directories

Path Synopsis
kg
Package kg implements a bitemporal knowledge graph backed by SQLite.
Package kg implements a bitemporal knowledge graph backed by SQLite.
patterns
Package patterns defines the YAML schema and loader for extraction rule sets.
Package patterns defines the YAML schema and loader for extraction rule sets.
Package memory – embeddings.go implements embedding generation for semantic search.
Package memory – embeddings.go implements embedding generation for semantic search.
Package security – audit.go implements security auditing for DevClaw configuration.
Package security – audit.go implements security auditing for DevClaw configuration.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL