Imported from eja-edo/CORTEX (
AGENTS.md). Install upstream withnpx skills add eja-edo/CORTEX. Copyright stays with the author.
AGENTS.md
Cortex is a monorepo with multiple services. Always identify which service a task touches before running commands.
Đọc
docs/DESIGN.mdtrước mọi task. Đó là nguồn sự thật duy nhất về sản phẩm là gì, nguyên tắc ràng buộc, mô hình dữ liệu và kế hoạch. Tài liệu này chỉ mô tả cách chạy code, không mô tả nên xây gì.
Codebase discovery
Project đã được index trong codebase-memory-mcp dưới tên home-duyanh-project-Cortex — luôn truyền project="home-duyanh-project-Cortex" khi gọi MCP. Ưu tiên graph tools (search_graph → trace_path → get_code_snippet → query_graph) hơn grep/glob. Chi tiết cách dùng từng tool (mode, paging, Cypher queries hay dùng, khi nào fallback) xem .opencode/MCP_USAGE.md.
Repository layout (services)
| Dir | Stack | Purpose | Default port |
|---|---|---|---|
backend/ |
FastAPI + SQLAlchemy + Alembic | Main API: auth, notes, schedules, workspaces, agent/chat, media, SSE | 8000 |
frontend/ |
Vite + React 19 + TS | Web UI; proxies /api/v1/{workflows,executions,webhooks,actions} → workflow_service |
5173 |
workflow_service/ |
FastAPI + Temporal | Workflow runtime (CRUD, triggers, actions); talks to backend via internal API key | 8001 |
ocr_service/ |
FastAPI + EasyOCR | Video/image OCR pipeline; Redis queue consumer | — |
stt_service/ |
FastAPI + WhisperX | Speech-to-text worker; Redis queue consumer | — |
sync-server/ |
Node/Yjs (sources empty here; see sync-server/tests/) |
Yjs CRDT sync endpoint | 1235 |
infrastructure/ |
docker-compose | Postgres+pgvector (5434), Mongo (27016), Redis (6377), MinIO (9002/9003), Temporal (7233, UI 8088), workflow_service | — |
lab/ |
Python | Throwaway OCR pipeline experiments — do not import from here in any service | — |
docs/ |
Markdown | docs/DESIGN.md là nguồn sự thật duy nhất về sản phẩm, kiến trúc và kế hoạch. Đọc trước mọi task. docs/archive/ là kế hoạch cũ, không làm theo |
— |
Run order (local dev)
cd infrastructure && docker compose up -d db mongo redis minio-cortex minio-cortex-init temporal temporal-ui— bring up infra.pgvector/pgvector:pg16is required (not plain postgres).- Backend migrations: from
backend/,python -m alembic upgrade head(orpython run_migration.py). Re-run after every schema change. - Backend:
python main.py(preferred) oruvicorn app:app --reload --host 0.0.0.0 --port 8000.main.pyconfigures the root logger before SQLAlchemy imports — use it, not bareuvicorn, when log noise matters. - Workflow service:
uvicorn app.main:app --port 8001fromworkflow_service/(usesapp/config.pySettings; readsworkflow_service/.envif present, else falls back to localhost defaults). - Frontend:
npm run devfromfrontend/. Vite proxy already routes workflow endpoints to:8001. - OCR/STT workers only needed if touching the media pipeline.
Health checks: http://localhost:8000/health, http://localhost:8001/health, http://localhost:8088 (Temporal UI), http://localhost:9002 (MinIO console :9003).
Phép thử A/B (DESIGN mục 12)
Cổng nghiệm thu duy nhất của sản phẩm. Nhóm chia bằng user_preferences.gate_bypass:
# gán một người vào nhóm A (nhắc ngây thơ, bỏ qua Attention Gate)
curl -X PATCH localhost:8000/internal/users/<user_id>/gate-bypass \
-H "X-Internal-API-Key: $INTERNAL_API_KEY" \
-H 'Content-Type: application/json' -d '{"enabled": true}'
# đọc hai con số của 12.3
cd backend && python -m scripts.ab_test_numbers
Cờ cố ý không có trong trang cài đặt: nó là biến điều khiển của một thí nghiệm, không phải tuỳ chọn sản phẩm — người dùng thấy nó là mời họ tự đổi nhóm giữa chừng.
Mezon bot
- Chạy:
cd mezon_bot && npm start— HTTP:8100(/health,POST /internal/delivervớiX-Internal-API-Key). Test:npm test(222 test, node:test). - ⚠️ Phải chạy trên Node 22, không phải 24.
mezon-sdkkéo theobetter-sqlite3@11.10, bản này không tương thích Node 24: biên dịch được nhưng crash trongStatement::~Statement()vớiAssertion failed: (env) != nullptr, kèm core dump ngay lúc khởi động.nvm use 22rồinpm rebuild better-sqlite3. - Nếu
npm rebuildbáoENOENT ... build/node_gyp_bins: đó là bug node-gyp, tạo tay thư mục đó rồi chạy lại (mkdir -p node_modules/better-sqlite3/build/node_gyp_bins).
Webhook bot họp
POST /api/internal/tasks (X-Internal-API-Key) — nhận action item, gom theo dự án. Luôn trả 200; item không vào được nằm trong items[].skipped_reason. Gửi external_id cho mỗi item, nếu không mỗi lần retry sẽ đẻ một bản sao. Chi tiết: backend/app/services/task_ingest.py.
Environment / secrets
backend/.envis not tracked in git (verified 2026-08-24). It holds non-prod keys (Google OAuth, Gemini, OpenAI-compatible local endpoint, Zep, MinIO, internal API keycortex-internal-key-2024). Treat as dev only — never reuse these in prod.backend/.envpoints to non-default ports (5434Postgres,27016Mongo,6377Redis,9002MinIO) to matchinfrastructure/docker-compose.yml. Don't change to defaults without updating compose.- The same
INTERNAL_API_KEYvalue (cortex-internal-key-2024) is used byworkflow_service/config.pydefault and by the Yjs sync-server tests (X-Internal-Token: cortex-internal-secretinsync-server/tests/test-headless-apply.mjs— note the value differs; sync-server usescortex-internal-secret). - JWT secret default is the literal string
change-this-in-production-super-secret-key(seebackend/.env,workflow_service/app/config.py:resolved_jwt_secret). Required for both backend and workflow_service to validate the same tokens.
Backend (FastAPI)
- Entry:
backend/app/__init__.py(constructsapp), started bybackend/main.py. Workers (transcription consumer, LLM processor, reminder, Google sync) are started in background threads viaWorkerThreadin the app lifespan. - DB: async SQLAlchemy (
database_async.py) + sync (database.py) for scripts. Schema migrations live inbackend/alembic/versions/. Migrations named like00X_*.py— use sequential numbering for new ones. - Settings:
backend/app/config.py(pydantic-settings), readsbackend/.env. - API routers in
backend/app/api/:auth,agent(chat/SSE),notes,schedules,workspaces,proposals,assets,images,upload,knowledge,notifications,google_calendar,internal(for workflow_service callbacks),sse/*. - Adding a new API route: register the router in
backend/app/__init__.py. - One-off scripts live in
backend/scripts/and add the parent tosys.paththemselves — run aspython -m scripts.<name>frombackend/.
Backend tests
- Config:
backend/pytest.ini→testpaths = tests, asyncio scope = function. Run frombackend/:python -m pytestorpython -m pytest tests/test_foo.py -v. - Many numbered ad-hoc tests sit at the top of
backend/(test_issue1_extract_memory_triggers.py…test_issue8_dead_code.py,test_e2e_*.py,test_function_unit.py,test_token_budget_history.py, etc.). They follow a "Part N" convention — see the file's module docstring forRun: python -m pytest <file> -v. - Integration tests for the agent require the agent infrastructure; check fixture docstrings before running.
backend/check_env.pyandbackend/check_migration.pyare diagnostic one-liners, not tests.
Workflow service
- Entry:
workflow_service/app/main.py— lifespan starts the internal-event Redis listener and a Temporal worker in the background. - Depends on: Temporal at
temporal:7233(compose service name), backend athttp://localhost:8000(orhost.docker.internal:8000from inside compose), Redis atredis:6377. - Talks to backend over
X-Internal-Token: $CORTEX_INTERNAL_API_KEY(defaultcortex-internal-key-2024); seebackend/app/api/internal.pyfor the receiving side. - Triggering: backend publishes events on Redis channel
cortex:workflow:events(seedocs/archive/workflow_feature/10_INTEGRATION_GUIDE.md). Webhooks usetrigger_type=webhookand a per-workflowwebhook_url+webhook_secret; secret is required only if set. - Tests:
workflow_service/pytest.ini(asyncio_mode=auto, session scope). Pytest suite:python -m pytestfromworkflow_service/. End-to-end integration script:bash workflow_service/tests/run_integration_tests.sh(requires a running service at$HOST, defaulthttp://localhost:8001). - ⚠️
workflow_serviceđã đóng băng (docs/DESIGN.mdmục 11): 0 workflow trong DB, gỡ khỏi compose mặc định. Không thêm tính năng vào đây. Design docs cũ nằm ởdocs/archive/workflow_feature/, chỉ để tra cứu.
Frontend
- Vite config:
frontend/vite.config.ts— proxy targets are for workflow_service paths only. Backend calls go directly to:8000. - Scripts:
npm run dev,npm run build(runstsc -bthen Vite build — type errors fail the build),npm run lint(ESLint flat config),npm run test(Vitest, jsdom, files matched bysrc/**/*.{test,spec}.{ts,tsx}). - Order matters when validating:
lint → build (typecheck via tsc) → test. - Main app shell:
frontend/src/App.tsx; workflow UI lives infrontend/src/components/workflow/plusWorkflowBuilder.tsx. - Don't forget to also run
npm run buildafter TypeScript changes —tsc -bis the project's typecheck. - ⚠️
npx tsc --noEmitchecks nothing and exits 0. The roottsconfig.jsonis"files": []+ project references, so a bare invocation has no input files. It looks like a clean typecheck and is not one — a deliberateconst x: number = 'str'passes. Alwaysnpx tsc -b(or-p tsconfig.app.json).
OCR / STT / Sync notes
ocr_service/main.pyandstt_service/main.pyeach load a local.envfrom their own directory and start a Redis queue consumer at startup. They depend on MinIO + Redis from compose.ocr_service/worker/contains the RQ-style task processors;stt_service/service/the WhisperX pipeline.sync-server/source tree is empty in this checkout (onlytests/anddist/,node_modules/); the Yjs protocol contract is indocs/feat_notes/03-prompt-migrate-realtime-collab-yjs.mdand04-prompt-crdt-agent-proposals.md. When asked to modify sync-server behavior, verify the running source elsewhere first.
Conventions / gotchas worth knowing
- Logging must be configured before SQLAlchemy is imported (
_configure_root_logger()inbackend/app/__init__.pyandbackend/main.py) — don't reorder imports or the SQLAlchemy banner floods stdout. - Internal service-to-service auth uses
INTERNAL_API_KEYheader (backend/app/api/internal.py); do not add JWT to internal calls. - Worker threads in
backend/app/__init__.pyrun async loops in their own threads — be careful sharing async resources (DB sessions, Redis pools) across them; each worker manages its own. backend/migrations/is an old folder, not used; the live migration tree isbackend/alembic/.lab/andlogs/are gitignored; safe to ignore unless the task explicitly references them.- Several
.bakfiles exist underbackend/tests/(test_search.py.bak) — leave them alone unless asked. - The codebase mixes English and Vietnamese comments/docs; preserve existing language in surrounding context when editing comments.
- The last line of an LLM prompt decides the JSON shape. The gateway does not support
response_formatschemas, so the only lever is the prompt — and the closing instruction of the user turn beats the system prompt. Naming keys ("a JSON object containing episodic_summary, semantic_memories, title") gets you arrays of bare strings; the shape has to be drawn out. Useresponse_shape()inbackend/app/services/memory_extraction_prompt.pyrather than writing a new closing line. - The Cortex backend runs as
./venv/bin/python main.py(notuvicorn app.main:app— that command line belongs to an unrelated container also bound to :8000). Prompts underbackend/app/ai/prompts/are loaded at import, so editing one needs a restart.
When the task touches more than one service
- Schema change: alembic migration in
backend/→ runalembic upgrade head→ if workflow_service reads the new columns, update its models inworkflow_service/app/models/and re-run its migrations. - New workflow trigger/action: implement handler in
workflow_service/app/triggers/orworkflow_service/app/actions/, then add backend publisher inbackend/app/services/redis/event_publisher.pyperworkflow_feature/10_INTEGRATION_GUIDE.md. - New frontend route under
/workflows: add the React Flow component underfrontend/src/components/workflow/and wire the route infrontend/src/App.tsx.
Agent Protocol
Before executing ANY task:
- Read
.opencode/protocol.mdin full. - Read
.opencode/state/TASK.md. That file, not the chat message, is the task. - Follow the mode contract in protocol.md section 1.
- Write
.opencode/state/REPORT.mdusing the template, and append to LOG.md.
.opencode/protocol.md takes priority over your default execution habits.
You are an executor, not a planner: never invent the next task, never widen scope.