Get ClosedLoopOS running locally and execute your first SOP in under 15 minutes.
Looking for the full walkthrough? The Getting Started guide covers everything here plus backend setup, troubleshooting, and a
clos demopreview.
Prerequisites: Node.js ≥ 22 (node --version), a terminal, and access to the internal Nextcloud sync of the ClosedLoopOS repo.
Important — CLI binaries are NOT synced to Nextcloud. The
dist/folder (compiled CLI binaries) is in.gitignoreand must be built locally after cloning/syncing. Runpnpm --filter @closedloopos/cli buildbefore usingclosfor the first time, or you will see module/shebang errors from stale.jsfiles. This takes ~30 seconds afterpnpm install.
Path note: The repo path
~/ClosedLoopOSappears throughout this guide. Replace it with wherever you synced the repo (e.g./home/you/Nextcloud/notes/03 Projects/ClosedLoopOS). Or create a symlink:ln -s /path/to/your/sync/ClosedLoopOS ~/ClosedLoopOS.
No pnpm installed? Enable it via corepack (Node.js 16+):
corepack enable && corepack prepare pnpm@latest --activate. Or use the Nix devshell:nix develop /path/to/ClosedLoopOS --command bash -c "pnpm <cmd>".
Build from source. Pick the path that matches your environment:
Internal access required. The ClosedLoopOS repo is private and maintained in Nextcloud. Sync it locally first:
nextcloudcmd ~/ClosedLoopOS https://your-nextcloud-instance/remote.php/dav/files/<username>/ClosedLoopOSOr open the Nextcloud web UI, navigate to the ClosedLoopOS folder, and click "Sync."
cd ~/ClosedLoopOS
nix develop . --command bash -c "
pnpm install &&
pnpm --filter @closedloopos/cli build &&
mkdir -p ~/.local/bin &&
ln -sf ../node_modules/@closedloopos/cli/dist/cli.js ~/.local/bin/clos &&
export PATH=\"$HOME/.local/bin:$PATH\"
"
# Add to ~/.bashrc or ~/.zshrc to persist PATH:
# echo 'export PATH=\"$HOME/.local/bin:$PATH\"' >> ~/.bashrc
Internal access required. The ClosedLoopOS repo is private and maintained in Nextcloud. Sync it locally first:
nextcloudcmd ~/ClosedLoopOS https://your-nextcloud-instance/remote.php/dav/files/<username>/ClosedLoopOS
cd ~/ClosedLoopOS
pnpm install
pnpm --filter @closedloopos/cli build
mkdir -p ~/.local/bin
ln -sf ../node_modules/@closedloopos/cli/dist/cli.js ~/.local/bin/clos
export PATH="$HOME/.local/bin:$PATH" # add to ~/.bashrc or ~/.zshrc
Verify:
clos --version
# 0.14.0
Expected time: ~2–4 minutes (first build); ~30 seconds for incremental rebuilds
PATH not set? If
clos --versionsayscommand not foundeven after the steps above, your shell doesn't have~/.local/binin its PATH. Add this line to your shell config and restart your shell:echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc # Or for zsh: echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc && source ~/.zshrc
clos --versionfails with module errors orcommand not found? The CLIdist/is stale (not synced to Nextcloud). Rebuild it:pnpm --filter @closedloopos/cli buildThen re-run
clos --version. This is required after every fresh clone/sync.
clos: command not found(PATH issue)? Make sure~/.local/binis in your PATH:export PATH="$HOME/.local/bin:$PATH" # Add that line to ~/.bashrc or ~/.zshrc to make it permanent
Pre-commit error when using
nix develop? If you seeFatalError: git failedwhen runningclos initfrom a directory that is not a Git repository — this is harmless. The command succeeds anyway. The error comes from the nix devshell's pre-commit hook trying to install into a non-git directory.
mkdir my-closedloopOS-project && cd my-closedloopOS-project
clos init
Note:
clos init --dir <path>supports both relative and absolute paths. For absolute paths, the project is created directly at the specified location regardless of cwd. See CLO-1369 for the fix details.
Expected output:
Initialized ClosedLoopOS project: my-closedloopOS-project
docs/sops/example-sop.yaml — starter SOP
.clos.config.json — project config
README.md
Next steps:
clos sop list — list your SOPs
clos sop validate --all — validate SOPs
clos sop run example-sop --dry-run — preview a run (no backend needed)
Note: The project name matches your directory name (e.g.
my-closedloopOS-project).
Your project contains:
my-closedloopOS-project/
├── docs/
│ └── sops/
│ └── example-sop.yaml # starter SOP — customise this
├── .clos.config.json # project config
└── README.md
clos sop list
Expected output:
info: Running from local YAML. Set CLOSEDLOOP_API_URL to use the SOP Registry.
SOPs (1 found in docs/sops)
ID Name Version Tags
────────────────────────────────────────
example-sop Example SOP 0.1.0 example
Note: The
info: Running from local YAML...line is normal, not an error — it means the CLI is reading SOPs from your localdocs/sops/directory. To list SOPs from a live server registry instead, setCLOSEDLOOP_COMPANY_ID(without it, the CLI stays in local mode regardless ofCLOSEDLOOP_API_URL). WhenCLOSEDLOOP_COMPANY_IDis set but the server does not implement the SOP Registry API (e.g., Thin Server),clos sop listautomatically falls back to local YAML mode with a 404 warning — this is expected and harmless.
Use --dry-run to validate your SOP and see what it would do — no API connection required:
clos sop run example-sop --dry-run
Expected output:
Dry run: Example SOP (example-sop v0.1.0)
Steps:
1. [ceo] Log message
What just happened? The SOP runner parsed
example-sop.yaml, resolved all inputs (using defaults for anything not supplied), and printed the execution plan. In a live run, each step is dispatched to a real AI agent — the runner orchestrates their work automatically.Note: The starter template is intentionally minimal (1 step). The repo source at
docs/sops/example-sop.yamlcontains a richer 3-step example demonstrating inputs, DAG dependencies, and outputs. Open that file directly to see the full version.
Open docs/sops/example-sop.yaml in your editor. It contains a minimal starter SOP:
schema_version: "1"
sop:
id: example-sop
version: 0.1.0
name: Example SOP
description: A starter SOP — replace with your own workflow.
tags: [example]
triggers:
- type: manual
inputs:
- name: message
type: string
description: A message to include in the run
required: false
default: Hello, ClosedLoopOS!
steps:
- id: log-message
name: Log message
agent:
role: ceo
action: |
Log the following message and mark this step done: {{ inputs.message }}
The starter SOP in your new project is intentionally minimal (1 step). The Nextcloud sync of the ClosedLoopOS repo at ~/ClosedLoopOS/docs/sops/example-sop.yaml contains a richer 3-step example demonstrating inputs, DAG dependencies, step outputs, and on_complete hooks. Open that file directly as a reference when writing your own SOPs.
What is
role: ceo? Agent roles are how a SOP addresses the right AI worker.ceois the built-in default. When you connect your ClosedLoopOS project to a running ClosedLoopOS instance, the step will be dispatched to whichever agent holds that role.
The SOP library ships with four ready-to-use templates. Scaffold one into your project's docs/sops/ directory:
clos sop init --template customer-onboarding docs/sops/my-sop.yaml
Important: always pass the output path as
docs/sops/<your-sop-id>.yaml. The CLI writes to whatever path you supply; omitting the directory prefix creates the file in your current working directory, whereclos sop validateandclos sop runwill not find it.
Available templates: api-agent, customer-onboarding, incident-response, weekly-team-review.
Validate and preview the new SOP:
clos sop validate my-sop
clos sop run my-sop --dry-run
Dry runs validate your SOPs without executing them. To execute real agent steps you need a running ClosedLoopOS instance.
ClosedLoopOS supports two backends:
| Backend | Port | Notes | |---------|------|-------| | Thin Server (recommended for local dev) | 3100 | Zero-config, SQLite, no external deps. Runs as Next.js API routes. | | Full Paperclip API | 3100 | Requires the full Paperclip platform deployment. |
Thin Server setup (skip if using the Paperclip API at port 3100):
# In one terminal — start the Thin Server (Next.js dev server)
cd ~/ClosedLoopOS
pnpm dev
# In another terminal — bootstrap the database (one-time)
curl -X POST http://localhost:3100/api/setup
# Save the companyId and agentId from the response
# Expected response: {"companyId":"...","agentId":"..."}
# Verify the server is running:
curl http://localhost:3100/health
# Expected: {"status":"ok","version":"0.1.0"}
# Point the CLI at the Thin Server
export CLOSEDLOOP_API_URL=http://localhost:3100
export CLOSEDLOOP_COMPANY_ID=<companyId-from-setup>
export CLOSEDLOOP_API_KEY=<agentId-from-setup>
# Important: The Thin Server does not implement the SOP Registry API.
# With all three env vars set, `clos sop list` calls the registry and gets a 404.
# For listing local SOPs (recommended), unset the two registry vars first:
# unset CLOSEDLOOP_COMPANY_ID CLOSEDLOOP_API_KEY
# clos sop list # lists docs/sops/*.yaml
# Then restore them before running against the server:
# export CLOSEDLOOP_COMPANY_ID=<companyId-from-setup>
# export CLOSEDLOOP_API_KEY=<agentId-from-setup>
# clos sop run docs/sops/example-sop.yaml # works with all three vars set
# ⚠️ Thin Server role note: The bootstrapped agent has role=engineer, NOT ceo.
# The `example-sop.yaml` scaffold uses role=ceo in every step and on_complete.
# Thin Server has no ceo agent by default — you must map the ceo role to your
# bootstrapped agent ID (see "Map agent roles" step below). Without this mapping,
# live SOP runs will fail to dispatch steps.
If using the full Paperclip API instead of the Thin Server, set:
export CLOSEDLOOP_API_URL=http://localhost:3100
export CLOSEDLOOP_COMPANY_ID=<your-company-id>
export CLOSEDLOOP_API_KEY=<your-api-key>
Where to find your credentials: Log into the dashboard at
http://localhost:3100, go to Settings, and copy the Company ID and API Key.Important: Use a user API key (from Settings → API Keys), not an agent JWT. Agent tokens lack the
tasks:assignpermission needed to dispatch SOP steps to agents.
Thin Server users:
clos agent-roles initwill fail with a 404 (the Thin Server doesn't implement that endpoint). Useclos agent-roles set ceo <agent-id-from-setup>instead. See the manual assignment below.
Before running a SOP against a live backend, the CLI needs to know which agent ID corresponds to each role name in your SOPs.
For the full Paperclip API (port 3100):
clos agent-roles init
For the Thin Server (port 3101): clos agent-roles init requires GET /api/companies/:id/agents which the Thin Server does not implement — it will fail with a 404. Use manual assignment instead:
clos agent-roles set ceo <agent-id-from-setup>
# example: clos agent-roles set ceo 00000000-0000-0000-0000-000000000002
Note: If you don't know your agent ID, run
curl -s -H "Authorization: Bearer <your-api-key>" http://localhost:3101/api/agents/me— theidfield in the response is your agent ID.
This writes a .clos/agent-roles.json mapping file in the project root. Re-run when your team roster changes.
Run clos doctor to verify your setup is correct before executing any SOPs:
clos doctor
This validates:
CLOSEDLOOP_API_URL, CLOSEDLOOP_COMPANY_ID, and CLOSEDLOOP_API_KEY are set.clos/agent-roles.json exists and is valid JSONThin Server:
clos doctorconnectivity check usesGET /api/agents/mewhich works on both backends. If the API connectivity check fails but env vars and.clos/agent-roles.jsonare valid, double-check yourCLOSEDLOOP_API_KEYmatches theagentIdfrom the/api/setupresponse. Do NOT runclos agent-roles initon Thin Server — that endpoint does not exist. Useclos agent-roles set ceo <agent-id>to create the roles file manually.
Fix any issues reported by clos doctor before running SOPs.
clos sop run dispatches each SOP step as a ClosedLoopOS issue, assigned to the matching agent role, and waits for them to complete:
clos sop run docs/sops/example-sop.yaml
Check the status of the run from the CLI:
clos sop status <instance-id>
Where <instance-id> is the run ID printed after "Run:" in the output of clos sop run (e.g. 14137AD7).
Thin Server:
clos run statusis not supported on the Thin Server (port 3101). Always useclos sop status <instance-id>— it reads the local run record from.clos/sop-runs/<instance-id>.jsonand works on both backends. Add--watchto stream step completions:clos sop status 14137AD7 --watch
SOP Registry (
clos sop push,clos run list,clos sop run <id>by registry ID) requires server-side SOP Registry endpoints. Useclos sop run <path>with a local file path if your backend does not yet support the registry.
| What you want to do | Where to go | |---|---| | Run the Thin Server locally | Thin Server | | Browse ready-to-use SOP templates | SOP Library | | Write a multi-step SOP | SOP Authoring Guide | | Full SOP YAML reference | SOP Schema | | Full CLI reference | CLI Reference | | TypeScript SDK | SDK Reference | | Self-host ClosedLoopOS | Self-hosting Guide | | Stuck? Common errors & fixes | Troubleshooting · Error Catalog | | Common questions | FAQ | | Multiple projects | Multi-Project Guide | | Configure agent hats | Hat Management |