English Β· δΈζ Β· νκ΅μ΄ Β· ζ₯ζ¬θͺ
A deep role-playing AI Agent built on LangChain/LangGraph and multimodal technology.
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.
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 firinglanggraph-checkpoint-sqlite) persists agent state across restarts; stale checkpoints are cleaned automaticallyread_file results are sliced, a no-LLM tail clip runs before any compression, and chained summaries are filtered out of the conversation payloadmultimodal_rag skill indexes documents/folders into an entityβrelationship graph (vendored LightRAG + RAG-Anything on snkv vector storage) and answers via multi-hop graph retrievalagent/tools/todolist/knowledge/), skills/auto/, and todos.dbname, description, optional scope: all | main_only | subagent_only) β the loader auto-discovers every SKILL.md under skills/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_wikiskills/plugins/) stay inactive until explicitly enabledskills/auto/ β see the Experience READMETOOLS_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 consumessessions_spawn, sessions_yield, sessions_send, sessions_kill, sessions_steer, agents_list, subagents_listcontinue verdict injects the judge's follow-up prompt as the next turn, bounded by the configured COMPLETION_JUDGE["goal_max_turns"] budget (default 5)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/sessions/ws) on 127.0.0.1:8080, serving uploaded media under /static, /images, /audio, /videotext_to_image skillat), 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 channelsHEARTBEAT.md for pending tasks, lets an LLM decide skip/run, and passes results through a notification gateBuilt on Python 3.13 (dependency management via uv), with the following core technologies:
| Module | Technology |
|---|---|
| Agent Framework | LangChain 1.3+ (create_agent + middlewares), LangGraph compiled graphs |
| Checkpointing | langgraph-checkpoint-sqlite (thread-safe async SQLite saver) |
| Web Server | Robyn (HTTP + WebSocket + static hosting) |
| Database | SQLite via aiosqlite (FTS5 full-text search, WAL mode) |
| Graph RAG | Vendored LightRAG + RAG-Anything (multimodal_rag skill), snkv[vector] storage |
| Local Inference | llama-cpp-python (GGUF: bge-m3 embedding, bge-reranker-v2-m3 reranker, auxiliary/ITTT/VTTT models), FunASR (STT) |
| Document Parsing | mineru-vl-utils |
| Web Search | langchain-tavily (Tavily API) |
| LLM Providers | langchain-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 Output | instructor, json_repair |
| Evaluation | RAGAS (graph-RAG quality metrics) + a homegrown sandboxed suite runner (evals/) |
| MCP | langchain-mcp-adapters (servers configured in plugins/mcp_server/) |
| Task Scheduling | croniter, asyncio |
| Async Messaging | asyncio queues (MessageBus, EventBus) |
| Media Processing | OpenCV (headless), Pillow, websockets / websocket-client |
| Desktop Client | Tauri 2 + Nuxt 4 (Vue 3, TypeScript, pnpm) |
| Logging | loguru (optional LangSmith tracing) |
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
Each major subsystem has its own detailed README:
| Submodule | Description | Documentation |
|---|---|---|
| Context Engine | Short-term session message memory (MesMemory) | EN Β· ZH Β· JA Β· KO |
| Experience | Four extraction lifecycle paths plus the background Curator that maintains skills/auto/ | EN Β· ZH Β· JA Β· KO |
| Session Memory | SESSION-plan capabilities: memory flush, compression cooldown, compaction lock, event log, semantic search | EN Β· ZH Β· JA Β· KO |
| Subagent System | Multi-level subagent spawn, parallel execution & result delivery | EN Β· ZH Β· JA Β· KO |
| Subagent Design | Design invariants: two-axis role model, spawn-privilege guards, four-layer completion gates | EN Β· ZH Β· JA Β· KO |
| Code Intel | Four retrieval layers for the code-intel roles: tree-sitter symbol index, ast-grep structural search, LSP precision, semantic search | EN Β· ZH Β· JA Β· KO |
| Middlewares | Agent lifecycle middleware pipeline | EN Β· ZH Β· JA Β· KO |
| Channels | Channel interface & adapter system | EN Β· ZH Β· JA Β· KO |
| Desktop Client | Tauri 2 + Nuxt 4 desktop/mobile SPA client | EN Β· ZH Β· JA Β· KO |
| Cron Service | Scheduled/periodic agent task execution | EN Β· ZH Β· JA Β· KO |
| Heartbeat Service | Periodic wake-up task check | EN Β· ZH Β· JA Β· KO |
| Summarization | Context compaction middleware: five trigger points, a 4-route overflow router, anti-thrash guards | EN Β· ZH Β· JA Β· KO |
| Loop Prevention | Runaway-loop guards, exponential-backoff breakers, and process crash gating | EN Β· ZH Β· JA Β· KO |
| Sandbox | Terminal & Python REPL confinement: env scrubbing, OS-native isolation, approval gate | EN Β· ZH Β· JA Β· KO |
| Long-Running Tasks | TaskFlow DAG engine, step judge, budgets, deadlines, verified completion gates, and cross-turn memory continuity | EN Β· ZH Β· JA Β· KO |
| Token Guard | Hard 128K context-window floor on both LLMs (boot, build, spawn, env write) | EN Β· ZH Β· JA Β· KO |
| Context Governance | Per-boundary persistence, tool-result & human-message eviction, read_file slice, overflow tail clip, summary filtering | EN Β· ZH Β· JA Β· KO |
.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
Copy the .env example and fill in at least the main chat model and Tavily key:
cp .env.example .env
| Variable | Required | Description |
|---|---|---|
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 search | Enables 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 |
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_MODEL_LOCAL=true (the default) uses the local bge-m3 Q8_0 GGUF, auto-downloaded on first run..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).*_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.
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.
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.
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
| Group | Marker | Contents |
|---|---|---|
| A | unit | pure-logic, fully mocked tests |
| B | integration or module or system | hermetic integration / module / system tests |
| C | regression | cross-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.
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:
-m "not llm_e2e" β set both in pyproject.toml addopts and by the runner),@pytest.mark.timeout budgets (pytest-timeout): 300 s per simple test, 600 s for the concurrent test,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 taggedllm_e2e, so the default addopts deselect it and the split runner never collects it (it also--ignorestests/full/). Run one explicitly withuv run --no-sync pytest -m llm_e2e tests/full/<file>. Hermetic tests do not belong there β the real-graph HITL test now lives attests/agent/middlewares/humanInTheLoop/test_hitl_real_graph.pyand runs in the standard groups.
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
| Suite | What it evaluates |
|---|---|
graph_rag | The multimodal_rag pipeline, scored with RAGAS (faithfulness, answer relevancy, context recall, context precision) |
subagent | The real spawn_subagent_direct pipeline on a bench of deterministic tasks (task success + latency) |
long_running_task | The TaskFlow orchestration loop over a dependent DAG (step success, flow completion, wall time) |
session_memory | The session-memory stack over 6 checks: cooldown, compaction lock, checkpoint restore, idempotent replay, context eligibility, semantic search ranking |
nudge_extraction | The 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.
The Agent's behavior is driven by the files under workspace/:
workspace/template/<lang>/ (selected via WORKSPACE_TEMPLATE_LANG) without ever overwriting user edits.Issues and Pull Requests are welcome! To add a new skill:
skills/ (or skills/plugins/ for third-party skills).SKILL.md with YAML frontmatter (name, description, optional scope) describing the skill's usage and steps.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
This project is licensed under the MIT License.
π‘ Tip: This project is inspired by the exploration of advanced AI agents and deep role-playing.
Python
87.8%
TypeScript
7.4%
Vue
4.4%
English Β· δΈζ Β· νκ΅μ΄ Β· ζ₯ζ¬θͺ
A deep role-playing AI Agent built on LangChain/LangGraph and multimodal technology.
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.
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 firinglanggraph-checkpoint-sqlite) persists agent state across restarts; stale checkpoints are cleaned automaticallyread_file results are sliced, a no-LLM tail clip runs before any compression, and chained summaries are filtered out of the conversation payloadmultimodal_rag skill indexes documents/folders into an entityβrelationship graph (vendored LightRAG + RAG-Anything on snkv vector storage) and answers via multi-hop graph retrievalagent/tools/todolist/knowledge/), skills/auto/, and todos.dbname, description, optional scope: all | main_only | subagent_only) β the loader auto-discovers every SKILL.md under skills/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_wikiskills/plugins/) stay inactive until explicitly enabledskills/auto/ β see the Experience READMETOOLS_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 consumessessions_spawn, sessions_yield, sessions_send, sessions_kill, sessions_steer, agents_list, subagents_listcontinue verdict injects the judge's follow-up prompt as the next turn, bounded by the configured COMPLETION_JUDGE["goal_max_turns"] budget (default 5)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/sessions/ws) on 127.0.0.1:8080, serving uploaded media under /static, /images, /audio, /videotext_to_image skillat), 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 channelsHEARTBEAT.md for pending tasks, lets an LLM decide skip/run, and passes results through a notification gateBuilt on Python 3.13 (dependency management via uv), with the following core technologies:
| Module | Technology |
|---|---|
| Agent Framework | LangChain 1.3+ (create_agent + middlewares), LangGraph compiled graphs |
| Checkpointing | langgraph-checkpoint-sqlite (thread-safe async SQLite saver) |
| Web Server | Robyn (HTTP + WebSocket + static hosting) |
| Database | SQLite via aiosqlite (FTS5 full-text search, WAL mode) |
| Graph RAG | Vendored LightRAG + RAG-Anything (multimodal_rag skill), snkv[vector] storage |
| Local Inference | llama-cpp-python (GGUF: bge-m3 embedding, bge-reranker-v2-m3 reranker, auxiliary/ITTT/VTTT models), FunASR (STT) |
| Document Parsing | mineru-vl-utils |
| Web Search | langchain-tavily (Tavily API) |
| LLM Providers | langchain-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 Output | instructor, json_repair |
| Evaluation | RAGAS (graph-RAG quality metrics) + a homegrown sandboxed suite runner (evals/) |
| MCP | langchain-mcp-adapters (servers configured in plugins/mcp_server/) |
| Task Scheduling | croniter, asyncio |
| Async Messaging | asyncio queues (MessageBus, EventBus) |
| Media Processing | OpenCV (headless), Pillow, websockets / websocket-client |
| Desktop Client | Tauri 2 + Nuxt 4 (Vue 3, TypeScript, pnpm) |
| Logging | loguru (optional LangSmith tracing) |
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
Each major subsystem has its own detailed README:
| Submodule | Description | Documentation |
|---|---|---|
| Context Engine | Short-term session message memory (MesMemory) | EN Β· ZH Β· JA Β· KO |
| Experience | Four extraction lifecycle paths plus the background Curator that maintains skills/auto/ | EN Β· ZH Β· JA Β· KO |
| Session Memory | SESSION-plan capabilities: memory flush, compression cooldown, compaction lock, event log, semantic search | EN Β· ZH Β· JA Β· KO |
| Subagent System | Multi-level subagent spawn, parallel execution & result delivery | EN Β· ZH Β· JA Β· KO |
| Subagent Design | Design invariants: two-axis role model, spawn-privilege guards, four-layer completion gates | EN Β· ZH Β· JA Β· KO |
| Code Intel | Four retrieval layers for the code-intel roles: tree-sitter symbol index, ast-grep structural search, LSP precision, semantic search | EN Β· ZH Β· JA Β· KO |
| Middlewares | Agent lifecycle middleware pipeline | EN Β· ZH Β· JA Β· KO |
| Channels | Channel interface & adapter system | EN Β· ZH Β· JA Β· KO |
| Desktop Client | Tauri 2 + Nuxt 4 desktop/mobile SPA client | EN Β· ZH Β· JA Β· KO |
| Cron Service | Scheduled/periodic agent task execution | EN Β· ZH Β· JA Β· KO |
| Heartbeat Service | Periodic wake-up task check | EN Β· ZH Β· JA Β· KO |
| Summarization | Context compaction middleware: five trigger points, a 4-route overflow router, anti-thrash guards | EN Β· ZH Β· JA Β· KO |
| Loop Prevention | Runaway-loop guards, exponential-backoff breakers, and process crash gating | EN Β· ZH Β· JA Β· KO |
| Sandbox | Terminal & Python REPL confinement: env scrubbing, OS-native isolation, approval gate | EN Β· ZH Β· JA Β· KO |
| Long-Running Tasks | TaskFlow DAG engine, step judge, budgets, deadlines, verified completion gates, and cross-turn memory continuity | EN Β· ZH Β· JA Β· KO |
| Token Guard | Hard 128K context-window floor on both LLMs (boot, build, spawn, env write) | EN Β· ZH Β· JA Β· KO |
| Context Governance | Per-boundary persistence, tool-result & human-message eviction, read_file slice, overflow tail clip, summary filtering | EN Β· ZH Β· JA Β· KO |
.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
Copy the .env example and fill in at least the main chat model and Tavily key:
cp .env.example .env
| Variable | Required | Description |
|---|---|---|
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 search | Enables 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 |
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_MODEL_LOCAL=true (the default) uses the local bge-m3 Q8_0 GGUF, auto-downloaded on first run..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).*_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.
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.
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.
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
| Group | Marker | Contents |
|---|---|---|
| A | unit | pure-logic, fully mocked tests |
| B | integration or module or system | hermetic integration / module / system tests |
| C | regression | cross-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.
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:
-m "not llm_e2e" β set both in pyproject.toml addopts and by the runner),@pytest.mark.timeout budgets (pytest-timeout): 300 s per simple test, 600 s for the concurrent test,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 taggedllm_e2e, so the default addopts deselect it and the split runner never collects it (it also--ignorestests/full/). Run one explicitly withuv run --no-sync pytest -m llm_e2e tests/full/<file>. Hermetic tests do not belong there β the real-graph HITL test now lives attests/agent/middlewares/humanInTheLoop/test_hitl_real_graph.pyand runs in the standard groups.
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
| Suite | What it evaluates |
|---|---|
graph_rag | The multimodal_rag pipeline, scored with RAGAS (faithfulness, answer relevancy, context recall, context precision) |
subagent | The real spawn_subagent_direct pipeline on a bench of deterministic tasks (task success + latency) |
long_running_task | The TaskFlow orchestration loop over a dependent DAG (step success, flow completion, wall time) |
session_memory | The session-memory stack over 6 checks: cooldown, compaction lock, checkpoint restore, idempotent replay, context eligibility, semantic search ranking |
nudge_extraction | The 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.
The Agent's behavior is driven by the files under workspace/:
workspace/template/<lang>/ (selected via WORKSPACE_TEMPLATE_LANG) without ever overwriting user edits.Issues and Pull Requests are welcome! To add a new skill:
skills/ (or skills/plugins/ for third-party skills).SKILL.md with YAML frontmatter (name, description, optional scope) describing the skill's usage and steps.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
This project is licensed under the MIT License.
π‘ Tip: This project is inspired by the exploration of advanced AI agents and deep role-playing.
Python
87.8%
TypeScript
7.4%
Vue
4.4%