Skip to main content

Architecture

A detailed look at how OpenCompany works under the hood. For contributors, system designers, and the curious. You do not need this page to use OpenCompany. For a navigable tour with hyperlinks, see the DeepWiki page.

System Overview

System overview diagram

The three tiers: React canvas, FastAPI backend, execution engines

OpenCompany is three loosely-coupled tiers talking over a single persistent WebSocket connection:

100+ Workflow Nodes

AI, agents, social, Android, documents, Google Workspace, code, browser, proxies, utilities

12 LLM Providers

OpenAI, Anthropic, Gemini, OpenRouter, xAI, DeepSeek, Kimi, Mistral, Groq, Cerebras, plus local Ollama and LM Studio

16 Specialized Agents

Android, Coding, Web, Task, Social, Travel, Tool, Productivity, Payments, Consumer, Autonomous, Orchestrator, AI Employee, RLM, Claude Code, Codex

WebSocket-First

A single persistent connection carries all frontend-backend RPC and push updates

Built-in Skills

Dozens of skills across many categories, DB-backed with SKILL.md defaults

Three Execution Modes

Temporal distributed, Redis parallel, sequential fallback

Execution Engine

Execution flow diagram

How a workflow run flows through the execution engine

Conductor’s Decide Pattern

Workflow orchestration is a single function with fork/join parallelism:
Each workflow run has its own isolated ExecutionContext. No shared global state between concurrent runs. Decide loops serialize per execution via Redis SETNX distributed locks.

Three Execution Modes

Layer Computation via Kahn’s Algorithm

Before execution, the DAG is sorted into layers. Layer 0 is the set of nodes with no dependencies; each subsequent layer depends only on earlier layers. Parallel execution runs each layer with asyncio.gather():
Toolkit sub-nodes (e.g., Android service nodes connected to androidTool) are detected and excluded from execution layers — they run only when the parent toolkit invokes them via tool calling.

Result Caching, Recovery, and DLQ

  • Prefect-style caching: every node result is stored in Redis/SQLite keyed by hash_inputs(inputs). Re-running an identical node returns the cached result with status TaskStatus.CACHED.
  • Heartbeat recovery: RecoverySweeper scans executions:active every 60s; nodes with stale heartbeats (> 5 min) are marked stuck and recovered on next startup.
  • Dead Letter Queue: failed nodes (after retry exhaustion) are quarantined with full input snapshot. Inspect, replay, or purge via the get_dlq_* / replay_dlq_entry / purge_dlq WebSocket handlers.

Edge Conditions

Edges carry optional conditions for runtime branching with 20+ operators (eq, neq, gt, lt, contains, exists, matches, in, …). Unmatched branches are marked TaskStatus.SKIPPED.

Event-Driven Deployment

Deployments are event-driven: each trigger event spawns an independent concurrent execution run. There is no iteration loop.
Multiple runs execute simultaneously with no interference. The firing trigger is marked complete before downstream execution starts; every other trigger node in the same run is auto-marked _pre_executed with {not_triggered: True} so they never block as event waiters.

Push vs Polling Triggers

Under the default Temporal execution path, each deployed trigger runs as a listener workflow that spawns an isolated child run per event. On the legacy local path, push triggers register a waiter with a filter closure and suspend until an external service dispatches a matching event (in-memory asyncio.Future, or Redis Streams for multi-worker deployments).

AI Agent System

AI agent routing diagram

Native SDK chat path vs LangChain agent path

12 LLM Providers

Each provider below has its own chat-model node and appears in every agent’s provider dropdown. (xAI is the exception: it is reachable on the native chat path through the shared OpenAI-compatible client, but it is not a standalone chat-model node.)

Dual-Path Architecture

The native path returns a normalized LLMResponse dataclass across all providers. Agent tool-calling still uses the LangChain chat-model wrappers (bind_tools) driven by the plain-async _run_agent_loop; the native SDK path powers direct chat completions and model discovery. Groq and Cerebras remain LangChain-only for chat as well.

16 Specialized Agents

All specialized agents share the same handle architecture (input-main, input-memory, input-skill, input-tools, input-task) and dispatch through the same BaseNode.execute() plugin path. They mostly differ in icon, title, theme color, and default prompt.
Routing:
  • Most agents run the shared plain-async agent loop (_run_agent_loop) for tool calling and delegation.
  • rlm_agent routes to RLMService (REPL-based recursive LM with llm_query / rlm_query / FINAL).
  • claude_code_agent and codex_agent run through the multi-provider CLI agent framework (Claude Code / Codex CLIs) instead of the standard LLM loop.

Agent Teams Topology

orchestrator_agent and ai_employee have an extra input-teammates handle. Connected agents become delegate_to_<agent_type> tools automatically:
The team lead’s LLM decides when to delegate based on task context. Under the default Temporal execution path, delegation spawns the teammate as a child workflow whose progress mirrors onto the parent’s canvas badge and whose result flows back into the parent’s loop. On the legacy local path, delegation is fire-and-forget: the child spawns as asyncio.create_task(), the parent continues, and the child broadcasts its own status independently. Either way, child completion can be consumed by taskTrigger nodes elsewhere in the workflow.

Agent Loop Flow

The loop routes purely on the model’s tool_calls. A recursion limit guards against infinite tool loops, and a token-based compaction threshold summarizes the transcript before it overflows the model’s context. Tools are built as Pydantic-schema StructuredTool instances with the node’s parameter schema.

Memory, Skills, Tokens, and Cost

Markdown-Based Memory

The simpleMemory node stores conversation history as editable Markdown in the parameter panel. The AI Agent handler reads, parses to LangChain messages, executes, appends the new exchange, trims to a window, and archives removed messages to an InMemoryVectorStore (optional) using HuggingFace BAAI/bge-small-en-v1.5 embeddings.

Skill System

Dozens of built-in skills organized into folders under server/skills/. Each top-level folder appears as an option in the Master Skill node’s folder dropdown:
Each skill is a folder with a SKILL.md file containing YAML frontmatter (name, description, allowed-tools, metadata) and Markdown instructions. First load seeds the database; after that the database is source of truth so users can edit skill instructions in the UI. “Reset to Default” reloads the original SKILL.md. The masterSkill node aggregates multiple skills with enable/disable toggles. A split-panel editor shows the skill list on the left and the selected skill’s Markdown on the right. When connected to an agent, the backend expands the skillsConfig parameter into individual skill entries injected into the agent’s system message.

Token Tracking and Compaction

Every AI execution stores a TokenUsageMetric row with input/output/cache/reasoning token counts and calculated costs (USD) based on server/config/pricing.json. Cumulative state per session lives in SessionTokenState. Compaction threshold priority (highest to lowest):
  1. Per-session custom_threshold (user-set)
  2. Per-user UserSettings.compaction_ratio x the model’s context window
  3. Environment COMPACTION_RATIO (default 0.8) x the model’s context window (e.g., ~838K for Claude Sonnet 4.6 with 1M context)
  4. JSON fallback llm_defaults.json ratio x context window
When the cumulative session tokens cross the threshold, CompactionService.compact_context() generates a 5-section summary (Task Overview, Current State, Important Discoveries, Next Steps, Context to Preserve) following the Claude Code pattern and replaces the memory content. Anthropic and OpenAI also have native compaction APIs (context_management edits, compact_threshold) that are configured transparently.

Communication Layer

A single persistent WebSocket at /ws/status handles most frontend-backend communication. Handlers live in the MESSAGE_HANDLERS map in server/routers/websocket.py plus plugin-registered handlers that self-wire through services.ws_handler_registry (each self-contained plugin folder can own its own WebSocket commands). Together they cover: node parameters, tool schemas, node execution, triggers, dead letter queue, deployment, AI operations, API keys, OAuth flows (Claude, Twitter, Google), Android, WhatsApp, Telegram, workflow storage, chat messages, console logs, skills, memory, user settings, pricing, agent teams, and model registry.

Request/Response Pattern

Broadcast Message Types

Auto-Reconnect and Keepalive

Frontend sends {"type": "ping"} every 30 seconds. Reconnection uses PartySocket with jittered exponential backoff and message replay, so a dropped socket recovers automatically and queued requests replay on the new connection. Connection is gated on isAuthenticated so logged-out users never connect.

Cache, Persistence, and Security

Cache Fallback Hierarchy

OpenCompany follows the n8n cache pattern with automatic environment-based fallback:
CacheService in server/core/cache.py checks each backend in order. TTL expiration is supported across all three. A background cleanup_expired_cache() task removes expired SQLite rows.

Encrypted Credentials

API keys and OAuth tokens live in a separate SQLite database (credentials.db) isolated from the main workflow database (workflow.db). Encryption uses Fernet (AES-128-CBC + HMAC-SHA256) with keys derived from a server-scoped config key via PBKDF2HMAC (600K iterations, OWASP 2024 recommendation).
Two separate credential systems inside credentials.db:
  • API key system (EncryptedAPIKey table): provider keys the user enters manually
  • OAuth token system (EncryptedOAuthToken table): tokens from OAuth flows (Google, Twitter, Claude.ai)
All routers access credentials through AuthService only. Direct database access is forbidden. Decrypted values are cached in AuthService memory dicts and never written to disk or Redis. For cloud deployments, CREDENTIAL_BACKEND can be switched to keyring (OS-native) or aws (Secrets Manager) via the CredentialBackend abstraction.

Authentication

JWT in HttpOnly cookies following the n8n pattern. Two modes:
  • AUTH_MODE=single - first user becomes owner, registration disabled after
  • AUTH_MODE=multi - open registration for cloud deployments
  • VITE_AUTH_ENABLED=false - bypass login entirely for local development
WebSocket connections check the JWT cookie before accepting. Auth context has exponential-backoff retry (5 attempts) to survive race conditions where the frontend starts before the backend is ready.

Node Catalog

Browse all workflow nodes by category

AI Models

12 LLM providers with native SDK and LangChain paths

AI Agents

16 specialized agents with teams and delegation

GitHub

Source code and in-repo docs-internal/ deep dives