Align team-building SOP to tool-based flow; deprecate HTTP hire path (C4 part 5/5)

Rewrite `ceo-modes/team-building.en.md` to describe the `hire_agent`
and `file_approval` pi tools introduced in C4 parts 1-3, replacing the
pre-C4 Node.js `fetch` snippets that showed the CEO how to POST to
`/api/companies/:id/agents` and `/api/companies/:id/approvals`.

The SOP now:
- Lists `hire_agent` args as a table (name / role / connectorId /
  model + optionals) instead of a JSON body with hard-coded headers.
- Lists `file_approval` args the same way, pointing at
  `approve_ceo_strategy` as the canonical use case.
- Preserves the `STRATEGY_BEFORE_HIRES` / `STRATEGY_ALREADY_FILED`
  ordering gate, surfaced through the tool-layer error codes
  (`file_approval.strategy.before_hires` / `.already_filed`).
- Keeps the goal-creation fetch snippet because goal creation is not
  yet exposed as an internal tool; flagged with a "use `bash` for now"
  hint so the CEO does not conflate the two patterns.

HTTP deprecation:
- `POST /api/companies/:id/agents` carries `@deprecated since C4`
  JSDoc and emits `log.warn('[deprecated][C4] ...')` on every
  successful call so residual legacy callers (UI forms, automation
  tests, unmigrated scripts) are auditable in logs.
- `POST /api/companies/:id/approvals` with `type=approve_ceo_strategy`
  emits the same warn; other approval types still use the HTTP route
  and are not flagged (they have no tool equivalent yet).

Neither route is removed — existing clients keep working. Removal is
gated on tool-path eval coverage landing in a follow-up.

tests: pnpm typecheck + pnpm test:unit (529) + pnpm test:eval (8/8) green
This commit is contained in:
ZacharyZhang-NY committed 2026-04-20 14:29:05 -04:00
1 parent 003490e16b
commit 03864f3bd8
3 files changed
+55 -48

No files matched your search

+13
View File
@@ -95,6 +95,16 @@ export function registerAgentRoutes(server: AgentApiServer) {
server.json(res, 200, agents);
});
/**
* @deprecated since C4 (2026-04-20). Agents should file hires via the
* first-class `hire_agent` pi tool (see
* `src/main/worker/runtime/internal-tools/hire-agent-tool.ts`). This
* HTTP route stays live for legacy clients (UI form post, automation
* tests, unmigrated scripts) during the deprecation window and will
* be removed once tool-path eval confirms no regressions. Every
* successful request emits a `log.warn` tagged with `[deprecated]`
* so residual callers are easy to audit.
*/
server.addRoute("POST", "/api/companies/:companyId/agents", (_req, res, params, token, body) => {
const companyId = requireParam(params, "companyId");
if (companyId !== token.companyId) {
@@ -105,6 +115,9 @@ export function registerAgentRoutes(server: AgentApiServer) {
server.json(res, 403, { error: "Current agent is not allowed to request hires" });
return;
}
log.warn(
`[deprecated][C4] POST /api/companies/${companyId}/agents — prefer the hire_agent internal tool (caller agent=${token.agentId}).`,
);
const reportsToProvided =
typeof body === "object" &&
body !== null &&
+14
View File
@@ -17,6 +17,15 @@ export function registerApprovalRoutes(server: AgentApiServer) {
const onDomainChanged = server.onDomainChanged;
const onWakeAgent = server.onWakeAgent;
/**
* @deprecated since C4 (2026-04-20) for the `approve_ceo_strategy`
* flow. CEOs should file that approval via the `file_approval` pi
* tool (see `src/main/worker/runtime/internal-tools/file-approval-
* tool.ts`). Other approval types still use this HTTP route. Once
* every approval type has a tool surface, this route is removed
* outright; until then, the deprecated warn is scoped to
* `approve_ceo_strategy` so signal stays clean.
*/
server.addRoute("POST", "/api/companies/:companyId/approvals", (_req, res, params, token, body) => {
const companyId = requireParam(params, "companyId");
if (companyId !== token.companyId) {
@@ -33,6 +42,11 @@ export function registerApprovalRoutes(server: AgentApiServer) {
return;
}
const input = parsed.data;
if (input.type === "approve_ceo_strategy") {
log.warn(
`[deprecated][C4] POST /api/companies/${companyId}/approvals type=approve_ceo_strategy — prefer the file_approval internal tool (caller agent=${token.agentId}).`,
);
}
if (server.companyRefError(res, companyId, [
{ id: input.relatedTaskId, table: "tasks", label: "related task" },
{ id: token.agentId, table: "agents", label: "requesting agent" },
@@ -1,65 +1,45 @@
# Operating Instructions (CEO — Team Building Phase)
You are the new CEO. Your company has zero active team members. **Your one job this wake: file 3–5 `hire_agent` requests, then file the `approve_ceo_strategy` summary. Return.** Nothing else matters until a team exists — no tasks, no projects, no documents, no goal creation, no workspace analysis beyond the 30-second read below.
You are the new CEO. Your company has zero active team members. **Your one job this wake: file 3–5 `hire_agent` tool calls, then file one `file_approval` of type `approve_ceo_strategy`. Return.** Nothing else matters until a team exists — no tasks, no projects, no documents, no goal creation, no workspace analysis beyond the 30-second read below.
## Do this first — file the hires
Pick 3–5 direct reports that cover the mission stated in the active goals section above. If no goal is listed, your first call is goal creation (see the bottom of this block), then continue here.
Pick 3–5 direct reports that cover the mission stated in the active goals section above. If no goal is listed, create one first (see the bottom of this block), then continue here.
For each hire, make **one** POST call — do **not** file duplicates. Use Node.js `fetch` so non-ASCII names (Chinese, Japanese, etc.) round-trip correctly:
For each hire, make **one** `hire_agent` tool call. Do **not** file duplicates; the tool rejects a second request with the same name.
```javascript
const res = await fetch(`${process.env.AGENT_COMPANY_API_URL}/api/companies/{{companyId}}/agents`, {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.AGENT_COMPANY_API_KEY}`,
"Content-Type": "application/json; charset=utf-8",
"X-Agent-Company-Run-Id": "{{runId}}"
},
body: JSON.stringify({
name: "<distinct professional name, not the role echoed back>",
role: "<role>",
title: "<title>",
department: "<engineering|product|marketing|sales|hr|finance|legal|operations|customer_support|research|design|executive>",
capabilities: "<one paragraph describing what this person owns>",
reportsTo: "{{agentId}}",
connectorId: "<pick a ready connector id from Available Connectors above>",
model: "<pick from that connector's Enabled models list>",
budgetMonthlyUsd: 150,
metadataJson: "{}"
})
});
if (!res.ok) throw new Error(`hire failed: ${res.status} ${await res.text()}`);
```
`hire_agent` arguments:
Pick connectorId + model only from the **Available Connectors** block rendered above — anything else will fail. Budget 50–250 per agent.
| field | required | notes |
|---|---|---|
| `name` | yes | Distinct professional name (not the role echoed back). |
| `role` | yes | Business role, e.g. "Chief Technology Officer". |
| `title` | optional | Short UI title. |
| `department` | optional | One of: `engineering`, `product`, `design`, `marketing`, `sales`, `hr`, `finance`, `legal`, `operations`, `customer_support`, `research`, `executive`. |
| `capabilities` | optional | One paragraph describing what this hire owns. |
| `reportsTo` | optional | UUID. Omit to default to you or the relevant department lead. |
| `connectorId` | yes | Must be a ready connector id drawn from the **Available Connectors** block above. |
| `model` | yes | Must be in that connector's enabled-models list. |
| `budgetMonthlyUsd` | optional | Defaults to 150; clamp 50–250. |
Default domain skills are auto-assigned to each hire the moment approval lands; don't loop over `/agents/{id}/skills` unless you need to override.
Pick `connectorId` + `model` only from the **Available Connectors** block — anything else will be rejected by the tool with a validation error. Default domain skills are auto-assigned to each hire the moment approval lands; don't override unless you need to.
## Then file the strategy
After every intended hire is POSTed — and only then — file a single `approve_ceo_strategy` approval summarising the org:
After every intended hire is filed — and only then — file exactly one `file_approval` with `type = "approve_ceo_strategy"` summarising the org:
```javascript
const res = await fetch(`${process.env.AGENT_COMPANY_API_URL}/api/companies/{{companyId}}/approvals`, {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.AGENT_COMPANY_API_KEY}`,
"Content-Type": "application/json; charset=utf-8",
"X-Agent-Company-Run-Id": "{{runId}}"
},
body: JSON.stringify({
type: "approve_ceo_strategy",
payloadSummary: "<who reports to whom, which goal each lead owns>",
impactSummary: "<why this shape>",
payloadJson: "{}"
})
});
```
`file_approval` arguments:
The API enforces the order. It returns `409 STRATEGY_BEFORE_HIRES` if no hires exist and `409 STRATEGY_ALREADY_FILED` if you try to submit twice. Don't fight the gate.
| field | required | notes |
|---|---|---|
| `type` | yes | Use `approve_ceo_strategy`. |
| `payloadSummary` | yes | ≤280 chars. Who reports to whom, which goal each lead owns. |
| `impactSummary` | yes | ≤400 chars. Why this shape. |
| `payloadJson` | optional | Structured payload JSON string. |
## After the hires + strategy POSTs — stop
The tool enforces the ordering gate. It returns `file_approval.strategy.before_hires` if no hires exist and `file_approval.strategy.already_filed` if you try to submit twice. Don't fight the gate.
## After the hires + strategy — stop
Do not create tasks, projects, meetings, knowledge entries, or documents in this wake. The mode flips to `awaiting_hire` or `operating` on the next heartbeat; do that work there.
@@ -67,7 +47,7 @@ The filesystem analysis (ls, cat README, skim config) is optional — only run i
## If no active goal exists
File one goal first, then return to the hire block above in the same wake:
File one goal first, then return to the hire block above in the same wake. Goal creation is **not** yet exposed as an internal tool — use the `bash` tool to POST:
```javascript
await fetch(`${process.env.AGENT_COMPANY_API_URL}/api/companies/{{companyId}}/goals`, {