Paperclip 的目标与项目层怎么组织:goals、projects 与 workspaces 三层接口拆解
在 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 分支,终态是 done 和 cancelled。两套状态只有 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"
}
只能确定这两个值是合法的,其余(比如是否有 done、archived)属于文档未说明。这一点和 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 必须至少包含 cwd 或 repoUrl 其中之一 |
| 纯仓库项目 | 只挂仓库时,省略 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——这同样属于未说明的部分。
一条最小链路:从目标到能干活的项目
把上面几组接口串起来,最短路径大致是四步:
POST /api/companies/{companyId}/goals建一条公司级目标,拿到 goalId;POST /api/companies/{companyId}/projects建项目,goalIds填上一步的 id,同时用内嵌的workspace对象把仓库或目录一次性播种好;- 需要多个工作区时,再用
POST /api/projects/{projectId}/workspaces追加,并确认isPrimary只落在你想让 agent 默认进入的那个上; - 后续 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 是什么:一个自己不跑 Agent 的控制平面,怎么管住一整家 AI 公司
- 本专题共 40 篇,完整分组目录见专题页
- Paperclip 流水线(pipelines)与例行任务(routines):阶段、关卡、触发器怎么配
- Paperclip 怎么装:onboard 一条命令背后的四种安装方式与更新回滚
本文依据 Paperclip 官方仓库(github.com/paperclipai/paperclip,MIT 协议)的 docs/ 用户文档
与 doc/ 下的规范、运维与连接器手册整理,核对日 2026-08-17。
我们没有部署或运行过 Paperclip,因此不涉及界面外观与操作手感;
部分规范文档描述的是目标架构而非当前实现,文中已就地标注,不构成对实际行为的保证。
请以仓库最新内容为准。