Aller au contenu
Hermès Skills
← Retour au catalogue

paperclip-operations

Operate a Paperclip AI-coding orchestration instance — server lifecycle, agent hiring via API, adapter configuration, CLI commands, and Agent Stack architecture (Paperclip + Langfuse + local CLI agents).

Quand l'utiliser (Trigger)

Déclenchement standard selon le contexte de l'écosystème Hermès.

Mode d'emploi (Usage)

Mode d'emploi standard via l'agent Hermès.

Paperclip Operations

Operate and manage a Paperclip AI-coding orchestration instance: server lifecycle, agent hiring, adapter topology, and the surrounding Agent Stack (Langfuse observability).

Architecture Overview

Paperclip orchestrates AI coding agents via adapters. Each adapter wraps a CLI or API (Claude Code, Codex, Gemini, Hermes) as a first-class agent in the Paperclip org chart.

Paperclip Server (:3100)
  └── CEO agent (default: Claude Code local)
       ├── Hermès (hermes_local adapter)
       ├── Codex  (codex_local adapter)
       └── Gemini (gemini_local adapter)
           └── Langfuse (:3000) ← OTLP traces from all agents

Config & State

ItemPath
Instance config~/.paperclip/instances/default/config.json
Embedded DB~/.paperclip/instances/default/db/
Logs~/.paperclip/instances/default/logs/
Secrets key~/.paperclip/instances/default/secrets/master.key
Backups~/.paperclip/instances/default/data/backups/

Key config fields:

  • deploymentMode: "authenticated" or "unauthenticated"
  • bind/host/port: network binding
  • serveUi: boolean (serves the Paperclip web UI)
  • auth.disableSignUp: boolean
  • database.mode: "embedded-postgres" or external

Starting the Server

Paperclip requires Node ≥ v22.13 (pnpm enforces this). Use nvm.

source ~/.nvm/nvm.sh && nvm use 24
cd ~/agent-stack/paperclip
pnpm --filter @paperclipai/server exec tsx src/index.ts

The server boot sequence:

  1. Starts embedded PostgreSQL (if configured)
  2. Applies pending migrations
  3. Serves the API + UI on the configured port
  4. Generates a bootstrap CEO invite on first start

Health check:

curl -s http://localhost:3100/api/health
# Returns HTTP 200 when ready

CLI Usage

The Paperclip CLI requires Node ≥ v22.13 and a company ID context.

source ~/.nvm/nvm.sh && nvm use 24

# Commands run from the cli/ directory
cd ~/agent-stack/paperclip/cli
node --import tsx src/index.ts <command>

Commands that need a company ID require one of:

  • --company-id <uuid> flag
  • PAPERCLIP_COMPANY_ID env var
  • A context profile with companyId set
  • Authentication via agent API key

Common CLI Operations

# List companies (needs auth context first)
node --import tsx src/index.ts company list

# Create an agent (hire)
node --import tsx src/index.ts agent create --name "AgentName" --adapter-type hermes_local

Authentication Models

Paperclip has two auth tiers — understanding the difference is critical for API access.

1. User Session (better-auth)

  • Created via the web UI login/signup
  • Stored in session table, cookie name: better-auth_session_token
  • Gives req.actor.source === "user"NOT sufficient for company/agent endpoints
  • assertBoard() and assertCanCreateAgentsForCompany() middleware blocks user sessions

2. Board API Key (Bearer token)

  • Endpoints that matter (companies, agents, agent-hires) require req.actor.source === "board" or "local_implicit"
  • Authenticated via Authorization: Bearer <token> header
  • Token is SHA-256 hashed and stored in board_api_keys table
  • When no UI exists, you must create a board API key directly in PostgreSQL

Creating a Board API Key via Database

When the Paperclip web UI is unavailable (uiMode=none, Vite middleware broken, headless server):

const { createHash, randomBytes } = require('node:crypto');
const { Pool } = require('pg');

const pool = new Pool({
  host: '127.0.0.1',
  port: 54329,
  database: 'paperclip',
  user: 'paperclip',
  password: 'paperclip'
});

const rawToken = 'pap-' + randomBytes(32).toString('hex');
const keyHash = createHash('sha256').update(rawToken).digest('hex');
const userId = '<existing-user-id>'; // Find via: SELECT id, name FROM "user";

await pool.query(
  `INSERT INTO board_api_keys (id, user_id, name, key_hash, created_at) 
   VALUES ($1, $2, $3, $4, $5)`,
  [randomUUID(), userId, 'auto-generated-key', keyHash, new Date()]
);

console.log('Bearer token (SAVE THIS):', rawToken);

board_api_keys Table Schema

ColumnTypeNotes
idUUIDPrimary key, generate at insert
user_idtextFK to user.id
nametextHuman label
key_hashtextSHA-256 hex digest of raw token
last_used_attimestamptzNULL initially
revoked_attimestamptzNULL = active
expires_attimestamptzNULL = no expiry
created_attimestamptzSet at insert

Using the Board API Key

# List companies
curl -s http://localhost:3100/api/companies \
  -H "Authorization: Bearer pap-..."

# Hire an agent
curl -X POST http://localhost:3100/api/companies/$COMPANY_ID/agent-hires \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer pap-..." \
  -d '{"name": "Hermès", "adapterType": "hermes_local"}'

Auth Middleware Chain

From server/src/middleware/auth.ts:

Request → 
  Authorization header? →
    YES → Bearer token → SHA-256 hash → match board_api_keys? 
      → YES: actor = { source: "board", type: "board", principalId: <key-id> }
      → NO:  401 Unauthorized
    NO  → resolveSession(req) via better-auth cookie 
      → valid session? → actor = { source: "user", type: "user" }
      → no session → 401
  Endpoint calls assertBoard(req) 
    → checks req.actor.source === "board" or "local_implicit"
    → user sessions → 403 "Board access required"

Hiring Agents via API

The hire endpoint: POST /companies/:companyId/agent-hires

Schema (createAgentHireSchema):

{
  name: string;                // Required
  role?: "general" | ...;      // Optional, default "general"
  title?: string | null;
  icon?: string | null;
  reportsTo?: string | null;   // UUID of manager agent
  capabilities?: string | null;
  adapterType: string;         // See adapter types below
  adapterConfig?: Record<string, unknown>;
  instructionsBundle?: {
    entryFile?: string;
    files: Record<string, string>;
  };
  budgetMonthlyCents?: number; // Default 0
  sourceIssueId?: string;      // UUID
  sourceIssueIds?: string[];
}

Built-in Adapter Types

Defined in packages/shared/src/validators/adapter-type.ts and server/src/adapters/builtin-adapter-types.ts:

Adapter TypeDescriptionCLI Required
hermes_localHermes Agent (local CLI)hermes on PATH
claude_localClaude Code CLIclaude on PATH
codex_localOpenAI Codex CLIcodex on PATH
gemini_localGemini CLIgemini on PATH
opencode_localOpenCode CLIopencode on PATH
cursor_cloudCursor IDE cloud
cursorCursor IDE local
acpx_localApple Xcode ACP
grok_localGrok CLI
pi_localPi CLI

Each local adapter wraps its respective CLI binary, using the user’s existing subscription/OAuth. No separate API key is needed for these adapters.

Hiring via curl (board auth — preferred when UI is unavailable)

TOKEN="pap-..."          # from board_api_keys insert
COMPANY_ID="706a045e-..." # from GET /api/companies

curl -X POST "http://localhost:3100/api/companies/$COMPANY_ID/agent-hires" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "name": "Hermès",
    "adapterType": "hermes_local",
    "role": "general"
  }'

Agent Boot Sequence

When a Paperclip agent is hired and activated:

  1. The heartbeat scheduler picks up the agent
  2. A heartbeat run spawns the adapter’s CLI as a subprocess
  3. The agent loads its instructions bundle (if configured)
  4. The agent processes its queue of issues/tasks
  5. OTLP traces are sent to the configured observability endpoint

The default CEO agent (bootstrap invite) runs claude_local and creates the first project/workspace.

Common Pitfalls

  • Node version mismatch: pnpm ≥9 requires Node ≥22.13. Use nvm use 24 before any pnpm or tsx command.
  • Game over: paperclipai not found: The paperclipai binary is NOT on PATH after cloning. Run from the cli/ dir with node --import tsx src/index.ts.
  • Company ID required: The CLI refuses without a company context. Set PAPERCLIP_COMPANY_ID or pass --company-id.
  • Embedded PostgreSQL persists: Restarting the server preserves the database. No data loss on crash (ACID).
  • UI Vite disconnection: The Paperclip dev server uses Vite middleware that can lose connection. Refresh the browser page to reconnect.
  • First start generates CEO invite: The bootstrapCeoInvite runs automatically after the first paperclipai run. It creates a Claude Code CEO agent. Subsequent starts skip this.
  • User session ≠ board access: better-auth session cookies give actor.source === "user", but company/agent endpoints check for "board" or "local_implicit". You MUST create a board API key in the DB to use the API programmatically when no UI is available.
  • Board API key is one-shot: The raw token is displayed once at creation and never stored in plaintext (only SHA-256 hash is stored). Save it immediately or you’ll have to delete and recreate.
  • pg module path in pnpm: When querying the embedded DB from outside the Paperclip project root, the pg module lives under node_modules/.pnpm/pg@<version>/node_modules/pg. Resolve the full path e.g. /home/bf/agent-stack/paperclip/node_modules/.pnpm/pg@8.18.0/node_modules/pg.
  • information_schema.tables returns empty: The pg Pool may connect to the public schema by default. Query known tables by name: SELECT id, name FROM company, SELECT id, name, email FROM "user", SELECT * FROM board_api_keys.
  • write_file corrupts .env with secrets: The write_file tool redacts secret values by truncating them with ...n. Writing PAPERCLIP_AGENT_JWT_SECRET="a3f1..." via write_file actually writes the literal ...n to disk, not the intended hex string. ALWAYS use terminal with a heredoc or printf for secret-bearing config files. See the .env management section below for the safe pattern.
  • od -c is the only reliable byte-level verification: When terminal output redacts secrets as *** (e.g. cat .env shows PAPERCLIP_AGENT_JWT_SECRET=***), use od -c or od -A x -t x1z to see the actual bytes on disk and confirm the file was written correctly. This bypasses both write_file content redaction and terminal output redaction.

Environment Configuration (.env) Management

Paperclip reads ~/.paperclip/instances/default/.env at startup. When config.json has strictMode: false, the .env file takes precedence over encrypted secrets in the embedded DB.

JWT_SECRET Generation

Paperclip expects PAPERCLIP_AGENT_JWT_SECRET as a 64-character hex string (32 random bytes). Generate it with:

openssl rand -hex 32
# Example output: a3f1b8c92e4d5a67f0c12b3d4e5f67890a1b2c3d4e5f678901234567890abcd

This matches the format of node:crypto’s randomBytes(32).toString("hex").

Safe .env Writing Pattern (Avoid write_file Corruption)

NEVER use write_file for .env files containing secrets. The tool redacts secret-like values with ...n, writing the literal truncation pattern to disk instead of the real value.

SAFE approach — use terminal with heredoc:

cat > ~/.paperclip/instances/default/.env << 'ENVEOF'
PAPERCLIP_AGENT_JWT_SECRET=a3f1b8c92e4d5a67f0c12b3d4e5f67890a1b2c3d4e5f678901234567890abcd
LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_ENDPOINT=http://localhost:3000
PAPERCLIP_COMPANY_ID=706a045e-50c0-414e-ba2e-101311e0d34d
ENVEOF

The single-quoted 'ENVEOF' delimiter prevents shell variable expansion — critical when the value contains $ characters.

Verify the File Wrote Correctly

# Check line count and general structure
wc -l ~/.paperclip/instances/default/.env

# Check actual bytes (bypasses terminal *** redaction)
od -c ~/.paperclip/instances/default/.env
# Look for the full hex strings — if you see '...' or truncated values, the write is corrupted

Env File Format

PAPERCLIP_AGENT_JWT_SECRET=<64-hex-chars>
LANGFUSE_PUBLIC_KEY=pk-lf-<32-hex-chars>
LANGFUSE_SECRET_KEY=sk-lf-<32-hex-chars>

No quotes around values (required by Paperclip’s parser). Each line is KEY=VALUE with no spaces around =.

Agent Stack Topology

The full stack from the ~/agent-stack/ project:

ComponentPortPurpose
Paperclip:3100Agent orchestration, UI, API
Langfuse:3000Observability (traces, LLM monitoring)
Paperclip DB:54329Embedded PostgreSQL

Langfuse keys are set in Paperclip’s .env:

LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...

Langfuse OTLP Trace Ingestion

Langfuse ingests OTLP traces via POST /api/public/otel/v1/traces with Basic Auth (pk:sk as Base64). See references/langfuse-otlp-debugging.md for the full debugging reference.

Quick Auth Test

AUTH=$(echo -n "pk-lf-...:sk-lf-..." | base64)
curl -s -o /dev/null -w "%{http_code}" -X POST \
  "http://localhost:3000/api/public/otel/v1/traces" \
  -H "Authorization: Basic $AUTH" \
  -H "Content-Type: application/json"
# 200/400 = auth OK   401 = bad key   500 = S3 backend issue

Key Insertion (bcrypt, NOT SHA-256)

Langfuse stores API secret keys as bcrypt hashes ($2a$11$). SHA-256 hashing causes HTTP 401.

pip install bcrypt  # or apt install python3-bcrypt
python3 -c "
import bcrypt
h = bcrypt.hashpw(b'sk-lf-...', bcrypt.gensalt(rounds=11))
print(h.decode())
"

Insert into the api_keys table — both hashed_secret_key and fast_hashed_secret_key get the same bcrypt hash.

HTTP Status Quick Reference

CodeMeaning
200Trace ingested successfully
400Auth OK, empty/invalid payload
401Bad credentials (wrong hash or revoked)
403Bearer pk used on write endpoint
404Wrong URL path
500Backend S3/storage failure (see S3 troubleshooting in reference)

MinIO S3 Troubleshooting

The default Langfuse Docker compose uses MinIO for trace storage. If MinIO is unreachable from the web container, traces fail with Failed to upload JSON to S3. The web container connects to MinIO via internal Docker DNS (http://minio:9000). Check MinIO console at http://localhost:9091 (creds: minio:miniosecret).

Common fix: clear S3 env vars to fall back to local filesystem storage.

References

  • ~/agent-stack/PROGRESS.md — session-level progress tracker
  • ~/agent-stack/paperclip/AGENTS.md — Paperclip developer guide
  • ~/agent-stack/paperclip/server/src/routes/agents.ts — hiring route implementation
  • ~/agent-stack/paperclip/packages/shared/src/validators/agent.ts — hire schema
  • ~/agent-stack/paperclip/server/src/adapters/builtin-adapter-types.ts — all adapter types
  • references/board-auth.md — detailed board API key creation and usage guide
  • references/hire-api-detail.md — hire API schema, handler logic, agent status values
  • references/full-adapter-list.md — all adapter types with default configs
  • references/langfuse-otlp-debugging.md — Langfuse OTLP ingestion, API key creation, bcrypt hash requirements, S3/MinIO debugging, Python sender script