Table of Contents
- The Challenge
- Architecture & Solution
- Tech Stack
- Key Engineering Decisions
- LSP-Bridge System
- Security Architecture
- Offline & Resilience
- Deployment
- Roadmap
The Challenge
Remote control of a development workstation from a mobile device typically requires over-engineered solutions that consume massive resources:
- Resource overhead: GATOR v1 required ~720MB RAM across 5 Docker containers (NestJS, Next.js, PostgreSQL, Redis, Worker Agent)
- Startup time: 30+ seconds to bring all services online
- LSP bottleneck: Language Server polling at 50ms intervals caused constant CPU and I/O churn
- Cross-process IPC tax: Gateway to Worker Agent communication via Redis queues added 5-20ms latency
- Bundle bloat: Next.js PWA generated 250KB+ for a chat interface that could be 12KB
The requirement: Build a lean, single-process system that provides chat-first, intent-based remote control from any device — with sub-second startup, under 150MB RAM, and zero external dependencies — using the phone as a control surface that emits intent, never commands.
Architecture & Solution
GATOR Lite replaces the v1 heavyweight architecture with a single Hono process containing embedded SQLite, in-memory priority queue, and the LSP-Bridge directly embedded.
Component Responsibilities
| Component | Runtime | Responsibility |
|---|---|---|
| Web SPA | Browser | Render UI, emit intent, display steps + logs |
| APEX Core | Node.js | Auth, intent parsing, task validation, queue, WS broadcast |
| LSP-Bridge | Inside Core | PORTA LSP logic: discovery, RPC relay, delta-polling |
| Task Runner | Inside Core | Shell sandbox, whitelisted execution, log streaming |
| SQLite | File on disk | Durable record store: devices, audit, tasks, conversations |
| In-Memory Queue | In Core | Priority buckets (low/med/high), SQLite-backed recovery |
Tech Stack
| Layer | Technology | Role |
|---|---|---|
| Runtime | Node.js 20+ LTS | ESM-native, stable |
| Web Framework | Hono 4.x | Fastest Node.js framework, 5x faster than NestJS |
| Database | SQLite (better-sqlite3) | Embedded, WAL mode, zero setup |
| ORM | Drizzle ORM | TypeScript-first, zero-runtime overhead |
| Real-time | Socket.IO | WebSocket with rooms and reconnection |
| Task Queue | Custom In-Memory Priority Queue | Map<priority, Queue> with AsyncLocalStorage |
| LSP-Bridge | PORTA (direct port) | Connect RPC protocol, delta-polling |
| Auth | Custom JWT (HS256) via jose | Lightweight token authentication |
| Logging | Pino 9.x | Fast structured JSON logger |
| Frontend | Vite + React | Instant HMR, minimal bundle (~12KB) |
| Process Manager | tsx | TypeScript execution without compilation |
| Network | Tailscale | Encrypted private mesh, zero port-forwarding |
Key Engineering Decisions
1. Hono over NestJS — 5x Performance
// apps/core/src/index.ts — Hono server setup
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
import { authMiddleware } from './middleware/auth';
import { chatRouter } from './routes/chat';
import { wsRouter } from './routes/ws';
const app = new Hono();
// Global middleware
app.use('*', logger());
app.use('*', cors({ origin: '*', credentials: true }));
// Routes
app.get('/health', (c) => c.json({ status: 'ok', uptime: process.uptime() }));
app.route('/api/chat', chatRouter);
app.route('/api/ws', wsRouter);
// Start with tsx — no compilation needed
export default app;2. SQLite over PostgreSQL — Single File, Zero Setup
// packages/database/src/schema.ts — Drizzle schema
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
export const devices = sqliteTable('devices', {
id: text('id').primaryKey(),
name: text('name').notNull(),
platform: text('platform').notNull(),
isActive: integer('is_active').default(1),
lastSeen: integer('last_seen'),
createdAt: integer('created_at').notNull(),
});
export const tasks = sqliteTable('tasks', {
id: text('id').primaryKey(),
deviceId: text('device_id').notNull(),
intent: text('intent').notNull(),
status: text('status').notNull(),
priority: text('priority').default('medium'),
result: text('result'),
createdAt: integer('created_at').notNull(),
completedAt: integer('completed_at'),
});3. Single Process Architecture — 720MB → 150MB
| v1 (GATOR) | v2 (GATOR Lite) | Reduction |
|---|---|---|
| NestJS Gateway (~120MB) | Hono Core (~15MB) | -105MB |
| Next.js PWA (~250MB) | Vite SPA (~25MB) | -225MB |
| Node.js Worker (~80MB) | Merged into Core | -80MB |
| PostgreSQL Docker (~250MB) | Embedded SQLite (~20MB) | -230MB |
| Redis Docker (~20MB) | In-Memory Queue | -20MB |
| Total | ~150MB | -570MB |
4. In-Memory Priority Queue with SQLite Persistence
// packages/queue/src/priority-queue.ts
type Task = {
id: string;
priority: 'low' | 'medium' | 'high';
payload: unknown;
createdAt: number;
};
class PriorityQueue {
private queues = {
high: [] as Task[],
medium: [] as Task[],
low: [] as Task[],
};
async enqueue(task: Task): Promise<void> {
this.queues[task.priority].push(task);
// Persist to SQLite for restart recovery
await db.insert(tasks).values(task);
}
async dequeue(): Promise<Task | undefined> {
for (const priority of ['high', 'medium', 'low'] as const) {
const task = this.queues[priority].shift();
if (task) return task;
}
return undefined;
}
}5. Intent-Based Interface — Phone as Control Surface
Instead of sending raw commands, users type intent:
User: "Run tests on backend"
GATOR: Parses intent → "Run test:unit on apps/api"
GATOR: Executes whitelisted task → Streams output
GATOR: Reports back with resultsLSP-Bridge System
GATOR inherits the verified PORTA LSP-Bridge for IDE communication:
Discovery Protocol
- Daemon scan: Find running language server processes
- Port probe: Identify LSP ports
- RPC enrich: Connect via JSON-RPC/HTTPS
- Cache: 10-second TTL for discovery results
Delta Polling State Machine
IDLE (20000ms heartbeat)
│
▼ (signals.activate())
ACTIVE (150ms serial polling)
│
▼ (signals.deactivate())
IDLEStep Recovery
- Oversized payload: Chunk into smaller delta messages
- UTF-8 corruption: Skip malformed chunks with MAX_SKIP guard
- Connection drop: Resume from last confirmed step
Security Architecture
Whitelist-Only Task Execution
// Task whitelist from task-registry.json
const taskRegistry = {
'test:unit': {
command: 'pnpm --filter apps/api test',
description: 'Run API unit tests',
timeout: 120000,
},
'build:core': {
command: 'pnpm --filter apps/core build',
description: 'Build core application',
timeout: 60000,
},
// Unknown tasks are rejected before execution
};
async function executeTask(taskName: string) {
if (!taskRegistry[taskName]) {
throw new Error(`Task "${taskName}" not whitelisted`);
}
return spawn(taskRegistry[taskName].command);
}Network Exposure Guard
The Core binds only to:
127.0.0.1(loopback)- Private LAN IP
- Tailscale CGNAT
Wildcard binds (0.0.0.0) throw at startup.
JWT Authentication
// Using jose library for lightweight JWT
import { SignJWT, jwtVerify } from 'jose';
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
async function signToken(payload: object): Promise<string> {
return new SignJWT(payload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('7d')
.sign(secret);
}
async function verifyToken(token: string): Promise<object> {
const { payload } = await jwtVerify(token, secret);
return payload;
}Offline & Resilience
Client-Side Offline Queue
- IndexedDB stores commands when offline
- On reconnect, replay in order
- No commands lost during connection drops
Server-Side Persistence
- SQLite WAL mode for concurrent reads
- In-memory queue persisted to SQLite on every write
- Tasks survive Core restarts
Reconnection Logic
- Socket.IO with automatic reconnection
- Delta-poller resumes from last confirmed step
- Task queue recovers from SQLite on startup
Deployment
Zero-Config Startup
# No Docker required
pnpm install
pnpm dev
# System starts in <1.5 seconds with:
# - Embedded SQLite (auto-created)
# - In-memory queue (empty)
# - LSP-Bridge readyTailscale Integration
# On the host machine, ensure Tailscale is running
tailscale up --autoconnect=true
# GATOR binds to Tailscale IP automatically
# Access from phone via: https://100.x.x.x:3000Production Checklist
# Install dependencies
pnpm install
# Build for production
pnpm build
# Start the core
pnpm start:core
# Access from mobile via Tailscale
# https://<tailcale-ip>/appRoadmap
Phase 1 — Core Foundation (Complete)
- Single Hono process architecture
- Embedded SQLite with Drizzle ORM
- Basic chat + intent parsing
Phase 2 — LSP-Bridge Integration (Complete)
- PORTA LSP logic ported
- Delta-polling state machine
- Step recovery mechanisms
Phase 3 — Mobile SPA (Complete)
- Vite + React frontend
- Real-time log streaming via Socket.IO
- IndexedDB offline queue
Phase 4 — Security Hardening (Complete)
- Task whitelist enforcement
- Network exposure guard
- JWT authentication
Phase 5 — Performance Tuning (In Progress)
- Startup time <1s target
- Memory <100MB target
Phase 6 — Multi-Device Support (Planned)
- iOS companion app
- Desktop SPA refinements
Phase 7 — Advanced Automation (Future)
- AI-powered intent classification
- Predictive task execution
Engineering Proof
Real-world validation, system demonstrations, and interface captures of the execution states.
System Demonstration
Video walkthrough detailing core logic, interactions, and system behaviors in action.
System Captures
Architecture Feedback
Spotted a potential optimization or antipattern? Let me know.
