Last updated: 2026-07
This is the entry point for Markus technical documentation. Each domain has one authoritative document; a mechanism is described in full only in its home document and cross-referenced elsewhere. Start here, then follow the map below.
| Document | Domain (single responsibility) |
|---|---|
| ARCHITECTURE.md (this file) | System overview, package structure, core concepts, channels, deployment, observability |
| COGNITIVE-ARCHITECTURE.md | Unified cognitive cycle, Cognitive Preparation Pipeline (CPP), heartbeat integration |
| MEMORY-SYSTEM.md | Memory layers (ROLE / MEMORY / session / notebook / activity), storage compaction, memory flush |
| PROMPT-ENGINEERING.md | Prompt & context assembly, LLM call taxonomy, context packing, prompt caching |
| MAILBOX-SYSTEM.md | Agent mailbox (priority queue) + attention controller (serial focus, interrupts, yield, cancel) |
| STATE-MACHINES.md | FSMs for tasks, requirements, callbacks, mailbox items, notebook |
| TOOL-SYSTEM.md | Tool selection, tool result envelope, tool-execution loop, subagent spawn & budgets |
| STREAMING-AND-REATTACH.md | SSE streaming, soft-disconnect, active-stream ring + UI snapshot, reattach, structured events |
| CODING-TOOLS.md | External coding CLI integration (Claude Code / Codex / Cursor Agent) |
| API.md | REST / WebSocket API reference |
| GUIDE.md | Setup, deployment, and usage guide |
| REMOTE-ACCESS.md | Remote access configuration |
| RELEASE-AND-DISTRIBUTION.md | Release process and distribution |
flowchart TD
ARCH[ARCHITECTURE - entry point]
subgraph cognition [Cognition]
COG[COGNITIVE-ARCHITECTURE]
MEM[MEMORY-SYSTEM]
PROMPT[PROMPT-ENGINEERING]
end
subgraph execution [Execution]
MAILBOX[MAILBOX-SYSTEM]
FSM[STATE-MACHINES]
TOOLS[TOOL-SYSTEM]
STREAM[STREAMING-AND-REATTACH]
end
subgraph integration [Integration and Reference]
CODING[CODING-TOOLS]
API[API]
GUIDE[GUIDE]
end
ARCH --> cognition
ARCH --> execution
ARCH --> integration
MAILBOX -->|item terminal states| FSM
MAILBOX -->|preempt aborts stream| STREAM
COG -->|context assembly| PROMPT
PROMPT -->|packing triggers flush| MEM
PROMPT -->|tool defs and results| TOOLS
TOOLS -->|tool errors surfaced as events| STREAM
COG -->|CPP writes notebook| MEM
- Single source of truth: a mechanism is fully specified only in its home document.
- Spec sections: feature specs follow a fixed template — Behavior / Invariants / Design rationale (with Pi/Hermes comparison where relevant) / Testing (required cases + test files) / Status (planned | implemented).
- Doc/code accuracy: documented behavior must match the code. When they diverge, fix the code or the doc in the same change; never leave a documented-but-unimplemented behavior unmarked.
Markus is an AI Digital Workforce Platform that lets organizations hire, manage, and coordinate multiple AI Agents that work proactively like real employees. The platform provides a full governance framework including project management, task approval, workspace isolation, formal delivery review, knowledge sharing, and periodic reporting.
┌─────────────────────────────────────────────────────────────────┐
│ Web UI (React) │
│ Chat · Agents · Tasks · Team · Dashboard · Settings │
│ Governance · Projects · Knowledge · Reports │
└──────────────────────────┬──────────────────────────────────────┘
│ HTTP + WebSocket
┌──────────────────────────▼──────────────────────────────────────┐
│ API Server (Node.js) │
│ REST API · WebSocket · Auth (JWT) · Static file serve │
└──┬──────────┬──────────┬──────────┬──────────┬─────────────────┘
│ │ │ │ │
┌──▼────┐ ┌──▼─────┐ ┌──▼──────┐ ┌▼───────┐ ┌▼────────────────┐
│OrgSvc │ │TaskSvc │ │AgentMgr │ │Project │ │Governance Layer │
│Org │ │Tasks │ │Agent │ │Service │ │Report·Deliver │
│Mgmt │ │+ Approve│ │Lifecycle│ │Reqs │ │Trust·Archive │
└──┬────┘ └──┬─────┘ └──┬──────┘ └┬───────┘ └┬────────────────┘
│ │ │ │ │
┌──▼─────────▼──────────▼─────────▼───────────▼───────────────┐
│ Agent Runtime (@markus/core) │
│ Agent · Mailbox · AttentionController · ContextEngine │
│ Notebook · Memory · CognitivePreparation · PendingCallback │
│ Goal/Loop · LLMRouter · Heartbeat · Tools · MCP · Review │
└──────────────────────────┬──────────────────────────────────┘
│
┌────────────▼──────────────┐
│ SQLite (node:sqlite) │
│ tasks · projects · reqs │
│ deliverables · reports │
│ users · chat · audit_logs │
└───────────────────────────┘
packages/
├── shared/ # Shared types, constants, utils (governance/project/knowledge types)
├── core/ # Agent runtime (core engine) + ReviewService
├── storage/ # SQLite persistence + Repository layer
├── org-manager/ # Org management + REST API + governance (Project/Report/Knowledge/Trust)
├── comms/ # Communication adapters (Feishu, etc.)
├── a2a/ # Agent-to-Agent protocol types + DelegationManager (A2ABus retired)
├── gui/ # GUI automation (VNC + OmniParser)
├── web-ui/ # Web admin UI (governance/project/knowledge/report pages)
└── cli/ # CLI entry point + service assembly
Each Agent consists of:
| Component | Description |
|---|---|
ROLE.md |
Role definition and system prompt |
HANDBOOK.md |
AGENT HANDBOOK — shared working Know-how for all agents (not injected into ROLE); hard rules live in L0 Collaboration Rules; single source at templates/roles/HANDBOOK.md, read on demand via injected absolute path |
SKILLS.md |
Skill list (tool permissions) |
HEARTBEAT.md |
Scheduled proactive tasks (e.g. daily issue checks) |
POLICIES.md |
Behavior rules and boundaries |
NOTEBOOK.md |
Persistent cognitive workspace (situational state, CPP output) |
knowledge.md / state.md |
Long-term knowledge + TTL state (legacy MEMORY.md migrates once) |
CONTEXT.md |
Organization context (shared knowledge base) |
The runtime also supports spawning lightweight LLM subagents (spawn_subagent / spawn_subagents) for delegated subtasks. Subagent limits (parallelism, retry policy, preview truncation) are centralized in packages/shared/src/limits.ts rather than hardcoded. The parent agent has a configurable tool-use iteration limit (AgentOptions.maxToolIterations, system settings; default 200, range 1–10000) on chat-style harnesses — task execution and subagent loops remain uncapped by default.
Agent role types:
worker-- Regular digital employee, executes tasksmanager-- Org leader, handles task routing, team coordination, reporting
Agent trust levels (Progressive Trust):
| Level | Condition | Permissions |
|---|---|---|
probation |
New Agent or score < 40 | All tasks require human approval |
standard |
score >= 40, >= 5 deliveries | Routine tasks auto-approved |
trusted |
score >= 60, >= 15 deliveries | Higher autonomy, can review others |
senior |
score >= 80, >= 25 deliveries | Highest autonomy, key reviewer |
Each agent has a single-threaded attention model — it processes one item at a time. Every LLM invocation flows through a per-agent Mailbox (priority queue), and an AttentionController manages which item the agent focuses on.
Key components:
- AgentMailbox — Priority queue accepting 15 item types including
human_chat,a2a_message,callback_result,heartbeat,memory_consolidation, and task/requirement events (see MAILBOX-SYSTEM.md) - AttentionController — Event-driven focus loop; reacts to new mail with interrupt signals
- Yield Points — Safe checkpoints in the tool loop where the agent can pause to evaluate interrupts
- Decision Engine — Produces decisions:
continue,preempt,cancel,merge,defer,drop. Heuristic rules handle clear cases (e.g., user chat always preempts); an LLM interrupt judge handles ambiguous cases with semantic understanding (e.g., "stop publishing" → cancel, "hold off for now" → preempt) - Preempt vs Cancel —
preemptpauses current work (item deferred, session preserved for later resumption);cancelpermanently stops current work (item dropped, will NOT be resumed) - Deferred Item Auto-Resume — Items deferred by preemption or explicit deferral are automatically resurfaced when the agent is idle (
resurfaceDue()) - Triage with Read-Only Tools — When multiple items compete for attention, the triage LLM can invoke a curated set of read-only tools (
task_list,task_get,requirement_list, etc.) to gather context before deciding priority
Agents now have tools to actively manage their mailbox queue and cognitive workspace:
check_mailbox(read-only inspection, all scenarios)defer_mailbox_item/drop_mailbox_item(queue management)update_notebook/clear_notebook(cognitive workspace management)
This shifts from system-driven to agent-driven cognition. The deliberation threshold is lowered to 2 items, making agent-driven triage the norm.
PendingCallbackRegistry tracks async operations (A2A messages, background_exec, etc.). Completions route back through the mailbox as callback_result items rather than being injected directly into active sessions.
External callers use the mailbox API exclusively:
agent.sendMessage()— Awaitable chat/notificationagent.sendMessageStream()— Streaming chat (SSE)agent.sendTaskExecution()— Task execution viatask_status_update(fire-and-forget)agent.sendSessionReply()— Post-task session replyagent.enqueueToMailbox()— Fire-and-forget notification
Internal processes (heartbeat, daily report, memory consolidation) also enqueue to the mailbox, ensuring no LLM call bypasses the attention controller. The mailbox timeline (items + decisions) forms the agent's episodic memory ground truth.
Task status notifications (task_status_update with invokesLLM: false) are informational only — the side-effect system in updateTaskStatus() handles all real actions automatically (execution start/cancel, reviewer notification, dependency unblocking). These notifications exist as episodic memory and triage decision context, not as work items requiring agent processing.
See MAILBOX-SYSTEM.md for the complete design.
The agent cognitive system is a continuous cycle backed by persistent stores and optional deliberate preparation:
Stimulus (Mailbox) → Triage → [CPP optional] → Context Assembly → Main LLM → Action → Reflection
↓
NOTEBOOK.md + MEMORY.md
| Component | Storage / Location | Role |
|---|---|---|
| Notebook | NOTEBOOK.md |
Persistent cognitive workspace — situational state, triage decisions, CPP outputs |
| Memory | MEMORY.md |
Unified long-term knowledge (curated sections) + raw ## _observations buffer |
| CPP | packages/core/src/cognitive.ts |
Opt-in multi-phase context preparation (Appraisal → Retrieval → Reflection) |
| Goal/Loop | GoalConfig on Requirements |
Persistent objectives with heartbeat integration |
| PendingCallbackRegistry | packages/core/src/pending-callback.ts |
Async operation tracking; completions → mailbox callback_result |
Cognitive Preparation Pipeline (CPP) — opt-in via agent.cognitive.enabled in markus.json (default false). When enabled, CPP runs between triage and the main LLM call, writing outputs to NOTEBOOK.md (not separate prompt sections). Four depth levels (D0–D3) control preparation intensity: D0 reflexive (heartbeat OK), D1 reactive (most chats/A2A), D2 deliberative (task execution), D3 meta-cognitive (high-stakes).
Goal/Loop mechanism — Requirements can carry a GoalConfig (loopEnabled, completionCriteria, maxIterations, etc.) turning them into standing objectives. Heartbeat injects active goals; agents manage them via goal_create, goal_update, and goal_status tools.
See COGNITIVE-ARCHITECTURE.md for the full design with theoretical foundations.
Organization (Org)
├── Teams -- Working groups of Agents and humans with shared goals
│ ├── Manager -- Approves work, sets direction
│ └── Members -- Agents and humans executing tasks
├── Projects -- Scopes with repos and governance rules
│ ├── Requirements -- User-authorized work items
│ │ └── Tasks -> Subtasks -- Atomic work units
│ ├── Knowledge Base -- Shared knowledge (ADRs, conventions, gotchas, etc.)
│ └── Governance Policy -- Approval rules, task caps
└── Reports -- Periodic reports + plan approval + human feedback
Relationship model:
- A Team can participate in multiple Projects; a Project can be worked on by multiple Teams
- Each Task belongs to one Project and traces to a Requirement
- Each Project can link multiple code repositories
Two-file cognitive model replaces the former volatile working memory + memories.json system:
| File | Role | Prompt injection |
|---|---|---|
NOTEBOOK.md |
Persistent cognitive workspace — situational state, CPP/triage outputs | Always loaded as ## Notebook |
MEMORY.md |
Curated long-term knowledge + raw ## _observations buffer |
Curated sections as ## Your Knowledge; observations excluded |
The dream cycle (memory_consolidation) operates within knowledge.md — consolidating observations into curated sections and pruning stale content. Post-task learning uses a separate distillation scenario (LEARNING-LOOP.md §0 / §2).
Memory layers (Tulving's classification):
| Layer | Storage | Role |
|---|---|---|
| Procedural | role/ROLE.md + skills |
How the agent operates. Identity, behavioral rules. |
| Semantic | MEMORY.md curated sections |
What the agent knows. Agent-organized knowledge. |
| Episodic | sessions/*.json (current) + SQLite agent_activities (past) |
What happened. Current conversation + searchable activity history. |
| Working Memory | NOTEBOOK.md |
Persistent, agent-managed keyed entries (update_notebook / clear_notebook). |
The agent retrieves past episodes via the recall_activity tool (keyword search on summary/keywords). Daily logs (daily-logs/) are a write-only audit trail for humans — never read back into prompts.
See MEMORY-SYSTEM.md for the complete architecture.
Project knowledge base (three scopes):
| Scope | Description | Tools |
|---|---|---|
personal |
Agent personal memory | memory_save / memory_search |
project |
Project-level shared knowledge | knowledge_contribute / knowledge_search |
org |
Org-level shared knowledge | knowledge_search (scope=org) |
Knowledge categories: architecture, convention, api, decision, gotcha, troubleshooting, dependency, process, reference
Built-in tools (all Agents have by default):
| Tool | Description |
|---|---|
shell_execute |
Run shell commands (auto-injects Agent identity into git commit) |
file_read / file_write / file_edit |
File read/write/edit (writes blocked only to other agents' directories) |
file_list |
List directory contents |
web_fetch / web_search |
HTTP requests / web search |
spawn_subagent / spawn_subagents |
Spawn lightweight LLM subagents for focused subtasks (parallel support) |
code_search |
Code search (ripgrep) |
git_* |
Git operations |
agent_send_message |
Send message to another Agent (A2A via mailbox) |
notify_user |
Send proactive message to user (appears in chat + notification bell) |
request_user_approval |
Request user decision/approval (blocks until user responds; supports custom options + freeform) |
recall_activity |
Query own execution history (activities + tool call logs) |
task_create / task_list / task_update / task_get / task_assign / task_note |
Task board ops (constrained by governance policy) |
task_submit_review |
Submit delivery for review |
requirement_propose / requirement_list |
Requirement management |
deliverable_create / deliverable_search / deliverable_list |
Shared deliverables |
Git commit metadata injection: When an Agent runs git commit, shell_execute auto-injects --author and --trailer with Agent ID, name, Team, Org, Task ID, etc., so all commits are traceable.
See Task & Requirement State Machines for the complete FSM specification.
Tasks and requirements share a unified status vocabulary: pending, in_progress, blocked, review, completed, failed, rejected, cancelled, archived. Not every status applies to both types, but the same name always means the same thing.
pending ──► in_progress ──► review ──► completed ──► archived
│ │ ▲ │
│ │ │ └── revision ──► in_progress
│ ▼ │
│ blocked ┘
▼ │
rejected failed ──► (retry) ──► in_progress
- Workers submit via
task_submit_review. The system notifies the reviewer. rejected= proposal denied before work.cancelled= stopped after work began.
pending → in_progress → review → completed → (scheduled rerun) → in_progress → ...
- After completion, scheduled tasks wait for
nextRunAtthen restart. - Scheduled tasks go through the same review pipeline as standard tasks.
pending ──► in_progress ──► completed
│ ▲
▼ │
rejected ── resubmit ──┘ any ──► cancelled
- User-created requirements auto-approve to
in_progress. - Agent proposals start as
pending, need human approval. - Rejected requirements can be resubmitted by the agent (with optional updates), returning to
pending. - Completion is automatic when all linked tasks terminate.
| Status | Label | Description |
|---|---|---|
pending |
Pending | Created, awaiting human approval |
in_progress |
In Progress | Approved, work is active |
blocked |
Blocked | On hold (dependencies, manual pause) |
review |
In Review | Execution done, awaiting reviewer |
completed |
Completed | Successfully finished |
failed |
Failed | Unrecoverable error |
rejected |
Rejected | Proposal not approved |
cancelled |
Cancelled | Deliberately stopped |
archived |
Archived | Historical record |
Task governance policy:
| Approval tier | Trigger | Approver |
|---|---|---|
auto |
Low-priority agent-created tasks | No approval (starts in_progress) |
manager |
Standard agent-created tasks | Team Manager Agent |
human |
High/urgent priority, shared-resource impact | Human (HITL) |
Human-created tasks always start as pending regardless of approval tier, with no HITL approval request or notification. The human user explicitly starts execution from the UI ("Start Execution" button). Agent trust level dynamically adjusts effective approval tier (e.g. senior Agent's manager-level tasks may auto-approve).
Before each conversation, the ContextEngine dynamically builds the system prompt:
- Role definition (ROLE.md — Identity store)
- Shared behavior norms (HANDBOOK.md: workflow, governance, knowledge sharing)
- Identity and org awareness (colleague list, manager, human members)
- Current project context (project name, repos, governance rules)
- Current workspace (agent workspace path, shared workspace, users/ and team/ directories)
- Agent trust level (current level and permission description)
- System announcements (urgent/high-priority announcements)
- Human feedback (annotations and instructions from report reviews)
- Project knowledge highlights (high-importance verified knowledge entries)
- Your Knowledge (MEMORY.md curated sections — observations excluded)
- Notebook (NOTEBOOK.md — cognitive workspace; CPP outputs land here when enabled)
- Active Goals (when heartbeat or goal-aware context)
- Task board (currently assigned Tasks)
- Current conversation identity (sender info)
- Environment info (OS, toolchain, runtime)
See PROMPT-ENGINEERING.md for the complete section ordering and COGNITIVE-ARCHITECTURE.md for the cognitive preparation pipeline.
LLMRouter
├── Primary Provider (OpenAI / Anthropic / DeepSeek)
└── Fallback Provider (auto-switch, retry on failure)
- Supports streaming (SSE) and non-streaming modes
- Timeouts: chat 60s / stream 120s
- Auto-fallback to backup provider on failure
- Exception:
CU_EXCEEDED/MARKUS_RATE_LIMITED(Markus Cloud credits) must not fall back to user BYOK providers — surface top-up/upgrade instead
Desktop does not own a separate personal ledger. Plan, quota, and keys come from the user's Hub organization. Authoritative Hub docs (sibling repo markus-hub):
- Subscription / CU / Waffo:
docs/subscription-billing.md - OpenRouter keys / hard-stop / reconcile:
docs/model-service.md
Client touchpoints:
| Surface | Role |
|---|---|
MarkusProvider |
Member OR key; on 402 / soft stop → POST /api/user/cu/sync once, then retry or emit CU_EXCEEDED only if Hub remaining is zero |
LLMRouter |
Must not route Markus credit exhaustion to BYOK |
| OverviewUsage / claim UI | Reads GET /api/user/plan; Free claim deep-links to Hub ?claim=1 |
Frozen response-field contract (keep in sync with Hub handlers): packages/core/test/hub-billing-contract.test.ts — mirrors Hub billing-crossflows plan + cu/sync keys (remainingCu, openrouter.remainingUsd, planSource, buckets, etc.).
| Function | Description |
|---|---|
stopAllAgents(reason) |
Stop all Agents with reason. Cancels active LLM streams, stops attention loops, requeues in-flight items. |
startAllAgents() |
Start all stopped Agents. Attention loops restart, deferred items resurface. |
emergencyStop() |
Emergency stop: cancel all active streams and stop all Agents |
agent.stop(reason) |
Stop a single agent. Cancels active LLM stream, stops attention, sets status to offline. |
| System announcements | Broadcast to all Agents and UI, injected into Agent system prompt |
Agent stopped state is persisted across process restarts. There is a single "not running" status: offline. The former paused status has been unified into offline.
- Individual agent:
agent.stop()sets status tooffline, which is written to theagents.statusDB column via thestateChangeHandler. On restart,startRestoredAgentsInBackgroundskips agents whose DB status isoffline, keeping them stopped. - Team-level stop:
stopTeamAgents(teamId)stops each member agent individually. Persistence is implicit — each member'sofflinestatus is stored in DB. On restart, stopped team members remain offline. - Global stop:
stopAllAgents()stops every agent individually. On startup,isGlobalStopped()dynamically checks whether all agents are offline.
Agents can manage other agents' lifecycle through tools with role-based permissions:
- Manager (
agentRole: 'manager'): getsagent_stop/agent_starttools, scoped to their own team members only. - Secretary (worker with
secretaryrole): getsteam_stop/team_starttools for managing any team.
Each agent has a dedicated workspace (~/.markus/agents/<agentId>/workspace/). The only hard enforcement is that agents cannot write to other agents' directories — this prevents cross-agent interference. All other file access (read and write) is unrestricted, allowing agents to respond to any user request. Prompt-based guidance encourages agents to work within their own workspace and use worktrees for project code.
- The platform enforces: cross-agent write isolation (deny writes to other agents' directories)
- The platform provides via prompt: workspace path, project context, best-practice guidance
- The agent decides: branching strategy, worktree layout, merge workflow
- Workflow details like branching conventions and review process are defined by role templates and team norms, not by the platform
Git command governance (three-tier model):
| Tier | Operations | Behavior |
|---|---|---|
| Allow | add, commit, fetch, log, diff, status, branch -a/-l, checkout -b, switch -c, worktree add/list/remove, push origin <task-branch> |
Execute immediately |
| Approval | checkout <existing-branch>, switch <existing-branch>, push ... main/master, merge, rebase |
Pause execution, request HITL approval via HITLService; agent receives approval or rejection with reason |
| Deny | push --force/-f |
Always blocked |
The approval tier integrates with the existing HITL approval pipeline (HITLService.requestApprovalAndWait()). Human reviewers can approve or reject with a comment; the agent receives the feedback and adjusts. This mechanism is extensible: new dangerous operations can be added via SecurityPolicy.requireApproval (config-driven) or new pattern arrays in shell.ts (code-driven).
Agent completes work
-> task_submit_review (summary, branch, test results)
-> Quality gates (TypeScript build, ESLint, Vitest)
-> Merge conflict pre-check (dry-run merge)
-> Task state -> review
-> Reviewer accept / request revision
-> accept -> merge branch -> completed
-> revision -> Agent reworks -> resubmit
| Report type | Frequency | Content |
|---|---|---|
| Daily | Daily | Task done/in-progress/blocked, token usage |
| Weekly | Weekly | Progress, cost trends, next week plan (may include plan approval) |
| Monthly | Monthly | Monthly summary, cost analysis, quality metrics |
Plan approval flow: Weekly reports' work plans need human approval -> approved plans auto-create tasks -> Agents must not start before plan approval
Human feedback: Annotations, comments, and instructions on reports can:
- Be sent to specific Agents
- Be broadcast as system announcements
- Be saved to project knowledge base
- Auto-create new tasks
- Completed tasks auto-archive after configurable days (
autoArchiveAfterDays) - Task logs and audit logs retained for configurable periods
| Condition | Threshold | Action |
|---|---|---|
Task in_progress too long |
> 24h or 2x avg completion time | Warn Agent -> report to Manager |
Task review unhandled |
> 12h | Report to human |
Task assigned not started |
> 4h | Remind Agent -> reassign |
Agents can be sourced from three paths:
| Source | Tool | Flow |
|---|---|---|
| Local package | package_install |
package_list → choose agent/team/skill → package_install → onboard |
| Markus Hub | hub_install |
hub_search → hub_install (download + install in one step) → onboard |
The Secretary agent is the sole default agent (builder agents have been removed). The Secretary holds all building skills (agent-building, team-building, skill-building). All agents have package tools (package_list, package_install) and hub tools (hub_search, hub_install).
The BuilderService (packages/org-manager/src/builder-service.ts) encapsulates artifact install/list logic, used by both the HTTP API and agent tools.
-- Users
users (id, org_id, name, email, role, password_hash, created_at, last_login_at)
-- Agent chat (each agent has one main session for activity log + optional conversation sessions)
chat_sessions (id, agent_id, user_id, title, is_main, created_at, last_message_at)
chat_messages (id, session_id, agent_id, role, content, metadata, tokens_used, created_at)
-- Channel messages (DM, group chat, team channels)
channel_messages (id, org_id, channel, sender_id, sender_type, sender_name, text, mentions, reply_to_id, created_at)
-- Group chats (custom groups with managed membership)
group_chats (id, org_id, name, channel_key, creator_id, creator_name, created_at, updated_at)
group_chat_members (id, group_chat_id, user_id, user_type, user_name, role, joined_at)
-- Task comments (threaded discussion on tasks)
task_comments (id, task_id, author_id, author_name, author_type, content, attachments, mentions, activity_id, reply_to_id, created_at)
-- Requirement comments (threaded discussion on requirements)
requirement_comments (id, requirement_id, author_id, author_name, author_type, content, attachments, mentions, activity_id, reply_to_id, created_at)
-- Tasks (extended)
tasks (id, org_id, title, description, status, priority, assigned_agent_id, subtasks,
project_id, requirement_id, due_at, created_at, updated_at)
-- Projects
projects (id, org_id, name, description, status, repositories,
team_ids, governance_policy, review_schedule, created_at, updated_at)
-- Requirements
requirements (id, org_id, project_id, title, description, priority, status,
source, tags, created_at, updated_at)
-- Deliverables
deliverables (id, org_id, project_id, agent_id, task_id, type, title,
summary, reference, tags, status, created_at, updated_at)
-- Project knowledge
project_knowledge (id, scope, scope_id, category, title, content, tags,
source, importance, status, verified_by, supersedes,
access_count, last_accessed_at, created_at, updated_at)
-- Reports
reports (id, type, scope, scope_id, period_start, period_end, status,
metrics, task_summary, cost_summary, highlights, blockers, learnings,
upcoming_plan, generated_at, generated_by, reviewed_by, reviewed_at)
-- Report feedback
report_feedback (id, report_id, author_id, author_name, type, anchor,
content, priority, disclosure, actions, created_at)
-- System announcements
system_announcements (id, type, title, content, priority, created_by,
target_scope, target_ids, acknowledged, created_at, expires_at)
-- Audit logs
audit_logs (id, org_id, agent_id, task_id, project_id, event_type,
action, metadata, created_at)
-- User notifications (persistent mailbox for humans)
user_notifications (id, user_id, type, title, body, priority,
read, action_type, action_target, metadata, created_at)
-- Mailbox items (agent attention queue)
mailbox_items (id, agent_id, source_type, source_id, priority, summary, payload,
status, received_at, processed_at, decision, decision_reason)
-- Agent decisions (attention decision log)
agent_decisions (id, agent_id, mailbox_item_id, decision, reason, context, decided_at)- JWT Cookie (
markus_token, 7-day validity) - Initial account:
admin@markus.local/markus123(onboarding wizard prompts user to set real name, email, and password) - Roles:
owner>admin>member>guest - Only
owner/admincan manage team members and Agents
| Operation | Access |
|---|---|
| Create / invite user | owner / admin |
| Set role | owner / admin (cannot promote above own role) |
| Delete user | owner / admin (cannot delete self or higher roles) |
| Invite link | Generated per user; expires in 7 days; new user sets password via link |
Invite flow: Admin creates user (name, email, role) → system generates invite token → invite link displayed → new user opens link → sets password → joins the platform (hasJoined flag set).
Each human user has their own chat sessions with agents. Chat sessions are scoped by user_id:
chat_sessions.user_idtracks which human owns the sessionGET /api/agents/:agentId/sessionsfilters by authenticated user- Agent "Main Sessions" (activity logs) are shared (visible to all users)
- Historical sessions with
user_id = NULLare auto-migrated to the first user on startup
Each human user has a profile file maintained by the Secretary agent:
| File | Path | Purpose |
|---|---|---|
USER.md |
~/.markus/users/{userId}/USER.md |
User preferences, communication style, context notes |
TEAM.md |
~/.markus/teams/{teamId}/TEAM.md |
Team norms, conventions, shared practices |
These files are injected into agent context when interacting with the corresponding user, allowing agents to personalize their behavior.
The Web UI chat supports slash commands dispatched via POST /api/agents/:id/command:
| Command | Action |
|---|---|
/goal [description] |
Prompt agent to create a standing goal via goal_create |
/status |
Request concise status: active goals, tasks, mailbox, recent activity |
/notebook |
Return current notebook entries (read-only snapshot) |
/task [description] |
Prompt agent to create a task via task_create |
Commands are rendered via SlashCommandMenu in the chat input. /notebook returns data directly; others enqueue a human_chat message to the agent.
Connection: ws://localhost:8056
| Event | Trigger |
|---|---|
agent:update |
Agent state change (idle/working/offline/error) |
agent:mailbox |
New item enqueued to an agent's mailbox |
agent:decision |
Agent attention decision (pick/defer/drop/triage) |
agent:attention |
Attention controller state change |
agent:focus |
Agent switches to a new mailbox item |
agent:triage |
Agent triage deliberation result (reasoning, process/defer/drop) |
agent:started |
Agent process started |
agent:stopped |
Agent process stopped |
task:update |
Task state update (including review/accepted/archived) |
task:create |
New task created |
requirement:created |
Requirement proposed |
requirement:approved / rejected / updated / completed / cancelled |
Requirement lifecycle |
notification |
User notification — targeted by userId (triggers NotificationBell refresh) |
chat:proactive_message |
Agent activity log or proactive message (main session) |
chat:message |
New channel/DM/group chat message (targeted to members) |
chat:group_created |
Group chat created |
chat:group_updated |
Group chat membership changed |
chat:group_deleted |
Group chat deleted |
chat |
Agent sends message in channel |
system:announcement |
System announcement broadcast |
system:pause-all |
Global pause event |
system:resume-all |
Global resume event |
system:emergency-stop |
Emergency stop event |
EventBus Architecture: Each Agent has a private EventBus; the AgentManager has a separate manager-level EventBus. Agent events are forwarded to the manager's bus via forwardAgentEvents() so that start.ts WS broadcast handlers receive them. See docs/MAILBOX-SYSTEM.md §19 for the full forwarding table.
| Channel format | Purpose |
|---|---|
#general / #dev / #support |
Team channels, @mention triggers Agent |
group:{teamId} |
Team group chat (all team members) |
group:custom:{id} |
Custom group chat (manually managed members) |
notes:{userId} |
Personal notes (not routed to any Agent) |
dm:{id1}:{id2} |
Direct message between two humans (not routed to any Agent) |
dm:a2a:{sorted_id_1}:{sorted_id_2} |
Agent-to-agent DM channel (deterministic key from sorted agent IDs) |
Agent-to-agent messaging uses DM Channels with deterministic keys (dm:a2a:{sorted_ids}), leveraging existing group-chat infrastructure. This provides persistent message history, stable routing, and mailbox integration — eliminating custom A2A session management. agent_send_message is fire-and-forget; substantial work should use requirements + tasks.
Markus supports multiple human users and agents communicating through various channels. The communication model varies by context:
Agent communication contexts and output visibility:
| Context | Agent output visible to | How to reach humans | How to reach agents |
|---|---|---|---|
| Chat (human_chat) | Directly visible to the chatting human (real-time stream) | Speak naturally — output is streamed live | agent_send_message |
| Task Execution | Visible in task execution logs (Work page) | notify_user for critical updates |
agent_send_message |
| Heartbeat | Not visible to anyone | notify_user (only way) |
agent_send_message |
| A2A | Visible to the peer agent only | notify_user |
Reply directly / agent_send_message for others |
| Comment Response | Not directly visible | task_comment / requirement_comment (comment thread) |
agent_send_message |
| Review | Not directly visible | task_update + optionally notify_user |
agent_send_message |
| Memory Consolidation | Not visible; purely internal | N/A (no communication) | N/A |
Human-to-human communication:
| Channel | Delivery mechanism | Notification |
|---|---|---|
DM (dm:{id1}:{id2}) |
WebSocket push to recipient + persisted to channel_messages |
Bell notification (type direct_message) with click-to-navigate |
Group chat (group:*) |
WebSocket push to all human members + persisted | Bell notification (type group_message) with click-to-navigate |
| @mention in comments | Persisted in task/requirement comments | Bell notification with click-to-navigate to task/requirement |
Key tools for agent communication:
| Tool | Purpose | When to use |
|---|---|---|
notify_user |
Proactive message to human (chat + bell) | Any non-chat context when human attention needed |
request_user_approval |
Block until human decides | Decisions, approvals, input needed |
agent_send_message |
Direct message to peer agent | Coordination, questions, context sharing |
task_comment / requirement_comment |
Post in comment thread | Responding to comments on tasks/requirements |
After Agent startup, HeartbeatScheduler triggers periodic tasks at configured intervals:
- Each run executes checks with
[HEARTBEAT CHECK-IN]prompt under the "Patrol, Don't Build" principle - Active goals check: injects standing objectives from requirements with
GoalConfig.loopEnabled - Callback timeout check: surfaces timed-out
PendingCallbackRegistryentries - Heartbeat includes task retrospective: calls task_list to check active tasks and update stale states
- Lightweight actions allowed: check status, send messages, create tasks, retry failed tasks, quick reviews, save insights
- Complex work goes into tasks: if something needs heavy implementation, heartbeat creates a task and notifies the user
- Infinite loop protection via a configurable tool-iteration safety cap (default 200,
maxToolIterations), not artificial per-heartbeat limits - Background process completions:
background_execresults route throughPendingCallbackRegistry→ mailboxcallback_resultitems (heartbeat also surfaces timed-out callbacks) - Governance mode: in_progress tasks are not auto-resumed on service start; requires manual trigger
Agents understand the workflow and governance rules through three layers:
| Layer | File | Role |
|---|---|---|
| HANDBOOK.md (static norms) | templates/roles/HANDBOOK.md |
Shared working Know-how for all Agents: workflow map, task governance, workspace discipline, formal delivery, knowledge management, trust mechanism, Git commit norms, reports and feedback. Single source of truth, shipped with the build, upgrades on rebuild/release — not copied per-agent |
| ContextEngine (dynamic injection) | packages/core/src/context-engine.ts |
Injected per interaction: current project context, workspace info, system announcements, human feedback, trust level, project knowledge highlights |
| Tools (mechanical enforcement) | packages/core/src/tools/ |
Enforcement: task_create blocks until approved, task_submit_review replaces direct completion, file writes blocked to other agents' directories, git commit auto-injects metadata |
Design principles:
- Things Agents need for decisions -> put in Context (project goals, governance rules, requirement context)
- Things Agents need to act on -> implement as Tools (submit review, manage deliverables, contribute knowledge)
- Things that must be enforced -> implement as transparent tool behavior (workspace limits, approval blocking, commit metadata injection)
AgentMetricsCollector (packages/core/src/agent-metrics.ts)
aggregates per-agent counters (tokens, cost, CU, requests, tool calls, errors, heartbeat
success, response time) from the audit callback and event bus, and exposes
AgentMetricsSnapshot over the API for the dashboard.
The existing counters cover cost/throughput but not the harness-discipline signals this architecture depends on. This spec adds four metrics so regressions in context and completion behavior are visible.
- Behavior: the collector additionally tracks:
- compression count — how often per-call context packing had to compress (over budget),
- completion-marker failure rate — share of non-chat turns that finished without a marker (see MAILBOX-SYSTEM.md completion marker),
- prompt cache-hit rate — from provider usage where reported (see the injection-point audit in PROMPT-ENGINEERING.md §2.2),
- per-turn cost — cost attributed per completed turn.
- Invariants: each metric increments on its triggering event and is exposed in the
snapshot under
AgentMetricsSnapshot.harness; adding them does not change agent behavior (measurement only). - Wiring:
- compression count —
ContextEngine.prepareMessagessetsusage.compressedwhen it runs token-budget compression;AgentcallsrecordCompression()at the chat/stream/ task consumers. - marker failure rate — the
Agentattention delegate callsrecordTurn({ isChat, hadCompletionMarker })after each mailbox turn (chat turns are excluded from the denominator). - cache-hit rate — accumulated from the
cacheReadTokens/cacheWriteTokensalready present onllm_requestaudit events, over total prompt-side tokens. - per-turn cost —
estimatedCost / turnsCompleted(0 when no USD cost is reported, e.g. CU-billed providers).
- compression count —
- Design rationale: Hermes emphasizes observable execution; these are the metrics that tell you whether packing, marker discipline, and caching are actually holding.
- Testing (
packages/core/test/agent-metrics.test.ts— the "C2:" cases): firing each triggering event increments the corresponding counter and surfaces insnapshot.harness. - Status: implemented (
HarnessHealthMetricson the snapshot;recordCompression/recordTurn+ cache-token accumulation inAgentMetricsCollector, wired inagent.tsandcontext-engine.ts).
Markus is deliberately a product runtime for a digital workforce, not a minimal coding harness. To keep scope disciplined (in the spirit of Pi defining itself by what it refuses), the following are explicit non-goals for the core runtime unless a concrete product need arises:
- In-core session tree / branch summaries — long-collaboration branching is on the roadmap, not core today (Pi's strength; deferred).
- Fully autonomous skills self-improvement loop — agents can use skills/store and dream
consolidation, but a default closed loop where the agent authors/edits its own
SKILL.mdis roadmap, not core (weaker than Hermes here by choice, for now). - Maximal always-on tool registry — Markus keeps a small always-on core + discovery rather than exposing the full registry every call (see TOOL-SYSTEM.md).
npm install -g @markus-global/cli
markus startOpen the dashboard at http://localhost:8056.
pnpm install && pnpm build
cp markus.json.example ~/.markus/markus.json # Add API keys
node packages/cli/dist/index.js startSame dashboard URL: http://localhost:8056.
| Variable | Description |
|---|---|
OPENAI_API_KEY |
OpenAI API key (primary LLM) |
ANTHROPIC_API_KEY |
Anthropic API key (optional) |
DEEPSEEK_API_KEY |
DeepSeek API key (fallback) |
DATABASE_URL |
SQLite path override (default: ~/.markus/data.db, format: sqlite:/path/to/db) |
JWT_SECRET |
JWT signing key (recommended for production) |
AUTH_ENABLED |
Enable login auth (default true) |