diff --git a/doc/plans/agent-authentication.md b/doc/plans/agent-authentication.md new file mode 100644 index 00000000..3e0527ce --- /dev/null +++ b/doc/plans/agent-authentication.md @@ -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? diff --git a/skills/paperclip/SKILL.md b/skills/paperclip/SKILL.md index 5c64f934..99aa74e3 100644 --- a/skills/paperclip/SKILL.md +++ b/skills/paperclip/SKILL.md @@ -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` diff --git a/skills/paperclip/references/api-reference.md b/skills/paperclip/references/api-reference.md new file mode 100644 index 00000000..4a010f79 --- /dev/null +++ b/skills/paperclip/references/api-reference.md @@ -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` |