Files
paperclip-docs/docs/reference/api/goals-and-projects.md
T
631a6a0c53 Release docs for paperclip v2026.817.0 (content + screenshot overhaul) (#95)
Ships the v2026.817.0 docs to live: the doc content that was authored
and verified
earlier, plus a full screenshot overhaul.

The content half (`cdf81e2` and the nightly commits behind it) has
already been
reviewed. **The screenshots are the part that needs eyes** — `f24d734`,
342 PNGs.

## Screenshots

Everything was recaptured against the release commit
`213dabab4f8e1f3bb1803a2924c0fea1289fcd4c`
(`upstream/candidate/release-2026.817.0`). The previous shots were 375
parent-commits stale (v2026.720.0), with 228 of 310 entries flagged
pending. All
342 registry entries now carry that SHA.

> ⚠️ Worth knowing: the release commit is **not** an ancestor of the
parent's
> `master`, and local `master` was four days *older* than it. Capturing
against
> `master` would have both missed release UI and leaked 93 post-tag
commits' worth
> of UI into the release docs.

### New coverage (34 entries)

Three guides shipped with **no images at all** despite being 115–136
lines. They
now have them:

| Guide | Shots |
|---|---|
| `guides/day-to-day/decisions.md` | queue, an agent-proposed decision
with its options, triage strip, filter panel, named queue, Decided
curtain, empty state |
| `experimental/status-cards.md` | board, New card dialog, and all four
drawer tabs |
| `experimental/task-chat.md` | thread, composer mode picker, side pane
|
| `reference/api/secrets.md` | the board-side Proposals tab |

Chat-Style Tasks is captured in a **separate pass**:
`enableTaskChatRedesign` is
instance-wide, so leaving it on would have turned every classic
issue/task/work-mode
shot into the redesigned chat.

## Two defects that were already live

Both were shipping in the docs before this PR:

1. **`issues/detail-chat.png` and the `tasks/*` shots were mostly plugin
debug
output.** The Kitchen Sink example plugin mounts a panel into the issue
page
that dumps the raw issue JSON, and the seed installed it. Those guides
were
illustrating a debug panel instead of the task page. The seed no longer
   installs it.
2. **The seeded task thread was a monologue** — four board-user messages
in a row,
because the board actor cannot post agent comments. It is now a real
two-sided
   exchange.

## Targets repointed because the UI moved

| Target | Why |
|---|---|
| `activity/*` | `Activity.tsx` deleted; `/activity` renders
`pages/audit/CompanyActivity.tsx` |
| `experimental/cloud-upstream` | `CloudUpstream.tsx` deleted; the route
now redirects to `/company/export` |
| `settings/*`, `plugins/*`, `adapters/*` | `/instance/settings/*` are
legacy redirects now |
| `dashboard/stale-tasks-panel` | panel removed from `Dashboard.tsx` —
target dropped |

Plus eight targets with **silent selector drift**. A failed interaction
step falls
back to "whatever state was reached", so these had been quietly
producing wrong
images — a plain inbox where the doc promised a mode picker, a whole
dashboard
where it promised one panel. Full table in
`docs/user-guides/screenshots/SCREENSHOTS_PLAN_v2026.817.0.md` §C2.

## Pipeline

Decisions, status-card summaries and secret proposals are all
**agent-authored**,
and the board actor the seed runs as is refused by every one of them
(`"Agent run context required"`, `"Secret proposals require a verified
run-bound
agent token"`). Rather than insert rows behind the API,
`seed-new-surfaces.mjs`
borrows the two doors a real run already has — a board-minted agent API
key, and a
locally signed agent JWT — so every write goes through the production
code path.

Also:
- `capture.mjs` records a real `captured_against` on a detached checkout
instead of
the literal string `"HEAD"` (which is what every pre-2026.817 entry
recorded), and
  gains a `scrollTo` step for content below the fold.
- `run.mjs` gains `--serve` for iterating on the seed, the task-chat
phase, and a
keeper that holds the demo board's issue statuses steady — the demo
`process`
agents finish runs without declaring a disposition, so the stalled-work
sweep
re-blocks issues every few minutes over a capture that runs far longer.

## Reviewing

Light and dark both captured at 1440×900 @2x. The new surfaces are the
ones to
scrutinise; the rest is a like-for-like refresh.

### One leftover

Dropping `dashboard/stale-tasks-panel` leaves
`{light,dark}/dashboard/stale-tasks-panel.png`
tracked but unused — they need deleting by hand.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Co-authored-by: DevRel Gemini <[email protected]>
Co-authored-by: Paperclip <[email protected]>
Co-authored-by: Dotta <[email protected]>
Co-authored-by: ClaudeCoder <[email protected]>
2026-08-18 12:43:56 +02:00

12 KiB

Goals and Projects

Goals answer “why are we doing this?” Projects answer “what concrete deliverable are we organizing around?” In Paperclip, goals form the higher-level intent tree, while projects group work, workspaces, and runtime behavior around a deliverable.


At a Glance

Concept Use it for Key fields Notes
Goal Company mission, team objective, or agent/task objective title, description, level, status, parentId, ownerAgentId level defaults to task; status defaults to planned
Project A deliverable with linked goals and workspaces name, description, status, goalId / goalIds, leadAgentId, targetDate, env, executionWorkspacePolicy goalIds is preferred; goalId is kept in sync for legacy callers
Workspace A repository or local folder attached to a project name, sourceType, cwd, repoUrl, repoRef, defaultRef, visibility, runtimeConfig, isPrimary The first workspace becomes primary automatically

All of these endpoints are company-scoped. If you cannot access the company, you cannot list or mutate its goals or projects.


Goals

Goals are the planning layer. They are useful when you want to express strategy, break objectives into sub-goals, or attach ownership to a specific agent.

Goal levels are:

  • company
  • team
  • agent
  • task

Goal statuses are:

  • planned
  • active
  • achieved
  • cancelled

Fields

  • title is required.
  • description is optional.
  • level defaults to task if you omit it.
  • status defaults to planned if you omit it.
  • parentId links the goal to another goal.
  • ownerAgentId links the goal to an agent.

Routes

List Goals

GET /api/companies/{companyId}/goals

Returns all goals for the company.

Get Goal

GET /api/goals/{goalId}

Returns a single goal by ID. The API checks company access after the goal is found.

Create Goal

Use this when you want to define a new objective or sub-objective.

curl -X POST "http://localhost:3100/api/companies/{companyId}/goals" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{
    "title": "Launch onboarding revamp",
    "description": "Reduce time-to-first-success for new users",
    "level": "company",
    "status": "active"
  }'
await fetch(`http://localhost:3100/api/companies/${companyId}/goals`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${token}`,
  },
  body: JSON.stringify({
    title: "Launch onboarding revamp",
    description: "Reduce time-to-first-success for new users",
    level: "company",
    status: "active",
  }),
});
import requests

requests.post(
    f"http://localhost:3100/api/companies/{company_id}/goals",
    headers={"Authorization": f"Bearer {token}"},
    json={
        "title": "Launch onboarding revamp",
        "description": "Reduce time-to-first-success for new users",
        "level": "company",
        "status": "active",
    },
)

Update Goal

Use this to move a goal forward, change ownership, or re-parent it.

PATCH /api/goals/{goalId}
{
  "status": "achieved",
  "description": "Updated description",
  "ownerAgentId": "{agentId}"
}

This is a partial update. Any field you omit stays unchanged.

Delete Goal

DELETE /api/goals/{goalId}

Deletes the goal and returns the deleted row.


Projects

Projects sit one level below goals in day-to-day execution. Use a project when you want to organize a deliverable, attach workspaces, and group related issues under one practical container.

Projects have a few important implementation details:

  • goalIds is the preferred way to link a project to one or more goals.
  • goalId is still accepted for compatibility, but the service keeps it in sync with the first linked goal.
  • Project names are normalized so shortname-style references stay unique within a company.
  • If you omit color, Paperclip assigns one automatically from its project palette.
  • The returned project includes urlKey, goalIds, goals, codebase, workspaces, primaryWorkspace, and executionWorkspacePolicy.

Fields

  • name is required.
  • description is optional.
  • status defaults to backlog.
  • leadAgentId is optional.
  • targetDate is optional.
  • goalIds is preferred for new code.
  • workspace can be supplied during create to seed the first workspace in the same request.
  • env is the project environment binding config. Secret bindings are normalized before storage.
  • executionWorkspacePolicy is an advanced runtime policy object.
  • archivedAt can be set when archiving a project.

Project statuses are:

  • backlog
  • planned
  • in_progress
  • completed
  • cancelled

Routes

List Projects

GET /api/companies/{companyId}/projects

Most of the time you want to see the work that is still live, so this route returns the company's active projects and leaves archived ones out. A project counts as archived once it has an archivedAt value, which you set through Update Project.

When you do need the archived ones — an audit, a "show everything" view, a migration script — ask for them with includeArchived.

Query Parameters

Param Description
includeArchived Set to true to include archived projects in the list. Anything else, including omitting it, returns active projects only.
GET /api/companies/{companyId}/projects?includeArchived=true

The value is matched as the exact string true, so includeArchived=1 and includeArchived=TRUE do not opt you in.

Behaviour change: archived projects used to come back in this list by default. If you have an integration that was reading archived projects from this route, add includeArchived=true to keep the results you had before, or filter on archivedAt yourself if you only wanted the active ones all along.

Get Project

GET /api/projects/{projectId}

You can pass either a UUID or a unique project shortname in :projectId. If the shortname is ambiguous, the API returns 409 Conflict and asks you to use the UUID.

Create Project

If you provide workspace, the project is created and the workspace is created in the same request. If the workspace payload is invalid, the project creation is rolled back and the API returns 422.

curl -X POST "http://localhost:3100/api/companies/{companyId}/projects" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{
    "name": "Auth System",
    "description": "End-to-end authentication and session flow",
    "goalIds": ["{goalId}"],
    "status": "planned",
    "workspace": {
      "name": "auth-repo",
      "cwd": "/path/to/workspace",
      "repoUrl": "https://github.com/org/repo",
      "repoRef": "main",
      "isPrimary": true
    }
  }'
await fetch(`http://localhost:3100/api/companies/${companyId}/projects`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${token}`,
  },
  body: JSON.stringify({
    name: "Auth System",
    description: "End-to-end authentication and session flow",
    goalIds: [goalId],
    status: "planned",
    workspace: {
      name: "auth-repo",
      cwd: "/path/to/workspace",
      repoUrl: "https://github.com/org/repo",
      repoRef: "main",
      isPrimary: true,
    },
  }),
});
import requests

requests.post(
    f"http://localhost:3100/api/companies/{company_id}/projects",
    headers={"Authorization": f"Bearer {token}"},
    json={
        "name": "Auth System",
        "description": "End-to-end authentication and session flow",
        "goalIds": [goal_id],
        "status": "planned",
        "workspace": {
            "name": "auth-repo",
            "cwd": "/path/to/workspace",
            "repoUrl": "https://github.com/org/repo",
            "repoRef": "main",
            "isPrimary": True,
        },
    },
)

Update Project

Use this to change status, goals, target date, environment bindings, archive state, or other project metadata.

PATCH /api/projects/{projectId}
{
  "status": "in_progress",
  "goalIds": ["{goalId}"],
  "archivedAt": null
}

This endpoint also accepts the legacy goalId field, but goalIds should be preferred.

Delete Project

DELETE /api/projects/{projectId}

Deletes the project and returns the deleted row.


Project Workspaces

A project can have one or more workspaces. The primary workspace is the default workspace for project-scoped work.

Workspace source types supported by the code are:

  • local_path
  • git_repo
  • remote_managed
  • non_git_path

Validation rules:

  • A workspace must include at least one of cwd or repoUrl.
  • A remote_managed workspace must include remoteWorkspaceRef or repoUrl.
  • The first workspace in a project becomes primary automatically.
  • Passing isPrimary: true makes that workspace primary.
  • If you remove the primary workspace, Paperclip promotes another workspace automatically.

List Workspaces

GET /api/projects/{projectId}/workspaces

Returns all workspaces for the project.

Create Workspace

Workspaces can be created after the project already exists.

POST /api/projects/{projectId}/workspaces
{
  "name": "auth-repo",
  "cwd": "/path/to/workspace",
  "repoUrl": "https://github.com/org/repo",
  "repoRef": "main",
  "isPrimary": true
}

Update Workspace

PATCH /api/projects/{projectId}/workspaces/{workspaceId}
{
  "name": "auth-repo-main",
  "isPrimary": true
}

Updating a workspace can also change its cwd, repoUrl, repoRef, defaultRef, visibility, setupCommand, cleanupCommand, remoteProvider, remoteWorkspaceRef, sharedWorkspaceKey, metadata, and runtimeConfig.

Delete Workspace

DELETE /api/projects/{projectId}/workspaces/{workspaceId}

Deletes the workspace and returns the deleted row.

Workspace Runtime Services

This endpoint controls runtime services attached to a specific project workspace.

POST /api/projects/{projectId}/workspaces/{workspaceId}/runtime-services/{action}

action must be one of:

  • start
  • stop
  • restart

Important behavior from the code:

  • start and restart require the workspace to have a local path (cwd).
  • start and restart also require workspace runtime configuration.
  • The route updates the workspace desiredState to running or stopped.
  • The response includes the updated workspace and an operation record.
curl -X POST "http://localhost:3100/api/projects/{projectId}/workspaces/{workspaceId}/runtime-services/start" \
  -H "Authorization: Bearer <token>"
await fetch(
  `http://localhost:3100/api/projects/${projectId}/workspaces/${workspaceId}/runtime-services/start`,
  {
    method: "POST",
    headers: { Authorization: `Bearer ${token}` },
  },
);
import requests

requests.post(
    f"http://localhost:3100/api/projects/{project_id}/workspaces/{workspace_id}/runtime-services/start",
    headers={"Authorization": f"Bearer {token}"},
)

Practical Model

Use a goal when you want to capture intent and hierarchy. Use a project when you want to attach actual work: workspaces, runtime behavior, and issue execution. In practice, most teams create a company goal, add one or more supporting goals, then create a project that points at the work they want the agents to execute against.

When you are unsure which one to create, ask:

  • “Is this about why we exist?” Create or update a goal.
  • “Is this a deliverable with real code or files?” Create or update a project.
  • “Do I need a repo or directory to run work against?” Add a workspace.