From e4752d00924ffba3bcc4b91959923fa14e597416 Mon Sep 17 00:00:00 2001 From: Forgotten Date: Mon, 16 Feb 2026 19:07:30 -0600 Subject: [PATCH] Add product spec and MCP task interface docs SPEC.md defines the Paperclip control plane specification including company model, board governance, and agent architecture. doc/TASKS-mcp.md defines the MCP function contracts for task management. Co-Authored-By: Claude Opus 4.6 --- SPEC.md | 206 +++++++++++++++++ doc/TASKS-mcp.md | 567 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 773 insertions(+) create mode 100644 SPEC.md create mode 100644 doc/TASKS-mcp.md diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 00000000..aa31c2a2 --- /dev/null +++ b/SPEC.md @@ -0,0 +1,206 @@ +# Paperclip Specification + +Target specification for the Paperclip control plane. Living document — updated incrementally during spec interviews. + +--- + +## 1. Company Model [NEEDS DETAIL] + +A company is a first-order object. One Paperclip instance runs multiple companies. + +### Fields (Draft) + +| Field | Type | Notes | +| --- | --- | --- | +| `id` | uuid | Primary key | +| `name` | string | Company name | +| `goal` | text/markdown | The company's top-level objective | +| `createdAt` | timestamp | | +| `updatedAt` | timestamp | | + +### Board Governance [DRAFT] + +Every company has a **board** that governs high-impact decisions. The board is the human oversight layer. + +**V1: Single human board.** One human operator approves: +- New agent hires (creating new agents) +- [TBD: other governance-gated actions] + +**Future governance models** (not V1): +- Hiring budgets (auto-approve hires within $X/month) +- Multi-member boards +- Delegated authority (CEO can hire within limits) + +The board is the boundary between "the company runs autonomously" and "humans retain control." The default is conservative — human approval for structural changes. + +### Open Questions + +- Revenue/expense tracking — how does financial data enter the system? +- Company-level settings and configuration? +- Company lifecycle (pause, archive, delete)? +- What other actions require board approval beyond hiring? (budget changes, company goal changes, firing agents?) + +--- + +## 2. Agent Model [NEEDS DETAIL] + +Every employee is an agent. Agents are the workforce. + +### Agent Identity (Adapter-Level) + +Concepts like SOUL.md (identity/mission) and HEARTBEAT.md (loop definition) are **not part of the Paperclip protocol**. They are adapter-specific configurations. For example, an OpenClaw adapter might use SOUL.md and HEARTBEAT.md files. A Claude Code adapter might use CLAUDE.md. A bare Python script might use command-line args. + +Paperclip doesn't prescribe how an agent defines its identity or behavior. It provides the control plane; the adapter defines the agent's inner workings. + +### Agent Configuration [DRAFT] + +Each agent has an **adapter type** and an **adapter-specific configuration blob**. The adapter defines what config fields exist. + +#### Paperclip Protocol (What Paperclip Knows) + +At the protocol level, Paperclip tracks: + +- Agent identity (id, name, role, title) +- Org position (who they report to, who reports to them) +- Adapter type + adapter config +- Status (active, paused, terminated) +- Cost tracking data (if the agent reports it) + +#### Adapter Configuration (Agent-Specific) + +Each adapter type defines its own config schema. Examples: + +- **OpenClaw adapter**: SOUL.md content, HEARTBEAT.md content, OpenClaw-specific settings +- **Process adapter**: command to run, environment variables, working directory +- **HTTP adapter**: endpoint URL, auth headers, payload template + +#### Exportable Org Configs + +A key goal: **the entire org's agent configurations are exportable.** You can export a company's complete agent setup — every agent, their adapter configs, org structure — as a portable artifact. This enables: + +- Sharing company templates ("here's a pre-built marketing agency org") +- Version controlling your company configuration +- Duplicating/forking companies + +#### Context Delivery + +Configurable per agent. Two ends of the spectrum: + +- **Fat payload** — Paperclip bundles relevant context (current tasks, messages, company state, metrics) into the heartbeat invocation. Suited for simple/stateless agents that can't call back to Paperclip. +- **Thin ping** — Heartbeat is just a wake-up signal. Agent calls Paperclip's API to fetch whatever context it needs. Suited for sophisticated agents that manage their own state. + +#### Minimum Contract + +The minimum requirement to be a Paperclip agent: **be callable.** That's it. Paperclip can invoke you via command or webhook. No requirement to report back — Paperclip infers basic status from process liveness. + +#### Integration Levels + +Beyond the minimum, Paperclip provides progressively richer integration: + +1. **Callable** (minimum) — Paperclip can start you. That's the only contract. +2. **Status reporting** — Agent reports back success/failure/in-progress after execution. +3. **Fully instrumented** — Agent reports status, cost/token usage, task updates, and logs. Bidirectional integration with the control plane. + +Paperclip ships **default agents** that demonstrate full integration: progress tracking, cost instrumentation, and a **Paperclip skill** (a Claude Code skill for interacting with the Paperclip API) for task management. These serve as both useful defaults and reference implementations for adapter authors. + +### Open Questions + +- What is the adapter interface? What must an adapter implement? +- How does an agent authenticate to the control plane? +- Agent lifecycle (create, pause, terminate, restart)? +- What does the Paperclip skill provide? (task CRUD, status updates, reading company context?) +- Export format for org configs — JSON? YAML? Directory of files? + +--- + +## 3. Org Structure [NEEDS DETAIL] + +Hierarchical reporting structure. CEO at top, reports cascade down. + +### Open Questions + +- Is this a strict tree or can agents report to multiple managers? +- Can org structure change at runtime? +- Do agents inherit configuration from their manager? + +--- + +## 4. Heartbeat System [DRAFT] + +The heartbeat is a protocol, not a runtime. Paperclip defines how to initiate an agent's cycle. What the agent does with that cycle — how long it runs, whether it's task-scoped or continuous — is entirely up to the agent. + +### Execution Adapters + +Agent configuration includes an **adapter** that defines how Paperclip invokes the agent. Initial adapters: + +| Adapter | Mechanism | Example | +| --- | --- | --- | +| `process` | Execute a child process | `python run_agent.py --agent-id {id}` | +| `http` | Send an HTTP request | `POST https://openclaw.example.com/hook/{id}` | + +More adapters can be added. The adapter interface is simple: "given this agent's config, initiate their cycle." + +### What Paperclip Controls + +- **When** to fire the heartbeat (schedule/frequency, per-agent) +- **How** to fire it (adapter selection + config) +- **What context** to include (thin ping vs. fat payload, per-agent) + +### What Paperclip Does NOT Control + +- How long the agent runs +- What the agent does during its cycle +- Whether the agent is task-scoped, time-windowed, or continuous + +### Open Questions + +- Heartbeat frequency — who controls it? Fixed? Per-agent? Cron-like? +- What happens when a heartbeat invocation fails? (process crashes, HTTP 500) +- Health monitoring — how does Paperclip distinguish "stuck" from "working on a long task"? +- Can agents self-trigger their next heartbeat? ("I'm done, wake me again in 5 min") + +--- + +## 5. Inter-Agent Communication [DRAFT] + +All agent communication flows through the **task system**. + +### Model: Tasks + Comments + +- **Delegation** = creating a task and assigning it to another agent +- **Coordination** = commenting on tasks +- **Status updates** = updating task status and fields + +There is no separate messaging or chat system. Tasks are the communication channel. This keeps all context attached to the work it relates to and creates a natural audit trail. + +### Implications + +- An agent's "inbox" is: tasks assigned to them + comments on tasks they're involved in +- The CEO delegates by creating tasks assigned to the CTO +- The CTO breaks those down into sub-tasks assigned to engineers +- Discussion happens in task comments, not a side channel +- If an agent needs to escalate, they comment on the parent task or reassign + +--- + +## 6. Cost Tracking [NEEDS DETAIL] + +Token budgets, spend tracking, burn rate. + +### Open Questions + +- How does cost data enter the system? +- Budget enforcement — hard limits vs. alerts? +- Granularity — per-agent, per-task, per-company? + +--- + +## 7. Knowledge Base [NEEDS DETAIL] + +Shared organizational memory. + +### Open Questions + +- What form does company knowledge take? +- How do agents read/write to it? +- Scoping — company-wide, team-level, agent-level? diff --git a/doc/TASKS-mcp.md b/doc/TASKS-mcp.md new file mode 100644 index 00000000..c70cf3e4 --- /dev/null +++ b/doc/TASKS-mcp.md @@ -0,0 +1,567 @@ +# Task Management MCP Interface + +Function contracts for the Paperclip task management system. Defines the +operations available to agents (and external tools) via MCP. Refer to +[TASKS.md](./TASKS.md) for the underlying data model. + +All operations return JSON. IDs are UUIDs. Timestamps are ISO 8601. +Issue identifiers (e.g. `ENG-123`) are accepted anywhere an issue `id` is +expected. + +--- + +## Issues + +### `list_issues` + +List and filter issues in the workspace. + +| Parameter | Type | Required | Notes | +| ----------------- | -------- | -------- | ----------------------------------------------------------------------------------------------- | +| `query` | string | no | Free-text search across title and description | +| `teamId` | string | no | Filter by team | +| `status` | string | no | Filter by specific workflow state | +| `stateType` | string | no | Filter by state category: `triage`, `backlog`, `unstarted`, `started`, `completed`, `cancelled` | +| `assigneeId` | string | no | Filter by assignee (agent id) | +| `projectId` | string | no | Filter by project | +| `parentId` | string | no | Filter by parent issue (returns sub-issues) | +| `labelIds` | string[] | no | Filter to issues with ALL of these labels | +| `priority` | number | no | Filter by priority (0-4) | +| `includeArchived` | boolean | no | Include archived issues. Default: false | +| `orderBy` | string | no | `created`, `updated`, `priority`, `due_date`. Default: `created` | +| `limit` | number | no | Max results. Default: 50 | +| `after` | string | no | Cursor for forward pagination | +| `before` | string | no | Cursor for backward pagination | + +**Returns:** `{ issues: Issue[], pageInfo: { hasNextPage, endCursor, hasPreviousPage, startCursor } }` + +--- + +### `get_issue` + +Retrieve a single issue by ID or identifier, with all relations expanded. + +| Parameter | Type | Required | Notes | +| --------- | ------ | -------- | -------------------------------------------------- | +| `id` | string | yes | UUID or human-readable identifier (e.g. `ENG-123`) | + +**Returns:** Full `Issue` object including: + +- `state` (expanded WorkflowState) +- `assignee` (expanded Agent, if set) +- `labels` (expanded Label[]) +- `relations` (IssueRelation[] with expanded related issues) +- `children` (sub-issue summaries: id, identifier, title, state, assignee) +- `parent` (summary, if this is a sub-issue) +- `comments` (Comment[], most recent first) + +--- + +### `create_issue` + +Create a new issue. + +| Parameter | Type | Required | Notes | +| ------------- | -------- | -------- | --------------------------------------------- | +| `title` | string | yes | | +| `teamId` | string | yes | Team the issue belongs to | +| `description` | string | no | Markdown | +| `status` | string | no | Workflow state. Default: team's default state | +| `priority` | number | no | 0-4. Default: 0 (none) | +| `estimate` | number | no | Point estimate | +| `dueDate` | string | no | ISO date | +| `assigneeId` | string | no | Agent to assign | +| `projectId` | string | no | Project to associate with | +| `milestoneId` | string | no | Milestone within the project | +| `parentId` | string | no | Parent issue (makes this a sub-issue) | +| `goalId` | string | no | Linked goal/objective | +| `labelIds` | string[] | no | Labels to apply | +| `sortOrder` | number | no | Ordering within views | + +**Returns:** Created `Issue` object with computed fields (`identifier`, `createdAt`, etc.) + +**Side effects:** + +- If `parentId` is set, inherits `projectId` from parent (unless explicitly provided) +- `identifier` is auto-generated from team key + next sequence number + +--- + +### `update_issue` + +Update an existing issue. + +| Parameter | Type | Required | Notes | +| ------------- | -------- | -------- | -------------------------------------------- | +| `id` | string | yes | UUID or identifier | +| `title` | string | no | | +| `description` | string | no | | +| `status` | string | no | Transition to a new workflow state | +| `priority` | number | no | 0-4 | +| `estimate` | number | no | | +| `dueDate` | string | no | ISO date, or `null` to clear | +| `assigneeId` | string | no | Agent id, or `null` to unassign | +| `projectId` | string | no | Project id, or `null` to remove from project | +| `milestoneId` | string | no | Milestone id, or `null` to clear | +| `parentId` | string | no | Reparent, or `null` to promote to standalone | +| `goalId` | string | no | Goal id, or `null` to unlink | +| `labelIds` | string[] | no | **Replaces** all labels (not additive) | +| `teamId` | string | no | Move to a different team | +| `sortOrder` | number | no | Ordering within views | + +**Returns:** Updated `Issue` object. + +**Side effects:** + +- Changing `status` to a state with category `started` sets `startedAt` (if not already set) +- Changing `status` to `completed` sets `completedAt` +- Changing `status` to `cancelled` sets `cancelledAt` +- Moving to `completed`/`cancelled` with sub-issue auto-close enabled completes open sub-issues +- Changing `teamId` re-assigns the identifier (e.g. `ENG-42` → `DES-18`); old identifier preserved in `previousIdentifiers` + +--- + +### `archive_issue` + +Soft-archive an issue. Sets `archivedAt`. Does not delete. + +| Parameter | Type | Required | +| --------- | ------ | -------- | +| `id` | string | yes | + +**Returns:** `{ success: true }` + +--- + +### `list_my_issues` + +List issues assigned to a specific agent. Convenience wrapper around +`list_issues` with `assigneeId` pre-filled. + +| Parameter | Type | Required | Notes | +| ----------- | ------ | -------- | ------------------------------ | +| `agentId` | string | yes | The agent whose issues to list | +| `stateType` | string | no | Filter by state category | +| `orderBy` | string | no | Default: `priority` | +| `limit` | number | no | Default: 50 | + +**Returns:** Same shape as `list_issues`. + +--- + +## Workflow States + +### `list_workflow_states` + +List workflow states for a team, grouped by category. + +| Parameter | Type | Required | +| --------- | ------ | -------- | +| `teamId` | string | yes | + +**Returns:** `{ states: WorkflowState[] }` -- ordered by category (triage, backlog, unstarted, started, completed, cancelled), then by `position` within each category. + +--- + +### `get_workflow_state` + +Look up a workflow state by name or ID. + +| Parameter | Type | Required | Notes | +| --------- | ------ | -------- | ------------------ | +| `teamId` | string | yes | | +| `query` | string | yes | State name or UUID | + +**Returns:** Single `WorkflowState` object. + +--- + +## Teams + +### `list_teams` + +List all teams in the workspace. + +| Parameter | Type | Required | +| --------- | ------ | -------- | -------------- | +| `query` | string | no | Filter by name | + +**Returns:** `{ teams: Team[] }` + +--- + +### `get_team` + +Get a team by name, key, or ID. + +| Parameter | Type | Required | Notes | +| --------- | ------ | -------- | ----------------------- | +| `query` | string | yes | Team name, key, or UUID | + +**Returns:** Single `Team` object. + +--- + +## Projects + +### `list_projects` + +List projects in the workspace. + +| Parameter | Type | Required | Notes | +| ----------------- | ------- | -------- | ------------------------------------------------------------------------------- | +| `teamId` | string | no | Filter to projects containing issues from this team | +| `status` | string | no | Filter by status: `backlog`, `planned`, `in_progress`, `completed`, `cancelled` | +| `includeArchived` | boolean | no | Default: false | +| `limit` | number | no | Default: 50 | +| `after` | string | no | Cursor | + +**Returns:** `{ projects: Project[], pageInfo }` + +--- + +### `get_project` + +Get a project by name or ID. + +| Parameter | Type | Required | +| --------- | ------ | -------- | +| `query` | string | yes | + +**Returns:** Single `Project` object including `milestones[]` and issue count by state category. + +--- + +### `create_project` + +| Parameter | Type | Required | +| ------------- | ------ | -------- | +| `name` | string | yes | +| `description` | string | no | +| `summary` | string | no | +| `leadId` | string | no | +| `startDate` | string | no | +| `targetDate` | string | no | + +**Returns:** Created `Project` object. Status defaults to `backlog`. + +--- + +### `update_project` + +| Parameter | Type | Required | +| ------------- | ------ | -------- | +| `id` | string | yes | +| `name` | string | no | +| `description` | string | no | +| `summary` | string | no | +| `status` | string | no | +| `leadId` | string | no | +| `startDate` | string | no | +| `targetDate` | string | no | + +**Returns:** Updated `Project` object. + +--- + +### `archive_project` + +Soft-archive a project. Sets `archivedAt`. Does not delete. + +| Parameter | Type | Required | +| --------- | ------ | -------- | +| `id` | string | yes | + +**Returns:** `{ success: true }` + +--- + +## Milestones + +### `list_milestones` + +| Parameter | Type | Required | +| ----------- | ------ | -------- | +| `projectId` | string | yes | + +**Returns:** `{ milestones: Milestone[] }` -- ordered by `sortOrder`. + +--- + +### `get_milestone` + +Get a milestone by ID. + +| Parameter | Type | Required | +| --------- | ------ | -------- | +| `id` | string | yes | + +**Returns:** Single `Milestone` object with issue count by state category. + +--- + +### `create_milestone` + +| Parameter | Type | Required | +| ------------- | ------ | -------- | +| `projectId` | string | yes | +| `name` | string | yes | +| `description` | string | no | +| `targetDate` | string | no | +| `sortOrder` | number | no | Ordering within the project | + +**Returns:** Created `Milestone` object. + +--- + +### `update_milestone` + +| Parameter | Type | Required | +| ------------- | ------ | -------- | +| `id` | string | yes | +| `name` | string | no | +| `description` | string | no | +| `targetDate` | string | no | +| `sortOrder` | number | no | Ordering within the project | + +**Returns:** Updated `Milestone` object. + +--- + +## Labels + +### `list_labels` + +List labels available for a team (includes workspace-level labels). + +| Parameter | Type | Required | Notes | +| --------- | ------ | -------- | ----------------------------------------- | +| `teamId` | string | no | If omitted, returns only workspace labels | + +**Returns:** `{ labels: Label[] }` -- grouped by label group, ungrouped labels listed separately. + +--- + +### `get_label` + +Get a label by name or ID. + +| Parameter | Type | Required | Notes | +| --------- | ------ | -------- | ------------------ | +| `query` | string | yes | Label name or UUID | + +**Returns:** Single `Label` object. + +--- + +### `create_label` + +| Parameter | Type | Required | Notes | +| ------------- | ------ | -------- | ----------------------------------- | +| `name` | string | yes | | +| `color` | string | no | Hex color. Auto-assigned if omitted | +| `description` | string | no | | +| `teamId` | string | no | Omit for workspace-level label | +| `groupId` | string | no | Parent label group | + +**Returns:** Created `Label` object. + +--- + +### `update_label` + +| Parameter | Type | Required | +| ------------- | ------ | -------- | +| `id` | string | yes | +| `name` | string | no | +| `color` | string | no | +| `description` | string | no | + +**Returns:** Updated `Label` object. + +--- + +## Issue Relations + +### `list_issue_relations` + +List all relations for an issue. + +| Parameter | Type | Required | +| --------- | ------ | -------- | +| `issueId` | string | yes | + +**Returns:** `{ relations: IssueRelation[] }` -- each with expanded `relatedIssue` summary (id, identifier, title, state). + +--- + +### `create_issue_relation` + +Create a relation between two issues. + +| Parameter | Type | Required | Notes | +| ---------------- | ------ | -------- | ---------------------------------------------- | +| `issueId` | string | yes | Source issue | +| `relatedIssueId` | string | yes | Target issue | +| `type` | string | yes | `related`, `blocks`, `blocked_by`, `duplicate` | + +**Returns:** Created `IssueRelation` object. + +**Side effects:** + +- `duplicate` auto-transitions the source issue to a cancelled state +- Creating `blocks` from A->B implicitly means B is `blocked_by` A (both + directions visible when querying either issue) + +--- + +### `delete_issue_relation` + +Remove a relation between two issues. + +| Parameter | Type | Required | +| --------- | ------ | -------- | +| `id` | string | yes | + +**Returns:** `{ success: true }` + +--- + +## Comments + +### `list_comments` + +List comments on an issue. + +| Parameter | Type | Required | Notes | +| --------- | ------ | -------- | ----------- | +| `issueId` | string | yes | | +| `limit` | number | no | Default: 50 | + +**Returns:** `{ comments: Comment[] }` -- threaded (top-level comments with nested `children`). + +--- + +### `create_comment` + +Add a comment to an issue. + +| Parameter | Type | Required | Notes | +| ---------- | ------ | -------- | ------------------------------------- | +| `issueId` | string | yes | | +| `body` | string | yes | Markdown | +| `parentId` | string | no | Reply to an existing comment (thread) | + +**Returns:** Created `Comment` object. + +--- + +### `update_comment` + +Update a comment's body. + +| Parameter | Type | Required | +| --------- | ------ | -------- | +| `id` | string | yes | +| `body` | string | yes | + +**Returns:** Updated `Comment` object. + +--- + +### `resolve_comment` + +Mark a comment thread as resolved. + +| Parameter | Type | Required | +| --------- | ------ | -------- | +| `id` | string | yes | + +**Returns:** Updated `Comment` with `resolvedAt` set. + +--- + +## Initiatives + +### `list_initiatives` + +| Parameter | Type | Required | Notes | +| --------- | ------ | -------- | -------------------------------- | +| `status` | string | no | `planned`, `active`, `completed` | +| `limit` | number | no | Default: 50 | + +**Returns:** `{ initiatives: Initiative[] }` + +--- + +### `get_initiative` + +| Parameter | Type | Required | +| --------- | ------ | -------- | +| `query` | string | yes | + +**Returns:** Single `Initiative` object with expanded `projects[]` (summaries with status and issue count). + +--- + +### `create_initiative` + +| Parameter | Type | Required | +| ------------- | -------- | -------- | +| `name` | string | yes | +| `description` | string | no | +| `ownerId` | string | no | +| `targetDate` | string | no | +| `projectIds` | string[] | no | + +**Returns:** Created `Initiative` object. Status defaults to `planned`. + +--- + +### `update_initiative` + +| Parameter | Type | Required | +| ------------- | -------- | -------- | +| `id` | string | yes | +| `name` | string | no | +| `description` | string | no | +| `status` | string | no | +| `ownerId` | string | no | +| `targetDate` | string | no | +| `projectIds` | string[] | no | + +**Returns:** Updated `Initiative` object. + +--- + +### `archive_initiative` + +Soft-archive an initiative. Sets `archivedAt`. Does not delete. + +| Parameter | Type | Required | +| --------- | ------ | -------- | +| `id` | string | yes | + +**Returns:** `{ success: true }` + +--- + +## Summary + +| Entity | list | get | create | update | delete/archive | +| ------------- | ---- | --- | ------ | ------ | -------------- | +| Issue | x | x | x | x | archive | +| WorkflowState | x | x | -- | -- | -- | +| Team | x | x | -- | -- | -- | +| Project | x | x | x | x | archive | +| Milestone | x | x | x | x | -- | +| Label | x | x | x | x | -- | +| IssueRelation | x | -- | x | -- | x | +| Comment | x | -- | x | x | resolve | +| Initiative | x | x | x | x | archive | + +**Total: 35 operations** + +Workflow states and teams are admin-configured, not created through the MCP. +The MCP is primarily for agents to manage their work: create issues, update +status, coordinate via relations and comments, and understand project context.