Languages: English | 简体中文
A text-only conversational agent with a Planner → Manager → Executor pipeline (LangGraph), optional Neo4j + Graphiti memory graph, and a React + Vite frontend. Secrets live in .env, not in code.
Engram (neuroscience): the physical trace of a memory in the brain—aligned with “dialogue agent + persistent memory graph.”
| Chat (streaming + sessions) | Memory graph (Neo4j / vis-network) |
|---|---|
![]() |
![]() |
- Agent workflow: Planner (JSON mode structured plan) routes work to role-based executors; Manager schedules steps; executors call the LLM (streaming for replies).
- Chat UI: SSE streaming, session list, history restored from the server after refresh.
- Memory graph: Optional Graphiti episodes + Neo4j; vis-network page embeddable from the frontend.
- Persistence:
DATABASE_URLor default SQLite atbackend/data/engram.db. - Extensible roles:
executors_config.yaml+ optional Python executor classes.
- Backend: Python 3.11+ recommended, uv
- Frontend: Node 18+ / npm
- Optional: Neo4j for the memory graph; OpenAI-compatible LLM API
engram/
├── docs/static/screenshots/ # UI screenshots (README)
├── backend/ # FastAPI, uv
│ ├── main.py # Entry, port 5000 by default
│ ├── config.py # Loads .env
│ ├── executors_config.yaml
│ ├── core/ # planner, manager, base_executor, state
│ ├── executors/ # registry + custom executors
│ ├── tools/ # @tool definitions → ALL_TOOLS
│ ├── tools_lib/ # TOOL_REGISTRY, get_tools_by_names
│ ├── graph/ # LangGraph workflow
│ ├── services/ # agent SSE, Graphiti, session store
│ ├── routes/ # /api/chat, /api/kg/*, sessions
│ ├── models/ # SQLAlchemy (conversations / messages)
│ └── templates/ # kg_graph.html (vis-network)
└── frontend/ # React (Vite), port 3000
└── src/pages/ # ChatPage, MemoryGraphPage
cd engram/backend
cp .env.example .env
# Edit .env: LLM_API_KEY, LLM_BASE_URL, LLM_MODEL, etc.
uv sync
# Production-style (no auto-reload on file changes)
uv run python main.py
# Dev hot reload:
# UVICORN_RELOAD=1 uv run python main.py
# # or: uv run uvicorn main:app --reload --port 5000- API base:
http://localhost:5000 - OpenAPI:
http://localhost:5000/docs
cd engram/frontend
npm install
npm run dev- App:
http://localhost:3000(Vite proxies/apitolocalhost:5000in dev)
| Method | Path | Description |
|---|---|---|
| GET | /api/sessions |
sessions (ids) + conversations (session_id, title, updated_at) |
| GET | /api/sessions/{session_id}/messages |
Full message list for reload/hydration |
| DELETE | /api/sessions/{session_id} |
Delete server-side session + messages |
If DATABASE_URL is unset, SQLite is used automatically; history still persists.
| Variable | Description | Required |
|---|---|---|
LLM_API_KEY |
API key | Yes |
LLM_BASE_URL |
OpenAI-compatible base URL | Yes |
LLM_MODEL |
Chat model name | Yes |
GRAPHITI_LLM_MODEL |
Model for Graphiti extraction (default gpt-4o) |
No |
GRAPHITI_EMBEDDING_MODEL |
Embedding model name | No |
NEO4J_URI |
e.g. bolt://localhost:7687 (empty disables graph writes) |
No |
NEO4J_USER |
Default neo4j |
No |
NEO4J_PASSWORD |
Password | No |
USER_ID |
Graphiti group_id (default 1) |
No |
DATABASE_URL |
SQLAlchemy URL; empty → ./data/engram.db |
No |
HOST / PORT |
Bind address (default 0.0.0.0:5000) |
No |
UVICORN_RELOAD |
1 / true to enable reload with python main.py |
No |
| Engine | DATABASE_URL |
Extra dependency |
|---|---|---|
| SQLite (default) | (omit) | — |
| PostgreSQL | postgresql+psycopg2://user:pass@host:5432/db |
uv add psycopg2-binary |
| MySQL | mysql+pymysql://user:pass@host:3306/db |
uv add pymysql |
- Declare
@toolfunctions underbackend/tools/and register them viatools/__init__.py(_TOOL_MODULES). - In
tools_lib/__init__.py, define named sets the same way asGENERAL_TOOLS/RESEARCHER_TOOLSin agent_platform:
GENERAL_TOOLS = get_tools_by_names(["get_current_time", "calculate"])
TOOL_PRESETS = {"general": GENERAL_TOOLS, "empty": EMPTY_TOOLS, ...}- Python executors: implement
get_tools()→return GENERAL_TOOLS(seeexecutors/example_executor.py). - YAML-only executors: set
tool_preset: general(orempty,analyst, …) instead of listing every tool name.
Optional tools: [name, ...] in YAML still overrides presets / class get_tools() when you need an explicit exception.
Use tools_lib.list_registered_tool_names() and list_tool_presets() for debugging.
Edit backend/executors_config.yaml (optional tools as above). Restart the backend. The Planner reads role_key values from the registry.
- Add e.g.
backend/executors/researcher.pyimplementingBaseExecutor(role_name,system_prompt,get_tools()), or rely on YAMLtoolsto inject shared tools without customget_tools. - Register in YAML:
- role_key: researcher
name: Researcher
desc: Tasks that need search or structured gathering
python_class: executors.researcher.ResearcherExecutorRestart the backend.
SSE stream. Body:
{ "query": "Hello", "session_id": "uuid-here" }Events include: user_message, todo_list, summary, tool_call, tool_result, answer_delta, answer, error, end.
HTML page for the memory graph (e.g. iframe). Uses USER_ID as default group_id when not overridden.
JSON nodes/edges for the graph viewer.
Languages: English | 简体中文

