Extending goals — programmatic goal injection
Goals can be created by humans via the VS Code extension, by autonomous agents via
MCP tools, or by external systems via the REST API. This page covers the REST API
surface and patterns for injecting goals from external triggers.
GoalNode vs. WorkUnit — the distinction
Every executing goal has two representations:
| WorkUnit | GoalNode |
|---|
| What it is | Execution container | Decision-centric metadata |
| Lifecycle | Pending → Active → Proposed → Reviewing → Merged / Abandoned | Exploring → Converging → Converged → Blocked → Abandoned |
| Owns | Branch, agent assignments, task tree, artifact chain | Exploration status, parent/child goal hierarchy |
| ID relationship | Primary identity | GoalId == WorkUnitId |
| What the extension reads | Activity Center “Active Goals” reads work units directly | GoalNode records are not yet read by any extension panel |
For most automation purposes, creating a work unit is sufficient — it will
appear in the extension and can have an agent spawned against it. Creating a goal
node additionally enrolls the work unit in the GoalNode store, used by
nm_v1_goal_list / GET /studio/goals.
REST API
POST /studio/goals
{
"goal": "Fix authentication timeout on /api/login",
"owner": "ci-pipeline",
"repositoryId": "repo-abc123",
"referenceFiles": ["src/Auth/LoginHandler.cs"]
}
Response:
{
"goalId": "WU-abc123",
"workUnitId": "WU-abc123",
"branchId": "work-abc123",
"status": "Exploring"
}
Create work unit only
POST /studio/workunits
{
"goal": "Fix authentication timeout on /api/login",
"branchId": "fix/auth-timeout",
"owner": "ci-pipeline",
"reviewPolicy": "AgentApproval"
}
The reviewPolicy field (HumanRequired / AgentApproval / Hybrid) is only
available here. The MCP tool nm_v1_workunit_create always defaults to
HumanRequired. If branchId is omitted, a stable ID is generated from the work
unit ID.
AgentApproval and Hybrid are available only via POST /studio/workunits. To use
agent-controlled review in an automated pipeline, create the work unit via REST and
pair with Workspace:AllowAgentGitCommits=true for a fully unattended flow.
Spawn an agent after creation
POST /studio/agents/spawn
{
"workUnitId": "WU-abc123",
"agentType": "orchestrator",
"provider": "anthropic",
"model": "claude-sonnet-4-6",
"apiKey": "sk-...",
"enabledDomainAgents": ["Security", "Test"]
}
enabledDomainAgents overrides the session-wide Workspace:EnabledDomainAgents
setting for this work unit’s execution — see Guides → Domain observers.
Visibility in the extension
Work units created by REST or a headless peer appear in the Activity Center and
Goal Workspace identically to interactively created ones — there is no “external
source” badge. The owner field on the work unit records the creator identity
(e.g., "ci-pipeline") and is shown in the Decision Lens metadata.
External trigger patterns
Pattern 1 — CI failure webhook
CI build fails
→ webhook fires to listener
→ POST /studio/goals { goal: "Investigate build failure: ...", reviewPolicy: "AgentApproval" }
→ POST /studio/agents/spawn { workUnitId: ..., provider: "anthropic", ... }
→ agent investigates, proposes fix, reviewer agent auto-approves
→ AllowAgentGitPush=true: branch pushed, PR opened externally
No headless peer is needed if the Studio host is already running (e.g., via the VS
Code extension).
Pattern 2 — Monitoring alert handler (persistent peer)
A headless peer (PeerType: "persistent-agent") subscribes to a metrics/alerting
bus. On alert receipt, it injects a goal in-process — no HTTP round-trip needed.
Because the peer is connected to the room (Peer:HostUri set), the resulting work
unit and agents appear in the extension in real time. See
Guides → Headless peer.
Pattern 3 — Scheduled maintenance (cron → ephemeral peer)
A cron job launches the peer binary in --mode peer. An initialization hook creates
a work unit and spawns an orchestrator at startup. With
Workspace:AllowAgentGitCommits=true and Workspace:AllowAgentGitPush=true, the
agent commits and pushes its work. The peer exits when the orchestrator finishes.
Set Workspace:AllowAutoRequeue=false so a failure surfaces as an exit code, not an
infinite retry loop.