Skip to content

Five Primitives

Five primitives describe your entire organization’s event topology. Define them in YAML, and OrgLoop handles the rest.

PrimitiveRole
SourcesEmit events from external systems
ActorsDo work when woken by events
RoutesWire sources to actors with filters and context
TransformsFilter, enrich, or drop events in the pipeline
LoggersObserve every event, transform, and delivery

Things that emit events. A GitHub repo, a Linear project, a Claude Code session, a cron schedule, a webhook endpoint — anything that changes state.

Sources use outbound polling by default. OrgLoop reaches out to external systems on a schedule. This means zero inbound attack surface, no webhook secrets to manage, and it works behind NAT and firewalls. Hook-based sources (like Claude Code) receive events via a local HTTP endpoint.

sources:
- id: github
connector: "@orgloop/connector-github"
config:
repo: "${GITHUB_REPO}"
token: "${GITHUB_TOKEN}"
poll:
interval: 5m
- id: linear
connector: "@orgloop/connector-linear"
config:
team: "${LINEAR_TEAM_KEY}"
api_key: "${LINEAR_API_KEY}"
poll:
interval: 5m
- id: claude-code
connector: "@orgloop/connector-claude-code"
config:
hook_type: post-exit

Secrets never live in YAML. Use ${VAR_NAME} for environment variable substitution.

Things that do work when woken. An OpenClaw agent, a webhook endpoint, a Slack channel — any system that can receive an event and act on it.

Actors receive events with optional launch prompts — focused, situational SOPs that tell the actor exactly how to approach a specific event type. Same actor, different prompts per route. The routing layer decides which SOP is relevant. The actor does not have to figure it out.

actors:
- id: engineering
connector: "@orgloop/connector-openclaw"
config:
agent_id: engineering
auth_token_env: "${OPENCLAW_WEBHOOK_TOKEN}"

Declarative wiring. When source X emits event Y, wake actor Z with context C. Pure routing logic, no business logic.

Routes support multi-route matching — a single event can match multiple routes and wake multiple actors. Routes are allow-lists: actors only see events their routes explicitly match. There is no broadcast bus.

routes:
- name: pr-review
when:
source: github
events: [resource.changed]
filter:
provenance.platform_event:
- pull_request.review_submitted
- pull_request_review_comment
transforms:
- ref: drop-bot-noise
- ref: injection-scanner
then:
actor: engineering
with:
prompt_file: "./sops/pr-review.md"
- name: ci-failure
when:
source: github
events: [resource.changed]
filter:
provenance.platform_event: workflow_run.completed
then:
actor: engineering
with:
prompt_file: "./sops/ci-failure.md"

The with.prompt_file field points to a launch prompt — a Markdown file with a focused SOP for this specific event type.

Optional pipeline steps between source and actor. Filter noise, scan for prompt injection, deduplicate, rate-limit. Transforms are mechanical — actors handle reasoning, transforms handle plumbing.

Two implementation types:

  • Package transforms — TypeScript implementations of the Transform interface (e.g., @orgloop/transform-filter)
  • Script transforms — shell scripts that read JSON from stdin, write modified JSON to stdout (exit code 0 = pass, exit code 78 = drop)
transforms:
- name: drop-bot-noise
type: package
package: "@orgloop/transform-filter"
config:
exclude:
provenance.author_type: bot
- name: dedup
type: package
package: "@orgloop/transform-dedup"
config:
window: 1h

Transforms run sequentially — order matters. A transform can pass the event through (optionally modified), or drop it entirely. The pipeline is fail-open by default: if a transform errors, the event passes through.

Passive observers. Every event, every transform result, every delivery is captured for debugging and audit. Loggers are first-class primitives, not optional add-ons.

loggers:
- name: file-log
type: "@orgloop/logger-file"
config:
path: ~/.orgloop/logs/orgloop.log
format: jsonl
rotation:
max_size: 100MB
- name: console-log
type: "@orgloop/logger-console"
config:
level: info

Two built-in loggers:

  • Console — ANSI colors, phase icons, level filtering
  • File — buffered JSONL, rotation by size/age/count, optional gzip compression

The defining feature: the org loops. When an actor finishes work, that completion is itself an event (actor.stopped), routed back into the system to trigger the next actor.

Source.poll() --> EventBus --> matchRoutes() --> Transforms --> Actor.deliver()
^ |
+------------ actor.stopped -------------------+

A Claude Code session finishes at 3am. That fires actor.stopped. A route matches it and wakes a supervisor agent. The supervisor evaluates the output, decides to relaunch for QA. That completion fires another actor.stopped. The organization sustains itself through continuous cycles of events triggering actors triggering events.

All five primitives in one config:

orgloop.yaml
sources:
- id: github
connector: "@orgloop/connector-github"
config:
repo: "${GITHUB_REPO}"
token: "${GITHUB_TOKEN}"
poll:
interval: 5m
- id: claude-code
connector: "@orgloop/connector-claude-code"
config:
hook_type: post-exit
actors:
- id: engineering
connector: "@orgloop/connector-openclaw"
config:
agent_id: engineering
auth_token_env: "${OPENCLAW_WEBHOOK_TOKEN}"
routes:
- name: pr-review
when:
source: github
events: [resource.changed]
transforms:
- ref: drop-bot-noise
then:
actor: engineering
with:
prompt_file: "./sops/pr-review.md"
- name: session-supervision
when:
source: claude-code
events: [actor.stopped]
then:
actor: engineering
with:
prompt_file: "./sops/evaluate-session.md"
transforms:
- name: drop-bot-noise
type: package
package: "@orgloop/transform-filter"
config:
exclude:
provenance.author_type: bot
loggers:
- name: file-log
type: "@orgloop/logger-file"
config:
path: ~/.orgloop/logs/orgloop.log
format: jsonl

See the Event Taxonomy for details on the three event types, or the Command Reference for how to validate, plan, and start this config.