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

  1. Dependency resolution — when a task's last blocking dependency resolves, the hub auto-assigns the unblocked task
  2. 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