Project Model
Status: Implemented (v0.1.9). Projects are package-native. A project is a directory with
orgloop.yaml+package.json. Connectors, transforms, and loggers are npm packages installed vianpm install. Routes are auto-discovered from theroutes/directory.
What Is a Project?
Section titled “What Is a Project?”A project is a directory that contains an OrgLoop configuration. It has two required files:
orgloop.yaml— the project manifest. Declares metadata, defaults, and file paths to connector, transform, and logger YAML files.package.json— the dependency manifest. Lists@orgloop/*packages (connectors, transforms, loggers) as dependencies.
my-org/├── orgloop.yaml # Project manifest├── package.json # npm dependencies (@orgloop/connector-*, etc.)├── connectors/│ ├── github.yaml # ConnectorGroup: sources and actors│ ├── linear.yaml│ ├── claude-code.yaml│ └── openclaw.yaml├── routes/│ └── engineering.yaml # RouteGroup: auto-discovered from routes/├── transforms/│ └── transforms.yaml # TransformGroup: filter, dedup, etc.├── loggers/│ └── default.yaml # LoggerGroup: file, console, etc.├── sops/ # Launch prompt files (markdown)│ ├── pr-review.md│ └── ci-failure.md└── node_modules/ # Installed packages (gitignored)This is the entire model. A project is a flat, explicit directory of YAML files and npm packages. orgloop init scaffolds it. orgloop start runs it.
Project Config (orgloop.yaml)
Section titled “Project Config (orgloop.yaml)”The root configuration file declares the project identity, defaults, and references to YAML files that define the project’s connectors, transforms, and loggers.
apiVersion: orgloop/v1alpha1kind: Projectmetadata: name: engineering-org description: "Engineering organization event routing"
defaults: poll_interval: 5m event_retention: 7d log_level: info
connectors: - connectors/github.yaml - connectors/linear.yaml - connectors/claude-code.yaml - connectors/openclaw.yaml
transforms: - transforms/transforms.yaml
loggers: - loggers/default.yamlSchema:
| Field | Type | Required | Description |
|---|---|---|---|
apiVersion | string | Yes | API version (orgloop/v1alpha1) |
kind | "Project" | Yes | Must be Project |
metadata.name | string | Yes | Project name (used for runtime identity) |
metadata.description | string | No | Human-readable description |
defaults.poll_interval | string | No | Default poll interval for sources |
defaults.event_retention | string | No | Event retention period |
defaults.log_level | string | No | Default log level |
connectors | string[] | No | Paths to ConnectorGroup YAML files |
transforms | string[] | No | Paths to TransformGroup YAML files |
loggers | string[] | No | Paths to LoggerGroup YAML files |
All file paths are resolved relative to the directory containing orgloop.yaml.
Routes are NOT listed in orgloop.yaml. They are auto-discovered from the routes/ directory (see Route Auto-Discovery).
package.json as Dependency Manifest
Section titled “package.json as Dependency Manifest”Connectors, transforms, and loggers are npm packages. The project’s package.json declares them as dependencies:
{ "name": "my-org", "private": true, "dependencies": { "@orgloop/connector-github": "^0.1.0", "@orgloop/connector-linear": "^0.1.0", "@orgloop/connector-claude-code": "^0.1.0", "@orgloop/connector-openclaw": "^0.1.0", "@orgloop/connector-cron": "^0.1.0", "@orgloop/transform-filter": "^0.1.0", "@orgloop/transform-dedup": "^0.1.0", "@orgloop/transform-enrich": "^0.1.0", "@orgloop/logger-file": "^0.1.0", "@orgloop/logger-console": "^0.1.0" }}Install everything with npm install (or pnpm install). The CLI resolves packages from the project’s node_modules/ at startup.
Plugin Resolution
Section titled “Plugin Resolution”When orgloop start runs, the CLI dynamically imports each connector, transform, and logger package referenced in the YAML config. The resolution order:
- Project
node_modules/— packages installed in the project directory (preferred) - CLI
node_modules/— packages bundled with or installed alongside@orgloop/cli(fallback)
If a package is not found in either location, the CLI reports the error and suggests the install command:
Error: Connector "@orgloop/connector-linear" not found. Fix: npm install @orgloop/connector-linearResolution uses Node’s standard import() with createRequire() scoped to the project directory, so the project’s node_modules/ takes priority. This means a project can pin specific versions of connectors independently of the CLI version.
YAML File Formats
Section titled “YAML File Formats”Each YAML file has a kind field that identifies its type. All share the same apiVersion.
ConnectorGroup
Section titled “ConnectorGroup”Defines sources (poll-based or hook-based) and actors (delivery targets).
apiVersion: orgloop/v1alpha1kind: ConnectorGroup
sources: - id: github description: GitHub PR and CI activity connector: "@orgloop/connector-github" config: repo: "${GITHUB_REPO}" token: "${GITHUB_TOKEN}" events: - "pull_request.review_submitted" - "pull_request_review_comment" - "workflow_run.completed" poll: interval: 5m emits: - resource.changed
actors: - id: openclaw-engineering-agent description: OpenClaw agent delivery connector: "@orgloop/connector-openclaw" config: agent: "${OPENCLAW_AGENT}" webhook_url: "http://127.0.0.1:18789/hooks/agent" token: "${OPENCLAW_WEBHOOK_TOKEN}"RouteGroup
Section titled “RouteGroup”Defines event routing rules: which source events match, what transforms to apply, and which actor receives the event.
apiVersion: orgloop/v1alpha1kind: RouteGroupmetadata: name: engineering-routes description: "Engineering event routing"
routes: - name: github-pr-review description: "PR review submitted -> Engineering agent" when: source: github events: - resource.changed filter: provenance.platform_event: pull_request.review_submitted transforms: - ref: drop-bot-noise - ref: dedup then: actor: openclaw-engineering-agent config: session_key: "hook:github:pr-review:engineering" with: prompt_file: "./sops/pr-review.md"TransformGroup
Section titled “TransformGroup”Defines transforms (package-based or script-based) that filter, deduplicate, or enrich events in the pipeline.
apiVersion: orgloop/v1alpha1kind: TransformGroup
transforms: - name: drop-bot-noise type: "@orgloop/transform-filter" config: exclude: provenance.author: "/\\[bot\\]$/"
- name: dedup type: "@orgloop/transform-dedup" config: window: 1h key_fields: - source - type - provenance.platform_event_idLoggerGroup
Section titled “LoggerGroup”Defines loggers that observe the event pipeline.
apiVersion: orgloop/v1alpha1kind: LoggerGroup
loggers: - name: file-log type: "@orgloop/logger-file" config: path: "~/.orgloop/logs/orgloop.log" format: jsonl rotate: max_size: 10mb max_age: 7d max_files: 5
- name: console-log type: "@orgloop/logger-console" config: level: infoEnvironment Variable Substitution
Section titled “Environment Variable Substitution”YAML config files support ${VAR_NAME} syntax for environment variable substitution. Variables are resolved at config load time (during orgloop start, orgloop validate, orgloop plan).
config: repo: "${GITHUB_REPO}" token: "${GITHUB_TOKEN}"If a referenced variable is not set, config loading fails with an error identifying the missing variable and the file that references it.
Use orgloop env to check which variables are required and which are set:
$ orgloop env
Environment Variables:
ok GITHUB_TOKEN connectors/github.yaml !! LINEAR_API_KEY connectors/linear.yaml -> Linear personal API key -> https://linear.app/settings/api ok OPENCLAW_WEBHOOK_TOKEN connectors/openclaw.yaml
2 of 3 variables set. 1 missing.Route Auto-Discovery
Section titled “Route Auto-Discovery”Routes are auto-discovered from the routes/ directory relative to orgloop.yaml. The CLI scans for all .yaml and .yml files in this directory and loads them as RouteGroup files.
my-org/├── orgloop.yaml└── routes/ ├── engineering.yaml # Loaded automatically ├── supervision.yaml # Loaded automatically └── experimental.yaml # Loaded automaticallyThis means adding a new route is as simple as creating a new YAML file in routes/. No changes to orgloop.yaml required.
Routes are NOT listed in orgloop.yaml. The connectors, transforms, and loggers arrays are explicit file references. Routes use directory-based auto-discovery. This asymmetry is intentional: routes change frequently (new workflows, new event patterns), while connectors and loggers are stable infrastructure.
Prompt file paths in routes (with.prompt_file) are resolved relative to the route YAML file, not the project root. This allows routes to reference SOPs in a co-located sops/ directory:
routes: - name: github-pr-review # ... with: prompt_file: "../sops/pr-review.md" # Relative to routes/How It Works at Runtime
Section titled “How It Works at Runtime”When you run orgloop start, the CLI:
- Reads
orgloop.yamlfrom CWD (or--configpath) - Loads all referenced ConnectorGroup, TransformGroup, and LoggerGroup YAML files
- Auto-discovers RouteGroup files from
routes/ - Resolves
${VAR}references in all loaded YAML - Dynamically imports connector/transform/logger packages from the project’s
node_modules/ - If no daemon is running: Creates a
Runtimeinstance and loads the project as its first module - If a daemon is already running: Registers this project as an additional module into the existing daemon via the control API
The runtime receives the fully resolved config — sources, actors, routes, transforms, loggers — and starts polling, routing, and delivering events. The project directory structure is a config-time concern; the runtime only sees the resolved primitives.
Run orgloop plan to see exactly what the resolved config looks like before starting. Run orgloop validate to check config syntax and reference integrity without starting.
Multi-Project Runtimes
Section titled “Multi-Project Runtimes”Multiple projects can share a single daemon process. Each project is loaded as a separate module with independent sources, actors, routes, and transforms. The shared runtime infrastructure (event bus, scheduler, HTTP server) is created once and used by all modules.
# Terminal 1: Start the first project (starts daemon)cd ~/projects/engineering-orgorgloop start --daemon
# Terminal 2: Register a second project into the same daemoncd ~/projects/ops-orgorgloop start --daemon# → "Module 'ops-org' registered into running daemon."
# Check status — shows both modulesorgloop status
# Stop just the ops-org module (daemon continues)cd ~/projects/ops-orgorgloop stop
# Stop everythingorgloop shutdownModule state is tracked in ~/.orgloop/modules.json. This file maps module names to their source directories and config paths, enabling CLI commands to associate the current working directory with the correct module.
Relationship to Internal Architecture
Section titled “Relationship to Internal Architecture”Internally, the runtime uses ModuleInstance and ModuleRegistry classes to manage workload lifecycle. The CLI loads each project config via runtime.loadModule(). Each project becomes a ModuleInstance with its own resources, lifecycle state, and event routing scope. Modules can be loaded, unloaded, and reloaded independently without affecting other modules.
The CLI’s daemon-client.ts provides HTTP client functions for communicating with the running daemon, and module-registry.ts manages the persistent module tracking in ~/.orgloop/modules.json.
For the runtime architecture, see Runtime Lifecycle. For CLI commands, see CLI Design.