Paperclip 的目标与项目层怎么组织:goals、projects 与 workspaces 三层接口拆解

2026-08-17

在 Paperclip 里跑一家”AI 公司”,最容易被跳过的就是目标层。公司建好了,agent 也招了,接下来顺手就是往看板上塞 issue——因为 issue 有标题、有状态、有指派人,看起来最像”活儿”。于是很快就出现一个尴尬局面:任务列表越堆越长,但没人说得清哪几条是奔着同一个交付物去的,哪几条其实早就没必要做了。

官方 API 文档对这层的定位说得很干脆:目标定义”为什么”,项目定义”做什么”(原文是 Goals define the “why” and projects define the “what” for organizing work)。这不是一句务虚的口号——在 Paperclip 的数据模型里,它对应的是两组独立的接口和两套独立的状态字段。核心概念文档也提到,公司这一层本身就带一个 goal,并且”所有工作都能追溯回公司目标”(task hierarchy 一项);issue 则明确带有一个 project 关联和一个可选的 goal 关联。也就是说,目标和项目不是可选的装饰,而是 issue 层级链条的上游。

这篇按官方接口文档把这三层——goals、projects、project workspaces——的字段、枚举和约束逐条摊开,顺带说清楚哪些地方文档写死了、哪些地方文档其实没写。

目标是一棵树:公司目标拆成团队目标,再拆到 agent

文档对 goals 的层次描述只有一句,但信息量不小:目标形成一个层次结构,公司目标拆成团队目标,团队目标再拆成 agent 层级的目标。

对应的四个接口很规整:

GET   /api/companies/{companyId}/goals
GET   /api/goals/{goalId}
POST  /api/companies/{companyId}/goals
PATCH /api/goals/{goalId}

注意列表和创建挂在 company 下(/api/companies/{companyId}/goals),而读取单条和更新挂在 goal 自己的 id 上(/api/goals/{goalId})。这是 Paperclip 接口里常见的路径写法,写脚本时别把两组路径混用。

创建请求体的形状是这样:

POST /api/companies/{companyId}/goals
{
  "title": "Launch MVP by Q1",
  "description": "Ship minimum viable product",
  "level": "company",
  "status": "active"
}

这里的 level 就是层次的载体。文档在示例里给出的值是 company,正文描述里提到了公司、团队、agent 三层,但没有像 status 那样明确列出 level 的完整取值清单。所以如果你要写自动化脚本批量建目标,稳妥做法是先手工建一条团队级目标、读回来看看实际存的是什么字符串,而不是照着”team""agent”这类猜测硬编码。这属于官方文档未说明的部分,不要当成已知事实。

goal 的四个状态值是明确列出来的

和 level 不同,goal 的 status 文档给得很死,就四个:

status含义(按字面)
planned已规划,尚未启动
active进行中
achieved已达成
cancelled已取消

更新走 PATCH,示例里同时改了状态和描述:

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

值得留意的是这套枚举和 issue 那套是两回事。核心概念文档里 issue 的生命周期是 backlog -> todo -> in_progress -> in_review -> done,外加一个 blocked 分支,终态是 donecancelled。两套状态只有 cancelled 是同名的,其余完全不重叠。所以做仪表盘或者报表聚合时,别指望用同一个状态映射表把 goal 和 issue 一起算——它们的语义粒度本来就不一样。接口层面的整体约定和鉴权方式可以先看一遍 Paperclip API 总览与鉴权,issue 和 agent 两组接口的字段细节则在 issues 与 agents 核心接口 里。

项目:把散落的 issue 聚成一个交付物

文档对 project 的定义是:项目把相关的 issue 归拢到一个交付物之下;项目可以关联到目标,也可以拥有 workspace(仓库/目录配置)。

接口同样是四条:

GET   /api/companies/{companyId}/projects
GET   /api/projects/{projectId}
POST  /api/companies/{companyId}/projects
PATCH /api/projects/{projectId}

其中 GET /api/projects/{projectId} 文档特别注明”返回的项目详情包含 workspaces”,也就是说你不必再单独发一次请求去拿工作区列表。

创建项目的请求体信息最密集:

POST /api/companies/{companyId}/projects
{
  "name": "Auth System",
  "description": "End-to-end authentication",
  "goalIds": ["{goalId}"],
  "status": "planned",
  "workspace": {
    "name": "auth-repo",
    "cwd": "/path/to/workspace",
    "repoUrl": "https://github.com/org/repo",
    "repoRef": "main",
    "isPrimary": true
  }
}

有三处要单独拎出来说:

第一,goalIds数组。这意味着一个项目可以同时挂在多个目标下,而不是一对一。文档没有说明数组是否允许为空或省略,实际使用前建议先试一条最小请求确认。

第二,status 的取值文档没有给完整枚举。创建示例里用的是 planned,更新示例里用的是 in_progress

PATCH /api/projects/{projectId}
{
  "status": "in_progress"
}

只能确定这两个值是合法的,其余(比如是否有 donearchived)属于文档未说明。这一点和 goal 的四值枚举形成对比,别把两边的经验互相套用。

第三,workspace 字段是在创建项目时顺带播种一个工作区的快捷写法,文档写明它是可选的:如果带上,项目创建的同时就用这份配置初始化一个 workspace。

workspace:项目最终要落到哪个仓库、哪个目录

workspace 是这三层里最”接地”的一层——它决定 agent 干活时人在哪个目录。

单独挂工作区的接口是:

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

管理类接口三条:

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

文档在这里给了两条硬约束,是最容易踩的地方:

约束文档原意
字段下限一个 workspace 必须至少包含 cwdrepoUrl 其中之一
纯仓库项目只挂仓库时,省略 cwd,只给 repoUrl
primary 的作用agent 用 primary workspace 来决定项目范围任务的工作目录

最后一条是整层的关键:isPrimary 不是给人看的标记,而是 agent 解析工作目录的依据。一个项目可以有多个 workspace,但 agent 执行 project-scoped 任务时认的是 primary 那个。所以当你发现 agent 在一个意料之外的目录里动手,第一件事应该是回头查这个项目的 workspace 列表和 isPrimary 落在谁身上,而不是去改 agent 的 adapter 配置。工作区在执行期的更多机制可以参考 执行工作区与 git worktree

repoRef 在示例里给的是 main,用途从字段名和上下文看是指定仓库引用(分支名之类),但文档没有展开说明它是否接受 tag 或 commit sha——这同样属于未说明的部分。

一条最小链路:从目标到能干活的项目

把上面几组接口串起来,最短路径大致是四步:

  1. POST /api/companies/{companyId}/goals 建一条公司级目标,拿到 goalId;
  2. POST /api/companies/{companyId}/projects 建项目,goalIds 填上一步的 id,同时用内嵌的 workspace 对象把仓库或目录一次性播种好;
  3. 需要多个工作区时,再用 POST /api/projects/{projectId}/workspaces 追加,并确认 isPrimary 只落在你想让 agent 默认进入的那个上;
  4. 后续 issue 创建时带上 project 关联(可选再带 goal 关联),整条链就完整了。

如果只是第一次搭公司、还没到写脚本的阶段,可以先按 建第一家公司的完整步骤 走一遍界面流程,再回过头来对照这些接口字段,理解起来会快得多。

从治理角度看,这层结构的价值在核心概念文档里也有呼应:CEO 是主要的委派者,你设定公司目标之后,CEO 会先出一份策略提交给你审批,审批通过再把目标拆成任务、按角色和能力分派下去。目标层写得含糊,拆出来的任务自然也含糊——这不是模型能力问题,是输入问题。

这层解决不了什么

得说清楚边界。goals 和 projects 提供的是归类和追溯,不是调度。文档里没有任何字段表明目标能触发执行:agent 的唤醒仍然走 heartbeat,由定时、指派、@提及、人工触发或审批结果这五类事件驱动,goal 状态改成 active 不会让谁动起来。

其次,目标之间的层次关系怎么表达,文档没写。正文说了公司目标拆成团队目标再拆到 agent,但创建请求体里只有一个 level 字段,没有出现 parentGoalId 之类的父子引用。层次到底是靠 level 值隐式分层,还是另有字段,官方文档未说明。相比之下 issue 那边是明确有 parent issue 的(核心概念文档里写了”a parent issue,形成可追溯回公司目标的层次”)。

第三,project 与 goal 的关联是通过 goalIds 数组建立的,但文档没有给解除关联的专门接口,也没说 PATCH 项目时传一个新的 goalIds 是覆盖还是追加。要改关联,先在测试公司上试一次再动生产数据。

最后一点提醒:本文所有字段、枚举和路径都来自 Paperclip 官方 API 文档中 goals-and-projects 一节与 core-concepts 一节,凡是文档没写死的地方都已经标注为”未说明”。Paperclip 是活跃开发中的开源项目(截至 2026-08-17,仓库地址是 github.com/paperclipai/paperclip,官网 paperclip.ing),接口字段有变动的可能,真要接进自动化流程之前,还是以你那个版本自带的文档为准。

延伸阅读


本文依据 Paperclip 官方仓库(github.com/paperclipai/paperclip,MIT 协议)的 docs/ 用户文档 与 doc/ 下的规范、运维与连接器手册整理,核对日 2026-08-17。 我们没有部署或运行过 Paperclip,因此不涉及界面外观与操作手感; 部分规范文档描述的是目标架构而非当前实现,文中已就地标注,不构成对实际行为的保证。 请以仓库最新内容为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。