Conversation
There was a problem hiding this comment.
💡 Codex Review
anna/internal/channel/telegram/model.go
Lines 88 to 89 in cf02a6d
/model uses channelForChat(c) with b.cmd, but b.cmd is initialized against the fallback pool in New(), not the per-message pool returned by resolvePool. In chats routed to a non-default agent (DM default-agent override or group chat_agents mapping), this makes model switches rotate/update the wrong pool while reporting success, so subsequent replies from the routed agent keep using the old model.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| sharedTools = append(sharedTools, | ||
| memorytool.NewGrepTool(memoryEngine), | ||
| memorytool.NewDescribeTool(memoryEngine), | ||
| memorytool.NewExpandTool(memoryEngine), | ||
| ) |
There was a problem hiding this comment.
Wire user_memory tool into runtime tool registration
Pool setup only registers grep_memory, describe_memory, and expand_memory from the memory tool package before constructing all agent pools; there is no runtime registration of user_memory. That means the assistant never receives a callable user_memory tool definition in production chats, so per-user memory in user_agent_memory cannot actually be read/written through tool calls.
Useful? React with 👍 / 👎.
Add ConfigureDB(db *sql.DB) that enables WAL mode and foreign keys on an existing connection. OpenDB now delegates to ConfigureDB internally. This allows multiple packages to share a single *sql.DB without re-opening the database. Entire-Checkpoint: b45afb0493dd
…engine Add NewEngineFromDB(db, summarizer, opts) that accepts an existing *sql.DB without opening a new one. The engine tracks ownership via ownsDB flag — Close() only closes the connection when the engine created it (via NewEngine). NewEngine now delegates to NewEngineFromDB. Entire-Checkpoint: b9cac0823ab3
Add table definitions for settings, providers, agents, channels, users, chat_agents, and user_agent_memory. These tables support the new normalized config model and per-user-per-agent memory. Entire-Checkpoint: a1c71faed2ba
Add nullable agent_id TEXT and user_id INTEGER columns to support scoping conversations to specific agents and users. Entire-Checkpoint: cf309e08b635
Add nullable agent_id TEXT and user_id INTEGER columns to support routing scheduled jobs to specific agents and users. Entire-Checkpoint: 4b1a19c6157f
…t schema Update main.sql to import all new table schemas. Generate Atlas migration 20260316075129_multi_user_agent that creates the 7 new tables and adds agent_id/user_id columns to conversations and scheduler_jobs. Regenerate sqlc models and queries. Entire-Checkpoint: cb2c53668218
Add CRUD query files for settings, providers, agents, channels, users, chat_agents, and user_agent_memory tables. Regenerate sqlc to produce Go query functions for all new operations. Entire-Checkpoint: 83fa95ecafb0
Add Store interface with typed CRUD methods for providers, agents, channels, users, chat agents, user agent memory, and settings. Define lean domain types (Provider, Agent, Channel, User) and a Snapshot type for downstream consumption. Entire-Checkpoint: a4b5c3f55210
DB-backed Store implementation converting between domain types and sqlc types. Includes env var fallbacks for provider API keys (ANTHROPIC_API_KEY, OPENAI_API_KEY), Snapshot assembly from agent + provider + settings, and MaxOpenConns(1) for SQLite. Entire-Checkpoint: 506233a6274b
Seeds default "anthropic" provider (API key from env) and default "anna" agent with default soul system prompt when the DB is empty. Idempotent — skips if providers/agents already exist. Entire-Checkpoint: 2587ca558f62
Replace YAML-based config loading/saving with DB store. On startup: create ANNA_HOME, open anna.db, seed defaults. Config GET/POST endpoints now read/write via Store. Add newStreamProviderFromCreds helper. Entire-Checkpoint: c3506a2623d7
Add ResolveModelID, ResolveModel, ResolveModelTier methods on Snapshot for tier-based model resolution. Add SkillsPath and LogPath helpers. Entire-Checkpoint: b2f5ee71e57f
- Delete config.Load(), LoadFrom(), Config struct, state.go, channels.go - Keep paths.go (AnnaHome, DBPath, CachePath), model.go types, config subtypes - Convert all callers to use DB-backed Store + Snapshot: - commands.go: setup() opens DB, creates Store, builds Snapshot - models.go: all subcommands use openStore() + defaultSnapshot() - skills.go: use Store for workspace path resolution - plugin.go: use settings table for plugin storage - gateway.go: load channel configs from DB JSON blobs - chat.go: use snap instead of cfg - Update tests: remove YAML/env loading tests, add Snapshot/model resolution tests - Remove caarlos0/env and gopkg.in/yaml.v3 from config package Entire-Checkpoint: c2abfdc59c8f
Entire-Checkpoint: 4672728b1077
SetupWorkspace(agentID, basePath) creates workspaces/{agentID}/skills/
directory structure for per-agent workspace isolation.
Entire-Checkpoint: ec279f7d1eac
Pool now stores which agent it belongs to, used for logging and session scoping. AgentID() getter exposes the value. Entire-Checkpoint: 5330f5d30daf
Move newRunnerFactory from cmd/anna/commands.go to internal/agent/factory.go as NewRunnerFactory so PoolManager can use it. Commands.go and tests now call the exported version. Entire-Checkpoint: 886bf0843230
Clarify that the workspace parameter to NewTool should be a per-agent workspace path for skill isolation. Entire-Checkpoint: 7b27a0c27754
PoolManager maps agentID to Pool. StartAll reads enabled agents from the config Store, sets up per-agent workspaces, creates runner factories, and starts reaper goroutines. Supports shared and per-agent tools. Entire-Checkpoint: 8d474829aba4
setup() now creates a PoolManager from enabled agents in DB instead of a single Pool. setupResult holds both poolManager and pool (default agent's pool) for backward compatibility with CLI/channel code. Skills tool creation moved into PoolManager (per-agent). Shared tools (scheduler, memory retrieval, plugins) passed via WithSharedExtraTools. Entire-Checkpoint: 8d9f9ba88d41
Entire-Checkpoint: 4a8c0fdb2f49
Add ResolveUser function to internal/channel/identity.go that upserts a user by external ID + platform, returning the user record from the config Store. This is Phase 4, Task 4.1. Entire-Checkpoint: db2c4ffb122e
Add ChatContext type and ResolveAgent function that routes messages to the correct agent: group chat uses chat_agents mapping, DM uses user's default_agent_id, fallback to first enabled agent. Phase 4, Task 4.2. Entire-Checkpoint: d843004789a2
Add BuildSessionKey(agentID, platform, externalUserID, channelContext)
that produces keys in the format {agentID}:{platform}:{userID}:{context}.
Phase 4, Task 4.3.
Entire-Checkpoint: 6f61bb8b1e33
- Add AgentID and UserID fields to memory.SessionInfo - Update CreateConversationFull query to accept agent_id and user_id - Add UpdateConversationAgentUser query - Update SaveInfo to write agent_id/user_id when creating conversations - Update convToSessionInfo to read agent_id/user_id - Update Pool.createSessionLocked to set agentID from pool and accept userID Phase 4, Tasks 4.4 + 4.5. Entire-Checkpoint: d30248bf8b47
Add UserMemoryStore wrapping config.Store for reading and writing user_agent_memory content. Phase 4, Task 4.6. Entire-Checkpoint: f50a56189c2e
Add UserMemoryTool that agents can use to read/write per-user notes via the user_agent_memory table. Supports 'read' and 'write' actions. Replaces the old SOUL.md/USER.md file editing. Phase 4, Task 4.7. Entire-Checkpoint: c025b5669ecb
Add new BuildSystemPromptFromDB function that composes system prompts from DB fields (agents.system_prompt + user_agent_memory.content) instead of SOUL.md/USER.md files. The old BuildSystemPrompt is kept for backward compatibility but marked as deprecated. Phase 4, Task 4.8. Entire-Checkpoint: 575434625807
User agent memory is now loaded from the DB and injected into the system prompt when a runner is created for a session. This means the agent always has context about the user from the first message, not just via tool calls. Key changes: - NewRunnerFunc now accepts RunnerParams (model + user memory) - Factory builds base prompt once, injects user memory per-session - Pool loads user memory in getOrCreateRunner via UserMemoryStore - PoolManager creates and wires UserMemoryStore into all pools - Updated user_memory tool description with recommended structure - Updated prompt framing to guide agent on using user preferences
…y write-only System prompt is now composed in three clear layers: 1. Basic system prompt (embedded default, override by SYSTEM.md in workspace) 2. Agent soul prompt (DB system_prompt, override by SOUL.md in workspace) 3. User memory (always present from DB, updated via write-only user_memory tool) Key changes: - Removed old BuildSystemPrompt, InjectUserMemory, promptMemories, memoriesTmpl - BuildSystemPromptFromDB now handles all 3 layers with file overrides - user_memory tool simplified to write-only (no read — content is always in prompt) - Factory builds full prompt per-session (not cached base + inject) - GoRunner fallback uses BuildSystemPromptFromDB - Removed unused embedded templates (defaultSoul, defaultUser, memoriesTemplate)
user.md and memories.md.tmpl are no longer referenced after the 3-layer prompt refactor. Soul/user identity is now managed via DB (agents.system_prompt + user_agent_memory) with file overrides (SOUL.md, SYSTEM.md).
- handoff.md: Add post-Phase 6 section documenting 3-layer prompt, write-only user_memory tool, per-session injection, deleted templates - tasks.md: Add tasks 7.1-7.8 for system prompt & user memory work - plan.md: Update system prompt assembly, assumptions, and final status Entire-Checkpoint: 4b99fcd76bd1
Cover all CRUD operations for providers, agents, channels, users, chat agents, user agent memory, settings, snapshot assembly, and seed defaults. Raises total coverage from 49.5% to 51.4%.
📊 Coverage ReportTotal coverage: 50.5% Per-package breakdown |
…ched_) Group tables by domain for clarity: - settings_* for config: providers, agents, channels, users, channel_agents - ctx_* for context/LCM: conversations, messages, message_parts, summaries, summary_messages, summary_parents, items, agent_memory - sched_* for scheduling: jobs Delete old migrations and generate fresh baseline. Regenerate sqlc and update all Go references to match new struct names. Entire-Checkpoint: 1cbf898b240c
- Remove deprecated baseUrl from tsconfig.json, fix collections path - Fix unbound-method lint warning in start.ts - Remove volatile structure sections from CLAUDE.md files - Update docs content and dependencies
- Move Google Fonts @import before Tailwind to fix PostCSS order error - Pre-include fumadocs/React deps in SSR optimizeDeps to prevent runtime dep discovery that resets React's dispatcher state - Dedupe react/react-dom to ensure single instance - Fix wrangler compatibility_date to match installed runtime
add session plan/task records for the recent lcm, sqlite-only persistence, and plugin system work. move ignores into .agents/.gitignore so local agent binaries and logs stay untracked while session artifacts are versioned.
- Remove ProviderID from Agent struct; model fields now use
{provider}/{model} format (e.g. anthropic/claude-sonnet-4-6),
allowing each tier (model, model_strong, model_fast) to use
a different provider.
- Add Snapshot.Providers map for per-tier credential resolution,
ParseModelRef() helper, and ResolveProviderCreds() method.
- Drop provider_id column from settings_agents (Atlas migration).
- Overhaul admin UI to match old onboard.html UX: provider presets
with default base URLs, structured channel forms (Telegram/QQ/Feishu),
model autocomplete from fetched models, scheduler schedule-type
radio toggle, and inline job enable/disable toggles.
Remove `run:` prefix from task names (chat, stream, gateway) and add onboard task for opening the admin panel.
Provider, Agent, Channel, and User structs were missing JSON tags, causing Go to serialize fields as PascalCase (e.g. APIKey, BaseURL) instead of snake_case (api_key, base_url) expected by the admin UI.
- Use x-model for provider fields instead of providerEdits indirection - Send current (unsaved) credentials with fetch models request - Track fetching/model state on provider objects for Alpine reactivity - Add collapsible models list to provider cards after fetching
- Replace static users table with interactive user cards - Add default agent selector per user with save - Add expandable per-user agent memory viewer/editor - Add backend APIs: user update, list/set/delete user memories - Add ListUserAgentMemoriesByUser sqlc query
- Replace static sessions table with clickable session cards
- Add session detail view with back navigation, metadata badges
- Add chat-style message timeline (user/assistant/tool messages)
- Collapsible thinking blocks and tool call/result panels
- Add GET /api/sessions/{id} and /api/sessions/{id}/messages APIs
- Serialize raw DB rows to preserve real message timestamps
- Add relative time formatting (e.g. "8h ago", "3d ago")
- Add GET /api/sessions/{id}/system-prompt endpoint
- Build full system prompt on the fly via BuildSystemPromptFromDB
- Includes agent identity, user memory, skills, and project context
- Collapsible system prompt panel at top of session detail view
Merge 4 separate memory tools (memory_grep, memory_describe, memory_expand, user_memory) into a unified `memory` tool with an `action` parameter, following the same pattern as the scheduler tool. Actions: grep, describe, expand, user_memory_update. Also fixes the bug where user_memory was never registered as a tool: - Add userID/agentID context propagation (memory.WithUserID/WithAgentID) - Pool.Chat() sets both from session metadata (loaded from DB) - Pool.ResolveSession/RotateSession/CreateSession accept optional userID - Telegram channel passes resolved userID to session creation - CLI auto-creates a "cli" user for local chat sessions - QQ/Feishu pass through without userID (backward compat)
Add user resolution and agent routing to QQ bot, matching the Telegram channel pattern: - Add WithPoolManager/WithStore options to qq.New() - Add resolvePool: resolve user → agent → pool per message - Add buildSessionKey: always use agent.BuildSessionKey (no legacy fallback) - Pass resolved userID to pool.ResolveSession for per-user memory - Use resolved pool for streaming (not hardcoded b.pool) - Wire poolManager and store in gateway
resolvePool now returns an error instead of silently falling back to the default pool. No legacy single-agent mode — resolution must succeed. Both channels: resolvePool returns (*Pool, int64, error). All callers handle the error and report it to the user. Telegram buildSessionKey also drops the legacy channelForChat fallback.
Add user resolution and agent routing to Feishu bot, matching the Telegram and QQ channel pattern: - Add WithPoolManager/WithStore options to feishu.New() - Add resolvePool: resolve user → agent → pool per message, no fallback - Add buildSessionKey: always use agent.BuildSessionKey - Pass resolved pool and userID through handler → command → stream chain - Wire poolManager and store in gateway
The /new, /compact, and /model commands were going through the Commander/PoolAdapter which doesn't pass userID, causing sessions to be created with UserID=0 and user memory to never load. Fix: handleCommand and handleModelCommand now receive pool+userID directly and bypass the Commander for session operations. Handlers call resolvePool once and pass both pool and userID through.
switchModelByName and switchModelByIdx were using the Commander which doesn't pass userID, causing sessions created by model switches to have UserID=0. Now both use resolvePool directly and pass userID to pool.RotateSession. Also removes dead channelForChat function (no longer used).
…ution Add channel.ResolvedChat struct and channel.Resolve() function that performs the full user → agent → pool → session key resolution once per incoming message. All three channels (Telegram, QQ, Feishu) now call resolve() once and thread the result through handlers, eliminating duplicated resolvePool/buildSessionKey/resolveSession methods and the error-prone (pool, userID, sessionKey) parameter threading.
- Remove Commander, PoolAdapter, SessionPool, SessionInfo from channel package — all dead code after ResolvedChat was introduced - Add ResolvedChat.Chat() — resolves session + streams in one call, eliminating session ID threading from all channel handlers - Add shared HandleCommand() for /start, /help, /new, /compact, /whoami — each channel delegates to it, only handling /model themselves - Remove `pool` field from all Bot structs — only poolManager + store - Simplify Bot constructors: required params instead of WithPoolManager/ WithStore options - Remove `cmd` field from all Bot structs — replaced by shared functions
Rewrite README and all docs to reflect the DB-backed config system, multi-agent support, multi-user routing, admin panel, and 3-layer system prompt architecture.
Summary
Replace anna's single-user/single-agent architecture with N×N multi-user × multi-agent support.
anna.dbreplacesconfig.yaml,state.yaml, andmemory.db. 7 new tables (providers, agents, channels, users, settings, chat_agents, user_agent_memory) with normalized schema.config.Storeinterface replaces YAML config loading.anna onboardseeds defaults and opens admin UI.PoolManagermanages one Pool per enabled agent with per-agent workspace isolation, runner factory, and skills.user_agent_memorytable +user_memorytool replaces sharedSOUL.md/USER.mdfiles. System prompt composed from DB fields./agentTelegram command for DM/group agent selection.--agentCLI flag.internal/admin/) + Alpine.js SPA for full CRUD of providers, agents, channels, users, sessions, scheduler, settings. Available viaanna onboardandanna gateway --admin-port.agent_id/user_id, routed to correct agent pool via PoolManager.96 files changed, 6270 insertions, 3279 deletions across 6 phases (44 commits).
Test plan
go test -race(unit tests)anna onboardend-to-end on fresh ANNA_HOME/agentswitching between agentsanna chat --agent <name>uses correct agent identity