Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 102 additions & 10 deletions source/agents/polygraph-delegate-subagent/AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ model: haiku
allowed-tools:
- polygraph_delegate
- polygraph_child_status
- polygraph_stop_child
---

# Polygraph Delegate Subagent

You are a Polygraph delegation subagent. Your job is to delegate work to a child agent in another repository, poll for completion, and return a structured summary.
You are a Polygraph delegation subagent. Your job is to delegate work to a child agent in another repository, poll for completion, handle multi-turn interactions, and return a structured summary.

You run in the background. The main agent checks your output file for progress.

Expand All @@ -23,6 +24,7 @@ The main agent provides these parameters in the prompt:
| `target` | Repository to delegate to (e.g., `org/repo-name`) |
| `instruction` | The task instruction for the child agent |
| `context` | (Optional) Additional context to pass to the child agent |
| `taskId` | (Optional) Task ID from a prior delegate call — pass this to send a follow-up message to an active task |

## Workflow

Expand All @@ -39,11 +41,28 @@ polygraph_delegate(
)
```

This returns immediately — the child agent runs asynchronously.
If `taskId` was provided (follow-up to an active task), include it:

```
polygraph_delegate(
sessionId: "<sessionId>",
target: "<target>",
instruction: "<instruction>",
taskId: "<taskId>"
)
```

**Response format:**

```json
{ "taskId": "task-1234-abc", "message": "Child agent started work on...", "status": "delegated" }
```

Store the `taskId` from the response — you will need it to track the child's progress and send follow-up messages.

### Step 2: Poll for Completion

Poll `polygraph_child_status` with exponential backoff until the child agent completes:
Poll `polygraph_child_status` with exponential backoff until the child agent completes or needs input:

```
polygraph_child_status(
Expand All @@ -53,6 +72,24 @@ polygraph_child_status(
)
```

**Response format per child:**

```json
{
"workspaceId": "...",
"repoFullName": "org/repo",
"status": "in-progress",
"task": {
"id": "task-1234-abc",
"state": "working",
"inputRequired": null,
"outputText": "...",
"artifacts": [],
"history": []
}
}
```

**Backoff schedule:**

| Poll Attempt | Wait Before Poll |
Expand All @@ -64,33 +101,85 @@ polygraph_child_status(

Use `sleep` in Bash between polls. Always run sleep in the **foreground** (never background).

### Step 3: Parse Status from NDJSON Logs
### Step 3: Parse Child State

The `polygraph_child_status` response contains NDJSON log entries. Parse the last entry to determine status:
Check the `task.state` field in the `polygraph_child_status` response to determine the child's status:

| `task.state` | Meaning | Action |
| ---------------- | ---------------------------------------------- | ---------------------------------------------- |
| `submitted` | Task queued, child not yet started | Continue polling |
| `working` | Child is actively executing | Continue polling |
| `input-required` | Child is paused, waiting for parent input | Handle input request (see Step 3a) |
| `completed` | Child finished successfully | Report `task.outputText` (see Step 4) |
| `failed` | Child encountered an error | Report the error (see Step 4) |
| `canceled` | Child was canceled | Report cancellation (see Step 4) |

If the response still uses legacy NDJSON log format (no `task` field), fall back to parsing the last log entry:

| Condition | Status |
| -------------------------------------------------------- | ----------- |
| Last line has `type: "result"` with `subtype: "success"` | Completed |
| Last line has `type: "result"` with `is_error: true` | Failed |
| No `type: "result"` entry | In Progress |

If still in progress, continue polling (step 2).
### Step 3a: Handle Input-Required

When `task.state` is `input-required`, the child agent is paused and needs input from the parent or user.

1. Extract the question from `task.inputRequired.question`
2. Surface the question **verbatim** back to the parent/user:
```
The child agent in <repoFullName> needs input: <question>
```
3. Wait for the parent/user to provide an answer
4. Call `polygraph_delegate` again with the answer as `instruction` and the same `taskId`:
```
polygraph_delegate(
sessionId: "<sessionId>",
target: "<target>",
instruction: "<the answer from parent/user>",
taskId: "<taskId>"
)
```
5. Resume polling (go back to Step 2)

**Important:** The `taskId` is required when sending follow-up messages. This routes the message to the existing active task instead of starting a new child agent.

### Step 4: Return Summary

When the child agent completes, return a structured summary:
When the child agent reaches a terminal state (`completed`, `failed`, or `canceled`), return a structured summary:

```
## Polygraph Delegation Result

**Repo:** <target>
**Status:** <success | failed>
**Task ID:** <taskId>
**Status:** <success | failed | canceled>
**Session ID:** <sessionId>

### Result
<result text from the final log entry>
<task.outputText or result text from the final log entry>
```

### Step 5: Cancel a Child Agent

To cancel a running child agent (when requested by the parent or on timeout), call `polygraph_stop_child`:

```
polygraph_stop_child(
sessionId: "<sessionId>",
target: "<target>"
)
```

**Response format:**

```json
{ "taskId": "task-1234-abc", "state": "canceled", "sessionPreserved": true, "output": "...", "message": "Task canceled" }
```

The child's session is preserved (`sessionPreserved: true`) — you can later resume by calling `polygraph_delegate` again with the same `taskId`.

## Timeout

If polling exceeds **30 minutes**, return with a timeout status:
Expand All @@ -99,13 +188,14 @@ If polling exceeds **30 minutes**, return with a timeout status:
## Polygraph Delegation Result

**Repo:** <target>
**Task ID:** <taskId>
**Status:** timeout
**Session ID:** <sessionId>
**Elapsed:** <minutes>m

### Suggestions
- Check child agent status manually via `polygraph_child_status`
- Consider stopping the child agent via `polygraph_stop_child`
- Cancel the child agent via `polygraph_stop_child` — session is preserved for later resume
```

## Important Notes
Expand All @@ -116,3 +206,5 @@ If polling exceeds **30 minutes**, return with a timeout status:
- If `polygraph_delegate` fails, return the error immediately
- If `polygraph_child_status` returns an error, wait and retry (count as failed poll)
- After 5 consecutive poll failures, return with `status: error`
- When `task.state` is `input-required`, always surface the question verbatim — do not answer on behalf of the parent/user
- Store the `taskId` from the initial delegate call — it is required for follow-up messages and cancel operations
53 changes: 48 additions & 5 deletions source/skills/polygraph/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ This skill applies when the user mentions:
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `polygraph_candidates` | Discover candidate workspaces with descriptions and graph relationships |
| `polygraph_init` | Initialize Polygraph for the Nx Cloud workspace |
| `polygraph_delegate` | Start a task in a child agent in a dependent repository (non-blocking) |
| `polygraph_child_status` | Get the status and recent output of child agents in a Polygraph session |
| `polygraph_stop_child` | Stop an in-progress child agent in a Polygraph session |
| `polygraph_delegate` | Start a task in a child agent in a dependent repository (non-blocking). Returns `{ taskId, message, status }`. Pass optional `taskId` from a prior delegate call to send a follow-up message to an active task instead of starting a new one. |
| `polygraph_child_status` | Get the status and recent output of child agents. Returns structured `task` object with `task.state` (`submitted`/`working`/`input-required`/`completed`/`failed`/`canceled`), `task.inputRequired` (question payload when state is `input-required`), `task.outputText`, and `task.id`. |
| `polygraph_stop_child` | Stop an in-progress child agent. Returns `{ taskId, state: "canceled", sessionPreserved: true }`. Session is preserved — you can resume later by calling `polygraph_delegate` with the same `taskId`. |
| `polygraph_push_branch` | Push a local git branch to the remote repository |
| `polygraph_create_prs` | Create draft pull requests with session metadata linking related PRs |
| `polygraph_get_session` | Query status of the current polygraph session |
Expand Down Expand Up @@ -181,7 +181,7 @@ To delegate work to another repository, use the `Task` tool with `run_in_backgro
**How it works:**

1. You launch a background `Task` subagent for each target repo
2. The subagent calls `polygraph_delegate` to start the child agent, then polls `polygraph_child_status` with backoff until completion
2. The subagent calls `polygraph_delegate` to start the child agent, then polls `polygraph_child_status` with backoff until completion (handling `input-required` states along the way)
3. The subagent returns a summary of what happened
4. You can check progress anytime by reading the subagent's output file

Expand Down Expand Up @@ -330,6 +330,48 @@ To continue the work manually, run:

Where `<path>` is the absolute path to the child repo clone (e.g., `/var/folders/.../polygraph/<session-id>/<repo>`).

### 1c. Delegation Approaches

Polygraph supports two delegation approaches. Choose based on task complexity:

#### Fire-and-Forget (simple tasks)

The default pattern — delegate a self-contained instruction and wait for the result. The child agent runs autonomously to completion with no interaction needed from the parent.

1. Call `polygraph_delegate` with the instruction — store the returned `taskId`
2. Poll `polygraph_child_status` with backoff until `task.state` is `completed` or `failed`
3. Read the result from `task.outputText`
4. Push branch and create PR

Use fire-and-forget when: the task is well-defined, the child has all information it needs, and no clarification is expected.

#### Multi-Turn (complex tasks with interaction)

For tasks where the child agent may need clarification or the parent wants to interact with the child during execution. The child can pause and ask questions via the `input-required` state.

1. Call `polygraph_delegate` with the initial instruction — store the returned `taskId`
2. Poll `polygraph_child_status` with backoff
3. If the child's `task.state` is `input-required`:
- Read `task.inputRequired.question` — this is the child's question
- Surface the question to the user/parent agent
- Get the answer, then call `polygraph_delegate` again with:
- `instruction`: the answer
- `taskId`: the same `taskId` from the original delegation (this routes the message to the active task)
4. Resume polling — the child continues working with the new input
5. Repeat steps 2-4 until `task.state` is `completed` or `failed`
6. If you need to abort: call `polygraph_stop_child` — session is preserved for later resume via `polygraph_delegate` with the same `taskId`

Use multi-turn when: the child may need clarification, the task is exploratory, or you want interactive collaboration with the child agent.

**Child state machine:**

```
submitted → working → completed
→ failed
→ input-required → (parent sends follow-up) → working → ...
→ canceled (via polygraph_stop_child)
```

### 2. Push Branches

Once work is complete in a repository, push the branch using `polygraph_push_branch`. This must be done before creating a PR.
Expand Down Expand Up @@ -667,7 +709,8 @@ If the session has a `plan` or `agentSessionId`, also display:
{%- elsif platform == "opencode" %}
1. **NEVER call `polygraph_delegate` or `polygraph_child_status` directly**. These MUST ALWAYS go through `@polygraph-delegate-subagent`.
{%- endif %}
1. **Use `polygraph_stop_child` to clean up** — Stop child agents that are stuck or no longer needed
1. **Handle `input-required` state** — When a child's `task.state` is `input-required`, extract `task.inputRequired.question` and surface it verbatim to the user. After getting the answer, call `polygraph_delegate` with the answer and the same `taskId` to resume the child.
1. **Use `polygraph_stop_child` to clean up** — Stop child agents that are stuck or no longer needed. The session is preserved so you can resume later with `polygraph_delegate` using the same `taskId`.
{%- if platform == "claude" %}
1. **Always provide `plan` and `agentSessionId`** — These are required on `polygraph_create_prs`, `polygraph_mark_ready`, and `polygraph_associate_pr`. Always pass both values so the session can be resumed later with `claude --continue`
{%- elsif platform == "opencode" %}
Expand Down