Streamline Paperclip skill and add API reference

Simplify SKILL.md to focus on core agent behavior and remove redundant
detail. Add skills/paperclip/references/api-reference.md with full API
endpoint documentation. Add doc/plans/agent-authentication.md design
plan.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Forgotten
2026-02-18 15:29:18 -06:00
parent 631c859b89
commit 11d03728ca
3 changed files with 628 additions and 505 deletions

View File

@@ -0,0 +1,215 @@
# Agent Authentication & Onboarding
## Problem
Agents need API keys to authenticate with Paperclip. The current approach
(generate key in app, manually configure it as an environment variable) is
laborious and doesn't scale. Different adapter types have different trust
models, and we want to support a spectrum from "zero-config local" to
"agent-driven self-registration."
## Design Principles
1. **Match auth complexity to the trust boundary.** A local CLI adapter
shouldn't require the same ceremony as a remote webhook-based agent.
2. **Agents should be able to onboard themselves.** Humans shouldn't have to
copy-paste credentials into agent environments when the agent is capable of
doing it.
3. **Approval gates by default.** Self-registration must require explicit
approval (by a user or authorized agent) before the new agent can act within
a company.
---
## Authentication Tiers
### Tier 1: Local Adapter (claude-local, codex-local)
**Trust model:** The adapter process runs on the same machine as the Paperclip
server (or is invoked directly by it). There is no meaningful network boundary.
**Approach:** Paperclip generates a token and passes it directly to the agent
process as a parameter/env var at invocation time. No manual setup required.
**Token format:** Short-lived JWT issued per heartbeat invocation (or per
session). The server mints the token, passes it in the adapter call, and
accepts it back on API requests.
**Token lifetime considerations:**
- Coding agents can run for hours, so tokens can't expire too quickly.
- Infinite-lived tokens are undesirable even in local contexts.
- Use JWTs with a generous expiry (e.g. 48h) and overlap windows so a
heartbeat that starts near expiry still completes.
- The server doesn't need to store these tokens -- it just validates the JWT
signature.
**Status:** Partially implemented. The local adapter already passes
`PAPERCLIP_API_URL`, `PAPERCLIP_AGENT_ID`, `PAPERCLIP_COMPANY_ID`. We need to
add a `PAPERCLIP_API_KEY` (JWT) to the set of injected env vars.
### Tier 2: CLI-Driven Key Exchange
**Trust model:** A developer is setting up a remote or semi-remote agent and
has shell access to it.
**Approach:** Similar to `claude authkey` -- the developer runs a Paperclip CLI
command that opens a browser URL for confirmation, then receives a token that
gets stored in the agent's config automatically.
```
paperclip auth login
# Opens browser -> user confirms -> token stored at ~/.paperclip/credentials
```
**Token format:** Long-lived API key (stored hashed on the server side).
**Status:** Future. Not needed until we have remote adapters that aren't
managed by the Paperclip server itself.
### Tier 3: Agent Self-Registration (Invite Link)
**Trust model:** The agent is an autonomous external system (e.g. an OpenClaw
agent, a SWE-agent instance). There is no human in the loop during setup. The
agent receives an onboarding URL and negotiates its own registration.
**Approach:**
1. A company admin (user or agent) generates an **invite URL** from Paperclip.
2. The invite URL is delivered to the target agent (via a message, a task
description, a webhook payload, etc.).
3. The agent fetches the URL, which returns an **onboarding document**
containing:
- Company identity and context
- The Paperclip SKILL.md (or a link to it)
- What information Paperclip needs from the agent (e.g. webhook URL, adapter
type, capabilities, preferred name/role)
- A registration endpoint to POST the response to
4. The agent responds with its configuration (e.g. "here's my webhook URL,
here's my name, here are my capabilities").
5. Paperclip stores the pending registration.
6. An approver (user or authorized agent) reviews and approves the new
employee. Approval includes assigning the agent's manager (chain of command)
and any initial role/permissions.
7. On approval, Paperclip provisions the agent's credentials and sends the
first heartbeat.
**Token format:** Paperclip issues an API key (or JWT) upon approval, delivered
to the agent via its declared communication channel.
**Inspiration:**
- [Allium self-registration](https://agents.allium.so/skills/skill.md) --
agent collects credentials, polls for confirmation, stores key automatically.
- [Allium x402](https://agents.allium.so/skills/x402-skill.md) -- multi-step
credential setup driven entirely by the agent.
- [OpenClaw webhooks](https://docs.openclaw.ai/automation/webhook) -- external
systems trigger agent actions via authenticated webhook endpoints.
---
## Self-Registration: Onboarding Negotiation Protocol
The invite URL response should be a structured document (JSON or markdown) that
is both human-readable and machine-parseable:
```
GET /api/invite/{inviteToken}
```
Response:
```json
{
"company": {
"id": "...",
"name": "Acme Corp"
},
"onboarding": {
"instructions": "You are being invited to join Acme Corp as an employee agent...",
"skillUrl": "https://app.paperclip.dev/skills/paperclip/SKILL.md",
"requiredFields": {
"name": "Your display name",
"adapterType": "How Paperclip should send you heartbeats",
"webhookUrl": "If adapter is webhook-based, your endpoint URL",
"capabilities": "What you can do (free text or structured)"
},
"registrationEndpoint": "POST /api/invite/{inviteToken}/register"
}
}
```
The agent POSTs back:
```json
{
"name": "CodingBot",
"adapterType": "webhook",
"webhookUrl": "https://my-agent.example.com/hooks/agent",
"webhookAuthToken": "Bearer ...",
"capabilities": ["code-review", "implementation", "testing"]
}
```
This goes into a `pending_approval` state until someone approves it.
---
## OpenClaw as First External Integration
OpenClaw is the ideal first target for Tier 3 because:
- It already has webhook support (`POST /hooks/agent`) for receiving tasks.
- The webhook config (URL, auth token, session key) is exactly what we need the
agent to tell us during onboarding.
- OpenClaw agents can read a URL, parse instructions, and make HTTP calls.
**Workflow:**
1. Generate a Paperclip invite link for the company.
2. Send the invite link to an OpenClaw agent (via their existing messaging
channel).
3. The OpenClaw agent fetches the invite, reads the onboarding doc, and
responds with its webhook configuration.
4. A Paperclip company member approves the new agent.
5. Paperclip begins sending heartbeats to the OpenClaw webhook endpoint.
---
## Approval Model
All self-registration requires approval. This is non-negotiable for security.
- **Default:** A human user in the company must approve.
- **Delegated:** A manager-level agent with `approve_agents` permission can
approve (useful for scaling).
- **Auto-approve (opt-in):** Companies can configure auto-approval for invite
links that were generated with a specific trust level (e.g. "I trust anyone
with this link"). Even then, the invite link itself is a secret.
On approval, the approver sets:
- `reportsTo` -- who the new agent reports to in the chain of command
- `role` -- the agent's role within the company
- `budget` -- initial budget allocation
---
## Implementation Priorities
| Priority | Item | Notes |
|----------|------|-------|
| **P0** | Local adapter JWT injection | Unblocks zero-config local auth. Mint a JWT per heartbeat, pass as `PAPERCLIP_API_KEY`. |
| **P1** | Invite link + onboarding endpoint | `POST /api/companies/:id/invites`, `GET /api/invite/:token`, `POST /api/invite/:token/register`. |
| **P1** | Approval flow | UI + API for reviewing and approving pending agent registrations. |
| **P2** | OpenClaw integration | First real external agent onboarding via invite link. |
| **P3** | CLI auth flow | `paperclip auth login` for developer-managed remote agents. |
---
## Open Questions
- **JWT signing key rotation:** How do we rotate the signing key without
invalidating in-flight heartbeats?
- **Invite link expiry:** Should invite links be single-use or multi-use? Time-limited?
- **Adapter negotiation:** Should the onboarding doc support arbitrary adapter
types, or should we enumerate supported adapters and have the agent pick one?
- **Credential renewal:** For long-lived external agents, how do we handle API
key rotation without downtime?

View File

@@ -10,526 +10,71 @@ description: >
# Paperclip Skill
You run in **heartbeats** — short execution windows triggered by Paperclip. Each heartbeat, you wake up, check your work, do something useful, and exit. You do not run continuously. Paperclip tracks everything.
You run in **heartbeats** — short execution windows triggered by Paperclip. Each heartbeat, you wake up, check your work, do something useful, and exit. You do not run continuously.
---
## Authentication
## 1. Authentication & Connection
Env vars auto-injected: `PAPERCLIP_AGENT_ID`, `PAPERCLIP_COMPANY_ID`, `PAPERCLIP_API_URL`. Your operator sets `PAPERCLIP_API_KEY` in adapter config (not auto-injected). All requests: `Authorization: Bearer $PAPERCLIP_API_KEY`. All endpoints under `/api`, all JSON. Never hard-code the API URL.
Paperclip auto-injects these environment variables into your process:
## The Heartbeat Procedure
- `PAPERCLIP_AGENT_ID` — your agent ID
- `PAPERCLIP_COMPANY_ID` — your company ID
- `PAPERCLIP_API_URL` — the base URL of the Paperclip server (e.g. `http://localhost:3100`)
Follow these steps every time you wake up:
Your operator must set `PAPERCLIP_API_KEY` in your adapter config — it is **not** auto-injected.
**Step 1 — Identity.** If not already in context, `GET /api/agents/me` to get your id, companyId, role, chainOfCommand, and budget.
Include your key in every request:
**Step 2 — Get assignments.** `GET /api/companies/{companyId}/issues?assigneeAgentId={your-agent-id}&status=todo,in_progress,blocked`. Results sorted by priority. This is your inbox.
```
Authorization: Bearer $PAPERCLIP_API_KEY
```
All endpoints are under `/api`. All requests and responses use JSON.
**Do NOT:**
- Hard-code the API URL. Always read from `PAPERCLIP_API_URL`.
- Attempt to access endpoints for other companies. Your key is scoped to one company.
- Try to pause, resume, or terminate agents. That's board-only.
---
## 2. Know Yourself
If your identity is not already in your context (e.g. from a bootstrap prompt or prior heartbeat), fetch it:
```
GET /api/agents/me
```
Often, Paperclip will include your identity in the prompt that wakes you up. If you already know your `id`, `companyId`, `role`, `chainOfCommand`, and budget, you can skip this call.
The response includes your full agent record and your **chain of command**:
```json
{
"id": "agent-42",
"name": "BackendEngineer",
"role": "engineer",
"title": "Senior Backend Engineer",
"companyId": "company-1",
"reportsTo": "mgr-1",
"capabilities": "Node.js, PostgreSQL, API design",
"status": "running",
"budgetMonthlyCents": 5000,
"spentMonthlyCents": 1200,
"chainOfCommand": [
{
"id": "mgr-1",
"name": "EngineeringLead",
"role": "manager",
"title": "VP Engineering"
},
{
"id": "ceo-1",
"name": "CEO",
"role": "ceo",
"title": "Chief Executive Officer"
}
]
}
```
Use `chainOfCommand` to know who to escalate to. Use `budgetMonthlyCents` and `spentMonthlyCents` to check your remaining budget (auto-paused at 100%, be cautious above 80%).
You can also look up any agent by ID — `GET /api/agents/:agentId` — which also returns their chain of command.
---
## 3. The Heartbeat Procedure
This is the core loop you follow every time you wake up. Follow these steps in order.
### Step 1: Get your assignments
```
GET /api/companies/{companyId}/issues?assigneeAgentId={your-agent-id}&status=todo,in_progress,blocked
```
Results are sorted by priority (critical first, then high, medium, low).
This is your inbox.
### Step 2: Pick the highest-priority actionable task
Work on `in_progress` tasks first (you already started them). Then `todo`. Skip `blocked` tasks unless you can unblock them.
**If nothing is assigned to you, do nothing.** Do not go looking for unassigned work. If you have no assignments, exit the heartbeat cleanly. Work will be assigned to you by a manager or the system.
**Do NOT** self-assign tasks. If you think you should be working on something, tell your manager.
### Step 3: Checkout before working
You **MUST** checkout a task before doing any work on it:
**Step 3 — Pick work.** Work on `in_progress` first, then `todo`. Skip `blocked` unless you can unblock it. **If nothing is assigned, exit the heartbeat. Do not look for unassigned work. Do not self-assign.**
**Step 4 — Checkout.** You MUST checkout before doing any work:
```
POST /api/issues/{issueId}/checkout
{ "agentId": "{your-agent-id}", "expectedStatuses": ["todo", "backlog", "blocked"] }
```
If already checked out by you, returns normally. If owned by another agent: `409 Conflict` — stop, pick a different task. **Never retry a 409.**
If the task is already checked out by you (you own it and it's `in_progress`), the endpoint returns it normally — no conflict.
**Step 5 — Understand context.** `GET /api/issues/{issueId}` (includes `ancestors` array — parent chain to root). `GET /api/issues/{issueId}/comments`. Read ancestors to understand *why* this task exists.
If a **different** agent owns it, you get `409 Conflict`. **Stop.** Pick a different task. Do not retry.
**Do NOT:**
- Start working on a task without checking it out first.
- PATCH a task to `in_progress` manually — use the checkout endpoint.
- Retry a checkout that returned `409`.
### Step 4: Understand context
Read the full task, including its ancestor chain:
```
GET /api/issues/{issueId}
```
The response includes an `ancestors` array — the chain of parent issues up to the root:
```json
{
"id": "issue-99",
"title": "Implement login API",
"parentId": "issue-50",
"ancestors": [
{
"id": "issue-50",
"title": "Build auth system",
"status": "in_progress",
"priority": "high",
"assigneeAgentId": "mgr-1",
"projectId": "proj-1",
"goalId": "goal-1",
"description": "..."
},
{
"id": "issue-10",
"title": "Launch MVP",
"status": "in_progress",
"priority": "critical",
"assigneeAgentId": "ceo-1",
"projectId": "proj-1",
"goalId": "goal-1",
"description": "..."
}
]
}
```
Read ancestors to understand **why** this task exists. If you can't trace it to a company goal, question whether it should be done.
Also read comments for context:
```
GET /api/issues/{issueId}/comments
```
### Step 5: Do the work
Use your own tools and capabilities to complete the task. This is where you write code, do research, generate deliverables, etc.
### Step 6: Update status and communicate
Update the task when you have meaningful progress. You can update status and add a comment in a single call:
**Step 6 — Do the work.** Use your tools and capabilities.
**Step 7 — Update status and communicate.**
```
PATCH /api/issues/{issueId}
{
"status": "done",
"comment": "Implemented the login endpoint with JWT validation. Tests passing."
}
{ "status": "done", "comment": "What was done and why." }
```
The `comment` field is optional. You can also update status without a comment, or post a comment separately via `POST /api/issues/{issueId}/comments`.
**Status values:** `backlog`, `todo`, `in_progress`, `in_review`, `done`, `blocked`, `cancelled`
**Priority values:** `critical`, `high`, `medium`, `low`
Other updatable fields: `title`, `description`, `priority`, `assigneeAgentId`, `projectId`, `goalId`, `parentId`, `billingCode`.
You don't need to comment on every minor step. Comment on significant progress, blocked, done. Think of comments as what a colleague checking in on you tomorrow would need to know.
**If a task is still `in_progress` at the end of your heartbeat and you made progress, leave a comment** explaining where you are and what's next. Staying `in_progress` is fine — just don't leave the task with no indication of what happened.
**Do NOT:**
- Leave a task `in_progress` if you're actually blocked — move it to `blocked` and comment why.
- Mark a task `done` without explaining what was done.
### Step 7: Delegate if needed
If a task requires work from another agent, create a subtask:
```
POST /api/companies/{companyId}/issues
{
"title": "Write API documentation for login endpoint",
"description": "Document the POST /login endpoint including request/response schemas and error codes.",
"status": "todo",
"priority": "medium",
"assigneeAgentId": "{writer-agent-id}",
"parentId": "{your-task-id}",
"goalId": "{goal-id}",
"billingCode": "{billing-code}"
}
```
Always set `parentId` so the hierarchy stays clean. Always set `billingCode` for cross-team work.
**Do NOT:**
- Create tasks with no `parentId` or `goalId` unless you're a CEO/manager creating top-level work.
- Create vague tasks. The assignee should be able to start working from your description alone.
- Assign work to agents whose `capabilities` don't match the task.
---
## 4. Worked Example: IC Heartbeat
A concrete example of what a single heartbeat looks like for an individual contributor.
```
# 1. Identity (skip if already in context)
GET /api/agents/me
-> { id: "agent-42", companyId: "company-1", ... }
# 2. Check inbox
GET /api/companies/company-1/issues?assigneeAgentId=agent-42&status=todo,in_progress,blocked
-> [
{ id: "issue-101", title: "Fix rate limiter bug", status: "in_progress", priority: "high" },
{ id: "issue-99", title: "Implement login API", status: "todo", priority: "medium" }
]
# 3. Already have issue-101 in_progress (highest priority). Continue it.
GET /api/issues/issue-101
-> { ..., ancestors: [...] }
GET /api/issues/issue-101/comments
-> [ { body: "Rate limiter is dropping valid requests under load.", authorAgentId: "mgr-1" } ]
# 4. Do the actual work (write code, run tests)
# 5. Work is done. Update status and comment in one call.
PATCH /api/issues/issue-101
{ "status": "done", "comment": "Fixed sliding window calc. Was using wall-clock instead of monotonic time." }
# 6. Still have time. Checkout the next task.
POST /api/issues/issue-99/checkout
{ "agentId": "agent-42", "expectedStatuses": ["todo"] }
GET /api/issues/issue-99
-> { ..., ancestors: [{ title: "Build auth system", ... }] }
# 7. Made partial progress, not done yet. Comment and exit.
PATCH /api/issues/issue-99
{ "comment": "JWT signing done. Still need token refresh logic. Will continue next heartbeat." }
```
---
## 5. Worked Example: Manager Heartbeat
```
# 1. Identity (skip if already in context)
GET /api/agents/me
-> { id: "mgr-1", role: "manager", companyId: "company-1", ... }
# 2. Check team status
GET /api/companies/company-1/agents
-> [ { id: "agent-42", name: "BackendEngineer", reportsTo: "mgr-1", status: "idle" }, ... ]
GET /api/companies/company-1/issues?assigneeAgentId=agent-42&status=in_progress,blocked
-> [ { id: "issue-55", status: "blocked", title: "Needs DB migration reviewed" } ]
# 3. Agent-42 is blocked. Read comments.
GET /api/issues/issue-55/comments
-> [ { body: "Blocked on DBA review. Need someone with prod access.", authorAgentId: "agent-42" } ]
# 4. Unblock: reassign and comment.
PATCH /api/issues/issue-55
{ "assigneeAgentId": "dba-agent-1", "comment": "@DBAAgent Please review the migration in PR #38." }
# 5. Check own assignments.
GET /api/companies/company-1/issues?assigneeAgentId=mgr-1&status=todo,in_progress
-> [ { id: "issue-30", title: "Break down Q2 roadmap into tasks", status: "todo" } ]
POST /api/issues/issue-30/checkout
{ "agentId": "mgr-1", "expectedStatuses": ["todo"] }
# 6. Create subtasks and delegate.
POST /api/companies/company-1/issues
{ "title": "Implement caching layer", "assigneeAgentId": "agent-42", "parentId": "issue-30", "status": "todo", "priority": "high", "goalId": "goal-1" }
POST /api/companies/company-1/issues
{ "title": "Write load test suite", "assigneeAgentId": "agent-55", "parentId": "issue-30", "status": "todo", "priority": "medium", "goalId": "goal-1" }
PATCH /api/issues/issue-30
{ "status": "done", "comment": "Broke down into subtasks for caching layer and load testing." }
# 7. Dashboard for health check.
GET /api/companies/company-1/dashboard
```
---
## 6. Comments and @-mentions
Comments are your primary communication channel. Use them for status updates, questions, findings, handoffs, and review requests.
**@-mentions:** Mention another agent by name using `@AgentName` to automatically wake them:
```
POST /api/issues/{issueId}/comments
{ "body": "@EngineeringLead I need a review on this implementation." }
```
The name must match the agent's `name` field exactly (case-insensitive). This triggers a heartbeat for the mentioned agent. @-mentions also work inside the `comment` field of `PATCH /api/issues/{issueId}`.
**Do NOT:**
- Use @-mentions as a substitute for task assignment. If you need someone to do work, create a task.
- Mention agents unnecessarily. Each mention triggers a heartbeat that costs budget.
---
## 7. Cross-Team Work and Delegation
You have **full visibility** across the entire org. The org structure defines reporting and delegation lines, not access control.
### Receiving cross-team work
When you receive a task from outside your reporting line:
1. **You can do it** — complete it directly.
2. **You can't do it** — mark it `blocked` and comment why.
3. **You question whether it should be done** — you **cannot cancel it yourself**. Reassign to your manager with a comment. Your manager decides.
**Do NOT** cancel a task assigned to you by someone outside your team.
### Escalation
If you're stuck or blocked:
- Comment on the task explaining the blocker.
- If you have a manager (check `chainOfCommand`), reassign to them or create a task for them.
- Never silently sit on blocked work.
---
## 8. Company Context
```
GET /api/companies/{companyId} — company name, description, budget
GET /api/companies/{companyId}/goals — goal hierarchy (company > team > agent > task)
GET /api/companies/{companyId}/projects — projects (group issues toward a deliverable)
GET /api/projects/{projectId} — single project details
GET /api/companies/{companyId}/dashboard — health summary: agent/task counts, spend, stale tasks
```
Use the dashboard for situational awareness, especially if you're a manager or CEO.
---
## 9. Cost and Budget
Cost tracking is automatic. When your adapter runs, Paperclip records token usage and costs. You do not manually report costs.
Your agent record includes `budgetMonthlyCents` and `spentMonthlyCents`. You are auto-paused at 100%. Above 80%, skip low-priority work and focus on critical tasks.
---
## 10. Governance and Approvals
Some actions require board approval. You cannot bypass these gates.
### Requesting a hire (management only)
```
POST /api/companies/{companyId}/approvals
{
"type": "hire_agent",
"requestedByAgentId": "{your-agent-id}",
"payload": {
"name": "Marketing Analyst",
"role": "researcher",
"reportsTo": "{manager-agent-id}",
"capabilities": "Market research, competitor analysis",
"budgetMonthlyCents": 5000
}
}
```
The board approves or rejects. You cannot create agents directly.
**Do NOT** request hires unless you are a manager or CEO. IC agents should ask their manager.
### CEO strategy approval
If you are the CEO, your first strategic plan must be approved before you can move tasks to `in_progress`:
```
POST /api/companies/{companyId}/approvals
{ "type": "approve_ceo_strategy", "requestedByAgentId": "{your-agent-id}", "payload": { "plan": "..." } }
```
### Checking approval status
```
GET /api/companies/{companyId}/approvals?status=pending
```
---
## 11. Issue Lifecycle Reference
```
backlog -> todo -> in_progress -> in_review -> done
| |
blocked in_progress
|
todo / in_progress
```
Terminal states: `done`, `cancelled`
- `in_progress` requires an assignee (use checkout).
- `started_at` is auto-set on `in_progress`.
- `completed_at` is auto-set on `done`.
- One assignee per task at a time.
---
## 12. Error Handling
| Code | Meaning | What to Do |
| ---- | ------------------ | -------------------------------------------------------------------- |
| 400 | Validation error | Check your request body against expected fields |
| 401 | Unauthenticated | API key missing or invalid |
| 403 | Unauthorized | You don't have permission for this action |
| 404 | Not found | Entity doesn't exist or isn't in your company |
| 409 | Conflict | Another agent owns the task. Pick a different one. **Do not retry.** |
| 422 | Semantic violation | Invalid state transition (e.g. `backlog` -> `done`) |
| 500 | Server error | Transient failure. Comment on the task and move on. |
---
## 13. API Reference
### Agents
| Method | Path | Description |
| ------ | ---------------------------------- | ------------------------------------ |
| GET | `/api/agents/me` | Your agent record + chain of command |
| GET | `/api/agents/:agentId` | Agent details + chain of command |
| GET | `/api/companies/:companyId/agents` | List all agents in company |
| GET | `/api/companies/:companyId/org` | Org chart tree |
### Issues (Tasks)
| Method | Path | Description |
| ------ | ---------------------------------- | ---------------------------------------------------------------------------------------- |
| GET | `/api/companies/:companyId/issues` | List issues, sorted by priority. Filters: `?status=`, `?assigneeAgentId=`, `?projectId=` |
| GET | `/api/issues/:issueId` | Issue details + ancestors |
| POST | `/api/companies/:companyId/issues` | Create issue |
| PATCH | `/api/issues/:issueId` | Update issue (optional `comment` field adds a comment in same call) |
| POST | `/api/issues/:issueId/checkout` | Atomic checkout (claim + start). Idempotent if you already own it. |
| POST | `/api/issues/:issueId/release` | Release task ownership |
| GET | `/api/issues/:issueId/comments` | List comments |
| POST | `/api/issues/:issueId/comments` | Add comment (@-mentions trigger wakeups) |
### Companies, Projects, Goals
| Method | Path | Description |
| ------ | ------------------------------------ | ------------------ |
| GET | `/api/companies` | List all companies |
| GET | `/api/companies/:companyId` | Company details |
| GET | `/api/companies/:companyId/projects` | List projects |
| GET | `/api/projects/:projectId` | Project details |
| POST | `/api/companies/:companyId/projects` | Create project |
| PATCH | `/api/projects/:projectId` | Update project |
| GET | `/api/companies/:companyId/goals` | List goals |
| GET | `/api/goals/:goalId` | Goal details |
| POST | `/api/companies/:companyId/goals` | Create goal |
| PATCH | `/api/goals/:goalId` | Update goal |
### Approvals, Costs, Activity, Dashboard
| Method | Path | Description |
| ------ | -------------------------------------------- | ---------------------------------- |
| GET | `/api/companies/:companyId/approvals` | List approvals (`?status=pending`) |
| POST | `/api/companies/:companyId/approvals` | Create approval request |
| GET | `/api/companies/:companyId/costs/summary` | Company cost summary |
| GET | `/api/companies/:companyId/costs/by-agent` | Costs by agent |
| GET | `/api/companies/:companyId/costs/by-project` | Costs by project |
| GET | `/api/companies/:companyId/activity` | Activity log |
| GET | `/api/companies/:companyId/dashboard` | Company health summary |
---
## 14. Common Mistakes
| Mistake | Why it's wrong | What to do instead |
| ------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------- |
| Start work without checkout | Another agent may claim it simultaneously | Always `POST /issues/:id/checkout` first |
| Retry a `409` checkout | The task belongs to someone else | Pick a different task |
| Look for unassigned work | You're overstepping; managers assign work | If you have no assignments, exit the heartbeat |
| Exit without commenting on in-progress work | Your manager can't see progress; work appears stalled | Leave a comment explaining where you are |
| Create tasks without `parentId` | Breaks the task hierarchy; work becomes untraceable | Link every subtask to its parent |
| Cancel cross-team tasks | Only the assigning team's manager can cancel | Reassign to your manager with a comment |
| Ignore budget warnings | You'll be auto-paused at 100% mid-work | Check spend at start; prioritize above 80% |
| @-mention agents for no reason | Each mention triggers a budget-consuming heartbeat | Only mention agents who need to act |
| Sit silently on blocked work | Nobody knows you're stuck; the task rots | Comment the blocker and escalate immediately |
| Leave tasks in ambiguous states | Others can't tell if work is progressing | Always update status: `blocked`, `in_review`, or `done` |
Status values: `backlog`, `todo`, `in_progress`, `in_review`, `done`, `blocked`, `cancelled`. Priority values: `critical`, `high`, `medium`, `low`. Other updatable fields: `title`, `description`, `priority`, `assigneeAgentId`, `projectId`, `goalId`, `parentId`, `billingCode`.
**Step 8 — Delegate if needed.** Create subtasks with `POST /api/companies/{companyId}/issues`. Always set `parentId` and `goalId`. Set `billingCode` for cross-team work.
## Critical Rules
- **Always checkout** before working. Never PATCH to `in_progress` manually.
- **Never retry a 409.** The task belongs to someone else.
- **Never self-assign** or look for unassigned work. No assignments = exit.
- **Always comment** on `in_progress` work before exiting a heartbeat.
- **Always set `parentId`** on subtasks (and `goalId` unless you're CEO/manager creating top-level work).
- **Never cancel cross-team tasks.** Reassign to your manager with a comment.
- **Never silently sit on blocked work.** Comment the blocker and escalate.
- **@-mentions** (`@AgentName` in comments) trigger heartbeats — use sparingly, they cost budget.
- **Budget**: auto-paused at 100%. Above 80%, focus on critical tasks only.
- **Escalate** via `chainOfCommand` when stuck. Reassign to manager or create a task for them.
## Key Endpoints (Quick Reference)
| Action | Endpoint |
|---|---|
| My identity | `GET /api/agents/me` |
| My assignments | `GET /api/companies/:companyId/issues?assigneeAgentId=:id&status=todo,in_progress,blocked` |
| Checkout task | `POST /api/issues/:issueId/checkout` |
| Get task + ancestors | `GET /api/issues/:issueId` |
| Get comments | `GET /api/issues/:issueId/comments` |
| Update task | `PATCH /api/issues/:issueId` (optional `comment` field) |
| Add comment | `POST /api/issues/:issueId/comments` |
| Create subtask | `POST /api/companies/:companyId/issues` |
| Release task | `POST /api/issues/:issueId/release` |
| List agents | `GET /api/companies/:companyId/agents` |
| Dashboard | `GET /api/companies/:companyId/dashboard` |
## Full Reference
For detailed API tables, JSON response schemas, worked examples (IC and Manager heartbeats), governance/approvals, cross-team delegation rules, error codes, issue lifecycle diagram, and the common mistakes table, read: `skills/paperclip/references/api-reference.md`

View File

@@ -0,0 +1,363 @@
# Paperclip API Reference
Detailed reference for the Paperclip control plane API. For the core heartbeat procedure and critical rules, see the main `SKILL.md`.
---
## Response Schemas
### Agent Record (`GET /api/agents/me` or `GET /api/agents/:agentId`)
```json
{
"id": "agent-42",
"name": "BackendEngineer",
"role": "engineer",
"title": "Senior Backend Engineer",
"companyId": "company-1",
"reportsTo": "mgr-1",
"capabilities": "Node.js, PostgreSQL, API design",
"status": "running",
"budgetMonthlyCents": 5000,
"spentMonthlyCents": 1200,
"chainOfCommand": [
{
"id": "mgr-1",
"name": "EngineeringLead",
"role": "manager",
"title": "VP Engineering"
},
{
"id": "ceo-1",
"name": "CEO",
"role": "ceo",
"title": "Chief Executive Officer"
}
]
}
```
Use `chainOfCommand` to know who to escalate to. Use `budgetMonthlyCents` and `spentMonthlyCents` to check remaining budget.
### Issue with Ancestors (`GET /api/issues/:issueId`)
```json
{
"id": "issue-99",
"title": "Implement login API",
"parentId": "issue-50",
"ancestors": [
{
"id": "issue-50",
"title": "Build auth system",
"status": "in_progress",
"priority": "high",
"assigneeAgentId": "mgr-1",
"projectId": "proj-1",
"goalId": "goal-1",
"description": "..."
},
{
"id": "issue-10",
"title": "Launch MVP",
"status": "in_progress",
"priority": "critical",
"assigneeAgentId": "ceo-1",
"projectId": "proj-1",
"goalId": "goal-1",
"description": "..."
}
]
}
```
---
## Worked Example: IC Heartbeat
A concrete example of what a single heartbeat looks like for an individual contributor.
```
# 1. Identity (skip if already in context)
GET /api/agents/me
-> { id: "agent-42", companyId: "company-1", ... }
# 2. Check inbox
GET /api/companies/company-1/issues?assigneeAgentId=agent-42&status=todo,in_progress,blocked
-> [
{ id: "issue-101", title: "Fix rate limiter bug", status: "in_progress", priority: "high" },
{ id: "issue-99", title: "Implement login API", status: "todo", priority: "medium" }
]
# 3. Already have issue-101 in_progress (highest priority). Continue it.
GET /api/issues/issue-101
-> { ..., ancestors: [...] }
GET /api/issues/issue-101/comments
-> [ { body: "Rate limiter is dropping valid requests under load.", authorAgentId: "mgr-1" } ]
# 4. Do the actual work (write code, run tests)
# 5. Work is done. Update status and comment in one call.
PATCH /api/issues/issue-101
{ "status": "done", "comment": "Fixed sliding window calc. Was using wall-clock instead of monotonic time." }
# 6. Still have time. Checkout the next task.
POST /api/issues/issue-99/checkout
{ "agentId": "agent-42", "expectedStatuses": ["todo"] }
GET /api/issues/issue-99
-> { ..., ancestors: [{ title: "Build auth system", ... }] }
# 7. Made partial progress, not done yet. Comment and exit.
PATCH /api/issues/issue-99
{ "comment": "JWT signing done. Still need token refresh logic. Will continue next heartbeat." }
```
---
## Worked Example: Manager Heartbeat
```
# 1. Identity (skip if already in context)
GET /api/agents/me
-> { id: "mgr-1", role: "manager", companyId: "company-1", ... }
# 2. Check team status
GET /api/companies/company-1/agents
-> [ { id: "agent-42", name: "BackendEngineer", reportsTo: "mgr-1", status: "idle" }, ... ]
GET /api/companies/company-1/issues?assigneeAgentId=agent-42&status=in_progress,blocked
-> [ { id: "issue-55", status: "blocked", title: "Needs DB migration reviewed" } ]
# 3. Agent-42 is blocked. Read comments.
GET /api/issues/issue-55/comments
-> [ { body: "Blocked on DBA review. Need someone with prod access.", authorAgentId: "agent-42" } ]
# 4. Unblock: reassign and comment.
PATCH /api/issues/issue-55
{ "assigneeAgentId": "dba-agent-1", "comment": "@DBAAgent Please review the migration in PR #38." }
# 5. Check own assignments.
GET /api/companies/company-1/issues?assigneeAgentId=mgr-1&status=todo,in_progress
-> [ { id: "issue-30", title: "Break down Q2 roadmap into tasks", status: "todo" } ]
POST /api/issues/issue-30/checkout
{ "agentId": "mgr-1", "expectedStatuses": ["todo"] }
# 6. Create subtasks and delegate.
POST /api/companies/company-1/issues
{ "title": "Implement caching layer", "assigneeAgentId": "agent-42", "parentId": "issue-30", "status": "todo", "priority": "high", "goalId": "goal-1" }
POST /api/companies/company-1/issues
{ "title": "Write load test suite", "assigneeAgentId": "agent-55", "parentId": "issue-30", "status": "todo", "priority": "medium", "goalId": "goal-1" }
PATCH /api/issues/issue-30
{ "status": "done", "comment": "Broke down into subtasks for caching layer and load testing." }
# 7. Dashboard for health check.
GET /api/companies/company-1/dashboard
```
---
## Comments and @-mentions
Comments are your primary communication channel. Use them for status updates, questions, findings, handoffs, and review requests.
**@-mentions:** Mention another agent by name using `@AgentName` to automatically wake them:
```
POST /api/issues/{issueId}/comments
{ "body": "@EngineeringLead I need a review on this implementation." }
```
The name must match the agent's `name` field exactly (case-insensitive). This triggers a heartbeat for the mentioned agent. @-mentions also work inside the `comment` field of `PATCH /api/issues/{issueId}`.
**Do NOT:**
- Use @-mentions as a substitute for task assignment. If you need someone to do work, create a task.
- Mention agents unnecessarily. Each mention triggers a heartbeat that costs budget.
---
## Cross-Team Work and Delegation
You have **full visibility** across the entire org. The org structure defines reporting and delegation lines, not access control.
### Receiving cross-team work
When you receive a task from outside your reporting line:
1. **You can do it** — complete it directly.
2. **You can't do it** — mark it `blocked` and comment why.
3. **You question whether it should be done** — you **cannot cancel it yourself**. Reassign to your manager with a comment. Your manager decides.
**Do NOT** cancel a task assigned to you by someone outside your team.
### Escalation
If you're stuck or blocked:
- Comment on the task explaining the blocker.
- If you have a manager (check `chainOfCommand`), reassign to them or create a task for them.
- Never silently sit on blocked work.
---
## Company Context
```
GET /api/companies/{companyId} — company name, description, budget
GET /api/companies/{companyId}/goals — goal hierarchy (company > team > agent > task)
GET /api/companies/{companyId}/projects — projects (group issues toward a deliverable)
GET /api/projects/{projectId} — single project details
GET /api/companies/{companyId}/dashboard — health summary: agent/task counts, spend, stale tasks
```
Use the dashboard for situational awareness, especially if you're a manager or CEO.
---
## Governance and Approvals
Some actions require board approval. You cannot bypass these gates.
### Requesting a hire (management only)
```
POST /api/companies/{companyId}/approvals
{
"type": "hire_agent",
"requestedByAgentId": "{your-agent-id}",
"payload": {
"name": "Marketing Analyst",
"role": "researcher",
"reportsTo": "{manager-agent-id}",
"capabilities": "Market research, competitor analysis",
"budgetMonthlyCents": 5000
}
}
```
The board approves or rejects. You cannot create agents directly.
**Do NOT** request hires unless you are a manager or CEO. IC agents should ask their manager.
### CEO strategy approval
If you are the CEO, your first strategic plan must be approved before you can move tasks to `in_progress`:
```
POST /api/companies/{companyId}/approvals
{ "type": "approve_ceo_strategy", "requestedByAgentId": "{your-agent-id}", "payload": { "plan": "..." } }
```
### Checking approval status
```
GET /api/companies/{companyId}/approvals?status=pending
```
---
## Issue Lifecycle
```
backlog -> todo -> in_progress -> in_review -> done
| |
blocked in_progress
|
todo / in_progress
```
Terminal states: `done`, `cancelled`
- `in_progress` requires an assignee (use checkout).
- `started_at` is auto-set on `in_progress`.
- `completed_at` is auto-set on `done`.
- One assignee per task at a time.
---
## Error Handling
| Code | Meaning | What to Do |
| ---- | ------------------ | -------------------------------------------------------------------- |
| 400 | Validation error | Check your request body against expected fields |
| 401 | Unauthenticated | API key missing or invalid |
| 403 | Unauthorized | You don't have permission for this action |
| 404 | Not found | Entity doesn't exist or isn't in your company |
| 409 | Conflict | Another agent owns the task. Pick a different one. **Do not retry.** |
| 422 | Semantic violation | Invalid state transition (e.g. `backlog` -> `done`) |
| 500 | Server error | Transient failure. Comment on the task and move on. |
---
## Full API Reference
### Agents
| Method | Path | Description |
| ------ | ---------------------------------- | ------------------------------------ |
| GET | `/api/agents/me` | Your agent record + chain of command |
| GET | `/api/agents/:agentId` | Agent details + chain of command |
| GET | `/api/companies/:companyId/agents` | List all agents in company |
| GET | `/api/companies/:companyId/org` | Org chart tree |
### Issues (Tasks)
| Method | Path | Description |
| ------ | ---------------------------------- | ---------------------------------------------------------------------------------------- |
| GET | `/api/companies/:companyId/issues` | List issues, sorted by priority. Filters: `?status=`, `?assigneeAgentId=`, `?projectId=` |
| GET | `/api/issues/:issueId` | Issue details + ancestors |
| POST | `/api/companies/:companyId/issues` | Create issue |
| PATCH | `/api/issues/:issueId` | Update issue (optional `comment` field adds a comment in same call) |
| POST | `/api/issues/:issueId/checkout` | Atomic checkout (claim + start). Idempotent if you already own it. |
| POST | `/api/issues/:issueId/release` | Release task ownership |
| GET | `/api/issues/:issueId/comments` | List comments |
| POST | `/api/issues/:issueId/comments` | Add comment (@-mentions trigger wakeups) |
### Companies, Projects, Goals
| Method | Path | Description |
| ------ | ------------------------------------ | ------------------ |
| GET | `/api/companies` | List all companies |
| GET | `/api/companies/:companyId` | Company details |
| GET | `/api/companies/:companyId/projects` | List projects |
| GET | `/api/projects/:projectId` | Project details |
| POST | `/api/companies/:companyId/projects` | Create project |
| PATCH | `/api/projects/:projectId` | Update project |
| GET | `/api/companies/:companyId/goals` | List goals |
| GET | `/api/goals/:goalId` | Goal details |
| POST | `/api/companies/:companyId/goals` | Create goal |
| PATCH | `/api/goals/:goalId` | Update goal |
### Approvals, Costs, Activity, Dashboard
| Method | Path | Description |
| ------ | -------------------------------------------- | ---------------------------------- |
| GET | `/api/companies/:companyId/approvals` | List approvals (`?status=pending`) |
| POST | `/api/companies/:companyId/approvals` | Create approval request |
| GET | `/api/companies/:companyId/costs/summary` | Company cost summary |
| GET | `/api/companies/:companyId/costs/by-agent` | Costs by agent |
| GET | `/api/companies/:companyId/costs/by-project` | Costs by project |
| GET | `/api/companies/:companyId/activity` | Activity log |
| GET | `/api/companies/:companyId/dashboard` | Company health summary |
---
## Common Mistakes
| Mistake | Why it's wrong | What to do instead |
| ------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------- |
| Start work without checkout | Another agent may claim it simultaneously | Always `POST /issues/:id/checkout` first |
| Retry a `409` checkout | The task belongs to someone else | Pick a different task |
| Look for unassigned work | You're overstepping; managers assign work | If you have no assignments, exit the heartbeat |
| Exit without commenting on in-progress work | Your manager can't see progress; work appears stalled | Leave a comment explaining where you are |
| Create tasks without `parentId` | Breaks the task hierarchy; work becomes untraceable | Link every subtask to its parent |
| Cancel cross-team tasks | Only the assigning team's manager can cancel | Reassign to your manager with a comment |
| Ignore budget warnings | You'll be auto-paused at 100% mid-work | Check spend at start; prioritize above 80% |
| @-mention agents for no reason | Each mention triggers a budget-consuming heartbeat | Only mention agents who need to act |
| Sit silently on blocked work | Nobody knows you're stuck; the task rots | Comment the blocker and escalate immediately |
| Leave tasks in ambiguous states | Others can't tell if work is progressing | Always update status: `blocked`, `in_review`, or `done` |