lasertsole/sherry_agent

Python

4

578 commits

updated Sep 23, 2026

See the code

README

🍊 EMA AI Agent - Sherry

Python LangChain License

English Β· δΈ­ζ–‡ Β· ν•œκ΅­μ–΄ Β· ζ—₯本θͺž

A deep role-playing AI Agent built on LangChain/LangGraph and multimodal technology.

✨ Introduction

EMA AI Agent is a highly anthropomorphic AI agent system with long-term memory and complex reasoning capabilities. It's more than just a chatbot β€” it's a virtual companion with an independent Persona, a dynamic Skill System, and proactive behavior through scheduled tasks and background subagents.

The Agent's character, Sherry (Tachibana Sherry), is a self-proclaimed girl detective: ever-cheerful and energetic on the outside, calm and razor-sharp at the core. The entire system is designed to support immersive, persistent role-playing with memory that accumulates across sessions.


πŸš€ Key Features

1. 🧠 Layered Memory System (Context Engine)

  • Short-term Session Memory (MesMemory): conversation history persisted to SQLite (WAL mode) with automatic FTS5 indexing β€” including a trigram tokenizer table for Chinese full-text search; persistence runs at two timings (MessagePersistenceMiddleware): tool results are flushed the moment they return, and every model-call boundary incrementally flushes the remaining new human/ai/tool messages β€” write-once via the persisted_message_ids watermark β€” so the raw store does not depend on a compression ever firing
  • History Retrieval: last-N-turns, paginated history, or turn-range queries formatted as prompt context
  • Session Checkpointing: thread-safe async SQLite checkpointer (langgraph-checkpoint-sqlite) persists agent state across restarts; stale checkpoints are cleaned automatically
  • Conversation Summarization: an auxiliary LLM compresses long histories mid-conversation via the Summarization middleware
  • Context Governance: oversized tool results and human messages are evicted off the prompt with a recoverable on-disk copy, read_file results are sliced, a no-LLM tail clip runs before any compression, and chained summaries are filtered out of the conversation payload
  • Private Knowledge Graph RAG: the multimodal_rag skill indexes documents/folders into an entity–relationship graph (vendored LightRAG + RAG-Anything on snkv vector storage) and answers via multi-hop graph retrieval
  • Experience Extraction: four lifecycle paths turn conversation history into durable experience: the compression-time memory review (on every compression), plan extraction when the todo list is all-complete at a compression, the pre-compression memory flush, and the post-compression todo fork. They write to MEMORY.md / USER.md, the plan knowledge base (agent/tools/todolist/knowledge/), skills/auto/, and todos.db
  • ▢️ See the Context Engine README for architecture, data models, and API details
  • ▢️ See the Experience README for the extraction paths and the skill Curator

2. πŸ› οΈ Dynamic Skill System

  • SKILL.md Standard: skills are Markdown files with YAML frontmatter (name, description, optional scope: all | main_only | subagent_only) β€” the loader auto-discovers every SKILL.md under skills/
  • Built-in Skills (skills/builtin/): cron, heartbeat, clawhub (GitHub skill installer), skill_creator (generates new skills), image_to_text, speech_to_text, video_text_to_text, text_to_image, multimodal_rag, code_wiki, llm_wiki
  • Skill Management Tools: the agent can list, view, and manage skills at runtime; third-party uploads (skills/plugins/) stay inactive until explicitly enabled
  • SkillSpector Security Scanning (server/service/skill_scanner.py): third-party skills are scanned by NVIDIA SkillSpector (static YARA/rule analysis + optional LLM semantic analysis via the auxiliary LLM) before activation; flagged skills are blocked from installation
  • Skill Curator: the context-engine curator thread maintains auto-learned skills under skills/auto/ β€” see the Experience README
  • Tool Timeouts: real per-tool limits come from the TOOLS_TIMEOUTS registry (WEB_SEARCH_TIMEOUT=15, TERMINAL_TIMEOUT=30, PYTHON_REPL_TIMEOUT=30); TOOL_CALL_TIMEOUT_MINUTES is a stored setting that no execution path consumes
  • ▢️ See the Middlewares README for the middleware pipeline (guardrails, iteration budget, HITL, normalization, summarization, multimodal processing)

3. πŸ€– Multi-level Subagent System

  • 7 Runtime Tools: sessions_spawn, sessions_yield, sessions_send, sessions_kill, sessions_steer, agents_list, subagents_list
  • Hierarchical Roles: depth-limited nesting (default max depth 2, hard cap 2) with MAIN β†’ ORCHESTRATOR β†’ LEAF roles and least-privilege tool scoping
  • Isolated Context: every subagent runs with a fresh, independent context β€” the parent transcript is never inherited; file attachments are supported
  • Reliable Delivery: results return through an EventBus announce pipeline with idempotency checks and exponential-backoff retries
  • Durable Registry: run records persisted to SQLite; a sweeper recovers orphaned runs and a followup checker enforces run timeouts when configured (default: none)
  • Swarm Mode: batch sub-task execution with FIFO scheduling and configurable concurrency
  • Verified completion: every spawn runs an auxiliary-LLM completion judge between child turns; a continue verdict injects the judge's follow-up prompt as the next turn, bounded by the configured COMPLETION_JUDGE["goal_max_turns"] budget (default 5)
  • Functional Roles (opt-in): sessions_spawn(functional_role=...) specializes a worker (general / researcher / executor / reviewer / librarian); the role drives the LLM tier, the tool allow-list, and the child's system-prompt sections
  • ▢️ See the Subagent System README for the full architecture

4. 🌐 Multi-Channel Access

  • Robyn Backend (server/): async HTTP API + WebSocket (/sessions/ws) on 127.0.0.1:8080, serving uploaded media under /static, /images, /audio, /video
  • Desktop Client (client/): Tauri 2 + Nuxt 4 (Vue 3 + TypeScript) SPA with system tray, global shortcut, offline history cache (Dexie/IndexedDB), dark/light mode, and i18n
  • QQ Bot: QQ channel adapter via the plugin system (plugins/channels/qq/)
  • Message Bus (bus/core.py): internal async queues decouple channels from the agent core

5. πŸ‘οΈ Multimodal Interaction

  • Image Understanding (ITTT): Image-to-Text vision models for analyzing user-uploaded images
  • Video Understanding (VTTT): Video-Text-to-Text models for video content analysis
  • Speech Recognition (STT): FunASR-based local speech-to-text
  • Text-to-Image (TTI): image generation from text descriptions via the text_to_image skill
  • Document Parsing: MinerU-based multimodal document ingestion for the knowledge-graph RAG pipeline

6. ⏰ Scheduled & Proactive Behavior

  • Cron Service (skills/builtin/core/cron/): one-shot (at), interval (every), or cron-expression (cron, via croniter + timezone) agent tasks, persisted to a JSON job store with per-job run history and delivery to channels
  • Heartbeat Service (skills/builtin/core/heartbeat/): periodic wake-up (default 30 min) that checks HEARTBEAT.md for pending tasks, lets an LLM decide skip/run, and passes results through a notification gate

πŸ—οΈ Tech Stack

Built on Python 3.13 (dependency management via uv), with the following core technologies:

ModuleTechnology
Agent FrameworkLangChain 1.3+ (create_agent + middlewares), LangGraph compiled graphs
Checkpointinglanggraph-checkpoint-sqlite (thread-safe async SQLite saver)
Web ServerRobyn (HTTP + WebSocket + static hosting)
DatabaseSQLite via aiosqlite (FTS5 full-text search, WAL mode)
Graph RAGVendored LightRAG + RAG-Anything (multimodal_rag skill), snkv[vector] storage
Local Inferencellama-cpp-python (GGUF: bge-m3 embedding, bge-reranker-v2-m3 reranker, auxiliary/ITTT/VTTT models), FunASR (STT)
Document Parsingmineru-vl-utils
Web Searchlangchain-tavily (Tavily API)
LLM Providerslangchain-openai, langchain-deepseek, langchain-community + a 20+ provider registry (OpenAI, Anthropic, DeepSeek, Zhipu GLM, DashScope Qwen, Gemini, Moonshot Kimi, MiniMax, Groq, OpenRouter, SiliconFlow, Volcengine, Azure OpenAI, Ollama, vLLM, and more)
Structured Outputinstructor, json_repair
EvaluationRAGAS (graph-RAG quality metrics) + a homegrown sandboxed suite runner (evals/)
MCPlangchain-mcp-adapters (servers configured in plugins/mcp_server/)
Task Schedulingcroniter, asyncio
Async Messagingasyncio queues (MessageBus, EventBus)
Media ProcessingOpenCV (headless), Pillow, websockets / websocket-client
Desktop ClientTauri 2 + Nuxt 4 (Vue 3, TypeScript, pnpm)
Loggingloguru (optional LangSmith tracing)

πŸ“‚ Project Structure

EMA_AI_agent/
β”œβ”€β”€ agent/                  # Agent core logic
β”‚   β”œβ”€β”€ core.py             # Main agent loop (LangChain create_agent β†’ LangGraph graph)
β”‚   β”œβ”€β”€ wrapper/            # Graph-level wrappers (repetition guard, context limit)
β”‚   β”œβ”€β”€ checkpointer/       # Thread-safe async SQLite checkpointers
β”‚   β”œβ”€β”€ middlewares/        # Middleware pipeline (summarization, guardrails, HITL, ...)
β”‚   └── tools/              # Agent-accessible tools
β”‚       β”œβ”€β”€ subagent/       # Multi-level subagent system (spawn/registry/swarm/...)
β”‚       β”œβ”€β”€ todolist/       # Session-scoped todo planning layer
β”‚       β”‚   └── knowledge/  # Plan knowledge base + `knowledge` tool
β”‚       β”œβ”€β”€ file_tools/     # File I/O tools (read, write, patch, search)
β”‚       β”œβ”€β”€ skill_tools/    # Skill management tools (list, view, manage)
β”‚       β”œβ”€β”€ pub_base/       # Shared tool utilities & infrastructure (BaseSQLiteRepository, path utils)
β”‚       β”œβ”€β”€ mcp_plugin.py   # MCP tool integration
β”‚       β”œβ”€β”€ web_search.py   # Web search tool (Tavily)
β”‚       β”œβ”€β”€ python_repl.py  # Python code execution
β”‚       β”œβ”€β”€ terminal.py     # Terminal command execution
β”‚       β”œβ”€β”€ memory.py       # Memory inspection tool
β”‚       β”œβ”€β”€ question.py     # HITL multi-choice question prompt
β”‚       └── message_search.py # Conversation FTS5 search tool
β”‚
β”œβ”€β”€ bus/                    # Message bus (async queues)
β”‚   └── core.py             # MessageBus β€” inbound/outbound queues
β”‚
β”œβ”€β”€ channels/               # Channel interface definitions
β”‚   β”œβ”€β”€ base.py             # Abstract channel base
β”‚   β”œβ”€β”€ manager.py          # Channel lifecycle manager
β”‚   └── registry.py         # Channel registration
β”‚
β”œβ”€β”€ client/                 # Desktop client (Tauri 2 + Nuxt 4, pnpm)
β”‚   β”œβ”€β”€ app/                # Nuxt 4 SPA source (Vue 3)
β”‚   β”œβ”€β”€ src-tauri/          # Tauri 2 native shell (Rust)
β”‚   └── README.md           # Client documentation
β”‚
β”œβ”€β”€ config/                 # Centralized configuration (paths, feature TypedDicts, schema, settings)
β”‚   β”œβ”€β”€ __init__.py         # API host/port (127.0.0.1:8080)
β”‚   β”œβ”€β”€ path.py             # File path configuration
β”‚   β”œβ”€β”€ schema.py           # Configuration schema models
β”‚   β”œβ”€β”€ sherry_settings.py  # sherry.jsonc loader
β”‚   └── features/           # Per-object feature TypedDicts + default instances
β”‚
β”œβ”€β”€ context_engine/         # Memory engine (MesMemory)
β”‚   β”œβ”€β”€ core.py             # History retrieval & FTS5 search APIs
β”‚   β”œβ”€β”€ store/              # Session message store (SQLite + FTS5, WAL)
β”‚   β”œβ”€β”€ events/             # Append-only event log + projector
β”‚   β”œβ”€β”€ embeddings/         # Vector semantic search (indexer / search)
β”‚   └── curator/            # Auto-skill curation
β”‚
β”œβ”€β”€ docs/                   # Subsystem design docs (per-language READMEs)
β”‚   β”œβ”€β”€ experience/         # Experience extraction paths + skill curation
β”‚   β”œβ”€β”€ session_memory/     # Session-memory capabilities
β”‚   β”œβ”€β”€ summarization/      # Compression triggers & cooldown
β”‚   β”œβ”€β”€ loop-prevention/    # Runaway-loop prevention harness
β”‚   β”œβ”€β”€ sandbox/            # Eval sandbox & tool isolation
β”‚   β”œβ”€β”€ token-guard/        # 128K context-window floor
β”‚   β”œβ”€β”€ context-governance/ # Persistence, eviction, tail clip, summary filtering
β”‚   └── long-running-tasks/ # TaskFlow orchestration
β”‚
β”œβ”€β”€ evals/                  # Evaluation framework (dispatcher + 5 suites)
β”‚   β”œβ”€β”€ evals.py            # Suite runner: uv run python evals/evals.py [suite]
β”‚   β”œβ”€β”€ sandbox.py          # Sandbox that redirects repo writes during a run
β”‚   β”œβ”€β”€ graph_rag/          # RAGAS metrics for the graph-RAG pipeline
β”‚   β”œβ”€β”€ subagent/           # Subagent spawn-pipeline benchmark
β”‚   β”œβ”€β”€ long_running_task/  # TaskFlow DAG eval
β”‚   β”œβ”€β”€ session_memory/     # Session-memory stack checks
β”‚   β”œβ”€β”€ nudge_extraction/   # AI-judged plan extraction
β”‚   └── results/            # Per-run reports (gitignored)
β”‚
β”œβ”€β”€ logs/                   # Logging system
β”‚   β”œβ”€β”€ logger.py           # Log configuration (loguru)
β”‚   └── output/             # Log output directory
β”‚
β”œβ”€β”€ models/                 # Model wrappers & weights
β”‚   β”œβ”€β”€ LLMs/               # LLM configs (main_llm.py, reasoner_llm.py, auxiliary_llm/, reasoning_* providers)
β”‚   β”œβ”€β”€ ITTT_model/         # Image-to-Text model (cloud API or local GGUF)
β”‚   β”œβ”€β”€ VTTT_model/         # Video-Text-to-Text model (cloud API or local GGUF)
β”‚   β”œβ”€β”€ STT_model/          # Speech-to-Text model (FunASR)
β”‚   β”œβ”€β”€ embed_model/        # Embedding model (local bge-m3 GGUF or cloud API)
β”‚   β”œβ”€β”€ reranker_model/     # Cross-encoder reranker (local GGUF or cloud API)
β”‚   β”œβ”€β”€ extract_model/      # Entity extraction model (third-party weights)
β”‚   └── providers/          # LLM provider specifications & registry
β”‚       └── registry.py    # ProviderSpec entries for 20+ providers
β”‚
β”œβ”€β”€ plugins/                # Plugin system
β”‚   β”œβ”€β”€ channels/           # Channel plugins (QQ bot adapter)
β”‚   └── mcp_server/         # MCP server configuration
β”‚
β”œβ”€β”€ pub/                    # Shared utilities & data models
β”‚   β”œβ”€β”€ func/               # Common utility functions
β”‚   β”‚   β”œβ”€β”€ format/         # Text formatting utilities
β”‚   β”‚   β”œβ”€β”€ media/          # Media processing utilities
β”‚   β”‚   β”œβ”€β”€ message/        # Message processing utilities
β”‚   β”‚   └── validator/      # Input validation utilities
β”‚   └── types/              # Shared data models
β”‚       β”œβ”€β”€ message.py      # MultiModalMessage, Chat, etc.
β”‚       β”œβ”€β”€ bus.py          # Message bus data models
β”‚       └── client.py       # Client data models
β”‚
β”œβ”€β”€ runtime/                # Runtime state & utilities
β”‚   β”œβ”€β”€ session/            # Session-scoped registers
β”‚   β”‚   β”œβ”€β”€ core.py         # Singleton SessionRegister base + per-session cleanup
β”‚   β”‚   β”œβ”€β”€ relation_register.py # Session/socket relation registry
β”‚   β”‚   β”œβ”€β”€ state_register.py   # State registry
β”‚   β”‚   β”œβ”€β”€ state_keys.py       # Typed StateKey registry + TypedState facade
β”‚   β”‚   β”œβ”€β”€ count_call_register.py # Usage/statistics counters
β”‚   β”‚   β”œβ”€β”€ timer_call_register.py # Timer registry
β”‚   β”‚   └── _callback_executor.py # Async callback executor
β”‚   └── process/            # Process-scoped services
β”‚       β”œβ”€β”€ crash_loop_breaker.py # Boot crash-loop detection
β”‚       └── periodic_backoff.py   # Periodic backoff state
β”‚
β”œβ”€β”€ server/                 # Robyn backend service
β”‚   β”œβ”€β”€ __main__.py         # Server entry point (python -m server)
β”‚   β”œβ”€β”€ DAO/                # Data access objects
β”‚   β”œβ”€β”€ service/            # Business logic services (incl. skill_scanner.py)
β”‚   └── trigger/            # Route & handler registration
β”‚       β”œβ”€β”€ http/           # HTTP endpoint triggers
β”‚       β”œβ”€β”€ ws/             # WebSocket triggers
β”‚       β”œβ”€β”€ channels/       # Incoming channel triggers
β”‚       └── subagent/       # Subagent result triggers
β”‚
β”œβ”€β”€ skills/                 # Skill library (SKILL.md definition files)
β”‚   β”œβ”€β”€ loader.py           # Skill autodiscovery & registration
β”‚   β”œβ”€β”€ skills_snapshot.py  # Builds the skill prompt snapshot
β”‚   β”œβ”€β”€ auto/               # Auto-learned skills (maintained by curator)
β”‚   β”œβ”€β”€ plugins/            # Third-party uploaded skills (inactive by default)
β”‚   └── builtin/            # Built-in skills
β”‚       β”œβ”€β”€ core/           # cron, heartbeat, clawhub, skill_creator, image_to_text,
β”‚       β”‚                   # speech_to_text, video_text_to_text, multimodal_rag
β”‚       β”œβ”€β”€ text_to_image/  # Text-to-image skill
β”‚       β”œβ”€β”€ code_wiki/      # Codebase wiki generation skill
β”‚       └── llm_wiki/       # Markdown knowledge-base skill
β”‚
β”œβ”€β”€ src/                    # Runtime data directories
β”‚   β”œβ”€β”€ checkpoints/        # Session checkpoints
β”‚   β”œβ”€β”€ data/               # Data storage
β”‚   β”œβ”€β”€ store/              # Data stores
β”‚   β”œβ”€β”€ rag/                # RAG index output
β”‚   └── images/ audio/ video/ # Uploaded media (served statically)
β”‚
β”œβ”€β”€ temp/                   # Temporary files
β”‚
β”œβ”€β”€ tests/                  # Mirror-structured pytest suite (tests/<source>/...) + run_tests_split.py (marker-based runner)
β”‚
β”œβ”€β”€ workspace/              # Character profile & behavior definition
β”‚   β”œβ”€β”€ SOUL.md             # Personality contrasts, speech style
β”‚   β”œβ”€β”€ AGENTS.md           # Tool usage priorities, safety boundaries
β”‚   β”œβ”€β”€ USER.md             # User-specific interaction preferences
β”‚   β”œβ”€β”€ HEARTBEAT.md        # Pending tasks for heartbeat service
β”‚   β”œβ”€β”€ prompt_builder.py   # Profile-to-prompt builder
β”‚   β”œβ”€β”€ file_sync.py        # Lazy workspace template sync (per language)
β”‚   β”œβ”€β”€ template/           # Persona templates (en / zh / ja / ko)
β”‚   └── memory/             # Long-term memory storage
β”‚
β”œβ”€β”€ .env.example            # Environment variable template
β”œβ”€β”€ pyproject.toml          # Python dependencies (uv managed)
β”œβ”€β”€ uv.lock                 # Lockfile for uv
β”œβ”€β”€ start.sh                # Backend startup script
└── cron_jobs.json          # Cron job schedule data

πŸ“š Submodule Documentation

Each major subsystem has its own detailed README:

SubmoduleDescriptionDocumentation
Context EngineShort-term session message memory (MesMemory)EN Β· ZH Β· JA Β· KO
ExperienceFour extraction lifecycle paths plus the background Curator that maintains skills/auto/EN Β· ZH Β· JA Β· KO
Session MemorySESSION-plan capabilities: memory flush, compression cooldown, compaction lock, event log, semantic searchEN Β· ZH Β· JA Β· KO
Subagent SystemMulti-level subagent spawn, parallel execution & result deliveryEN Β· ZH Β· JA Β· KO
Subagent DesignDesign invariants: two-axis role model, spawn-privilege guards, four-layer completion gatesEN Β· ZH Β· JA Β· KO
Code IntelFour retrieval layers for the code-intel roles: tree-sitter symbol index, ast-grep structural search, LSP precision, semantic searchEN Β· ZH Β· JA Β· KO
MiddlewaresAgent lifecycle middleware pipelineEN Β· ZH Β· JA Β· KO
ChannelsChannel interface & adapter systemEN Β· ZH Β· JA Β· KO
Desktop ClientTauri 2 + Nuxt 4 desktop/mobile SPA clientEN Β· ZH Β· JA Β· KO
Cron ServiceScheduled/periodic agent task executionEN Β· ZH Β· JA Β· KO
Heartbeat ServicePeriodic wake-up task checkEN Β· ZH Β· JA Β· KO
SummarizationContext compaction middleware: five trigger points, a 4-route overflow router, anti-thrash guardsEN Β· ZH Β· JA Β· KO
Loop PreventionRunaway-loop guards, exponential-backoff breakers, and process crash gatingEN Β· ZH Β· JA Β· KO
SandboxTerminal & Python REPL confinement: env scrubbing, OS-native isolation, approval gateEN Β· ZH Β· JA Β· KO
Long-Running TasksTaskFlow DAG engine, step judge, budgets, deadlines, verified completion gates, and cross-turn memory continuityEN Β· ZH Β· JA Β· KO
Token GuardHard 128K context-window floor on both LLMs (boot, build, spawn, env write)EN Β· ZH Β· JA Β· KO
Context GovernancePer-boundary persistence, tool-result & human-message eviction, read_file slice, overflow tail clip, summary filteringEN Β· ZH Β· JA Β· KO

⚑ Quick Start

1. Prerequisites

  • Python 3.13+
  • uv β€” the dependency manager. It creates and manages .venv automatically; there is no need to create a virtual environment manually.
git clone <your-repo-url>
cd EMA_AI_agent
uv sync   # creates .venv and installs the exact dependencies from uv.lock

2. Configure Environment Variables

Copy the .env example and fill in at least the main chat model and Tavily key:

cp .env.example .env
VariableRequiredDescription
MAIN_LLM_PROVIDER / MAIN_LLM_NAME / MAIN_LLM_API_BASE / MAIN_LLM_API_KEY / MAIN_LLM_MAX_TOKENβœ…Primary chat model (must support JSON output and tool calling); MAIN_LLM_MAX_TOKEN must be >= 131072 (128K)
MAIN_LLM_ENABLE_THINKING / MAIN_LLM_REASONING_EFFORTβ€”Universal reasoning switch, mapped per provider (DeepSeek / OpenAI / GLM / Anthropic)
TAVILY_API_KEYβœ… for web searchEnables the web search tool
REASONER_LLM_*β€”Chain-of-thought reasoning model
AUXILIARY_LLM_*β€”Lightweight model for summarization / simple tasks (cloud API by default; set AUXILIARY_LLM_MODEL_LOCAL=true for a local GGUF model); AUXILIARY_LLM_MAX_TOKEN must be >= 131072 (128K)
ITTT_* / VTTT_* / TTI_* / STT_*β€”Image / video / text-to-image / speech model configuration
RERANKER_* / EMBEDDING_*β€”Reranker & embedding for retrieval (see model notes below)
SKILL_SCANNER_ENABLED / SKILL_SCANNER_LLMβ€”SkillSpector security scanner switch (on by default); LLM semantic analysis is opt-in (off by default) and requires a provider supporting json_schema structured output
TOOL_CALL_TIMEOUT_MINUTES (sherry.jsonc) / LOG_LEVELβ€”Stored setting only (default 5) β€” no tool-execution path consumes it; log level (INFO)
WORKSPACE_TEMPLATE_LANGβ€”Persona template language: en / zh / ja / ko (lazy-copied on first use)
"LANGSMITH" (sherry.jsonc)β€”Optional LangSmith tracing

3. Model Notes (HuggingFace Auto-Download)

Models configured for local GGUF mode are downloaded automatically from Hugging Face into models/<model>/model_weight/ on first use β€” no manual download is required:

  • Embedding: EMBEDDING_MODEL_LOCAL=true (the default) uses the local bge-m3 Q8_0 GGUF, auto-downloaded on first run.
  • Reranker: the .env template defaults to a cloud API (RERANKER_MODEL_LOCAL=false, OpenAI-compatible bge-reranker-v2-m3). Set it to true to run the local GGUF reranker instead, which is then auto-downloaded (~636 MB).
  • ITTT / VTTT / Auxiliary LLM: default to cloud APIs in the template; set *_MODEL_LOCAL=true to switch to local GGUF models (also auto-downloaded).

Network access to huggingface.co is required for first-run downloads (users in China may need a proxy or a mirror). Interrupted downloads are resumed on the next start; delete models/<model>/model_weight/ to force a re-download.

4. Start the Backend

start.sh activates the uv-managed .venv and launches the Robyn backend (it does not start Ollama or any frontend):

chmod +x start.sh
./start.sh          # runs the .venv interpreter: python -m server --fast --disable-openapi

Manual start (equivalent):

uv run python -m server

The backend listens on http://127.0.0.1:8080 with a WebSocket endpoint at /sessions/ws.

5. (Optional) Desktop Client

The Tauri 2 + Nuxt 4 client lives in client/ and requires Node.js 18+, pnpm, and Rust:

cd client
pnpm install
pnpm dev          # browser mode, dev server at http://localhost:3000
pnpm tauri dev    # native desktop mode

The client connects to the Python backend at http://127.0.0.1:8080 by default (configurable via VITE_API_BACK_URL in client/.env). See the client README for details.


πŸ§ͺ Testing

Tests live under tests/, mirroring the source tree (tests/agent/..., tests/server/..., tests/context_engine/...), and run with pytest via uv (uv run pytest for a single test file or a small selection). Every test file carries a module-level pytestmark (unit / integration / module / system / regression) that selects which runner group executes it.

For the full suite (and for CI), use the split runner β€” it executes the suite in three sequential pytest processes (never parallel), selecting tests by MARKER (not by directory), aggregates their exit codes, and prints a per-group summary plus a final verdict (exit code 0 only if all groups pass):

uv run python tests/run_tests_split.py                  # hermetic suite (default, llm_e2e excluded)
uv run python tests/run_tests_split.py --with-llm-e2e   # ONLY the real-LLM e2e tests (dedicated-job mode)
uv run python tests/run_tests_split.py -- -k spawn -q   # args after `--` are forwarded to pytest
GroupMarkerContents
Aunitpure-logic, fully mocked tests
Bintegration or module or systemhermetic integration / module / system tests
Cregressioncross-module regression tests

Why separate processes? tests/agent/tools/subagent/conftest.py installs stub callables into process-global sys.modules at conftest import time. In a single-process full-suite run, pytest imports every conftest and test module during collection β€” before any test executes β€” so those stubs are live for the whole process and leak across suites: lazy (call-time) imports resolve the stub, while modules that bound the real object earlier keep stale bindings. The result is confusing, order-dependent failures in suites far away from the subagent tests (e.g. skill-scope assertions seeing a stub's fixed skill list, TypeError tracebacks naming conftest lambdas). Running the groups in separate processes makes this cross-suite pollution structurally impossible. (The stubs themselves are restore-safe since c730a46; the runner is the defense-in-depth operational layer.)

Windows note: child pytest processes get PYTHONIOENCODING=utf-8 in their environment and the runner captures their output with errors="replace", so GBK console codepages can neither corrupt the output nor crash the run.

Real-LLM e2e tests (llm_e2e marker)

Three tests in tests/agent/tools/subagent/ (test_real_e2e.py, test_spawn_direct_e2e.py) call real LLM APIs. They are:

  • deselected by default (-m "not llm_e2e" β€” set both in pyproject.toml addopts and by the runner),
  • bounded by @pytest.mark.timeout budgets (pytest-timeout): 300 s per simple test, 600 s for the concurrent test,
  • run explicitly, in a dedicated job: uv run python tests/run_tests_split.py --with-llm-e2e (selects -m llm_e2e) or uv run pytest -m llm_e2e.

Expected runtimes (solo, real backend): simple task β‰ˆ 30–60 s; complex worst case β‰ˆ 10 min; concurrent tasks β‰ˆ 2–9 min. A run that exceeds these budgets is a real hang, not normal slowness β€” the per-test timeout bounds it (300 s simple / 600 s concurrent).

CI: .github/workflows/ci.yml runs uv run python tests/run_tests_split.py on every push/PR to main, executing the suite as three sequential pytest processes (never parallel): A = unit, B = integration + module + system, C = regression. The --with-llm-e2e suite stays a separate, slower job (it costs API tokens; never run it in parallel with other suites).

Note: tests/full/ is an auxiliary/experimental directory outside the standard groups above. Every file there that drives a live LLM is tagged llm_e2e, so the default addopts deselect it and the split runner never collects it (it also --ignores tests/full/). Run one explicitly with uv run --no-sync pytest -m llm_e2e tests/full/<file>. Hermetic tests do not belong there β€” the real-graph HITL test now lives at tests/agent/middlewares/humanInTheLoop/test_hitl_real_graph.py and runs in the standard groups.

Evals

evals/ is a homegrown, sandboxed evaluation framework that sits beside pytest. Run every registered suite, or one by name:

uv run python evals/evals.py                # all registered suites
uv run python evals/evals.py graph_rag      # a single suite by name
SuiteWhat it evaluates
graph_ragThe multimodal_rag pipeline, scored with RAGAS (faithfulness, answer relevancy, context recall, context precision)
subagentThe real spawn_subagent_direct pipeline on a bench of deterministic tasks (task success + latency)
long_running_taskThe TaskFlow orchestration loop over a dependent DAG (step success, flow completion, wall time)
session_memoryThe session-memory stack over 6 checks: cooldown, compaction lock, checkpoint restore, idempotent replay, context eligibility, semantic search ranking
nudge_extractionThe plan-extraction pass, judged by an auxiliary LLM for grounded, reusable, non-generic skills

Every suite runs inside evals/sandbox.py, which redirects repo writes into a temp sandbox, and writes its reports under evals/results/<suite>/<run_id>/. That directory is gitignored; per-run reports are never committed.


πŸ“ Character Profile Examples

The Agent's behavior is driven by the files under workspace/:

  • SOUL.md: Defines personality contrasts, speech style, and behavioral logic.
  • AGENTS.md: Defines tool usage priorities, safety boundaries, and ethical guidelines.
  • USER.md: Stores user-specific interaction preferences and known facts.
  • HEARTBEAT.md: Lists pending tasks for the heartbeat scheduled service.
  • prompt_builder.py: Builds the system prompt from the profile files.
  • file_sync.py: Lazily copies any missing persona files from workspace/template/<lang>/ (selected via WORKSPACE_TEMPLATE_LANG) without ever overwriting user edits.

🀝 Contributing

Issues and Pull Requests are welcome! To add a new skill:

  1. Create a folder under skills/ (or skills/plugins/ for third-party skills).
  2. Write a SKILL.md with YAML frontmatter (name, description, optional scope) describing the skill's usage and steps.
  3. Restart the Agent β€” the loader auto-discovers every SKILL.md and exposes it to the model. (You can also ask the running Agent to use the built-in skill_creator skill to generate one.)

Third-party skills under skills/plugins/ are scanned by SkillSpector and stay inactive until explicitly enabled.


Contact Information: QQ 3132225629

πŸ“„ License

This project is licensed under the MIT License.


πŸ’‘ Tip: This project is inspired by the exploration of advanced AI agents and deep role-playing.

Contributors

Languages

Python

87.8%

TypeScript

7.4%

Vue

4.4%

lasertsole/sherry_agent

Python

4

578 commits

updated Sep 23, 2026

See the code

README

🍊 EMA AI Agent - Sherry

Python LangChain License

English Β· δΈ­ζ–‡ Β· ν•œκ΅­μ–΄ Β· ζ—₯本θͺž

A deep role-playing AI Agent built on LangChain/LangGraph and multimodal technology.

✨ Introduction

EMA AI Agent is a highly anthropomorphic AI agent system with long-term memory and complex reasoning capabilities. It's more than just a chatbot β€” it's a virtual companion with an independent Persona, a dynamic Skill System, and proactive behavior through scheduled tasks and background subagents.

The Agent's character, Sherry (Tachibana Sherry), is a self-proclaimed girl detective: ever-cheerful and energetic on the outside, calm and razor-sharp at the core. The entire system is designed to support immersive, persistent role-playing with memory that accumulates across sessions.


πŸš€ Key Features

1. 🧠 Layered Memory System (Context Engine)

  • Short-term Session Memory (MesMemory): conversation history persisted to SQLite (WAL mode) with automatic FTS5 indexing β€” including a trigram tokenizer table for Chinese full-text search; persistence runs at two timings (MessagePersistenceMiddleware): tool results are flushed the moment they return, and every model-call boundary incrementally flushes the remaining new human/ai/tool messages β€” write-once via the persisted_message_ids watermark β€” so the raw store does not depend on a compression ever firing
  • History Retrieval: last-N-turns, paginated history, or turn-range queries formatted as prompt context
  • Session Checkpointing: thread-safe async SQLite checkpointer (langgraph-checkpoint-sqlite) persists agent state across restarts; stale checkpoints are cleaned automatically
  • Conversation Summarization: an auxiliary LLM compresses long histories mid-conversation via the Summarization middleware
  • Context Governance: oversized tool results and human messages are evicted off the prompt with a recoverable on-disk copy, read_file results are sliced, a no-LLM tail clip runs before any compression, and chained summaries are filtered out of the conversation payload
  • Private Knowledge Graph RAG: the multimodal_rag skill indexes documents/folders into an entity–relationship graph (vendored LightRAG + RAG-Anything on snkv vector storage) and answers via multi-hop graph retrieval
  • Experience Extraction: four lifecycle paths turn conversation history into durable experience: the compression-time memory review (on every compression), plan extraction when the todo list is all-complete at a compression, the pre-compression memory flush, and the post-compression todo fork. They write to MEMORY.md / USER.md, the plan knowledge base (agent/tools/todolist/knowledge/), skills/auto/, and todos.db
  • ▢️ See the Context Engine README for architecture, data models, and API details
  • ▢️ See the Experience README for the extraction paths and the skill Curator

2. πŸ› οΈ Dynamic Skill System

  • SKILL.md Standard: skills are Markdown files with YAML frontmatter (name, description, optional scope: all | main_only | subagent_only) β€” the loader auto-discovers every SKILL.md under skills/
  • Built-in Skills (skills/builtin/): cron, heartbeat, clawhub (GitHub skill installer), skill_creator (generates new skills), image_to_text, speech_to_text, video_text_to_text, text_to_image, multimodal_rag, code_wiki, llm_wiki
  • Skill Management Tools: the agent can list, view, and manage skills at runtime; third-party uploads (skills/plugins/) stay inactive until explicitly enabled
  • SkillSpector Security Scanning (server/service/skill_scanner.py): third-party skills are scanned by NVIDIA SkillSpector (static YARA/rule analysis + optional LLM semantic analysis via the auxiliary LLM) before activation; flagged skills are blocked from installation
  • Skill Curator: the context-engine curator thread maintains auto-learned skills under skills/auto/ β€” see the Experience README
  • Tool Timeouts: real per-tool limits come from the TOOLS_TIMEOUTS registry (WEB_SEARCH_TIMEOUT=15, TERMINAL_TIMEOUT=30, PYTHON_REPL_TIMEOUT=30); TOOL_CALL_TIMEOUT_MINUTES is a stored setting that no execution path consumes
  • ▢️ See the Middlewares README for the middleware pipeline (guardrails, iteration budget, HITL, normalization, summarization, multimodal processing)

3. πŸ€– Multi-level Subagent System

  • 7 Runtime Tools: sessions_spawn, sessions_yield, sessions_send, sessions_kill, sessions_steer, agents_list, subagents_list
  • Hierarchical Roles: depth-limited nesting (default max depth 2, hard cap 2) with MAIN β†’ ORCHESTRATOR β†’ LEAF roles and least-privilege tool scoping
  • Isolated Context: every subagent runs with a fresh, independent context β€” the parent transcript is never inherited; file attachments are supported
  • Reliable Delivery: results return through an EventBus announce pipeline with idempotency checks and exponential-backoff retries
  • Durable Registry: run records persisted to SQLite; a sweeper recovers orphaned runs and a followup checker enforces run timeouts when configured (default: none)
  • Swarm Mode: batch sub-task execution with FIFO scheduling and configurable concurrency
  • Verified completion: every spawn runs an auxiliary-LLM completion judge between child turns; a continue verdict injects the judge's follow-up prompt as the next turn, bounded by the configured COMPLETION_JUDGE["goal_max_turns"] budget (default 5)
  • Functional Roles (opt-in): sessions_spawn(functional_role=...) specializes a worker (general / researcher / executor / reviewer / librarian); the role drives the LLM tier, the tool allow-list, and the child's system-prompt sections
  • ▢️ See the Subagent System README for the full architecture

4. 🌐 Multi-Channel Access

  • Robyn Backend (server/): async HTTP API + WebSocket (/sessions/ws) on 127.0.0.1:8080, serving uploaded media under /static, /images, /audio, /video
  • Desktop Client (client/): Tauri 2 + Nuxt 4 (Vue 3 + TypeScript) SPA with system tray, global shortcut, offline history cache (Dexie/IndexedDB), dark/light mode, and i18n
  • QQ Bot: QQ channel adapter via the plugin system (plugins/channels/qq/)
  • Message Bus (bus/core.py): internal async queues decouple channels from the agent core

5. πŸ‘οΈ Multimodal Interaction

  • Image Understanding (ITTT): Image-to-Text vision models for analyzing user-uploaded images
  • Video Understanding (VTTT): Video-Text-to-Text models for video content analysis
  • Speech Recognition (STT): FunASR-based local speech-to-text
  • Text-to-Image (TTI): image generation from text descriptions via the text_to_image skill
  • Document Parsing: MinerU-based multimodal document ingestion for the knowledge-graph RAG pipeline

6. ⏰ Scheduled & Proactive Behavior

  • Cron Service (skills/builtin/core/cron/): one-shot (at), interval (every), or cron-expression (cron, via croniter + timezone) agent tasks, persisted to a JSON job store with per-job run history and delivery to channels
  • Heartbeat Service (skills/builtin/core/heartbeat/): periodic wake-up (default 30 min) that checks HEARTBEAT.md for pending tasks, lets an LLM decide skip/run, and passes results through a notification gate

πŸ—οΈ Tech Stack

Built on Python 3.13 (dependency management via uv), with the following core technologies:

ModuleTechnology
Agent FrameworkLangChain 1.3+ (create_agent + middlewares), LangGraph compiled graphs
Checkpointinglanggraph-checkpoint-sqlite (thread-safe async SQLite saver)
Web ServerRobyn (HTTP + WebSocket + static hosting)
DatabaseSQLite via aiosqlite (FTS5 full-text search, WAL mode)
Graph RAGVendored LightRAG + RAG-Anything (multimodal_rag skill), snkv[vector] storage
Local Inferencellama-cpp-python (GGUF: bge-m3 embedding, bge-reranker-v2-m3 reranker, auxiliary/ITTT/VTTT models), FunASR (STT)
Document Parsingmineru-vl-utils
Web Searchlangchain-tavily (Tavily API)
LLM Providerslangchain-openai, langchain-deepseek, langchain-community + a 20+ provider registry (OpenAI, Anthropic, DeepSeek, Zhipu GLM, DashScope Qwen, Gemini, Moonshot Kimi, MiniMax, Groq, OpenRouter, SiliconFlow, Volcengine, Azure OpenAI, Ollama, vLLM, and more)
Structured Outputinstructor, json_repair
EvaluationRAGAS (graph-RAG quality metrics) + a homegrown sandboxed suite runner (evals/)
MCPlangchain-mcp-adapters (servers configured in plugins/mcp_server/)
Task Schedulingcroniter, asyncio
Async Messagingasyncio queues (MessageBus, EventBus)
Media ProcessingOpenCV (headless), Pillow, websockets / websocket-client
Desktop ClientTauri 2 + Nuxt 4 (Vue 3, TypeScript, pnpm)
Loggingloguru (optional LangSmith tracing)

πŸ“‚ Project Structure

EMA_AI_agent/
β”œβ”€β”€ agent/                  # Agent core logic
β”‚   β”œβ”€β”€ core.py             # Main agent loop (LangChain create_agent β†’ LangGraph graph)
β”‚   β”œβ”€β”€ wrapper/            # Graph-level wrappers (repetition guard, context limit)
β”‚   β”œβ”€β”€ checkpointer/       # Thread-safe async SQLite checkpointers
β”‚   β”œβ”€β”€ middlewares/        # Middleware pipeline (summarization, guardrails, HITL, ...)
β”‚   └── tools/              # Agent-accessible tools
β”‚       β”œβ”€β”€ subagent/       # Multi-level subagent system (spawn/registry/swarm/...)
β”‚       β”œβ”€β”€ todolist/       # Session-scoped todo planning layer
β”‚       β”‚   └── knowledge/  # Plan knowledge base + `knowledge` tool
β”‚       β”œβ”€β”€ file_tools/     # File I/O tools (read, write, patch, search)
β”‚       β”œβ”€β”€ skill_tools/    # Skill management tools (list, view, manage)
β”‚       β”œβ”€β”€ pub_base/       # Shared tool utilities & infrastructure (BaseSQLiteRepository, path utils)
β”‚       β”œβ”€β”€ mcp_plugin.py   # MCP tool integration
β”‚       β”œβ”€β”€ web_search.py   # Web search tool (Tavily)
β”‚       β”œβ”€β”€ python_repl.py  # Python code execution
β”‚       β”œβ”€β”€ terminal.py     # Terminal command execution
β”‚       β”œβ”€β”€ memory.py       # Memory inspection tool
β”‚       β”œβ”€β”€ question.py     # HITL multi-choice question prompt
β”‚       └── message_search.py # Conversation FTS5 search tool
β”‚
β”œβ”€β”€ bus/                    # Message bus (async queues)
β”‚   └── core.py             # MessageBus β€” inbound/outbound queues
β”‚
β”œβ”€β”€ channels/               # Channel interface definitions
β”‚   β”œβ”€β”€ base.py             # Abstract channel base
β”‚   β”œβ”€β”€ manager.py          # Channel lifecycle manager
β”‚   └── registry.py         # Channel registration
β”‚
β”œβ”€β”€ client/                 # Desktop client (Tauri 2 + Nuxt 4, pnpm)
β”‚   β”œβ”€β”€ app/                # Nuxt 4 SPA source (Vue 3)
β”‚   β”œβ”€β”€ src-tauri/          # Tauri 2 native shell (Rust)
β”‚   └── README.md           # Client documentation
β”‚
β”œβ”€β”€ config/                 # Centralized configuration (paths, feature TypedDicts, schema, settings)
β”‚   β”œβ”€β”€ __init__.py         # API host/port (127.0.0.1:8080)
β”‚   β”œβ”€β”€ path.py             # File path configuration
β”‚   β”œβ”€β”€ schema.py           # Configuration schema models
β”‚   β”œβ”€β”€ sherry_settings.py  # sherry.jsonc loader
β”‚   └── features/           # Per-object feature TypedDicts + default instances
β”‚
β”œβ”€β”€ context_engine/         # Memory engine (MesMemory)
β”‚   β”œβ”€β”€ core.py             # History retrieval & FTS5 search APIs
β”‚   β”œβ”€β”€ store/              # Session message store (SQLite + FTS5, WAL)
β”‚   β”œβ”€β”€ events/             # Append-only event log + projector
β”‚   β”œβ”€β”€ embeddings/         # Vector semantic search (indexer / search)
β”‚   └── curator/            # Auto-skill curation
β”‚
β”œβ”€β”€ docs/                   # Subsystem design docs (per-language READMEs)
β”‚   β”œβ”€β”€ experience/         # Experience extraction paths + skill curation
β”‚   β”œβ”€β”€ session_memory/     # Session-memory capabilities
β”‚   β”œβ”€β”€ summarization/      # Compression triggers & cooldown
β”‚   β”œβ”€β”€ loop-prevention/    # Runaway-loop prevention harness
β”‚   β”œβ”€β”€ sandbox/            # Eval sandbox & tool isolation
β”‚   β”œβ”€β”€ token-guard/        # 128K context-window floor
β”‚   β”œβ”€β”€ context-governance/ # Persistence, eviction, tail clip, summary filtering
β”‚   └── long-running-tasks/ # TaskFlow orchestration
β”‚
β”œβ”€β”€ evals/                  # Evaluation framework (dispatcher + 5 suites)
β”‚   β”œβ”€β”€ evals.py            # Suite runner: uv run python evals/evals.py [suite]
β”‚   β”œβ”€β”€ sandbox.py          # Sandbox that redirects repo writes during a run
β”‚   β”œβ”€β”€ graph_rag/          # RAGAS metrics for the graph-RAG pipeline
β”‚   β”œβ”€β”€ subagent/           # Subagent spawn-pipeline benchmark
β”‚   β”œβ”€β”€ long_running_task/  # TaskFlow DAG eval
β”‚   β”œβ”€β”€ session_memory/     # Session-memory stack checks
β”‚   β”œβ”€β”€ nudge_extraction/   # AI-judged plan extraction
β”‚   └── results/            # Per-run reports (gitignored)
β”‚
β”œβ”€β”€ logs/                   # Logging system
β”‚   β”œβ”€β”€ logger.py           # Log configuration (loguru)
β”‚   └── output/             # Log output directory
β”‚
β”œβ”€β”€ models/                 # Model wrappers & weights
β”‚   β”œβ”€β”€ LLMs/               # LLM configs (main_llm.py, reasoner_llm.py, auxiliary_llm/, reasoning_* providers)
β”‚   β”œβ”€β”€ ITTT_model/         # Image-to-Text model (cloud API or local GGUF)
β”‚   β”œβ”€β”€ VTTT_model/         # Video-Text-to-Text model (cloud API or local GGUF)
β”‚   β”œβ”€β”€ STT_model/          # Speech-to-Text model (FunASR)
β”‚   β”œβ”€β”€ embed_model/        # Embedding model (local bge-m3 GGUF or cloud API)
β”‚   β”œβ”€β”€ reranker_model/     # Cross-encoder reranker (local GGUF or cloud API)
β”‚   β”œβ”€β”€ extract_model/      # Entity extraction model (third-party weights)
β”‚   └── providers/          # LLM provider specifications & registry
β”‚       └── registry.py    # ProviderSpec entries for 20+ providers
β”‚
β”œβ”€β”€ plugins/                # Plugin system
β”‚   β”œβ”€β”€ channels/           # Channel plugins (QQ bot adapter)
β”‚   └── mcp_server/         # MCP server configuration
β”‚
β”œβ”€β”€ pub/                    # Shared utilities & data models
β”‚   β”œβ”€β”€ func/               # Common utility functions
β”‚   β”‚   β”œβ”€β”€ format/         # Text formatting utilities
β”‚   β”‚   β”œβ”€β”€ media/          # Media processing utilities
β”‚   β”‚   β”œβ”€β”€ message/        # Message processing utilities
β”‚   β”‚   └── validator/      # Input validation utilities
β”‚   └── types/              # Shared data models
β”‚       β”œβ”€β”€ message.py      # MultiModalMessage, Chat, etc.
β”‚       β”œβ”€β”€ bus.py          # Message bus data models
β”‚       └── client.py       # Client data models
β”‚
β”œβ”€β”€ runtime/                # Runtime state & utilities
β”‚   β”œβ”€β”€ session/            # Session-scoped registers
β”‚   β”‚   β”œβ”€β”€ core.py         # Singleton SessionRegister base + per-session cleanup
β”‚   β”‚   β”œβ”€β”€ relation_register.py # Session/socket relation registry
β”‚   β”‚   β”œβ”€β”€ state_register.py   # State registry
β”‚   β”‚   β”œβ”€β”€ state_keys.py       # Typed StateKey registry + TypedState facade
β”‚   β”‚   β”œβ”€β”€ count_call_register.py # Usage/statistics counters
β”‚   β”‚   β”œβ”€β”€ timer_call_register.py # Timer registry
β”‚   β”‚   └── _callback_executor.py # Async callback executor
β”‚   └── process/            # Process-scoped services
β”‚       β”œβ”€β”€ crash_loop_breaker.py # Boot crash-loop detection
β”‚       └── periodic_backoff.py   # Periodic backoff state
β”‚
β”œβ”€β”€ server/                 # Robyn backend service
β”‚   β”œβ”€β”€ __main__.py         # Server entry point (python -m server)
β”‚   β”œβ”€β”€ DAO/                # Data access objects
β”‚   β”œβ”€β”€ service/            # Business logic services (incl. skill_scanner.py)
β”‚   └── trigger/            # Route & handler registration
β”‚       β”œβ”€β”€ http/           # HTTP endpoint triggers
β”‚       β”œβ”€β”€ ws/             # WebSocket triggers
β”‚       β”œβ”€β”€ channels/       # Incoming channel triggers
β”‚       └── subagent/       # Subagent result triggers
β”‚
β”œβ”€β”€ skills/                 # Skill library (SKILL.md definition files)
β”‚   β”œβ”€β”€ loader.py           # Skill autodiscovery & registration
β”‚   β”œβ”€β”€ skills_snapshot.py  # Builds the skill prompt snapshot
β”‚   β”œβ”€β”€ auto/               # Auto-learned skills (maintained by curator)
β”‚   β”œβ”€β”€ plugins/            # Third-party uploaded skills (inactive by default)
β”‚   └── builtin/            # Built-in skills
β”‚       β”œβ”€β”€ core/           # cron, heartbeat, clawhub, skill_creator, image_to_text,
β”‚       β”‚                   # speech_to_text, video_text_to_text, multimodal_rag
β”‚       β”œβ”€β”€ text_to_image/  # Text-to-image skill
β”‚       β”œβ”€β”€ code_wiki/      # Codebase wiki generation skill
β”‚       └── llm_wiki/       # Markdown knowledge-base skill
β”‚
β”œβ”€β”€ src/                    # Runtime data directories
β”‚   β”œβ”€β”€ checkpoints/        # Session checkpoints
β”‚   β”œβ”€β”€ data/               # Data storage
β”‚   β”œβ”€β”€ store/              # Data stores
β”‚   β”œβ”€β”€ rag/                # RAG index output
β”‚   └── images/ audio/ video/ # Uploaded media (served statically)
β”‚
β”œβ”€β”€ temp/                   # Temporary files
β”‚
β”œβ”€β”€ tests/                  # Mirror-structured pytest suite (tests/<source>/...) + run_tests_split.py (marker-based runner)
β”‚
β”œβ”€β”€ workspace/              # Character profile & behavior definition
β”‚   β”œβ”€β”€ SOUL.md             # Personality contrasts, speech style
β”‚   β”œβ”€β”€ AGENTS.md           # Tool usage priorities, safety boundaries
β”‚   β”œβ”€β”€ USER.md             # User-specific interaction preferences
β”‚   β”œβ”€β”€ HEARTBEAT.md        # Pending tasks for heartbeat service
β”‚   β”œβ”€β”€ prompt_builder.py   # Profile-to-prompt builder
β”‚   β”œβ”€β”€ file_sync.py        # Lazy workspace template sync (per language)
β”‚   β”œβ”€β”€ template/           # Persona templates (en / zh / ja / ko)
β”‚   └── memory/             # Long-term memory storage
β”‚
β”œβ”€β”€ .env.example            # Environment variable template
β”œβ”€β”€ pyproject.toml          # Python dependencies (uv managed)
β”œβ”€β”€ uv.lock                 # Lockfile for uv
β”œβ”€β”€ start.sh                # Backend startup script
└── cron_jobs.json          # Cron job schedule data

πŸ“š Submodule Documentation

Each major subsystem has its own detailed README:

SubmoduleDescriptionDocumentation
Context EngineShort-term session message memory (MesMemory)EN Β· ZH Β· JA Β· KO
ExperienceFour extraction lifecycle paths plus the background Curator that maintains skills/auto/EN Β· ZH Β· JA Β· KO
Session MemorySESSION-plan capabilities: memory flush, compression cooldown, compaction lock, event log, semantic searchEN Β· ZH Β· JA Β· KO
Subagent SystemMulti-level subagent spawn, parallel execution & result deliveryEN Β· ZH Β· JA Β· KO
Subagent DesignDesign invariants: two-axis role model, spawn-privilege guards, four-layer completion gatesEN Β· ZH Β· JA Β· KO
Code IntelFour retrieval layers for the code-intel roles: tree-sitter symbol index, ast-grep structural search, LSP precision, semantic searchEN Β· ZH Β· JA Β· KO
MiddlewaresAgent lifecycle middleware pipelineEN Β· ZH Β· JA Β· KO
ChannelsChannel interface & adapter systemEN Β· ZH Β· JA Β· KO
Desktop ClientTauri 2 + Nuxt 4 desktop/mobile SPA clientEN Β· ZH Β· JA Β· KO
Cron ServiceScheduled/periodic agent task executionEN Β· ZH Β· JA Β· KO
Heartbeat ServicePeriodic wake-up task checkEN Β· ZH Β· JA Β· KO
SummarizationContext compaction middleware: five trigger points, a 4-route overflow router, anti-thrash guardsEN Β· ZH Β· JA Β· KO
Loop PreventionRunaway-loop guards, exponential-backoff breakers, and process crash gatingEN Β· ZH Β· JA Β· KO
SandboxTerminal & Python REPL confinement: env scrubbing, OS-native isolation, approval gateEN Β· ZH Β· JA Β· KO
Long-Running TasksTaskFlow DAG engine, step judge, budgets, deadlines, verified completion gates, and cross-turn memory continuityEN Β· ZH Β· JA Β· KO
Token GuardHard 128K context-window floor on both LLMs (boot, build, spawn, env write)EN Β· ZH Β· JA Β· KO
Context GovernancePer-boundary persistence, tool-result & human-message eviction, read_file slice, overflow tail clip, summary filteringEN Β· ZH Β· JA Β· KO

⚑ Quick Start

1. Prerequisites

  • Python 3.13+
  • uv β€” the dependency manager. It creates and manages .venv automatically; there is no need to create a virtual environment manually.
git clone <your-repo-url>
cd EMA_AI_agent
uv sync   # creates .venv and installs the exact dependencies from uv.lock

2. Configure Environment Variables

Copy the .env example and fill in at least the main chat model and Tavily key:

cp .env.example .env
VariableRequiredDescription
MAIN_LLM_PROVIDER / MAIN_LLM_NAME / MAIN_LLM_API_BASE / MAIN_LLM_API_KEY / MAIN_LLM_MAX_TOKENβœ…Primary chat model (must support JSON output and tool calling); MAIN_LLM_MAX_TOKEN must be >= 131072 (128K)
MAIN_LLM_ENABLE_THINKING / MAIN_LLM_REASONING_EFFORTβ€”Universal reasoning switch, mapped per provider (DeepSeek / OpenAI / GLM / Anthropic)
TAVILY_API_KEYβœ… for web searchEnables the web search tool
REASONER_LLM_*β€”Chain-of-thought reasoning model
AUXILIARY_LLM_*β€”Lightweight model for summarization / simple tasks (cloud API by default; set AUXILIARY_LLM_MODEL_LOCAL=true for a local GGUF model); AUXILIARY_LLM_MAX_TOKEN must be >= 131072 (128K)
ITTT_* / VTTT_* / TTI_* / STT_*β€”Image / video / text-to-image / speech model configuration
RERANKER_* / EMBEDDING_*β€”Reranker & embedding for retrieval (see model notes below)
SKILL_SCANNER_ENABLED / SKILL_SCANNER_LLMβ€”SkillSpector security scanner switch (on by default); LLM semantic analysis is opt-in (off by default) and requires a provider supporting json_schema structured output
TOOL_CALL_TIMEOUT_MINUTES (sherry.jsonc) / LOG_LEVELβ€”Stored setting only (default 5) β€” no tool-execution path consumes it; log level (INFO)
WORKSPACE_TEMPLATE_LANGβ€”Persona template language: en / zh / ja / ko (lazy-copied on first use)
"LANGSMITH" (sherry.jsonc)β€”Optional LangSmith tracing

3. Model Notes (HuggingFace Auto-Download)

Models configured for local GGUF mode are downloaded automatically from Hugging Face into models/<model>/model_weight/ on first use β€” no manual download is required:

  • Embedding: EMBEDDING_MODEL_LOCAL=true (the default) uses the local bge-m3 Q8_0 GGUF, auto-downloaded on first run.
  • Reranker: the .env template defaults to a cloud API (RERANKER_MODEL_LOCAL=false, OpenAI-compatible bge-reranker-v2-m3). Set it to true to run the local GGUF reranker instead, which is then auto-downloaded (~636 MB).
  • ITTT / VTTT / Auxiliary LLM: default to cloud APIs in the template; set *_MODEL_LOCAL=true to switch to local GGUF models (also auto-downloaded).

Network access to huggingface.co is required for first-run downloads (users in China may need a proxy or a mirror). Interrupted downloads are resumed on the next start; delete models/<model>/model_weight/ to force a re-download.

4. Start the Backend

start.sh activates the uv-managed .venv and launches the Robyn backend (it does not start Ollama or any frontend):

chmod +x start.sh
./start.sh          # runs the .venv interpreter: python -m server --fast --disable-openapi

Manual start (equivalent):

uv run python -m server

The backend listens on http://127.0.0.1:8080 with a WebSocket endpoint at /sessions/ws.

5. (Optional) Desktop Client

The Tauri 2 + Nuxt 4 client lives in client/ and requires Node.js 18+, pnpm, and Rust:

cd client
pnpm install
pnpm dev          # browser mode, dev server at http://localhost:3000
pnpm tauri dev    # native desktop mode

The client connects to the Python backend at http://127.0.0.1:8080 by default (configurable via VITE_API_BACK_URL in client/.env). See the client README for details.


πŸ§ͺ Testing

Tests live under tests/, mirroring the source tree (tests/agent/..., tests/server/..., tests/context_engine/...), and run with pytest via uv (uv run pytest for a single test file or a small selection). Every test file carries a module-level pytestmark (unit / integration / module / system / regression) that selects which runner group executes it.

For the full suite (and for CI), use the split runner β€” it executes the suite in three sequential pytest processes (never parallel), selecting tests by MARKER (not by directory), aggregates their exit codes, and prints a per-group summary plus a final verdict (exit code 0 only if all groups pass):

uv run python tests/run_tests_split.py                  # hermetic suite (default, llm_e2e excluded)
uv run python tests/run_tests_split.py --with-llm-e2e   # ONLY the real-LLM e2e tests (dedicated-job mode)
uv run python tests/run_tests_split.py -- -k spawn -q   # args after `--` are forwarded to pytest
GroupMarkerContents
Aunitpure-logic, fully mocked tests
Bintegration or module or systemhermetic integration / module / system tests
Cregressioncross-module regression tests

Why separate processes? tests/agent/tools/subagent/conftest.py installs stub callables into process-global sys.modules at conftest import time. In a single-process full-suite run, pytest imports every conftest and test module during collection β€” before any test executes β€” so those stubs are live for the whole process and leak across suites: lazy (call-time) imports resolve the stub, while modules that bound the real object earlier keep stale bindings. The result is confusing, order-dependent failures in suites far away from the subagent tests (e.g. skill-scope assertions seeing a stub's fixed skill list, TypeError tracebacks naming conftest lambdas). Running the groups in separate processes makes this cross-suite pollution structurally impossible. (The stubs themselves are restore-safe since c730a46; the runner is the defense-in-depth operational layer.)

Windows note: child pytest processes get PYTHONIOENCODING=utf-8 in their environment and the runner captures their output with errors="replace", so GBK console codepages can neither corrupt the output nor crash the run.

Real-LLM e2e tests (llm_e2e marker)

Three tests in tests/agent/tools/subagent/ (test_real_e2e.py, test_spawn_direct_e2e.py) call real LLM APIs. They are:

  • deselected by default (-m "not llm_e2e" β€” set both in pyproject.toml addopts and by the runner),
  • bounded by @pytest.mark.timeout budgets (pytest-timeout): 300 s per simple test, 600 s for the concurrent test,
  • run explicitly, in a dedicated job: uv run python tests/run_tests_split.py --with-llm-e2e (selects -m llm_e2e) or uv run pytest -m llm_e2e.

Expected runtimes (solo, real backend): simple task β‰ˆ 30–60 s; complex worst case β‰ˆ 10 min; concurrent tasks β‰ˆ 2–9 min. A run that exceeds these budgets is a real hang, not normal slowness β€” the per-test timeout bounds it (300 s simple / 600 s concurrent).

CI: .github/workflows/ci.yml runs uv run python tests/run_tests_split.py on every push/PR to main, executing the suite as three sequential pytest processes (never parallel): A = unit, B = integration + module + system, C = regression. The --with-llm-e2e suite stays a separate, slower job (it costs API tokens; never run it in parallel with other suites).

Note: tests/full/ is an auxiliary/experimental directory outside the standard groups above. Every file there that drives a live LLM is tagged llm_e2e, so the default addopts deselect it and the split runner never collects it (it also --ignores tests/full/). Run one explicitly with uv run --no-sync pytest -m llm_e2e tests/full/<file>. Hermetic tests do not belong there β€” the real-graph HITL test now lives at tests/agent/middlewares/humanInTheLoop/test_hitl_real_graph.py and runs in the standard groups.

Evals

evals/ is a homegrown, sandboxed evaluation framework that sits beside pytest. Run every registered suite, or one by name:

uv run python evals/evals.py                # all registered suites
uv run python evals/evals.py graph_rag      # a single suite by name
SuiteWhat it evaluates
graph_ragThe multimodal_rag pipeline, scored with RAGAS (faithfulness, answer relevancy, context recall, context precision)
subagentThe real spawn_subagent_direct pipeline on a bench of deterministic tasks (task success + latency)
long_running_taskThe TaskFlow orchestration loop over a dependent DAG (step success, flow completion, wall time)
session_memoryThe session-memory stack over 6 checks: cooldown, compaction lock, checkpoint restore, idempotent replay, context eligibility, semantic search ranking
nudge_extractionThe plan-extraction pass, judged by an auxiliary LLM for grounded, reusable, non-generic skills

Every suite runs inside evals/sandbox.py, which redirects repo writes into a temp sandbox, and writes its reports under evals/results/<suite>/<run_id>/. That directory is gitignored; per-run reports are never committed.


πŸ“ Character Profile Examples

The Agent's behavior is driven by the files under workspace/:

  • SOUL.md: Defines personality contrasts, speech style, and behavioral logic.
  • AGENTS.md: Defines tool usage priorities, safety boundaries, and ethical guidelines.
  • USER.md: Stores user-specific interaction preferences and known facts.
  • HEARTBEAT.md: Lists pending tasks for the heartbeat scheduled service.
  • prompt_builder.py: Builds the system prompt from the profile files.
  • file_sync.py: Lazily copies any missing persona files from workspace/template/<lang>/ (selected via WORKSPACE_TEMPLATE_LANG) without ever overwriting user edits.

🀝 Contributing

Issues and Pull Requests are welcome! To add a new skill:

  1. Create a folder under skills/ (or skills/plugins/ for third-party skills).
  2. Write a SKILL.md with YAML frontmatter (name, description, optional scope) describing the skill's usage and steps.
  3. Restart the Agent β€” the loader auto-discovers every SKILL.md and exposes it to the model. (You can also ask the running Agent to use the built-in skill_creator skill to generate one.)

Third-party skills under skills/plugins/ are scanned by SkillSpector and stay inactive until explicitly enabled.


Contact Information: QQ 3132225629

πŸ“„ License

This project is licensed under the MIT License.


πŸ’‘ Tip: This project is inspired by the exploration of advanced AI agents and deep role-playing.

Contributors

Languages

Python

87.8%

TypeScript

7.4%

Vue

4.4%