paperclip-ai-orchestration

Skill for using Paperclip — open-source orchestration platform for running autonomous AI-agent companies with org charts, budgets, governance, and heartbeats.

INSTALLATION
npx skills add https://github.com/aradotso/trending-skills --skill paperclip-ai-orchestration
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

Paperclip AI Orchestration

Skill by ara.so — Daily 2026 Skills collection.

Paperclip is an open-source Node.js + React platform that runs a company made of AI agents. It provides org charts, goal alignment, ticket-based task management, budget enforcement, heartbeat scheduling, governance, and a full audit log — so you manage business outcomes instead of individual agent sessions.

Installation

Quickstart (recommended)

npx paperclipai onboard --yes

This clones the repo, installs dependencies, seeds an embedded PostgreSQL database, and starts the server.

Manual setup

git clone https://github.com/paperclipai/paperclip.git

cd paperclip

pnpm install

pnpm dev

Requirements:

  • Node.js 20+
  • pnpm 9.15+

The API server starts at http://localhost:3100. An embedded PostgreSQL database is created automatically — no manual DB setup needed for local development.

Production setup

Point Paperclip at an external Postgres instance and object storage via environment variables:

# .env

DATABASE_URL=postgresql://user:password@host:5432/paperclip

STORAGE_BUCKET=your-s3-bucket

STORAGE_REGION=us-east-1

AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID

AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY

PORT=3100

Key CLI Commands

pnpm dev              # Start API + UI in development mode

pnpm build            # Build for production

pnpm start            # Start production server

pnpm db:migrate       # Run pending database migrations

pnpm db:seed          # Seed demo data

pnpm test             # Run test suite

npx paperclipai onboard --yes   # Full automated onboarding

Core Concepts

Concept

Description

Company

Top-level namespace. All agents, goals, tasks, and budgets are scoped to a company.

Agent

An AI worker (OpenClaw, Claude Code, Codex, Cursor, HTTP bot, Bash script).

Goal

Hierarchical business objective. Tasks inherit goal ancestry so agents know the "why".

Task / Ticket

A unit of work assigned to an agent. Conversations and tool calls are threaded to it.

Heartbeat

A cron-style schedule that wakes an agent to check for work or perform recurring tasks.

Org Chart

Hierarchical reporting structure. Agents have managers, direct reports, roles, and titles.

Budget

Monthly token/cost cap per agent. Atomic enforcement — agent stops when budget exhausted.

Governance

Approval gates for hires, strategy changes, and config rollbacks. You are the board.

REST API

The Paperclip API is served at http://localhost:3100/api/v1.

Authentication

// All requests require a bearer token

const headers = {

  'Authorization': `Bearer ${process.env.PAPERCLIP_API_KEY}`,

  'Content-Type': 'application/json',

};

Create a Company

const response = await fetch('http://localhost:3100/api/v1/companies', {

  method: 'POST',

  headers,

  body: JSON.stringify({

    name: 'NoteGenius Inc.',

    mission: 'Build the #1 AI note-taking app to $1M MRR.',

    slug: 'notegenius',

  }),

});

const { company } = await response.json();

console.log(company.id); // "cmp_abc123"

Register an Agent

const agent = await fetch(`http://localhost:3100/api/v1/companies/${companyId}/agents`, {

  method: 'POST',

  headers,

  body: JSON.stringify({

    name: 'Alice',

    role: 'CTO',

    runtime: 'claude-code',       // 'openclaw' | 'claude-code' | 'codex' | 'cursor' | 'bash' | 'http'

    endpoint: process.env.ALICE_AGENT_ENDPOINT,

    budget: {

      monthly_usd: 200,

    },

    heartbeat: {

      cron: '0 * * * *',          // every hour

      enabled: true,

    },

    reports_to: ceoAgentId,       // parent in org chart

  }),

}).then(r => r.json());

Create a Goal

const goal = await fetch(`http://localhost:3100/api/v1/companies/${companyId}/goals`, {

  method: 'POST',

  headers,

  body: JSON.stringify({

    title: 'Launch v1 to Product Hunt',

    description: 'Ship the MVP and generate 500 upvotes on launch day.',

    parent_goal_id: null,         // null = top-level goal

    owner_agent_id: ctoAgentId,

    due_date: '2026-06-01',

  }),

}).then(r => r.json());

Create a Task

const task = await fetch(`http://localhost:3100/api/v1/companies/${companyId}/tasks`, {

  method: 'POST',

  headers,

  body: JSON.stringify({

    title: 'Implement offline sync for notes',

    description: 'Use CRDTs to merge note edits made offline. See ADR-004.',

    assigned_to: engineerAgentId,

    goal_id: goal.id,              // links task to goal ancestry

    priority: 'high',

  }),

}).then(r => r.json());

console.log(task.id); // "tsk_xyz789"

List Tasks for an Agent

const { tasks } = await fetch(

  `http://localhost:3100/api/v1/agents/${agentId}/tasks?status=open`,

  { headers }

).then(r => r.json());

Post a Message to a Task Thread

await fetch(`http://localhost:3100/api/v1/tasks/${taskId}/messages`, {

  method: 'POST',

  headers,

  body: JSON.stringify({

    role: 'agent',

    content: 'Implemented CRDT merge logic. Tests passing. Ready for review.',

    tool_calls: [

      {

        tool: 'bash',

        input: 'pnpm test --filter=sync',

        output: '42 tests passed in 3.1s',

      },

    ],

  }),

});

Report Agent Cost

Agents self-report token usage; Paperclip enforces budget atomically:

await fetch(`http://localhost:3100/api/v1/agents/${agentId}/cost`, {

  method: 'POST',

  headers,

  body: JSON.stringify({

    tokens_in: 12400,

    tokens_out: 3800,

    model: 'claude-opus-4-5',

    task_id: taskId,

  }),

});

Heartbeat Ping

Agents call this endpoint on each scheduled wake-up:

const { instructions, tasks } = await fetch(

  `http://localhost:3100/api/v1/agents/${agentId}/heartbeat`,

  { method: 'POST', headers }

).then(r => r.json());

// instructions — what the org says to focus on now

// tasks        — open tasks assigned to this agent

TypeScript SDK Pattern

Wrap the REST API for cleaner agent integration:

// lib/paperclip-client.ts

export class PaperclipClient {

  private base: string;

  private headers: Record<string, string>;

  constructor(

    base = process.env.PAPERCLIP_BASE_URL ?? 'http://localhost:3100',

    apiKey = process.env.PAPERCLIP_API_KEY ?? '',

  ) {

    this.base = `${base}/api/v1`;

    this.headers = {

      Authorization: `Bearer ${apiKey}`,

      'Content-Type': 'application/json',

    };

  }

  private async req<T>(path: string, init?: RequestInit): Promise<T> {

    const res = await fetch(`${this.base}${path}`, {

      ...init,

      headers: { ...this.headers, ...init?.headers },

    });

    if (!res.ok) {

      const body = await res.text();

      throw new Error(`Paperclip API ${res.status}: ${body}`);

    }

    return res.json() as Promise<T>;

  }

  heartbeat(agentId: string) {

    return this.req<{ instructions: string; tasks: Task[] }>(

      `/agents/${agentId}/heartbeat`,

      { method: 'POST' },

    );

  }

  completeTask(taskId: string, summary: string) {

    return this.req(`/tasks/${taskId}`, {

      method: 'PATCH',

      body: JSON.stringify({ status: 'done', completion_summary: summary }),

    });

  }

  reportCost(agentId: string, payload: CostPayload) {

    return this.req(`/agents/${agentId}/cost`, {

      method: 'POST',

      body: JSON.stringify(payload),

    });

  }

}

Building an Agent That Works With Paperclip

A minimal agent loop that integrates with Paperclip:

// agent.ts

import { PaperclipClient } from './lib/paperclip-client';

const client = new PaperclipClient();

const AGENT_ID = process.env.PAPERCLIP_AGENT_ID!;

async function runHeartbeat() {

  console.log('[agent] heartbeat ping');

  const { instructions, tasks } = await client.heartbeat(AGENT_ID);

  for (const task of tasks) {

    console.log(`[agent] working on task: ${task.title}`);

    try {

      // --- your agent logic here ---

      const result = await doWork(task, instructions);

      await client.completeTask(task.id, result.summary);

      await client.reportCost(AGENT_ID, {

        tokens_in: result.tokensIn,

        tokens_out: result.tokensOut,

        model: result.model,

        task_id: task.id,

      });

      console.log(`[agent] task ${task.id} done`);

    } catch (err) {

      console.error(`[agent] task ${task.id} failed`, err);

      // Paperclip will reassign or escalate based on governance rules

    }

  }

}

// Heartbeat is usually driven by Paperclip's cron, but you can also self-poll:

setInterval(runHeartbeat, 60_000);

runHeartbeat();

Registering an HTTP Agent (any language)

Any process reachable over HTTP can be an agent. Paperclip sends a POST to your endpoint:

// Paperclip calls POST /work on your agent with this shape:

interface PaperclipWorkPayload {

  agent_id: string;

  task: {

    id: string;

    title: string;

    description: string;

    goal_ancestry: string[];   // full chain: company mission → goal → sub-goal

  };

  instructions: string;        // current org-level directives

  context: Record<string, unknown>;

}

Respond with:

interface PaperclipWorkResponse {

  status: 'done' | 'blocked' | 'delegated';

  summary: string;

  tokens_in?: number;

  tokens_out?: number;

  model?: string;

  delegate_to?: string;        // agent_id if status === 'delegated'

}

Multi-Company Setup

// Create isolated companies in one deployment

const companies = await Promise.all([

  createCompany({ name: 'NoteGenius', mission: 'Best note app' }),

  createCompany({ name: 'ShipFast', mission: 'Fastest deploy tool' }),

]);

// Each company has its own agents, goals, tasks, budgets, and audit log

// No data leaks between companies

Governance &#x26; Approvals

// Fetch pending approval requests (you are the board)

const { approvals } = await fetch(

  `http://localhost:3100/api/v1/companies/${companyId}/approvals?status=pending`,

  { headers }

).then(r => r.json());

// Approve a hire

await fetch(`http://localhost:3100/api/v1/approvals/${approvals[0].id}`, {

  method: 'PATCH',

  headers,

  body: JSON.stringify({ decision: 'approved', note: 'Looks good.' }),

});

// Roll back a bad config change

await fetch(`http://localhost:3100/api/v1/agents/${agentId}/config/rollback`, {

  method: 'POST',

  headers,

  body: JSON.stringify({ revision: 3 }),

});

Environment Variables Reference

# Required

PAPERCLIP_API_KEY=                  # Your API key for the Paperclip server

# Database (defaults to embedded Postgres in dev)

DATABASE_URL=                        # postgresql://user:pass@host:5432/db

# Storage (defaults to local filesystem in dev)

STORAGE_DRIVER=local                 # 'local' | 's3'

STORAGE_BUCKET=                      # S3 bucket name

STORAGE_REGION=                      # AWS region

AWS_ACCESS_KEY_ID=                   # From your environment

AWS_SECRET_ACCESS_KEY=               # From your environment

# Server

PORT=3100

BASE_URL=http://localhost:3100

# Agent-side (used inside agent processes)

PAPERCLIP_BASE_URL=http://localhost:3100

PAPERCLIP_AGENT_ID=                  # The agent's UUID from Paperclip

Common Patterns

Pattern: Manager delegates to reports

// In your manager agent's heartbeat handler:

const { tasks } = await client.heartbeat(MANAGER_AGENT_ID);

for (const task of tasks) {

  if (task.complexity === 'high') {

    // Delegate down the org chart

    await fetch(`http://localhost:3100/api/v1/tasks/${task.id}/delegate`, {

      method: 'POST',

      headers,

      body: JSON.stringify({ to_agent_id: engineerAgentId }),

    });

  }

}

Pattern: @-mention an agent in a task thread

await fetch(`http://localhost:3100/api/v1/tasks/${taskId}/messages`, {

  method: 'POST',

  headers,

  body: JSON.stringify({

    role: 'human',

    content: `@${designerAgentId} Can you review the UI for this feature?`,

  }),

});

// Paperclip delivers the mention as a trigger to the designer agent's next heartbeat

Pattern: Export a company template (Clipmart)

const blob = await fetch(

  `http://localhost:3100/api/v1/companies/${companyId}/export`,

  { headers }

).then(r => r.blob());

// Saves a .paperclip bundle with secrets scrubbed

fs.writeFileSync('my-saas-company.paperclip', Buffer.from(await blob.arrayBuffer()));

Pattern: Import a company template

const form = new FormData();

form.append('file', fs.createReadStream('my-saas-company.paperclip'));

await fetch('http://localhost:3100/api/v1/companies/import', {

  method: 'POST',

  headers: { Authorization: `Bearer ${process.env.PAPERCLIP_API_KEY}` },

  body: form,

});

Troubleshooting

Problem

Fix

ECONNREFUSED localhost:3100

Server not running. Run pnpm dev first.

401 Unauthorized

Check PAPERCLIP_API_KEY is set and matches server config.

Agent never wakes up

Verify heartbeat.enabled: true and cron expression is valid. Check server logs for scheduler errors.

Budget exhausted immediately

monthly_usd budget too low or tokens_in/tokens_out are being over-reported. Check POST /agents/:id/cost payloads.

Task stuck in open

Agent may be offline or heartbeat misconfigured. Check /api/v1/agents/:id/status.

Database migration errors

Run pnpm db:migrate after pulling new commits.

Embedded Postgres won't start

Port 5433 may be in use. Set EMBEDDED_PG_PORT=5434 in .env.

Org chart not resolving

reports_to agent ID must exist before creating the subordinate. Create top-down.

Resources

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card