Files
Memoh/AGENTS.md
T
Menci d5b410d7e3 refactor(workspace): new workspace v3 container architecture (#244)
* feat(mcp): workspace container with bridge architecture

Migrate MCP containers to use UDS-based bridge communication instead of
TCP gRPC. Containers now mount runtime binaries and Unix domain sockets
from the host, eliminating the need for a dedicated MCP Docker image.

- Remove Dockerfile.mcp and entrypoint.sh in favor of standard base images
- Add toolkit Dockerfile for building MCP binary separately
- Containers use bind mounts for /opt/memoh (runtime) and /run/memoh (UDS)
- Update all config files with new runtime_path and socket_dir settings
- Support custom base images per bot (debian, alpine, ubuntu, etc.)
- Legacy container detection and TCP fallback for pre-bridge containers
- Frontend: add base image selector in container creation UI

* feat(container): SSE progress bar for container creation

Add real-time progress feedback during container image pull and creation
using Server-Sent Events, without breaking the existing synchronous JSON
API (content negotiation via Accept header).

Backend:
- Add PullProgress/LayerStatus types and OnProgress callback to
  PullImageOptions (containerd service layer)
- DefaultService.PullImage polls ContentStore.ListStatuses every 500ms
  when OnProgress is set; AppleService ignores it
- CreateContainer handler checks Accept: text/event-stream and switches
  to SSE branch: pulling → pull_progress → creating → complete/error

Frontend:
- handleCreateContainer/handleRecreateContainer use fetch + SSE instead
  of the SDK's synchronous postBotsByBotIdContainer
- Progress bar shows layer-level pull progress (offset/total) during
  pulling phase and indeterminate animation during creating phase
- i18n keys added for pullingImage and creatingContainer (en/zh)

* fix(container): clear stale legacy route and type create SSE

* fix(ci): resolve lint errors and arm64 musl node.js download

- Fix unused-receiver lint: rename `s` to `_` on stub methods in
  manager_legacy_test.go
- Fix sloglint: use slog.DiscardHandler instead of
  slog.NewTextHandler(io.Discard, nil)
- Handle missing arm64 musl Node.js builds: unofficial-builds.nodejs.org
  does not provide arm64 musl binaries, fall back to glibc build

* fix(lint): address errcheck, staticcheck, and gosec findings

- Discard os.Setenv/os.Remove return values explicitly with _
- Use omitted receiver name instead of _ (staticcheck ST1006)
- Tighten directory permissions from 0o755 to 0o750 (gosec G301)

* fix(lint): sanitize socket path to satisfy gosec G703

filepath.Clean the env-sourced socket path before os.Remove
to avoid path-traversal taint warning.

* fix(lint): use nolint directive for gosec G703 on socket path

filepath.Clean does not satisfy gosec's taint analysis. The socket
path comes from MCP_SOCKET_PATH env (operator-configured) or a
compiled-in default, not from end-user input.

* refactor: rename MCP container/bridge to workspace/bridge

Split internal/mcp/ to separate container lifecycle management from
Model Context Protocol connections, eliminating naming confusion:

- internal/mcp/ (container mgmt) → internal/workspace/
- internal/mcp/mcpclient/ → internal/workspace/bridge/
- internal/mcp/mcpcontainer/ → internal/workspace/bridgepb/
- cmd/mcp/ → cmd/bridge/
- config: MCPConfig → WorkspaceConfig, [mcp] → [workspace]
- container prefix: mcp-{id} → workspace-{id}
- labels: mcp.bot_id → memoh.bot_id, add memoh.workspace=v1
- socket: mcp.sock → bridge.sock, env BRIDGE_SOCKET_PATH
- runtime: /opt/memoh/runtime/mcp → /opt/memoh/runtime/bridge
- devenv: mcp-build.sh → bridge-build.sh

Legacy containers (mcp- prefix) detected by container name prefix
and handled via existing fallback path.

* fix(container): use memoh.workspace=v3 label value

* refactor(container): drop LegacyBotLabelKey, infer bot ID from container name

Legacy containers use mcp-{botID} naming, so bot ID can be derived
via TrimPrefix instead of looking up the mcp.bot_id label.

* fix(workspace): resolve containers via manager and drop gateway container ID

* docs: fix stale mcp references in AGENTS.md and DEPLOYMENT.md

* refactor(workspace): move container lifecycle ownership into manager

* dev: isolate local devenv from prod config

* toolkit: support musl node runtime

* containerd: fix fallback resolv.conf permissions

* web: preserve container create progress on completion

* web: add bot creation wait hint

* fix(workspace): preserve image selection across recreate

* feat(web): shorten default docker hub image refs

* fix(container): address code review findings

- Remove synchronous CreateContainer path (SSE-only now)
- Move flusher check before WriteHeader to avoid committed 200 on error
- Fix legacy container IP not cached via ensureContainerAndTask path
- Add atomic guard to prevent stale pull_progress after PullImage returns
- Defensive copy for tzEnv slice to avoid mutating shared backing array
- Restore network failure severity in restartContainer (return + Error)
- Extract duplicate progress bar into ContainerCreateProgress component
- Fix codesync comments to use repo-relative paths
- Add SaaS image validation note and kernel version comment on reaper

* refactor(devenv): extract toolkit install into shared script

Unify the Node.js + uv download logic into docker/toolkit/install.sh,
used by the production Dockerfile and runnable locally for dev.

Dev environment no longer bakes toolkit into the Docker image — it is
volume-mounted from .toolkit/ instead, so wrapper script changes take
effect immediately without rebuilding. The entrypoint checks for the
toolkit directory and prints a clear error if missing.

* fix(ci): address go ci failures

* chore(docker): remove unused containerd image

* refactor(config): rename workspace image key

* fix(workspace): fix legacy container data loss on migration and stop swallowing errors

Three root causes were identified and fixed:

1. Delete() used hardcoded "workspace-" prefix to look up legacy "mcp-"
   containers, causing GetContainer to return NotFound. CleanupBotContainer
   then silently skipped the error and deleted the DB record without ever
   calling PreserveData. Fix: resolve the actual container ID via
   ContainerID() (DB → label → scan) before operating.

2. Multiple restore error paths were silently swallowed (logged as Warn
   but not returned), so the user saw HTTP 200/204 with no data and no
   error. Fix: all errors in the preserve/restore chain now block the
   workflow and propagate to the caller.

3. tarGzDir used cached DirEntry.Info() for tar header size, which on
   overlayfs can differ from the actual file size, causing "archive/tar:
   write too long". Fix: open the file first, Fstat the fd for a
   race-free size, and use LimitReader as a safeguard.

Also adds a "restoring" SSE phase so the frontend shows a progress
indicator ("Restoring data, this may take a while...") during data
migration on container recreation.

* refactor(workspace): single-point container ID resolution

Replace the `containerID func(string) string` field with a single
`resolveContainerID(ctx, botID)` method that resolves the actual
container ID via DB → label → scan → fallback. All ~16 lookup
callsites across manager.go, dataio.go, versioning.go, and
manager_lifecycle.go now go through this single resolver, which
correctly handles both legacy "mcp-" and new "workspace-" containers.

Only `ensureBotWithImage` inlines `ContainerPrefix + botID` for
creating brand-new containers — every other path resolves dynamically.

* fix(web): show progress during data backup phase of container recreate

The recreate flow (delete with preserve_data + create with restore_data)
blocked on the DELETE call while backing up /data with no progress
indication. Add a 'preserving' phase to the progress component so
users see "正在备份数据..." instead of an unexplained hang.

* chore: remove [MYDEBUG] debug logging

Clean up all 112 temporary debug log statements added during the
legacy container migration investigation. Kept only meaningful
warn-level logs for non-fatal errors (network teardown, rename
failures).
2026-03-18 15:19:09 +08:00

18 KiB

AGENTS.md

Project Overview

Memoh is a multi-member, structured long-memory, containerized AI agent system platform. Users can create AI bots and chat with them via Telegram, Discord, Lark (Feishu), Email, and more. Every bot has an independent container and memory system, allowing it to edit files, execute commands, and build itself — providing a secure, flexible, and scalable solution for multi-bot management.

Architecture Overview

The system consists of four core services:

Service Tech Stack Port Description
Server (Backend) Go + Echo 8080 Main service: REST API, auth, database, container management
Agent Gateway Bun + Elysia 8081 AI chat gateway: handles chat requests, tool execution, and SSE streaming
Web (Frontend) Vue 3 + Vite 8082 Management UI: visual configuration for Bots, Models, Channels, etc.
Browser Gateway Bun + Elysia + Playwright 8083 Browser automation service: headless browser actions for bots

Infrastructure dependencies:

  • PostgreSQL — Relational data storage
  • Qdrant — Vector database for memory semantic search
  • Containerd — Container runtime providing isolated environments per bot (Linux); Apple Virtualization on macOS

Tech Stack

Backend (Go)

  • Framework: Echo (HTTP)
  • Dependency Injection: Uber FX
  • Database Driver: pgx/v5
  • Code Generation: sqlc (SQL → Go)
  • API Docs: Swagger/OpenAPI (swaggo)
  • Containers: containerd v2 (Linux), Apple Virtualization (macOS)

Agent Gateway & Agent Library (TypeScript)

  • Runtime: Bun
  • Framework: Elysia (gateway), Vercel AI SDK (agent core)
  • AI Providers: Anthropic, OpenAI, Google (via Vercel AI SDK)
  • Tools: MCP, Web Search, Subagent, Skill

Frontend (TypeScript)

  • Framework: Vue 3 (Composition API)
  • Build Tool: Vite
  • State Management: Pinia + Pinia Colada
  • UI: Tailwind CSS 4 + custom component library (@memoh/ui) + Reka UI
  • i18n: vue-i18n
  • Markdown: markstream-vue + Shiki + Mermaid + KaTeX
  • Package Manager: pnpm monorepo

Tooling

  • Task Runner: mise
  • Package Managers: pnpm (frontend monorepo), Go modules (backend)
  • Linting: ESLint + typescript-eslint + vue-eslint-parser
  • Testing: Vitest
  • Typo Checker: typos
  • Version Management: bumpp
  • SDK Generation: @hey-api/openapi-ts

Project Structure

Memoh/
├── cmd/                        # Go application entry points
│   ├── agent/                  #   Main backend server (main.go)
│   ├── bridge/                 #   Bridge server binary (in-container gRPC, template/)
│   └── memoh/                  #   Unified binary wrapper (Cobra CLI)
├── internal/                   # Go backend core code (domain packages)
│   ├── accounts/               #   User account management (CRUD, password hashing)
│   ├── attachment/             #   Attachment normalization (MIME types, base64)
│   ├── auth/                   #   JWT authentication middleware and utilities
│   ├── bind/                   #   Channel identity-to-user binding code management
│   ├── boot/                   #   Runtime configuration provider (container backend detection)
│   ├── bots/                   #   Bot management (CRUD, lifecycle)
│   ├── browsercontexts/        #   Browser context management (CRUD)
│   ├── bun/                    #   Bun runtime manager (agent gateway process lifecycle)
│   ├── channel/                #   Channel adapter system (Telegram, Discord, Feishu, Local, Email)
│   ├── config/                 #   Configuration loading and parsing (TOML)
│   ├── containerd/             #   Container runtime abstraction (containerd / Apple Virtualization)
│   ├── conversation/           #   Conversation management and flow resolver
│   ├── db/                     #   Database connection and migration utilities
│   │   └── sqlc/               #   ⚠️ Auto-generated by sqlc — DO NOT modify manually
│   ├── email/                  #   Email provider and outbox management (Mailgun, generic SMTP)
│   ├── embedded/               #   Embedded filesystem assets (web, agent, bun)
│   ├── embeddings/             #   Embedding model resolver
│   ├── handlers/               #   HTTP request handlers (REST API endpoints)
│   ├── healthcheck/            #   Health check adapter system (MCP, channel checkers)
│   ├── heartbeat/              #   Heartbeat scheduling service (cron-based)
│   ├── identity/               #   Identity type utilities (human vs bot)
│   ├── inbox/                  #   Bot inbox service (notifications, triggers)
│   ├── logger/                 #   Structured logging (slog)
│   ├── mcp/                    #   MCP protocol manager (connections, OAuth, tool gateway)
│   ├── workspace/              #   Workspace container lifecycle (bridge client, protobuf)
│   ├── media/                  #   Content-addressed media asset service
│   ├── memory/                 #   Long-term memory system (Qdrant, BM25, LLM extraction)
│   ├── message/                #   Message persistence and event publishing
│   ├── models/                 #   LLM model management (CRUD, variants)
│   ├── policy/                 #   Access policy resolution (guest access, bot type)
│   ├── preauth/                #   Pre-authentication key management
│   ├── providers/              #   LLM provider management (OpenAI, Anthropic, etc.)
│   ├── prune/                  #   Text pruning utilities (truncation with head/tail)
│   ├── schedule/               #   Scheduled task service (cron)
│   ├── searchengines/          #   Search engine abstraction (reserved)
│   ├── searchproviders/        #   Search engine provider management (Brave, etc.)
│   ├── server/                 #   HTTP server wrapper (Echo setup, middleware, shutdown)
│   ├── settings/               #   Bot settings management
│   ├── storage/                #   Storage provider interface (filesystem, container FS)
│   ├── subagent/               #   Sub-agent management (CRUD)
│   └── version/                #   Build-time version information
├── apps/                       # Application services
│   ├── agent/                  #   Agent Gateway (Bun/Elysia)
│   │   └── src/
│   │       ├── index.ts        #     Elysia server entry point
│   │       ├── modules/        #     Route modules (chat, stream, trigger)
│   │       ├── middlewares/     #     CORS, error handling, bearer auth
│   │       ├── utils/          #     SSE utilities
│   │       └── models.ts       #     Zod request schemas
│   ├── browser/                #   Browser Gateway (Bun/Elysia/Playwright)
│   │   └── src/
│   │       ├── index.ts        #     Elysia server entry point
│   │       ├── browser.ts      #     Playwright browser lifecycle
│   │       ├── modules/        #     Route modules (action, context, devices)
│   │       ├── middlewares/     #     CORS, error handling, bearer auth
│   │       ├── types/          #     TypeScript type definitions
│   │       ├── storage.ts      #     Browser context storage
│   │       └── models.ts       #     Zod request schemas
│   └── web/                    #   Main web app (@memoh/web, Vue 3)
├── packages/                   # Shared TypeScript libraries
│   ├── agent/                  #   Core agent library (@memoh/agent)
│   │   └── src/
│   │       ├── agent.ts        #     Agent creation and streaming logic
│   │       ├── model.ts        #     Model configuration and creation
│   │       ├── tool-loop.ts    #     Tool execution loop
│   │       ├── sential.ts      #     Sential (sentinel) logic
│   │       ├── tools/          #     Tool implementations (MCP, web, subagent, skill)
│   │       ├── prompts/        #     System/heartbeat/schedule/subagent prompts
│   │       ├── types/          #     TypeScript type definitions
│   │       └── utils/          #     Attachments, headers, filesystem utilities
│   ├── ui/                     #   Shared UI component library (@memoh/ui)
│   ├── sdk/                    #   TypeScript SDK (@memoh/sdk, auto-generated from OpenAPI)
│   ├── cli/                    #   CLI tool (@memoh/cli, Commander.js)
│   └── config/                 #   Shared configuration utilities (@memoh/config)
├── spec/                       # OpenAPI specifications (swagger.json, swagger.yaml)
├── db/                         # Database
│   ├── migrations/             #   SQL migration files
│   └── queries/                #   SQL query files (sqlc input)
├── conf/                       # Configuration templates (app.example.toml, app.docker.toml, app.apple.toml, app.windows.toml)
├── devenv/                     # Dev environment (docker-compose, dev Dockerfiles, app.dev.toml, bridge-build.sh, server-entrypoint.sh)
├── docker/                     # Production Docker (Dockerfiles, entrypoints, nginx.conf, docker-compose.yml, docker-compose.cn.yml)
├── docs/                       # Documentation site
├── scripts/                    # Utility scripts (db, release, install)
├── docker-compose.yml          # Docker Compose orchestration (production)
├── mise.toml                   # mise tasks and tool version definitions
├── sqlc.yaml                   # sqlc code generation config
├── openapi-ts.config.ts        # SDK generation config (@hey-api/openapi-ts)
├── bump.config.ts              # Version bumping config (bumpp)
├── vitest.config.ts            # Test framework config (Vitest)
├── tsconfig.json               # TypeScript monorepo config
├── eslint.config.mjs           # ESLint config
└── typos.toml                  # Typo checker config

Development Guide

Prerequisites

  1. Install mise
  2. Install toolchains and dependencies: mise install
  3. Initialize the project: mise run setup
  4. Start the dev environment: mise run dev

Common Commands

Command Description
mise run dev Start the containerized dev environment (all services)
mise run dev:down Stop the dev environment
mise run dev:logs View dev environment logs
mise run dev:restart Restart a service (e.g. -- server)
mise run setup Copy config + install dependencies
mise run sqlc-generate Regenerate Go code after modifying SQL files
mise run swagger-generate Generate Swagger documentation
mise run sdk-generate Generate TypeScript SDK (depends on swagger-generate)
mise run db-up Initialize and migrate the database
mise run db-down Drop the database
mise run build-embedded-assets Build and stage embedded web/agent/bun assets
mise run build-unified Build unified memoh binary
mise run release Release new version (bumpp)
mise run release-binaries Build release archive for target (requires TARGET_OS TARGET_ARCH)
mise run install-cli Install CLI locally

Docker Deployment

docker compose up -d        # Start all services
# Visit http://localhost:8082

Key Development Rules

Database, sqlc & Migrations

  1. SQL queries are defined in db/queries/*.sql.
  2. All Go files under internal/db/sqlc/ are auto-generated by sqlc. DO NOT modify them manually.
  3. After modifying any SQL files (migrations or queries), run mise run sqlc-generate to update the generated Go code.

Migration Rules

Migrations live in db/migrations/ and follow a dual-update convention:

  • 0001_init.up.sql is the canonical full schema. It always contains the complete, up-to-date database definition (all tables, indexes, constraints, etc.). When adding schema changes, you must also update 0001_init.up.sql to reflect the final state.
  • Incremental migration files (0002_, 0003_, ...) contain only the diff needed to upgrade an existing database. They exist for environments that already have the schema and need to apply only the delta.
  • Both must be kept in sync: every schema change requires updating 0001_init.up.sql AND creating a new incremental migration file.
  • Naming: {NNNN}_{description}.up.sql and {NNNN}_{description}.down.sql, where {NNNN} is a zero-padded sequential number (e.g., 0005). Always use the next available number.
  • Paired files: Every incremental migration must have both an .up.sql (apply) and a .down.sql (rollback) file.
  • Header comment: Each file should start with a comment indicating the migration name and a brief description:
    -- 0005_add_feature_x
    -- Add feature_x column to bots table for ...
    
  • Idempotent DDL: Use IF NOT EXISTS / IF EXISTS guards (e.g., CREATE TABLE IF NOT EXISTS, ADD COLUMN IF NOT EXISTS, DROP TABLE IF EXISTS) so migrations are safe to re-run.
  • Down migration must fully reverse up: The .down.sql must cleanly undo everything its .up.sql does, in reverse order.
  • After creating or modifying migrations, run mise run sqlc-generate to regenerate the Go code, then mise run db-up to apply.

API Development Workflow

  1. Write handlers in internal/handlers/ with swaggo annotations.
  2. Run mise run swagger-generate to update the OpenAPI docs (output in spec/).
  3. Run mise run sdk-generate to update the frontend TypeScript SDK (packages/sdk/).
  4. The frontend calls APIs via the auto-generated @memoh/sdk.

Agent Development

  • The core agent logic lives in packages/agent/ (@memoh/agent), providing reusable agent streaming, tool execution, and prompt management.
  • The Agent Gateway (apps/agent/) is a thin Elysia HTTP service that uses @memoh/agent for processing.
  • AI model providers (Anthropic, OpenAI, Google) are integrated via Vercel AI SDK.
  • Tools (MCP, web search, subagent, skill) are defined in packages/agent/src/tools/.
  • Prompt templates (system, heartbeat, schedule, subagent) are in packages/agent/src/prompts/.

Frontend Development

  • Use Vue 3 Composition API with <script setup> style.
  • Shared components belong in packages/ui/.
  • API calls use the auto-generated @memoh/sdk.
  • State management uses Pinia; data fetching uses Pinia Colada.
  • i18n via vue-i18n.

Container Management

  • In Docker deployment, containerd runs inside the server container.
  • On macOS, Apple Virtualization is used as container backend.
  • Each bot has its own isolated container instance.

Database Tables

Table Description
users User accounts (username, email, role, display_name, avatar)
channel_identities Unified inbound identity subject (cross-platform)
user_channel_bindings Outbound delivery config per user/channel
llm_providers LLM provider configurations (name, base_url, api_key)
search_providers Search engine provider configurations
models Model definitions (chat/embedding types, modalities, reasoning)
model_variants Model variant definitions (weight, metadata)
bots Bot definitions with model references and settings
bot_members Bot membership (owner/admin/member)
mcp_connections MCP connection configurations per bot
bot_channel_configs Per-bot channel configurations
bot_preauth_keys Bot pre-authentication keys
channel_identity_bind_codes One-time codes for channel identity → user linking
bot_channel_routes Conversation route mapping (inbound thread → bot history)
bot_history_messages Unified message history under bot scope
bot_history_message_assets Message → content_hash asset links
containers Bot container instances
snapshots Container snapshots
container_versions Container version tracking
lifecycle_events Container lifecycle events
schedule Scheduled tasks (cron)
subagents Sub-agent definitions
browser_contexts Browser context configurations (Playwright)
storage_providers Pluggable object storage backends
bot_storage_bindings Per-bot storage backend selection
bot_inbox Per-bot inbox (notifications, triggers)
bot_heartbeat_logs Heartbeat execution records
email_providers Pluggable email service backends (Mailgun, generic SMTP)
bot_email_bindings Per-bot email provider binding with permissions
email_outbox Outbound email audit log

Configuration

The main configuration file is config.toml (copied from conf/app.example.toml or environment-specific templates for development), containing:

  • [log] — Logging configuration (level, format)
  • [server] — HTTP listen address
  • [admin] — Admin account credentials
  • [auth] — JWT authentication settings
  • [containerd] — Container runtime configuration (socket path, namespace)
  • [workspace] — Workspace image and data configuration
  • [postgres] — PostgreSQL connection
  • [qdrant] — Qdrant vector database connection
  • [agent_gateway] — Agent Gateway address
  • [browser_gateway] — Browser Gateway address
  • [web] — Web frontend address

Configuration templates available in conf/:

  • app.example.toml — Default template
  • app.docker.toml — Docker deployment
  • app.apple.toml — macOS (Apple Virtualization backend)
  • app.windows.toml — Windows

Development configuration in devenv/:

  • app.dev.toml — Development (connects to devenv docker-compose)

Web Design

Please refer to ./apps/web/AGENTS.md.