Skip to main content

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:
WorkUnitGoalNode
What it isExecution containerDecision-centric metadata
LifecyclePending → Active → Proposed → Reviewing → Merged / AbandonedExploring → Converging → Converged → Blocked → Abandoned
OwnsBranch, agent assignments, task tree, artifact chainExploration status, parent/child goal hierarchy
ID relationshipPrimary identityGoalId == WorkUnitId
What the extension readsActivity Center “Active Goals” reads work units directlyGoalNode 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

Create goal + GoalNode (preferred for full metadata)

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.