Runtime Modes
Key Insight: Library-First Architecture
Section titled “Key Insight: Library-First Architecture”The core is a library. Everything else is a thin wrapper.
If @orgloop/core is designed as a library first, all three runtime modes come naturally. The CLI is a wrapper that calls the library. The server is a wrapper that exposes the library over HTTP. Users embedding OrgLoop in their own applications import the library directly.
This is the most important architectural decision for long-term flexibility. Don’t design a CLI that happens to have a library. Design a library that happens to have a CLI.
// The core library — this is the foundation// Option A: OrgLoop wrapper (single-module, backward-compatible)import { OrgLoop, OrgLoopConfig, OrgLoopOptions } from '@orgloop/core';
const loop = new OrgLoop(config, { sources: sourcesMap, // Map<string, SourceConnector> actors: actorsMap, // Map<string, ActorConnector>});await loop.start();
// Option B: Runtime (multi-module, full control)import { Runtime } from '@orgloop/core';
const runtime = new Runtime();await runtime.start();await runtime.loadModule(moduleConfig, { sources, actors });// Load more modules dynamically...await runtime.loadModule(anotherModuleConfig, { sources: otherSources, actors: otherActors });Mode 1: CLI Mode (MVP)
Section titled “Mode 1: CLI Mode (MVP)”Command: orgloop start
For individual developers and small teams. The CLI manages the full lifecycle: load config, start the engine as a long-running daemon, handle signals, manage the PID file. The daemon manages all source polling internally — poll intervals declared in YAML replace external schedulers (LaunchAgents, systemd timers, cron). One OrgLoop process replaces N poller scripts.
Use orgloop install-service to generate platform-appropriate service files (LaunchAgent on macOS, systemd unit on Linux, Dockerfile for containers) that keep the daemon alive across reboots.
# Foreground (development)orgloop start
# Daemonized (production)orgloop start --daemon
# System service (production, managed restart)orgloop service install # Generates launchd/systemd unitWho uses this: Individual developers, small teams, anyone running OrgLoop on a single machine. This is the MVP and the default path.
Under the hood:
// cli/src/commands/start.ts — simplifiedimport { Runtime } from '@orgloop/core';import { loadCliConfig } from '../config';import { resolveConnectors } from '../resolve-connectors';
const config = await loadCliConfig({ configPath: flags.config });const { sources, actors } = await resolveConnectors(config);
const runtime = new Runtime();await runtime.start();await runtime.startHttpServer(); // Control API + webhook listenerawait runtime.loadModule( { name: config.project.name, sources: config.sources, actors: config.actors, routes: config.routes, ... }, { sources, actors });// Runtime is running. Modules can be loaded/unloaded dynamically.The CLI creates a Runtime instance and loads the config as a module. resolveConnectors() handles the bridge between declarative YAML config and instantiated connector objects. All runtime logic lives in @orgloop/core.
Mode 2: Library/SDK Mode
Section titled “Mode 2: Library/SDK Mode”Import: import { OrgLoop } from '@orgloop/core'
For teams building custom tooling or integrating OrgLoop into existing systems. The core is a library — embed it in your own application, hook into its events, extend its behavior programmatically.
import { OrgLoop, OrgLoopConfig } from '@orgloop/core';
// Programmatic configuration (not just YAML)const config: OrgLoopConfig = { sources: [{ id: 'github', connector: '@orgloop/connector-github', config: { repo: 'my-org/my-repo' }, poll: { interval: '5m' }, }], actors: [{ id: 'my-actor', connector: '@orgloop/connector-webhook', config: { url: 'https://my-service.com/hook' }, }], routes: [{ name: 'github-to-actor', when: { source: 'github', events: ['resource.changed'] }, then: { actor: 'my-actor' }, }],};
const loop = new OrgLoop(config);
// Hook into engine eventsloop.on('event', (event) => { console.log('Event received:', event.id);});
loop.on('delivery', (result) => { myMetricsSystem.record('orgloop.delivery', result);});
// Inject events programmaticallyloop.inject({ source: 'custom', type: 'resource.changed', payload: { /* ... */ },});
await loop.start();Who uses this: Platform teams embedding OrgLoop in a larger system. Internal tools that need event routing as a component, not a standalone daemon. Teams that want programmatic config (not YAML) or custom event sources.
This mode exists by default if we design library-first. No additional work needed — just export clean public APIs from @orgloop/core.
Mode 3: Server/API Mode (v1.1 — Not Yet Implemented)
Section titled “Mode 3: Server/API Mode (v1.1 — Not Yet Implemented)”Status:
@orgloop/serverexists as a placeholder package that re-exports@orgloop/core. The REST API,orgloop servecommand, and HTTP layer described below are the planned v1.1 design. NoserveCLI command exists today.
Planned command: orgloop serve
Will expose a REST API for programmatic control, event ingestion, and status monitoring. For production deployments, web dashboards, and enterprise integrations.
Note: The engine already has a lightweight WebhookServer (localhost-only, port 4800) for hook-based sources like Claude Code. The server mode described here is a full-featured API server — a separate concern.
Planned API Surface
Section titled “Planned API Surface”# Event ingestion (push-based sources)POST /api/v1/events Ingest an eventGET /api/v1/events Query recent events (with filters)GET /api/v1/events/:id Get a specific event's full trace
# Runtime managementGET /api/v1/status Runtime status (uptime, counts)GET /api/v1/health Health check (for load balancers)
# ObservabilityGET /api/v1/sources List sources and their statusGET /api/v1/sources/:id Source detail (checkpoint, stats)GET /api/v1/actors List actors and their statusGET /api/v1/actors/:id Actor detail (delivery stats)GET /api/v1/routes List routes and their statsGET /api/v1/routes/:id Route detail (match/drop counts)
# Configuration managementPOST /api/v1/config/validate Validate a config payloadPOST /api/v1/config/plan Compute a planPOST /api/v1/config/start Apply a config change
# LogsGET /api/v1/logs Stream logs (SSE)GET /api/v1/logs/query Query historical logs
# Webhook receiver (for push-based sources)POST /api/v1/webhooks/:source Receive webhook from a source platformWho will use this: Production deployments behind a load balancer. Web dashboards that show OrgLoop status. Enterprise integrations that need programmatic event ingestion. Teams that want an API-first interface instead of (or in addition to) CLI.
Architecture Diagram
Section titled “Architecture Diagram”┌─────────────────────────────────────────────────────────────────┐│ @orgloop/core ││ (THE library — all logic lives here) ││ ││ ┌──────────┐ ┌──────────┐ ┌────────┐ ┌───────────────────┐ ││ │ EventBus │ │ Router │ │ Xforms │ │ Logger Fan-out │ ││ │ (WAL) │ │ │ │ │ │ │ ││ └──────────┘ └──────────┘ └────────┘ └───────────────────┘ ││ ││ Public API: ││ new Runtime() — multi-module runtime ││ runtime.start() / runtime.stop() ││ runtime.loadModule(config, connectors) ││ runtime.unloadModule(name) / runtime.reloadModule(name) ││ ││ new OrgLoop(config) — backward-compatible single-module ││ loop.start() / loop.stop() / loop.inject(event) │└───────────┬──────────────────────┬───────────────────┬──────────┘ │ │ │ ┌────────▼────────┐ ┌────────▼────────┐ ┌───────▼────────┐ │ @orgloop/cli │ │ @orgloop/server │ │ Your app │ │ │ │ (placeholder) │ │ │ │ orgloop start │ │ │ │ import { │ │ orgloop status │ │ orgloop serve │ │ OrgLoop │ │ orgloop logs │ │ REST API │ │ } from core │ │ orgloop test │ │ (v1.1) │ │ │ └─────────────────┘ └─────────────────┘ └────────────────┘ CLI mode Server mode (v1.1) Library modePriority
Section titled “Priority”| Mode | Priority | Notes |
|---|---|---|
CLI mode (orgloop start) | MVP | Ship first. This proves the core works. |
Library mode (import { OrgLoop }) | MVP | Comes free with library-first design. The CLI already uses it. |
Server mode (orgloop serve) | v1.1 | After CLI is proven, add the HTTP layer. The library API is already there; server is just HTTP routing on top. |