Architecture
This page describes the full OmniLink system architecture, from the user interface layer down to the local runtime. Understanding these layers will help you make better design decisions when building agents.
High-Level Overview
OmniLink is a five-layer platform. Each layer has a clear responsibility, and communication between layers follows well-defined protocols.
┌─────────────────────────────────────────────────────────────┐
│ LAYER 1: USER INTERFACE │
│ Dashboard · Web SDK · Custom Clients │
├─────────────────────────────────────────────────────────────┤
│ LAYER 2: REST API │
│ Node.js server on Cloud Run (server.ts + api/*.ts) │
│ /api/chat · /api/stt · /api/tts · /api/memory · ... │
├─────────────────────────────────────────────────────────────┤
│ LAYER 3: AI ENGINES │
│ g1 (Gemini) · g2 (GPT) │
│ g3 (Grok) · g4 (Claude) │
├─────────────────────────────────────────────────────────────┤
│ LAYER 4: DATA LAYER │
│ Supabase (PostgreSQL) │
│ Auth · Profiles · Memory · Usage · Billing │
├─────────────────────────────────────────────────────────────┤
│ LAYER 5: LOCAL RUNTIME │
│ OmniLink Python Library │
│ OmniLinkEngine · ToolRunner · HTTP Bridge │
└─────────────────────────────────────────────────────────────┘
┌───────────────────────────┐
│ TRANSPORT LAYER │
│ REST (HTTPS) │
│ HTTP Bridge (port 8080) │
└───────────────────────────┘
Layer 1: User Interface
The UI layer is where humans and external systems interact with OmniLink. There are three primary entry points:
Dashboard
The web-based Dashboard at https://www.omnilink-agents.com
is the primary management interface. It provides pages for agent
configuration, voice settings, engine selection,
memory browsing, billing, usage monitoring, message visualization, and
more.
The Dashboard communicates with Layer 2 exclusively through the REST API, using the same endpoints available to external clients. There is no privileged internal API.
Custom Clients
Any HTTP client can call the REST API directly. The API uses standard Bearer token authentication and returns JSON responses.
curl -X POST https://www.omnilink-agents.com/api/chat \
-H "Authorization: Bearer olink_your_key_here" \
-H "Content-Type: application/json" \
-d '{"message": "Hello", "engine": "g1-engine"}'
Layer 2: REST API
The API layer runs as a single Node.js server (server.ts)
on Google Cloud Run, dispatching to per-route handlers in
api/*.ts. Each handler:
- Validates the Omni Key against Supabase
- Extracts user context (profile, permissions, plan tier)
- Routes the request to the appropriate AI engine or data service
- Deducts credits from the user’s balance
- Returns the response
Key Endpoints
| Endpoint | Method | Description |
|---|---|---|
/api/chat | POST | Send a message, receive an AI response |
/api/chat | POST (stream) | Stream response tokens via Server-Sent Events |
/api/stt | POST | Transcribe audio to text |
/api/tts | POST | Synthesize text to audio |
/api/translation | POST | Translate text between languages |
/api/memory | GET/POST/DELETE | Read, write, and delete memory entries |
/api/profiles | GET/POST/PUT/DELETE | CRUD operations on agent profiles |
/api/usage | GET | Retrieve credit balance and usage statistics |
Request Lifecycle
# Request lifecycle for /api/chat
#
# 1. Client sends POST with Bearer token + JSON body
# 2. Serverless function extracts token → validates against Supabase
# 3. User context loaded (profile, plan, remaining credits)
# 4. Credit check: sufficient balance?
# ├── No → 402 Payment Required
# └── Yes → continue
# 5. Memory injection (if enabled): load relevant entries
# 6. Prompt assembly: system instructions + memory + message
# 8. Route to selected engine (g1/g2/g3/g4)
# 9. Receive response from model provider
# 10. Deduct credits, log usage
# 11. Return response to client
Layer 3: AI Engines
OmniLink abstracts multiple model providers behind a unified engine
interface. The API layer routes requests based on the
engine parameter.
| Engine ID | Provider | Model | Strengths |
|---|---|---|---|
g1-engine |
Gemini | Lowest latency, highest throughput, cost-efficient | |
g2-engine |
OpenAI | GPT | Complex multi-step reasoning, code generation |
g3-engine |
xAI | Grok | Real-time knowledge, current events |
g4-engine |
Anthropic | Claude | Instruction following, safety, long context |
Model Routing
Engine selection happens at three levels, with later levels overriding earlier ones:
- Profile default — Set when the profile is created or updated.
- Dashboard selection — Changed from the AI Engine page.
- Per-request override — The
enginefield in the API request body.
This means you can set a default engine for your agent (e.g., g1 for speed) and override it for specific requests that need more reasoning power (e.g., g2 or g4).
Layer 4: Data Layer
OmniLink uses Supabase (PostgreSQL) as its primary data store. Supabase provides authentication, row-level security, real-time subscriptions, and vector storage.
Data Domains
| Domain | Storage | Description |
|---|---|---|
| Authentication | Supabase Auth | Omni Key validation, user sessions, API key management |
| Profiles | PostgreSQL | Agent names, personas, system instructions, engine preferences |
| Memory | PostgreSQL | Key-value pairs with categories and timestamps |
| Usage | PostgreSQL | Credit transactions, API call logs, per-endpoint statistics |
| Billing | PostgreSQL | Plan tier, subscription status, payment history |
Row-Level Security
Every table is protected by row-level security (RLS) policies. A user’s Omni Key resolves to a user ID, and all queries are scoped to that user. There is no way for one user to access another user’s data, even if they guess a record ID.
Layer 5: Local Runtime
The local runtime is the omnilink Python library. It runs
on your hardware and handles everything that does not require cloud
inference.
Components
OmniLinkEngine
The command parser. It uses template matching with typed variables to convert natural-language-like strings into function calls.
# Template syntax: "verb [variable_name:type] rest of pattern"
# Supported types: str, int, float, bool, and custom types via TypeRegistry
engine = OmniLinkEngine()
# Register a custom type
engine.type_registry.register("direction", ["north", "south", "east", "west"])
@engine.command("face [dir:direction]")
def face(dir: str):
return f"Facing {dir}"
# "face north" → face("north") ✓
# "face up" → no match (not in direction type) ✗
ToolRunner
The cloud-orchestrated local execution framework. Subclass
ToolRunner and implement three methods:
get_state(), execute_action(), and
is_game_over(). The framework handles the rest.
OmniLinkClient
A Python REST client that mirrors the Web SDK. Use it to call any platform endpoint from Python code.
OmniLinkHTTPBridge
Starts a local HTTP server on port 8080. External systems can send
commands via POST /command and receive results
synchronously.
Note: The legacy bridge class has been removed. Use OmniLinkHTTPBridge instead.
Use OmniLinkHTTPBridge for all new projects.
Transport Layer
Two transport mechanisms connect the layers:
REST (HTTPS)
The primary transport between UI/SDK and the cloud API. All requests use HTTPS with Bearer token authentication.
HTTP Bridge
A local REST server for bidirectional communication between the cloud platform and local runtime. Three endpoints are standard:
| Endpoint | Direction | Payload |
|---|---|---|
POST /command |
Cloud → Local | JSON-encoded command with action and parameters |
GET /feedback |
Local → Cloud | JSON-encoded result with status, data, and errors |
GET /context |
Local → Cloud | JSON-encoded current agent state and context |
Useful for integration with tools that speak REST (e.g., Home Assistant, Node-RED).
# Send a command via the HTTP bridge
curl -X POST http://localhost:8080/command \
-H "Content-Type: application/json" \
-d '{"command": "turn on the kitchen lights"}'
# Response:
# {"result": "kitchen lights turned on", "status": "ok"}
The ToolRunner Lifecycle
ToolRunner is the heart of OmniLink’s cost-efficient agent architecture. Here is the complete lifecycle:
┌──────────────────────────────────────────────────────────┐
│ TOOLRUNNER LIFECYCLE │
├──────────────────────────────────────────────────────────┤
│ │
│ 1. KICKOFF (1 credit) │
│ ├── User provides task description │
│ ├── get_state() captures initial state │
│ ├── State + task sent to cloud AI │
│ └── AI returns action plan │
│ │
│ 2. MAIN LOOP (0 credits) │
│ ├── execute_action() runs the AI's chosen action │
│ ├── get_state() captures new state │
│ ├── Memory persisted locally │
│ ├── UI command polling (check for user overrides) │
│ ├── is_game_over() checked │
│ │ ├── True → go to step 4 │
│ │ └── False → continue loop │
│ └── Repeat │
│ │
│ 3. PERIODIC REVIEW (1 credit, optional) │
│ ├── Triggered after N iterations or time threshold │
│ ├── Current state sent to cloud AI │
│ ├── AI adjusts strategy if needed │
│ └── Returns to main loop │
│ │
│ 4. FINAL ANALYSIS (1 credit) │
│ ├── Final state captured via get_state() │
│ ├── Complete execution log sent to cloud AI │
│ ├── AI generates summary and recommendations │
│ └── Result returned to caller │
│ │
│ Total: 2 credits (no review) or 3+ (with reviews) │
└──────────────────────────────────────────────────────────┘
Implementing a ToolRunner
You subclass ToolRunner and implement three required
methods:
from omnilink import ToolRunner
class MyRunner(ToolRunner):
def get_state(self) -> dict:
"""Return a JSON-serializable snapshot of current state.
Called at kickoff, after each action, and at final analysis."""
return {"status": "running", "progress": 0.5}
def execute_action(self, state: dict) -> None:
"""Execute an action decided by the cloud AI.
Receives the current state dict."""
# Parse and execute the action locally
pass
def is_game_over(self, state: dict) -> bool:
"""Return True when the task is done.
Checked after every action execution."""
return self.progress >= 1.0
State Polling & Memory
During the main loop, the ToolRunner persists state locally. This means that even if the process restarts, it can resume from the last known state. Memory entries are written to the platform’s memory store so the cloud AI can reference them during periodic reviews and final analysis.
Command Flow
Here is the complete flow from user input to executed action:
# Command flow
#
# 1. User input: "turn on the kitchen lights"
#
# 2. Cloud AI (if using chat endpoint):
# - Receives message + system instructions
# - Decides this maps to a tool call
# - Emits command string: "turn on the kitchen lights"
#
# 3. Transport (HTTP Bridge):
# - Command sent to POST /command
# - Local runtime receives it
#
# 4. OmniLinkEngine:
# - Matches against template: "turn [state:str] the [device:str]"
# - Extracts: state="on", device="kitchen lights"
# - Calls handler: toggle_device(state="on", device="kitchen lights")
#
# 5. Handler execution:
# - Sends HTTP request to device API → "on"
# - Returns: "kitchen lights turned on"
#
# 6. Feedback:
# - Result returned via GET /feedback
# - Cloud receives confirmation
# - User sees: "Done! The kitchen lights are now on."
AgentFeedback
Command handlers can send bidirectional messages during execution using
AgentFeedback. This allows the handler to report progress,
ask for clarification, or stream partial results back to the user.
from omnilink import OmniLinkEngine, AgentFeedback
engine = OmniLinkEngine()
@engine.command("deploy service [name:str]")
def deploy(name: str, feedback: AgentFeedback):
"""Deploy a service with progress updates."""
feedback.send("Starting deployment...")
# ... build step ...
feedback.send("Build complete. Running tests...")
# ... test step ...
feedback.send("Tests passed. Deploying to production...")
# ... deploy step ...
return f"Service {name} deployed successfully"
Authentication Flow
Every API request must include an Omni Key. Here is how authentication works:
# Authentication flow
#
# 1. Client sends request:
# Authorization: Bearer olink_abc123...
#
# 2. Serverless function extracts the token
#
# 3. Token validated against Supabase:
# SELECT user_id, plan, credits_remaining
# FROM api_keys
# WHERE key_hash = hash('olink_abc123...')
# AND is_active = true
#
# 4. If valid:
# - User context attached to request
# - Request proceeds to handler
#
# 5. If invalid:
# - 401 Unauthorized returned
# - Request rejected
Key Format
Omni Keys follow the format olink_ followed by a
cryptographically random string. The key is hashed before storage —
OmniLink never stores raw keys. If you lose your key, you must generate
a new one from the Dashboard.
Security Best Practices
- Store your Omni Key in environment variables, never in source code
- Rotate keys periodically from the Dashboard
- Use separate keys for development and production
- Monitor the Usage page for unexpected activity
Knowledge Pipeline
The cloud knowledge pipeline (file upload, chunking, pgvector storage,
/api/knowledge-index) was removed in the
2026-04-21 cloud-knowledge-removal work. Knowledge is now provided
via per-agent local folders (agents/<name>/knowledge/)
checked into the agent repo and read from Python via the
search_knowledge tool. See
the agents directory
for the current pattern.
Deployment Topology
A typical OmniLink deployment looks like this:
┌──────────────────┐
│ User Browser │
│ (Dashboard) │
└────────┬─────────┘
│ HTTPS
┌────────▼─────────┐
│ Cloud Run │
│ (REST API) │
└──┬─────┬──────┬──┘
│ │ │
┌─────────────▼┐ ┌─▼────┐ ┌▼──────────────┐
│ AI Engines │ │Supa- │ │ Supabase │
│ (g1/g2/g3/g4)│ │base │ │ pgvector │
│ │ │Auth │ │ (memory) │
└───────────────┘ └──────┘ └───────────────┘
═══════════════════════════════════════════════════════════
INTERNET
═══════════════════════════════════════════════════════════
┌──────────────────┐
│ Local Machine │
│ ┌──────────────┐ │
│ │ OmniLink Lib │ │
│ │ ┌─────────┐ │ │
│ │ │ Engine │ │ │
│ │ │ Runner │ │ │
│ │ │ Bridges │ │ │
│ │ └─────────┘ │ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────▼───────┐ │
│ │ Hardware │ │
│ │ (robot, │ │
│ │ sensors, │ │
│ │ actuators) │ │
│ └──────────────┘ │
└──────────────────┘
The cloud and local halves are connected by HTTPS (for API calls) and the HTTP bridge (for local agent communication). The local machine can be anything from a Raspberry Pi to a full server.
Next Steps
- Quickstart — Put this architecture into practice with a hands-on tutorial.
- Use Cases — See how the architecture applies to specific domains.
- Introduction — Revisit the platform overview and value proposition.