Architecture & Data Model
Technical deep-dive into Orchestratia's system architecture, communication channels, database schema, and security model.
System Architecture
flowchart TD
Internet["Internet"] --> Nginx["Nginx :443"]
subgraph Hub ["Orchestratia Hub"]
Nginx --> Backend["FastAPI :8000"]
Nginx --> Frontend["Next.js :3000"]
Backend --> DB["PostgreSQL :5432"]
Backend --> Telegram["Telegram Bots"]
end
Backend -.->|"REST + WS"| AD1["Agent Daemon"] --> AI1["Claude Code"]
Backend -.->|"REST + WS"| AD2["Agent Daemon"] --> AI2["Gemini CLI"]
Backend -.->|"REST + WS"| AD3["Agent Daemon"] --> AI3["Codex CLI"]
style Hub fill:#F0F9F7,stroke:#2A9D88
style Internet fill:#F3F0EA,stroke:#B8AFA2
style Nginx fill:#2A9D88,stroke:#186B5D,color:#fff
style Backend fill:#F0F9F7,stroke:#2A9D88
style Frontend fill:#F0F9F7,stroke:#2A9D88
style DB fill:#F0F9F7,stroke:#2A9D88
style Telegram fill:#F0F9F7,stroke:#2A9D88
style AD1 fill:#F0F9F7,stroke:#2A9D88
style AD2 fill:#F0F9F7,stroke:#2A9D88
style AD3 fill:#F0F9F7,stroke:#2A9D88
style AI1 fill:#F3F0EA,stroke:#918A80
style AI2 fill:#F3F0EA,stroke:#918A80
style AI3 fill:#F3F0EA,stroke:#918A80
Communication Channels
flowchart LR
Dashboard["Dashboard<br/>(Browser)"]
Agent["Agent<br/>Daemon"]
TG["Telegram<br/>Bots"]
subgraph HubBack ["Hub Backend (FastAPI)"]
REST["REST API<br/>/api/v1/*"]
WS["WebSocket<br/>/ws/*"]
EB["Event Bus<br/>(pub/sub)"]
end
Dashboard -->|"REST HTTPS<br/>JWT (24h)"| REST
Dashboard <-->|"WSS /ws/dashboard<br/>JWT first msg"| WS
Agent -->|"REST HTTPS<br/>API Key header"| REST
Agent <-->|"WSS /ws/server<br/>API Key first msg"| WS
TG <-->|"Pub/Sub<br/>per project"| EB
style HubBack fill:#F0F9F7,stroke:#2A9D88
style Dashboard fill:#F3F0EA,stroke:#918A80
style Agent fill:#F3F0EA,stroke:#918A80
style TG fill:#F3F0EA,stroke:#918A80
style REST fill:#F0F9F7,stroke:#2A9D88
style WS fill:#2A9D88,stroke:#186B5D,color:#fff
style EB fill:#F0F9F7,stroke:#2A9D88
Channel Details
| Channel |
Protocol |
Auth |
Purpose |
Latency |
| Dashboard to Hub |
REST HTTPS |
JWT (24h) |
CRUD operations |
Request/Response |
| Dashboard from Hub |
WebSocket WSS |
JWT (first msg) |
Real-time events, terminal I/O |
<100ms |
| Agent Daemon to Hub |
REST HTTPS |
API Key (X-API-Key) |
Register, heartbeat, task lifecycle |
Request/Response |
| Agent bidirectional |
WebSocket WSS |
API Key (first msg) |
PTY streaming, session control, task push |
<100ms |
| Agent CLI to Hub |
REST HTTPS |
API Key (X-API-Key) |
Task CRUD, assign, deps, agent list (14 endpoints) |
Request/Response |
| Hub to Telegram |
Event Bus to Bot API |
Bot Token |
Notifications, live output |
~500ms |
| Telegram to Hub |
Bot API to Event Bus |
Chat verification |
Commands, keyboard input |
~500ms |
Database Schema
Entity Relationship Diagram
erDiagram
projects {
uuid id PK
string name
string status
}
servers {
uuid id PK
string name
string status
jsonb repos
jsonb capabilities
uuid owner_id FK
}
tasks {
uuid id PK
string title
string status
string priority
jsonb structured_spec
uuid assigned_server_id FK
jsonb result
}
sessions {
uuid id PK
uuid server_id FK
uuid project_id FK
uuid task_id FK
string status
}
task_dependencies {
uuid id PK
uuid task_id FK
uuid depends_on_task_id FK
string dependency_type
string contract_key
}
interventions {
uuid id PK
uuid task_id FK
string status
}
activity_log {
uuid id PK
string event_type
jsonb details
}
task_output {
uuid id PK
uuid task_id FK
text content
}
task_notes {
bigint id PK
uuid task_id FK
string author
boolean urgent
}
admin_users {
uuid id PK
string email
string role
}
telegram_bots {
uuid id PK
uuid project_id FK
}
registration_tokens {
uuid id PK
uuid project_id FK
}
projects ||--o{ servers : "registers"
projects ||--o{ sessions : "contains"
projects ||--o{ telegram_bots : "configures"
projects ||--o{ registration_tokens : "generates"
servers ||--o{ sessions : "runs"
tasks ||--o{ sessions : "uses"
tasks ||--o{ task_output : "produces"
tasks ||--o{ task_notes : "has notes"
tasks ||--o{ interventions : "triggers"
tasks ||--o{ activity_log : "logs"
tasks ||--o{ task_dependencies : "has"
task_dependencies }o--|| tasks : "depends on"
Key fields shown. Each table has additional columns for timestamps, metadata, and audit fields. See the full schema in the Protocol Reference.
14 tables total — covering servers, tasks, sessions, task notes, interventions, activity log, admin users, projects, Telegram bots, and registration tokens.
Task State Machine
stateDiagram-v2
[*] --> Pending: created
Pending --> Assigned: assign
Assigned --> Running: start
Assigned --> Planning: plan mode
Planning --> PlanReview: submit plan
Planning --> Failed: fail
PlanReview --> Running: approve plan
PlanReview --> Planning: revise plan
Running --> Review: submit
Running --> Done: complete
Running --> Failed: fail
Running --> NeedsHuman: help
NeedsHuman --> Running: respond
Review --> Done: approve
Review --> Running: revise
Done --> [*]: cascade
note right of Done
Resolve deps
Unblock downstream
Auto-assign next
end note
Plan Mode: When a project has require_plan_approval enabled, tasks enter the planning state instead of running after assignment. The worker analyzes the task, submits a plan for review, and only begins execution after approval. This prevents over-deletion and scope issues by ensuring impact analysis happens before any changes.
Valid Transitions
| From |
To |
Trigger |
| pending |
assigned |
Manual assign or auto-assign |
| assigned |
running |
Agent calls /tasks/{id}/start (blocked if unresolved deps) |
| assigned |
planning |
Auto (when require_plan_approval is set) |
| planning |
plan_review |
Worker submits plan via /tasks/{id}/plan |
| planning |
failed |
Worker fails during analysis |
| plan_review |
running |
Admin approves plan |
| plan_review |
planning |
Admin requests plan revision |
| running |
review |
Agent submits for review |
| running |
done |
Agent calls /tasks/{id}/complete |
| running |
failed |
Agent calls /tasks/{id}/fail |
| running |
needs_human |
Agent calls /tasks/{id}/help |
| needs_human |
running |
Admin responds to intervention |
| review |
done |
Admin approves |
| review |
running |
Admin requests changes |
Dependency & Contract System
Dependency Types
flowchart TD
subgraph blocks ["BLOCKS — B waits for A"]
direction LR
A1["Task A"] ==>|"blocks"| B1["Task B"]
end
subgraph input ["INPUT — B needs A's contract data"]
direction LR
A2["Task A"] -->|"input"| B2["Task B"]
end
subgraph related ["RELATED — informational only"]
direction LR
A3["Task A"] -.-|"related"| B3["Task B"]
end
blocks ~~~ input
input ~~~ related
style blocks fill:#F0F9F7,stroke:#2A9D88
style input fill:#FAF8F5,stroke:#E8E3DA
style related fill:#F3F0EA,stroke:#B8AFA2
style A1 fill:#2A9D88,stroke:#186B5D,color:#fff
style B1 fill:#F0F9F7,stroke:#2A9D88
style A2 fill:#2A9D88,stroke:#186B5D,color:#fff
style B2 fill:#F0F9F7,stroke:#2A9D88
style A3 fill:#F3F0EA,stroke:#918A80
style B3 fill:#F3F0EA,stroke:#918A80
Contract Exchange Lifecycle
sequenceDiagram
participant A as Task A
participant Hub as Hub
participant B as Task B
Note over A: 1. DECLARE contracts<br/>in structured_spec
Note over B: 2. DEPEND on A<br/>type: input, key: api_schema
A->>Hub: Complete with result.contracts.api_schema
Note over A,Hub: 3. FULFILL
Hub->>Hub: Validate → resolve deps → unblock → auto-assign
Hub->>B: Push resolved_inputs.api_schema
Note over Hub,B: 4. RESOLVE
Note over B: Receives: resolved_inputs.api_schema<br/>{endpoints: [], schemas: {}}
Cascade Flow
What happens when a task completes:
flowchart TD
Start(["Task A completes with result"]) --> Check
Check["1. CHECK CONTRACTS<br/>Compare expected vs actual contracts"]
Check --> Resolve
Resolve["2. RESOLVE DEPENDENCIES<br/>blocks/input/related → mark resolved"]
Resolve --> Downstream
Downstream["3. CHECK DOWNSTREAM<br/>All blocking deps resolved? → UNBLOCK"]
Downstream --> AutoAssign
AutoAssign["4. AUTO-ASSIGN<br/>Score servers → pick best → assign via WS"]
AutoAssign --> Broadcast
Broadcast["5. BROADCAST<br/>WS → dashboards, Event Bus → Telegram, Log → DB"]
style Start fill:#2A9D88,stroke:#186B5D,color:#fff
style Check fill:#F0F9F7,stroke:#2A9D88
style Resolve fill:#F0F9F7,stroke:#2A9D88
style Downstream fill:#F0F9F7,stroke:#2A9D88
style AutoAssign fill:#F0F9F7,stroke:#2A9D88
style Broadcast fill:#FAF8F5,stroke:#E8E3DA
Capability Matching & Auto-Assignment
Scoring Algorithm
| Requirement |
Type |
Score |
Rule |
repo |
Hard |
+100 |
Must exist in agent's repos (disqualify if missing) |
languages |
Hard |
+50 |
ALL must be in agent's capabilities (disqualify) |
environments |
Hard |
+30 |
At least ONE must match (disqualify) |
tools |
Soft |
+10/each |
Per matching tool |
tags |
Soft |
+5/each |
Per matching tag |
prefer_server |
Soft |
+200 |
If server name matches |
| Online |
Soft |
+25 |
If agent is online |
| Has capacity |
Soft |
+50 |
Running tasks < max_concurrent |
Auto-Assignment Triggers
- Dependency resolution — when a task's last blocking dependency resolves, the hub auto-assigns the unblocked task
- Agent heartbeat — when an agent comes online, the hub checks for pending tasks it could handle
Security Model
flowchart TD
L1["**NETWORK**<br/>TLS + HSTS, ports 80/443 only<br/>PostgreSQL on internal network"]
L2["**AUTHENTICATION**<br/>JWT (24h) + Argon2id for admins<br/>API keys (SHA-256) for agents<br/>WS auth in first message"]
L3["**AUTHORIZATION**<br/>Agents scoped to own tasks<br/>One-time registration tokens<br/>Admin roles: admin / viewer"]
L4["**INFRASTRUCTURE**<br/>SSH key-only, Docker non-root<br/>Secrets in .env, not in git"]
L1 --> L2 --> L3 --> L4
style L1 fill:#F0F9F7,stroke:#2A9D88
style L2 fill:#F0F9F7,stroke:#2A9D88
style L3 fill:#FAF8F5,stroke:#E8E3DA
style L4 fill:#F3F0EA,stroke:#B8AFA2
Deployment Architecture
flowchart TD
subgraph Server ["Single Server (4 vCPU, 8GB RAM)"]
Nginx["Nginx :443 — TLS + WS upgrade"]
Backend["FastAPI Backend :8000"]
Frontend["Next.js Frontend :3000"]
Postgres["PostgreSQL :5432 (internal only)"]
Nginx -->|"/api/*, /ws/*"| Backend
Nginx -->|"/*"| Frontend
Backend --> Postgres
end
style Server fill:#FAF8F5,stroke:#E8E3DA
style Nginx fill:#2A9D88,stroke:#186B5D,color:#fff
style Backend fill:#F0F9F7,stroke:#2A9D88
style Frontend fill:#F0F9F7,stroke:#2A9D88
style Postgres fill:#F3F0EA,stroke:#918A80
| Layer |
Technology |
Container |
| Reverse Proxy |
Nginx 1.25 |
orchestratia-nginx |
| Frontend |
Next.js 14 (App Router) |
orchestratia-frontend |
| Backend |
Python 3.12 + FastAPI |
orchestratia-backend |
| Database |
PostgreSQL 16 |
orchestratia-postgres |
WebSocket Message Reference
Agent to Hub
| Type |
Payload |
When |
auth |
{api_key} |
First message after connect |
session_started |
{session_id, pid, tmux_name} |
PTY spawned |
session_output |
{session_id, data} (base64) |
PTY output (continuous) |
session_screen |
{session_id, lines[]} |
Rendered screen (every 5s) |
session_closed |
{session_id, exit_code} |
PTY exited |
session_error |
{session_id, error} |
PTY spawn failed |
session_recovered |
{tmux_name, pid} |
Orphan reattached |
Hub to Agent
| Type |
Payload |
When |
auth_ok |
{} |
Auth succeeded |
session_start |
{session_id, working_directory, cols, rows} |
Start PTY |
session_input |
{session_id, data} (base64) |
Keyboard from dashboard |
session_resize |
{session_id, cols, rows} |
Terminal resize |
session_close |
{session_id} |
Graceful close |
task_assigned |
{task_id, title, spec, structured_spec, resolved_inputs, require_plan} |
Task assigned (via dashboard or CLI) |
intervention_response |
{intervention_id, task_id, response, session_id} |
Admin responded |
task_note |
{task_id, content, urgent, author, session_id} |
Note added to task |
task_updated |
{task_id, title, spec, session_id} |
Task spec updated |
plan_approved |
{task_id, session_id} |
Admin approved worker's plan |
plan_revision |
{task_id, feedback, session_id} |
Admin requested plan revision |
task_status_update |
{task_id, title, status, status_label, summary, session_id} |
Task completed/failed/needs_human (pushed to orchestrator) |
Agent CLI REST Endpoints
The orchestratia CLI tool (running inside agent sessions) uses these REST endpoints for task management. All authenticated via X-API-Key header.
| Method |
Path |
Purpose |
POST |
/api/v1/server/tasks |
Create task |
GET |
/api/v1/server/tasks |
List tasks |
GET |
/api/v1/server/tasks/{id} |
View task (with deps, resolved_inputs) |
PUT |
/api/v1/server/tasks/{id} |
Update task fields |
DELETE |
/api/v1/server/tasks/{id} |
Cancel task |
POST |
/api/v1/server/tasks/{id}/start |
Start task |
POST |
/api/v1/server/tasks/{id}/complete |
Complete task |
POST |
/api/v1/server/tasks/{id}/fail |
Fail task |
POST |
/api/v1/server/tasks/{id}/help |
Request help |
POST |
/api/v1/server/tasks/{id}/assign |
Assign to session by name |
POST |
/api/v1/server/tasks/{id}/dependencies |
Add dependency |
DELETE |
/api/v1/server/tasks/{id}/dependencies/{dep_id} |
Remove dependency |
POST |
/api/v1/server/tasks/{id}/notes |
Add note to task |
POST |
/api/v1/server/tasks/{id}/plan |
Submit plan for review |
GET |
/api/v1/server/servers |
List all servers |
Hub to Dashboard
| Type |
Payload |
When |
task_output |
{task_id, server_id, content, stream} |
Task output |
task_event |
{event_type, task_id, details} |
State change |
session_output |
{session_id, server_id, data} |
PTY output |
session_event |
{event, session_id, details} |
Session lifecycle |