diff --git a/source/agents/polygraph-delegate-subagent/AGENT.md b/source/agents/polygraph-delegate-subagent/AGENT.md index 1fdbbd6..37bef8d 100644 --- a/source/agents/polygraph-delegate-subagent/AGENT.md +++ b/source/agents/polygraph-delegate-subagent/AGENT.md @@ -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. @@ -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 @@ -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: "", + target: "", + instruction: "", + 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( @@ -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 | @@ -64,9 +101,20 @@ 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 | | -------------------------------------------------------- | ----------- | @@ -74,23 +122,64 @@ The `polygraph_child_status` response contains NDJSON log entries. Parse the las | 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 needs input: + ``` +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: "", + target: "", + instruction: "", + 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:** -**Status:** +**Task ID:** +**Status:** **Session ID:** ### Result - + ``` +### 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: "", + 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: @@ -99,13 +188,14 @@ If polling exceeds **30 minutes**, return with a timeout status: ## Polygraph Delegation Result **Repo:** +**Task ID:** **Status:** timeout **Session ID:** **Elapsed:** 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 @@ -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 diff --git a/source/skills/polygraph/SKILL.md b/source/skills/polygraph/SKILL.md index 687158c..19cddfe 100644 --- a/source/skills/polygraph/SKILL.md +++ b/source/skills/polygraph/SKILL.md @@ -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 | @@ -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 @@ -330,6 +330,48 @@ To continue the work manually, run: Where `` is the absolute path to the child repo clone (e.g., `/var/folders/.../polygraph//`). +### 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. @@ -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" %}