@polygraph/opencode-plugin 0.4.41 → 0.4.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -38,6 +38,7 @@ It detects your AI agent — Claude Code, Codex, OpenCode, and more — and inst
38
38
  ## Skills
39
39
 
40
40
  - **polygraph** — Comprehensive guidance for Polygraph sessions: shared context, repository graph visibility, PR/CI state, delegation, and session management
41
+ - **adversarial-review** — Second-opinion review of a session's work by independent reviewer agents, one per repo, running under a read-only `reviewer` role
41
42
  - **await-polygraph-ci** — Wait for CI pipelines to settle across all repos in a session, investigate failures, and present fix options
42
43
  - **get-latest-ci** — One-shot fetch of the latest CI pipeline execution for the current branch
43
44
  - **session-debrief** — Analyze the raw logs of past Polygraph sessions and produce structured, rank-ordered debriefs for use in a different session
@@ -8,13 +8,25 @@
8
8
  // every session start and compaction; the CLI reader looks for mapping-*.json
9
9
  // files in the per-session sidecars directory.
10
10
  //
11
- // File contract:
11
+ // File contract (must match the Polygraph CLI reader exactly):
12
+ // <sessionsRoot>/<POLYGRAPH_SESSION_ID>/sidecars/mapping-opencode-<sessionId>.json
13
+ // where sessionsRoot = $POLYGRAPH_ROOT, else `globalRoot` from
14
+ // ~/.polygraph/config.json, else ~/.polygraph/sessions
15
+ // Legacy fallback, used ONLY when <sessionsRoot>/<POLYGRAPH_SESSION_ID>
16
+ // does not exist (for real sessions nothing new is written here):
12
17
  // ~/.polygraph/sidecars/<POLYGRAPH_SESSION_ID>/mapping-opencode-<sessionId>.json
13
18
  //
19
+ // The session folder is a trustworthy location for this parent-transcript
20
+ // binding because the Polygraph CLI's child-agent sandboxes exclude the
21
+ // session root — children cannot write there. The CLI reads mappings from
22
+ // the session folder first, with the flat dir as a read-only fallback.
23
+ //
14
24
  // Behaviour:
15
25
  // - Silent no-op when POLYGRAPH_SESSION_ID is unset or POLYGRAPH_CHILD_AGENT is set.
16
26
  // - Atomic write via tmp-file rename.
17
- // - Refresh: preserves firstSeenAt when a valid prior mapping exists.
27
+ // - Refresh: preserves firstSeenAt when a valid prior mapping exists
28
+ // (checked in the new location first, then the legacy flat dir — keeps
29
+ // firstSeenAt continuity when migrating a mapping from the legacy dir).
18
30
  // - All failures are silently swallowed.
19
31
 
20
32
  import {
@@ -82,9 +94,34 @@ export function logHookFailure(
82
94
  }
83
95
  }
84
96
 
97
+ // Resolve the root directory that holds per-session folders:
98
+ // $POLYGRAPH_ROOT, else `globalRoot` from ~/.polygraph/config.json, else
99
+ // ~/.polygraph/sessions. Must match the Polygraph CLI's own resolution.
100
+ function sessionsRoot(home) {
101
+ const fromEnv = process.env.POLYGRAPH_ROOT?.trim();
102
+ if (fromEnv) return fromEnv;
103
+
104
+ try {
105
+ const config = JSON.parse(
106
+ readFileSync(path.join(home, '.polygraph', 'config.json'), 'utf8')
107
+ );
108
+ if (typeof config?.globalRoot === 'string' && config.globalRoot.trim()) {
109
+ return config.globalRoot.trim();
110
+ }
111
+ } catch {
112
+ // no config — use the default
113
+ }
114
+
115
+ return path.join(home, '.polygraph', 'sessions');
116
+ }
117
+
85
118
  /**
86
119
  * Write (or refresh) the agent-capture mapping for an OpenCode session.
87
- * Reads POLYGRAPH_SESSION_ID and POLYGRAPH_CHILD_AGENT from process.env.
120
+ *
121
+ * Written into the session folder (`<sessionsRoot>/<sessionId>/sidecars/`)
122
+ * when the session directory exists; only when it does not exist does the
123
+ * write fall back to the legacy flat `~/.polygraph/sidecars/<sessionId>/`
124
+ * dir. Reads POLYGRAPH_SESSION_ID and POLYGRAPH_CHILD_AGENT from process.env.
88
125
  *
89
126
  * @param {string} agentSessionId The OpenCode session id (input.sessionID).
90
127
  * @param {string} [home] Override HOME for testing.
@@ -99,19 +136,34 @@ export function writeAgentCaptureMapping(
99
136
  if (process.env.POLYGRAPH_CHILD_AGENT) return;
100
137
  if (!agentSessionId) return;
101
138
 
102
- const sidecarDir = path.join(home, '.polygraph', 'sidecars', polygraphSessionId);
103
- mkdirSync(sidecarDir, { recursive: true });
104
-
105
139
  const filenamePart = sanitizeMappingFilename(`opencode-${agentSessionId}`);
106
- const finalPath = path.join(sidecarDir, `mapping-${filenamePart}.json`);
140
+ const fileName = `mapping-${filenamePart}.json`;
141
+
142
+ const sessionDir = path.join(sessionsRoot(home), polygraphSessionId);
143
+ const sessionSidecarDir = path.join(sessionDir, 'sidecars');
144
+ const legacyDir = path.join(home, '.polygraph', 'sidecars', polygraphSessionId);
145
+
146
+ // New location when the session directory exists; legacy flat dir only
147
+ // when it does not.
148
+ const targetDir = existsSync(sessionDir) ? sessionSidecarDir : legacyDir;
149
+ mkdirSync(targetDir, { recursive: true });
150
+
151
+ const finalPath = path.join(targetDir, fileName);
107
152
  const tmpPath = `${finalPath}.tmp-${process.pid}`;
108
153
 
109
154
  const now = Date.now();
110
- let firstSeenAt = now;
111
155
 
112
- if (existsSync(finalPath)) {
156
+ // Refresh semantics: preserve firstSeenAt from a valid prior mapping.
157
+ // Check the new location first, then the legacy flat dir — this keeps
158
+ // firstSeenAt continuity when migrating a mapping from the legacy dir.
159
+ let firstSeenAt = now;
160
+ for (const candidate of [
161
+ path.join(sessionSidecarDir, fileName),
162
+ path.join(legacyDir, fileName),
163
+ ]) {
164
+ if (!existsSync(candidate)) continue;
113
165
  try {
114
- const existing = JSON.parse(readFileSync(finalPath, 'utf8'));
166
+ const existing = JSON.parse(readFileSync(candidate, 'utf8'));
115
167
  if (
116
168
  existing.version === 1 &&
117
169
  existing.polygraphSessionId === polygraphSessionId &&
@@ -119,6 +171,7 @@ export function writeAgentCaptureMapping(
119
171
  Number.isFinite(existing.firstSeenAt)
120
172
  ) {
121
173
  firstSeenAt = existing.firstSeenAt;
174
+ break;
122
175
  }
123
176
  } catch {
124
177
  // ignore — treat as missing
@@ -20,13 +20,14 @@ The main agent provides these parameters in the prompt:
20
20
  | `sessionId` | The Polygraph session ID |
21
21
  | `repo` | Repository to delegate to (e.g., `org/repo-name`) |
22
22
  | `instruction` | The task instruction for the child agent |
23
+ | `role` | (Optional) Agent slot within the repo; omit for the default role. Pass the SAME role on every `spawn_agent`/`show_agent`/`stop_agent` call for this delegation. |
23
24
  | `context` | (Optional) Additional context to pass to the child agent |
24
25
 
25
26
  ## Delegating work
26
27
 
27
- Call the `spawn_agent` tool to start a child agent on the repo or to send a follow-up to an active task. Follow-up routing is automatic: if the repo already has an active child task (working or paused on input), the orchestrator delivers your `instruction` to that task as a follow-up message; otherwise it starts a new child run.
28
+ Call the `spawn_agent` tool to start a child agent on the repo or to send a follow-up to an active task. Follow-up routing is automatic per (repo, role): if that (repo, role) already has an active child task (working or paused on input), the orchestrator delivers your `instruction` to that task as a follow-up message rather than starting a second run; otherwise it starts a new child run. A repo therefore has at most one active child per role, though agents with different roles can run in the same repo concurrently.
28
29
 
29
- `repo` must be a repository other than the one the parent agent is working in — never delegate into the parent's own repo. A repo has at most one active child: while one is active, any `spawn_agent` call for that repo is routed to it as a follow-up rather than starting a second run.
30
+ `repo` must be a repository other than the one the parent agent is working in — never delegate into the parent's own repo.
30
31
 
31
32
  **Resume/reconstruction is read-only.** If the parent asks you to resume, reconnect, restore, or reconstruct a preserved session without an explicit new change request from the user, do not call `spawn_agent` to continue work. Use `show_agent` only as needed to read status/log context, return a concise restoration summary, and stop. After resuming, wait for explicit user instructions before any child agent makes changes.
32
33
 
@@ -35,6 +36,7 @@ spawn_agent(
35
36
  sessionId: "<sessionId>",
36
37
  repo: "<repo>",
37
38
  instruction: "<instruction>",
39
+ role: "<role, if any>",
38
40
  context: "<context>"
39
41
  )
40
42
  ```
@@ -49,6 +51,7 @@ Call `show_agent` in a loop, passing `waitForTransitionMs: 50000` on every call:
49
51
  show_agent(
50
52
  sessionId: "<sessionId>",
51
53
  repo: "<repo>",
54
+ role: "<role, if any>",
52
55
  waitForTransitionMs: 50000
53
56
  )
54
57
  ```
@@ -65,7 +68,7 @@ After calling `spawn_agent`, parse the structured JSON response:
65
68
 
66
69
  Then poll `show_agent` in a loop with `waitForTransitionMs: 50000`. **Do not pass a `tail` argument** — the tool's default is sized for status polling. Only set `tail` if you have a specific reason (e.g., the default truncated output you actually need to inspect, or you are hunting for an earlier failure that scrolled off). Never ratchet `tail` upward across polls; that is what causes the polling loop to flood your context window.
67
70
 
68
- The response's `children[]` array has a single entry the child for your repo. On it, inspect:
71
+ The response's `children[]` array has one entry per agent matching your query. Find YOUR delegation's entry (match on `role`; absent means the default role) and inspect:
69
72
 
70
73
  - `child.status` — an AcpRunStatus value: one of `'created'`, `'in-progress'`, `'input-required'`, `'permission-required'`, `'completed'`, `'failed'`, `'cancelled'` (British double-L on `'cancelled'`). Note `'permission-required'` and `'input-required'` are DIFFERENT states handled by different cases below — do not conflate them.
71
74
  - `child.inputRequiredQuestion` — populated only when `child.status === 'input-required'`; contains the verbatim question the child agent has asked the parent.
@@ -79,14 +82,14 @@ State machine:
79
82
  - Read `child.inputRequiredQuestion`.
80
83
  - Surface this question verbatim to the parent/user: "The child agent in `{child.repoFullName}` needs input: {child.inputRequiredQuestion}".
81
84
  - Wait for the parent/user to supply an answer.
82
- - Call `spawn_agent` again with the same `repo` and `instruction: <the answer>` — the orchestrator routes it to the active task automatically.
85
+ - Call `spawn_agent` again with the same `repo`, the same `role`, and `instruction: <the answer>` — the orchestrator routes it to that (repo, role)'s active task automatically.
83
86
  - Resume polling.
84
87
 
85
88
  3. `child.status === 'permission-required'` — child is paused waiting for a permission grant decision:
86
89
  - Read `child.pendingPermission` — inspect `harness`, `action`, `target`, `repoFullName`, `scope`, `availableScopes`, and optional `reason`/`rawInput`.
87
90
  - Surface the request to the parent/user: "Child agent in `{repoFullName}` requests `{scope}` permission to run `{action}` on `{target}`."
88
91
  - Wait for the parent/user to decide.
89
- - Call `allow_agent` (to grant) or `deny_agent` (to refuse) with `{ sessionId, repo }` — `allow_agent` also takes `scope` (`'one-time'` or `'session'`) and an optional `reason`; `deny_agent` takes only `{ sessionId, repo }` plus an optional `reason`.
92
+ - Call `allow_agent` (to grant) or `deny_agent` (to refuse) with `{ sessionId, repo, role? }` — `allow_agent` also takes `scope` (`'one-time'` or `'session'`) and an optional `reason`; `deny_agent` takes only an optional `reason` on top.
90
93
  - **Fail-closed:** When you see `permission-required`, you MUST call either `allow_agent` or `deny_agent`. Failing to call one leaves the gate held open until the child's idle timer fires; the child cannot make progress until you decide.
91
94
  - Resume polling.
92
95
 
@@ -96,7 +99,7 @@ State machine:
96
99
 
97
100
  ## Cancelling a running child
98
101
 
99
- To cancel a running child mid-work, call `stop_agent` with the repo. Response:
102
+ To cancel a running child mid-work, call `stop_agent` with the repo (and `role`, per the parameter rule above). Response:
100
103
 
101
104
  ```json
102
105
  {
@@ -118,6 +121,7 @@ When the child agent reaches a terminal status, return a structured summary:
118
121
  ## Polygraph Delegation Result
119
122
 
120
123
  **Repo:** <repo>
124
+ **Role:** <role, or "default" when none was given>
121
125
  **Status:** <success | failed | cancelled>
122
126
  **Session ID:** <sessionId>
123
127
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polygraph/opencode-plugin",
3
- "version": "0.4.41",
3
+ "version": "0.4.43",
4
4
  "description": "AI agent skills and subagents for Polygraph sessions, repository context, and coordination",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
package/server.js CHANGED
@@ -166,33 +166,57 @@ function polygraphCompactionNote(agentSessionId, root) {
166
166
  );
167
167
  }
168
168
 
169
- function readPolygraphSession(
170
- agentSessionId,
171
- root = path.join(homedir(), '.polygraph')
172
- ) {
173
- if (!agentSessionId) {
169
+ // Scan the immediate subdirectories of `baseDir`; for each, `candidatePath`
170
+ // maps the subdirectory name to a candidate parent-sidecar file. Returns the
171
+ // sessionId of the first match, or undefined.
172
+ function scanForParentSidecar(baseDir, candidatePath) {
173
+ if (!existsSync(baseDir)) {
174
174
  return undefined;
175
175
  }
176
176
 
177
- const sidecarsDir = path.join(root, 'sidecars');
178
- if (!existsSync(sidecarsDir)) {
177
+ let entries;
178
+ try {
179
+ entries = readdirSync(baseDir, { withFileTypes: true });
180
+ } catch {
179
181
  return undefined;
180
182
  }
181
183
 
182
- const fileName = `parent-${agentSessionId}.json`;
183
- let polygraphSessionId;
184
- for (const entry of readdirSync(sidecarsDir, { withFileTypes: true })) {
184
+ for (const entry of entries) {
185
185
  if (!entry.isDirectory()) continue;
186
- const candidate = path.join(sidecarsDir, entry.name, fileName);
186
+ const candidate = candidatePath(entry.name);
187
187
  if (existsSync(candidate)) {
188
188
  try {
189
- polygraphSessionId = JSON.parse(readFileSync(candidate, 'utf8')).sessionId;
189
+ return JSON.parse(readFileSync(candidate, 'utf8')).sessionId;
190
190
  } catch {
191
- polygraphSessionId = undefined;
191
+ return undefined;
192
192
  }
193
- break;
194
193
  }
195
194
  }
195
+ return undefined;
196
+ }
197
+
198
+ function readPolygraphSession(
199
+ agentSessionId,
200
+ root = path.join(homedir(), '.polygraph')
201
+ ) {
202
+ if (!agentSessionId) {
203
+ return undefined;
204
+ }
205
+
206
+ const fileName = `parent-${agentSessionId}.json`;
207
+
208
+ // New layout: <sessionsRoot>/<sessionId>/sidecars/parent-<agentSessionId>.json
209
+ // (sessionsRoot = $POLYGRAPH_ROOT or <root>/sessions), with a fallback to
210
+ // the legacy shared sidecars directory for sessions created by older CLIs.
211
+ const sessionsDir = process.env.POLYGRAPH_ROOT?.trim() || path.join(root, 'sessions');
212
+ const legacyDir = path.join(root, 'sidecars');
213
+ const polygraphSessionId =
214
+ scanForParentSidecar(sessionsDir, (sessionId) =>
215
+ path.join(sessionsDir, sessionId, 'sidecars', fileName)
216
+ ) ??
217
+ scanForParentSidecar(legacyDir, (sessionId) =>
218
+ path.join(legacyDir, sessionId, fileName)
219
+ );
196
220
  if (!polygraphSessionId) {
197
221
  return undefined;
198
222
  }
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: adversarial-review
3
+ description: Independent second-opinion review of a Polygraph session's work. USE WHEN user says "adversarial review", "review this session", or when the Polygraph CLI launches an agent with an instruction to load this skill.
4
+
5
+ ---
6
+
7
+ # Adversarial Review
8
+
9
+ 1. **Pick the agent.** Ask whether Claude, Codex, or OpenCode should review — `claude`, `codex`, or `opencode` for `spawn_agent`'s `agent` parameter. Skip if the user already named one.
10
+ 2. **Get the session description.**
11
+ 3. **Get each repo's plan.** Ask each repo's agent to provide the plan.
12
+ 4. **Delegate one reviewer per repo** in parallel, with `role: "reviewer"`. Pass the overall plan and that repo's plan, and ask it to review the code, identify issues, and return a summary. Do the delegation even for the "initiator" repo.
13
+ 5. **Summarize.** Once every review is back, analyze them and present one summary to the user.
14
+ 6. **Ask what next.** Address the feedback, upload the summary via `upload_artifact`, or continue with the session. Skip if the user already said.
15
+ 7. If the user selects "address the feedback", pass each repo's feedback to the repo default agent (not the reviewer). The initiator should fix things itself without delegating.
@@ -163,7 +163,7 @@ For each repo with `ciStatus: FAILED`, branch on the PR's `ci` object from `show
163
163
  show_agent(sessionId: "<session-id>", repo: "frontend")
164
164
  ```
165
165
 
166
- Poll until the child agent's status indicates completion. Use the `tail` parameter to retrieve recent output lines containing the investigation results.
166
+ Poll until the child agent's status indicates completion (pass the same `role` you spawned with, if any). Use the `tail` parameter to retrieve recent output lines containing the investigation results.
167
167
 
168
168
  4. Collect each child agent's response from the status output. If a child agent fails or gets stuck, use `stop_agent` to terminate it and skip that repo.
169
169
 
@@ -20,9 +20,9 @@ Polygraph functionality is available via both MCP tools and CLI commands. Use wh
20
20
  | --- | --- | --- |
21
21
  | `list_repos` | `polygraph repo list` | Discover candidate repositories. Candidate entries do not include repository descriptions; use `semanticQuery` for natural-language discovery. |
22
22
  | `start_session` | `polygraph session start --repo <ids>` | Initialize a Polygraph session with selected repositories |
23
- | `spawn_agent` | — | Start a new child task or send a follow-up to an active task in another repository. Input: `{ sessionId, repo, instruction, context? }`. Output: `{ taskId, message, status: 'delegated' }`. Follow-up routing is automatic: if the repo already has an active child task, the instruction is delivered to it as a follow-up message; otherwise a new child run starts. A repo has at most one active child at a time. A session resume or reconstruction is read-only context restoration; after resuming, do not use `spawn_agent` to continue changes unless the user explicitly asks for changes. |
24
- | `show_agent` | — | Poll the status of the specified repo's child (`repo` is required — one call covers one repo). Output: `{ children: PolygraphChildStatusItem[] }` with a single entry for that repo; the item exposes `repositoryId`, `repoFullName`, `status`, `lastOutputLines`, `durationMs`, `instruction`, `agentType?`, `inputRequiredQuestion?`. `status` is an AcpRunStatus: `'created' \| 'in-progress' \| 'input-required' \| 'permission-required' \| 'completed' \| 'failed' \| 'cancelled'` (British double-L on `'cancelled'`). `inputRequiredQuestion` is populated only when `status === 'input-required'`. |
25
- | `stop_agent` | — | Cancel an in-progress child. Output: `{ taskId, state: 'cancelled', sessionPreserved: true, output, message }`. Because `sessionPreserved: true`, the preserved agent session can be restored later for context, but resume must wait for explicit user instructions before making changes. |
23
+ | `spawn_agent` | — | Start a new child task or send a follow-up to an active task in another repository. Input: `{ sessionId, repo, instruction, role?, context? }`. Output: `{ taskId, message, status: 'delegated' }`. `role` selects the agent slot within the repo (see "Agent roles"). Follow-up routing is automatic per (repo, role): if that (repo, role) already has an active child task, the instruction is delivered to it as a follow-up message; otherwise a new child run starts. A session resume or reconstruction is read-only context restoration; after resuming, do not use `spawn_agent` to continue changes unless the user explicitly asks for changes. |
24
+ | `show_agent` | — | Poll one repo's child status (`repo` required — one repo per call; pass `role` to narrow to that role's agent). Returns `{ children: [...] }` with one self-describing entry per matching agent, exposing `status`, `lastOutputLines`, `role` (absent for the default role), `inputRequiredQuestion`, etc. Full status enum and the poll/state-machine flow are under "Multi-turn tasks". |
25
+ | `stop_agent` | — | Cancel an in-progress child. Input: `{ sessionId, repo, role? }`. Output: `{ taskId, state: 'cancelled', sessionPreserved: true, output, message }`. Because `sessionPreserved: true`, the preserved agent session can be restored later for context, but resume must wait for explicit user instructions before making changes. |
26
26
  | `push_branch` | — | Push a local git branch to the remote repository. For the repo you are in, this pushes from your current checkout. Requires a session description. |
27
27
  | `create_pr` | — | Create draft PRs with session metadata linking related PRs |
28
28
  | `show_session` | `polygraph session show <id> [--details]` | Query status of the current session. Use details when session summary, repo IDs, PR URLs, and PR descriptions are needed. |
@@ -71,10 +71,10 @@ After logging in (or if logged in but no org is selected), use `polygraph accoun
71
71
  The delegate/monitor/stop steps apply only when working across repos. A single-repo session skips them and still benefits from shared progress, resume, and CI visibility.
72
72
 
73
73
  0. **Initialize or join Polygraph session** - If you were spawned inside an existing session (the startup banner names a session ID), reuse it. Call `show_session` first; if it already has repos and the user did not ask to add more, you're done. If the user asks to add exact repo refs, call `add_repo` directly with those refs and skip candidate discovery. If the session has no repos and no exact refs were provided, launch the `polygraph-init-subagent` with that `sessionId` so it discovers candidates and uses `add_repo` (NOT `start_session`). Only when there is no session ID at all should the init subagent create a new session.
74
- 1. **Delegate work to each repo** - Use the `polygraph-delegate-subagent` to start child agents in other repositories. Delegate only to *other* repos — never to the repo you are in; work on it directly (your regular subagents are fine for local work — only Polygraph delegation is reserved for other repos). Parallel delegation across repos is encouraged, but only one active child per repo. Choose the Simple (fire-and-forget) or Multi-turn (interactive) pattern described below based on whether the child may need clarification.
74
+ 1. **Delegate work to each repo** - Use the `polygraph-delegate-subagent` to start child agents in other repositories. Delegate only to *other* repos — never to the repo you are in; work on it directly (your regular subagents are fine for local work — only Polygraph delegation is reserved for other repos). Parallel delegation across repos is encouraged; a repo can also host multiple concurrent agents under distinct roles (see "Agent roles"). Choose the Simple (fire-and-forget) or Multi-turn (interactive) pattern described below based on whether the child may need clarification.
75
75
 
76
- 4. **Monitor child agents** - Use `show_agent` to poll one repo's child (`repo` is required) and read its `status` and `lastOutputLines` from the single-entry `children[]` array.
77
- 5. **Stop child agents** (if needed) - Use `stop_agent` to cancel an in-progress child agent. The underlying agent session is preserved for later read-only context restoration; after a resume, wait for explicit user instructions before making changes.
76
+ 4. **Monitor child agents** - Use `show_agent` to poll one repo's children (`repo` is required; pass `role` to narrow to one agent) and read each entry's `status` and `lastOutputLines` from the `children[]` array.
77
+ 5. **Stop child agents** (if needed) - Use `stop_agent` (with `role` when targeting a non-default agent) to cancel an in-progress child agent. The agent's session is preserved for later read-only context restoration; after a resume, wait for explicit user instructions before making changes.
78
78
  6. **Push branches** - Use `push_branch` after making commits. A required `description` must follow the Session Description Policy.
79
79
  7. **Update session description** - Use `update_session` to update the session description; must follow the Session Description Policy. Independent of PR creation or mark-ready.
80
80
  8. **Create draft PRs** - Use `create_pr` to create linked draft PRs. Always pass `description` following the Session Description Policy.
@@ -202,15 +202,23 @@ polygraph session search --sha a1b2c3d
202
202
  - Multiple sessions may match a sha. They come back newest first — pick the most relevant one and report the others if they matter.
203
203
  - **A "no match" result does NOT prove the commit had no work behind it.** Not every commit is linked to an explicit session: commits pushed directly (rather than via an ingested PR) and gaps in ingestion metadata mean the sha may simply not be recorded, and implicit sessions are never returned. Report "no linked session found for that sha" — never assert that no work exists behind the commit.
204
204
 
205
+ ## Agent roles
206
+
207
+ A repository in a session can host multiple child agents at once, distinguished by **role**:
208
+
209
+ - **Purpose.** Roles let independent streams of work run concurrently in one repo — e.g. a default agent implementing a feature while a `reviewer` or `ci-investigator` runs alongside. Each (repo, role) pair has at most one active child.
210
+ - **Default role.** An omitted `role` means the default role: `spawn_agent` without `role` starts or follows up with the repo's default-role agent.
211
+ - **Logs.** Only default-role agents upload logs to the cloud and appear in the multiplexed log stream (`polygraph session logs`). Inspect non-default agents locally with `polygraph agent attach --role <role>`.
212
+
205
213
  ## Simple tasks (fire-and-forget)
206
214
 
207
215
  Use this pattern when the task is well-defined and the child is not expected to need clarification. It is a single-round delegation: kick it off, poll until terminal, then push branch + create PR.
208
216
 
209
217
  **CRITICAL:** `spawn_agent` and `show_agent` MUST ALWAYS be called via `@polygraph-delegate-subagent`, NEVER directly from the main conversation. Direct calls flood the context window with polling noise and degrade the user experience. This is a hard requirement, not a suggestion.
210
218
 
211
- 1. For each repo, invoke `@polygraph-delegate-subagent` with `sessionId`, `repo`, `instruction`, and optional `context`. The subagent calls `spawn_agent`, then polls `show_agent` via chained `waitForTransitionMs` long-poll calls until terminal.
212
- 2. Delegate to multiple repos in parallel by launching multiple `@polygraph-delegate-subagent` invocations — one delegation per repo at a time.
213
- 3. The subagent watches `child.status` on the single `children[]` entry for its repo and exits when it sees a terminal status — typically `'completed'` or `'failed'` (and `'cancelled'` if it was stopped).
219
+ 1. For each repo, invoke `@polygraph-delegate-subagent` with `sessionId`, `repo`, `instruction`, optional `role`, and optional `context`. The subagent calls `spawn_agent`, then polls `show_agent` via chained `waitForTransitionMs` long-poll calls until terminal.
220
+ 2. Delegate to multiple repos in parallel by launching multiple `@polygraph-delegate-subagent` invocations — one delegation per (repo, role) at a time.
221
+ 3. The subagent watches `child.status` on its delegation's `children[]` entry the one matching its repo and role — and exits when it sees a terminal status — typically `'completed'` or `'failed'` (and `'cancelled'` if it was stopped).
214
222
  4. Once all subagents report a terminal status, continue to `push_branch` + `create_pr`.
215
223
 
216
224
  Use Simple when the task is well-defined and the child will not need clarification.
@@ -219,13 +227,13 @@ Use Simple when the task is well-defined and the child will not need clarificati
219
227
 
220
228
  Use this pattern when the child may need clarification, the task is exploratory, or interactive collaboration is desired. The orchestrator exposes paused children via the `'input-required'` status.
221
229
 
222
- 1. Call `spawn_agent` with the initial `instruction`. Parse the response:
230
+ 1. Call `spawn_agent` with the initial `instruction` (and optionally `role`). Parse the response:
223
231
 
224
232
  ```json
225
233
  { "taskId": "…", "message": "…", "status": "delegated" }
226
234
  ```
227
235
 
228
- 2. Poll `show_agent` via chained `waitForTransitionMs` long-poll calls (`repo` is required). The response shape is `{ children: PolygraphChildStatusItem[] }` with a single entry for that repo. On it, inspect:
236
+ 2. Poll `show_agent` via chained `waitForTransitionMs` long-poll calls (`repo` is required; pass the same `role` you spawned with to narrow to that agent). The response shape is `{ children: PolygraphChildStatusItem[] }` with one entry per matching agent in that repo. On your delegation's entry, inspect:
229
237
 
230
238
  - `child.status` — one of `'created'`, `'in-progress'`, `'input-required'`, `'permission-required'`, `'completed'`, `'failed'`, `'cancelled'` (British double-L on `'cancelled'`).
231
239
  - `child.inputRequiredQuestion` — populated only when `child.status === 'input-required'`.
@@ -235,13 +243,13 @@ Use this pattern when the child may need clarification, the task is exploratory,
235
243
  Drive the state machine:
236
244
 
237
245
  - `child.status === 'in-progress'` or `'created'` — continue polling.
238
- - `child.status === 'input-required'` — read `child.inputRequiredQuestion`, surface it to the user verbatim (e.g. "The child agent in `{child.repoFullName}` needs input: {child.inputRequiredQuestion}"), get the answer, then call `spawn_agent` again with the same `repo` and `instruction: <answer>` — it is routed to the active task automatically. Continue polling.
246
+ - `child.status === 'input-required'` — read `child.inputRequiredQuestion`, surface it to the user verbatim (e.g. "The child agent in `{child.repoFullName}` needs input: {child.inputRequiredQuestion}"), get the answer, then call `spawn_agent` again with the same `repo`, the same `role`, and `instruction: <answer>` — it is routed to that (repo, role)'s active task automatically. Continue polling.
239
247
  - `child.status === 'completed'` — read `child.lastOutputLines`, proceed to `push_branch` + `create_pr`.
240
248
  - `child.status === 'failed'` — read `child.lastOutputLines`, surface the failure.
241
249
  - `child.status === 'cancelled'` — the child was stopped via `stop_agent`; see below.
242
250
  - `child.status === 'permission-required'` — the child is waiting on a permission decision; see "Handling permission requests" below.
243
251
 
244
- 3. To abort mid-flight, call `stop_agent` with `{ sessionId, repo }`. The response is:
252
+ 3. To abort mid-flight, call `stop_agent` with `{ sessionId, repo, role? }`. The response is:
245
253
 
246
254
  ```json
247
255
  {
@@ -253,7 +261,7 @@ Use this pattern when the child may need clarification, the task is exploratory,
253
261
  }
254
262
  ```
255
263
 
256
- Because `sessionPreserved: true`, the preserved agent session can be restored later for context. After resuming, do not make changes or continue prior work until the user explicitly asks for changes.
264
+ Because `sessionPreserved: true`, the stopped agent's session can be restored later for context. After resuming, do not make changes or continue prior work until the user explicitly asks for changes.
257
265
 
258
266
  Use Multi-turn when the child may need clarification, the task is exploratory, or interactive collaboration is desired. Otherwise use Simple.
259
267
 
@@ -267,7 +275,7 @@ Child agents running in other repositories may pause and ask the parent agent wh
267
275
 
268
276
  **Native path (MCP permission dialog):** If your MCP client supports the permission dialog UI, the user picks directly in that dialog — you (the agent) won't see `permission-required` tasks in that flow.
269
277
 
270
- **Structured fallback path:** When `cloud_polygraph_child_status` reports a task in `permission-required` state, read the `pendingPermission` object on that task (carries `harness`, `action`, `target`, `repositoryId`, `repoFullName`, `scope`, `availableScopes`, optional `reason`, optional `rawInput`), then call `allow_agent` (to grant the requested action) or `deny_agent` (to refuse it) with the `{sessionId, repo}` of the child agent.
278
+ **Structured fallback path:** When `cloud_polygraph_child_status` reports a task in `permission-required` state, read the `pendingPermission` object on that task (carries `harness`, `action`, `target`, `repositoryId`, `repoFullName`, `scope`, `availableScopes`, optional `reason`, optional `rawInput`), then call `allow_agent` (to grant the requested action) or `deny_agent` (to refuse it) with the `{sessionId, repo, role?}` of the child agent.
271
279
 
272
280
  ### Answering a permission request
273
281
 
@@ -276,6 +284,7 @@ Child agents running in other repositories may pause and ask the parent agent wh
276
284
  {
277
285
  "sessionId": "...",
278
286
  "repo": "org/repo-name",
287
+ "role": "reviewer", // optional
279
288
  "scope": "session", // or "one-time"
280
289
  "reason": "Trusted local repo" // optional
281
290
  }
@@ -285,6 +294,7 @@ Child agents running in other repositories may pause and ask the parent agent wh
285
294
  {
286
295
  "sessionId": "...",
287
296
  "repo": "org/repo-name",
297
+ "role": "reviewer", // optional
288
298
  "reason": "Action looks risky" // optional
289
299
  }
290
300
  // — call `deny_agent` with this payload.
@@ -307,29 +317,14 @@ When polling `cloud_polygraph_child_status` (or `show_agent`), treat `permission
307
317
  1. Read `child.pendingPermission` — inspect `harness`, `action`, `target`, `repoFullName`, and `scope`.
308
318
  2. Surface the request to the user: "Child agent in `{repoFullName}` requests `{scope}` permission to run `{action}` on `{target}`."
309
319
  3. Obtain the user's decision.
310
- 4. Call `allow_agent` (to grant) or `deny_agent` (to refuse) with `{ sessionId, repo }`.
320
+ 4. Call `allow_agent` (to grant) or `deny_agent` (to refuse) with `{ sessionId, repo, role? }`.
311
321
  5. Resume polling.
312
322
 
313
- ### 2. Push Branches
323
+ ### 2. Publish Changes (Push Branches, Create PRs, Mark Ready)
314
324
 
315
- Once work is complete in a repository, push the branch using `push_branch`. This must be done before creating a PR.
325
+ Publishing covers the branch-to-PR flow: `push_branch` (push local commits; must precede PR creation), `create_pr` (linked draft PRs, including fork PRs via `targetRepository`), `mark_pr_ready` (transition drafts to OPEN), and `associate_pr` (link PRs created outside Polygraph).
316
326
 
317
- `push_branch` pushes from the local checkout: for the repo you are in, that is your current working directory with your commits; for delegated repos, it is the Polygraph-managed clone the child agent worked in. There is no separate session copy of the current repo.
318
-
319
- **Parameters:**
320
-
321
- - `sessionId` (required): The Polygraph session ID
322
- - `repo` (required): Repository name or repository ID to push from
323
- - `branch` (required): Branch name to push to remote
324
- - `description` (required): A session description is required. Must follow the Session Description Policy.
325
-
326
- ```
327
- push_branch(
328
- sessionId: "<session-id>",
329
- repo: "org/repo-name",
330
- branch: "polygraph/ad5fa-add-user-preferences"
331
- )
332
- ```
327
+ **Whenever you push a branch, create or associate a PR, or mark PRs ready, read [`reference/publish-changes.md`](reference/publish-changes.md) first.** That reference file holds the full flow: parameters and examples for each tool, `push_branch` local-checkout semantics, the PR title format rules, and the session-URL printing steps. `push_branch`, `create_pr`, and `associate_pr` all require a `description` following the Session Description Policy below.
333
328
 
334
329
  ### Session Description Policy
335
330
 
@@ -337,121 +332,18 @@ push_branch(
337
332
 
338
333
  **Whenever you write or update a session description, read [`reference/session-description.md`](reference/session-description.md) first.** That reference file holds the full policy: the canonical Markdown-heading template (`## Goal` / `## Current progress` / `## What worked` / `## Next steps`), the dual-audience guidance (humans in the web UI now, agents reconstructing history later), and the formatting building blocks the app renders (callouts, tables, mermaid, links, `link_reference`).
339
334
 
340
- ### 3. Create Draft PRs
341
-
342
- Create PRs for all repositories at once using `create_pr`. PRs are created as drafts with session metadata that links related PRs across repos. Branches must be pushed first. For fork PR creation or registration, include `targetRepository` on the PR spec to identify the repository that should receive the PR.
343
-
344
- **Parameters:**
345
-
346
- - `sessionId` (required): The Polygraph session ID
347
- - `prs` (required): Array of PR specifications, each containing:
348
- - `owner` (required): GitHub repository owner
349
- - `repo` (required): GitHub repository name
350
- - `title` (required): PR title
351
- - `body` (required): PR description (session metadata is appended automatically)
352
- - `branch` (required): Branch name that was pushed
353
- - `targetRepository` (optional): Target GitHub repository for fork PR creation or registration, as `owner/repo`. Omit for same-repository PRs.
354
- - `description` (required): Must follow the Session Description Policy.
355
-
356
- **PR title format (applies to parent and child agents):**
335
+ ### 3. Get Current Polygraph Session
357
336
 
358
- - PR titles become squash-merge commit messages in most repos. They MUST follow the target repo's commit convention (e.g., Conventional Commits: `<type>(<scope>): <subject>`).
359
- - Do NOT add agent-identifier prefixes such as `[codex]`, `[claude]`, or `[opencode]` to PR titles. These prefixes violate commit-lint rules and pollute the git history.
360
-
361
- ```
362
- create_pr(
363
- sessionId: "<session-id>",
364
- prs: [
365
- {
366
- owner: "org",
367
- repo: "frontend",
368
- title: "feat: Add user preferences UI",
369
- body: "Part of multi-repo user preferences feature",
370
- branch: "polygraph/ad5fa-add-user-preferences"
371
- },
372
- {
373
- owner: "org",
374
- repo: "backend",
375
- title: "feat: Add user preferences API",
376
- body: "Part of multi-repo user preferences feature",
377
- branch: "polygraph/ad5fa-add-user-preferences"
378
- }
379
- ]
380
- )
381
- ```
382
-
383
- For fork PR creation or registration, keep `owner` and `repo` set to the source repository that owns the pushed branch and set `targetRepository` to the target repository:
384
-
385
- ```
386
- create_pr(
387
- sessionId: "<session-id>",
388
- prs: [
389
- {
390
- owner: "contributor",
391
- repo: "frontend-fork",
392
- targetRepository: "org/frontend",
393
- title: "feat: Add user preferences UI",
394
- body: "Part of multi-repo user preferences feature",
395
- branch: "polygraph/ad5fa-add-user-preferences"
396
- }
397
- ]
398
- )
399
- ```
400
-
401
- **After creating PRs**, always print the Polygraph session URL:
402
-
403
- ```
404
- **Polygraph session:** POLYGRAPH_SESSION_URL
405
- ```
406
-
407
- ### 4. Get Current Polygraph Session
408
-
409
- Check the details of a session using `show_session` or `polygraph session show --details <session-id>`. Returns the full session state including repositories, PRs, CI status, and the Polygraph session URL.
337
+ Check the details of a session using `show_session` or `polygraph session show --details <session-id>`. Returns the full session state basic metadata like id, url & description timeline, plus the connected repositories, `pullRequests[]`, per-PR `ciStatus`, and `session.linkedReferences`.
410
338
 
411
339
  **Parameters:**
412
340
 
413
341
  - `sessionId` (required): The Polygraph session ID
414
342
 
415
- **Returns:**
343
+ **CI status rules:**
416
344
 
417
- - `session.sessionId`: The session ID
418
- - `session.polygraphSessionUrl`: URL to the Polygraph session UI
419
- - `session.description`: DescriptionItem[] timeline describing the session.
420
- - `session.agentSessionId`: The agent CLI session ID — captured automatically by the MCP server (null if no agent has run yet).
421
- - `session.linkedReferences`: Array of references linked to this session
422
- - Session repository entries: Array of connected repositories, each with:
423
- - `id`: Repository ID
424
- - `name`: Repository name
425
- - `defaultBranch`: Default branch (e.g., `main`)
426
- - `vcsConfiguration.repositoryFullName`: Full repo name (e.g., `org/repo`)
427
- - `vcsConfiguration.provider`: VCS provider (e.g., `GITHUB`)
428
- - description field: AI-generated description of what this repository does (may be null)
429
- - `initiator`: Whether this repository initiated the session
430
- - `session.dependencyGraph`: Graph of repository dependency `edges`
431
- - `session.pullRequests[]`: Array of PRs, each with:
432
- - `url`: PR URL
433
- - `branch`: Branch name
434
- - `baseBranch`: Target branch
435
- - `title`: PR title
436
- - `status`: One of `DRAFT`, `OPEN`, `MERGED`, `CLOSED`
437
- - `repoId`: Associated repository ID
438
- - `relatedPRs`: Array of related PR URLs across repos
439
- - `session.ciStatus`: CI pipeline status keyed by PR ID, each containing:
440
- - `status`: One of `SUCCEEDED`, `FAILED`, `IN_PROGRESS`, `NOT_STARTED` (null if no CIPE and no external CI)
441
- - `cipeUrl`: URL to the CI pipeline execution details (null if no CIPE). This is a human-facing Nx Cloud web link — display it to the user, but never fetch, curl, or poll it directly; CIPE data is only accessible programmatically via the Nx MCP `ci_information` tool
442
- - `completedAt`: Epoch millis timestamp, set only when the CIPE has completed (null otherwise)
443
- - `selfHealingStatus`: The self-healing fix status string from Nx Cloud's AI fix feature (null if no AI fix exists)
444
- - `externalCIRuns`: Array of external CI runs (present when no CIPE but external CI data exists, e.g., GitHub Actions). Each run contains:
445
- - `runId`: GitHub Actions run ID
446
- - `name`: Workflow name
447
- - `status`: Run status (`completed`, `in_progress`, `queued`)
448
- - `conclusion`: Run conclusion (`success`, `failure`, `cancelled`, `timed_out`, or null)
449
- - `url`: GitHub Actions run URL
450
- - `jobs`: Array of jobs in the run, each with:
451
- - `jobId`: Job ID (use with `get_ci_logs`)
452
- - `name`: Job name
453
- - `status`: Job status
454
- - `conclusion`: Job conclusion (or null)
345
+ - `ciStatus[prId].cipeUrl` (null if no CIPE) is a human-facing Nx Cloud web link — display it to the user, but never fetch, curl, or poll it directly; CIPE data is only accessible programmatically via the Nx MCP `ci_information` tool.
346
+ - When no CIPE exists, external CI data (e.g., GitHub Actions) appears in `ciStatus[prId].externalCIRuns[]` as runs with nested `jobs[]`; each job's `jobId` is the input for `get_ci_logs`.
455
347
 
456
348
  ```
457
349
  show_session(sessionId: "<session-id>")
@@ -498,67 +390,7 @@ link_reference({
498
390
 
499
391
  The canonical MCP parameters are `{ sessionId, reference }`. There is no unlink command.
500
392
 
501
- ### 5. Mark PRs Ready
502
-
503
- Once all changes are verified and ready to merge, use `mark_pr_ready` to transition PRs from DRAFT to OPEN status.
504
-
505
- **Parameters:**
506
-
507
- - `sessionId` (required): The Polygraph session ID
508
- - `prUrls` (required): Array of PR URLs to mark as ready for review
509
-
510
- ```
511
- mark_pr_ready(
512
- sessionId: "<session-id>",
513
- prUrls: [
514
- "https://github.com/org/frontend/pull/123",
515
- "https://github.com/org/backend/pull/456"
516
- ]
517
- )
518
- ```
519
-
520
- **After marking PRs as ready**, always print the Polygraph session URL so the user can easily access the session overview. Call `show_session` and display:
521
-
522
- ```
523
- **Polygraph session:** POLYGRAPH_SESSION_URL
524
- ```
525
-
526
- Where `POLYGRAPH_SESSION_URL` is from `polygraphSessionUrl` in the response.
527
-
528
- ### 6. Associate Existing PRs
529
-
530
- Use `associate_pr` to link pull requests that were created outside of Polygraph (e.g., manually or by CI) to the current session. This is useful when PRs already exist for the branches in the session and you want Polygraph to track them.
531
-
532
- Provide either a `prUrl` to associate a specific PR, or a `branch` name plus `repo` to find and associate PRs for a source repository.
533
-
534
- **Parameters:**
535
-
536
- - `sessionId` (required): The Polygraph session ID
537
- - `prUrl` (optional): URL of an existing pull request to associate
538
- - `branch` (optional): Branch name to find and associate PRs for
539
- - `repo` (optional): Source repository for branch-based association. Required when using `branch` in a multi-repo session.
540
- - `description` (required): Must follow the Session Description Policy.
541
-
542
- ```
543
- associate_pr(
544
- sessionId: "<session-id>",
545
- prUrl: "https://github.com/org/repo/pull/123"
546
- )
547
- ```
548
-
549
- Or by branch:
550
-
551
- ```
552
- associate_pr(
553
- sessionId: "<session-id>",
554
- repo: "org/repo",
555
- branch: "feature/my-changes"
556
- )
557
- ```
558
-
559
- **Returns** the list of PRs now associated with the session.
560
-
561
- ### 7. Add Repositories to a Session
393
+ ### 4. Add Repositories to a Session
562
394
 
563
395
  Use `add_repo` to add repositories to an existing Polygraph session after it has already started.
564
396
 
@@ -578,7 +410,7 @@ add_repo(
578
410
  )
579
411
  ```
580
412
 
581
- ### 8. Archive Session
413
+ ### 5. Archive Session
582
414
 
583
415
  **IMPORTANT: Only call this tool when the user explicitly asks to archive or close the session.** Do not archive sessions automatically as part of the workflow.
584
416
 
@@ -692,6 +524,6 @@ If the session has a description timeline, also display:
692
524
 
693
525
  1. **NEVER call `spawn_agent` or `show_agent` directly**. These MUST ALWAYS go through `@polygraph-delegate-subagent`.
694
526
 
695
- 1. **Use `stop_agent` to clean up** — Stop child agents that are stuck or no longer needed. The child's session is preserved (`sessionPreserved: true`) so the context can be restored later, but after resuming you must wait for explicit user instructions before making changes.
527
+ 1. **Use `stop_agent` to clean up** — Stop child agents that are stuck or no longer needed (pass `role` to target a non-default agent). The child's session is preserved (`sessionPreserved: true`) so the context can be restored later, but after resuming you must wait for explicit user instructions before making changes.
696
528
  1. **Only archive sessions when asked** — Only call `archive_session` when the user explicitly requests it. Archiving hides the session from active lists; it can still be resumed later.
697
529
 
@@ -0,0 +1,151 @@
1
+ # Publishing Changes Reference
2
+
3
+ The branch-to-PR flow: push branches, create draft PRs, mark them ready, and associate PRs created outside Polygraph. `push_branch`, `create_pr`, and `associate_pr` all require a `description` following the Session Description Policy — read [`session-description.md`](session-description.md) before writing one.
4
+
5
+ ## Push Branches
6
+
7
+ Once work is complete in a repository, push the branch using `push_branch`. This must be done before creating a PR.
8
+
9
+ `push_branch` pushes from the local checkout: for the repo you are in, that is your current working directory with your commits; for delegated repos, it is the Polygraph-managed clone the child agent worked in. There is no separate session copy of the current repo.
10
+
11
+ **Parameters:**
12
+
13
+ - `sessionId` (required): The Polygraph session ID
14
+ - `repo` (required): Repository name or repository ID to push from
15
+ - `branch` (required): Branch name to push to remote
16
+ - `description` (required): A session description is required. Must follow the Session Description Policy.
17
+
18
+ ```
19
+ push_branch(
20
+ sessionId: "<session-id>",
21
+ repo: "org/repo-name",
22
+ branch: "polygraph/ad5fa-add-user-preferences"
23
+ )
24
+ ```
25
+
26
+ ## Create Draft PRs
27
+
28
+ Create PRs for all repositories at once using `create_pr`. PRs are created as drafts with session metadata that links related PRs across repos. Branches must be pushed first. For fork PR creation or registration, include `targetRepository` on the PR spec to identify the repository that should receive the PR.
29
+
30
+ **Parameters:**
31
+
32
+ - `sessionId` (required): The Polygraph session ID
33
+ - `prs` (required): Array of PR specifications, each containing:
34
+ - `owner` (required): GitHub repository owner
35
+ - `repo` (required): GitHub repository name
36
+ - `title` (required): PR title
37
+ - `body` (required): PR description (session metadata is appended automatically)
38
+ - `branch` (required): Branch name that was pushed
39
+ - `targetRepository` (optional): Target GitHub repository for fork PR creation or registration, as `owner/repo`. Omit for same-repository PRs.
40
+ - `description` (required): Must follow the Session Description Policy.
41
+
42
+ **PR title format (applies to parent and child agents):**
43
+
44
+ - PR titles become squash-merge commit messages in most repos. They MUST follow the target repo's commit convention (e.g., Conventional Commits: `<type>(<scope>): <subject>`).
45
+ - Do NOT add agent-identifier prefixes such as `[codex]`, `[claude]`, or `[opencode]` to PR titles. These prefixes violate commit-lint rules and pollute the git history.
46
+
47
+ ```
48
+ create_pr(
49
+ sessionId: "<session-id>",
50
+ prs: [
51
+ {
52
+ owner: "org",
53
+ repo: "frontend",
54
+ title: "feat: Add user preferences UI",
55
+ body: "Part of multi-repo user preferences feature",
56
+ branch: "polygraph/ad5fa-add-user-preferences"
57
+ },
58
+ {
59
+ owner: "org",
60
+ repo: "backend",
61
+ title: "feat: Add user preferences API",
62
+ body: "Part of multi-repo user preferences feature",
63
+ branch: "polygraph/ad5fa-add-user-preferences"
64
+ }
65
+ ]
66
+ )
67
+ ```
68
+
69
+ For fork PR creation or registration, keep `owner` and `repo` set to the source repository that owns the pushed branch and set `targetRepository` to the target repository:
70
+
71
+ ```
72
+ create_pr(
73
+ sessionId: "<session-id>",
74
+ prs: [
75
+ {
76
+ owner: "contributor",
77
+ repo: "frontend-fork",
78
+ targetRepository: "org/frontend",
79
+ title: "feat: Add user preferences UI",
80
+ body: "Part of multi-repo user preferences feature",
81
+ branch: "polygraph/ad5fa-add-user-preferences"
82
+ }
83
+ ]
84
+ )
85
+ ```
86
+
87
+ **After creating PRs**, always print the Polygraph session URL:
88
+
89
+ ```
90
+ **Polygraph session:** POLYGRAPH_SESSION_URL
91
+ ```
92
+
93
+ ## Mark PRs Ready
94
+
95
+ Once all changes are verified and ready to merge, use `mark_pr_ready` to transition PRs from DRAFT to OPEN status.
96
+
97
+ **Parameters:**
98
+
99
+ - `sessionId` (required): The Polygraph session ID
100
+ - `prUrls` (required): Array of PR URLs to mark as ready for review
101
+
102
+ ```
103
+ mark_pr_ready(
104
+ sessionId: "<session-id>",
105
+ prUrls: [
106
+ "https://github.com/org/frontend/pull/123",
107
+ "https://github.com/org/backend/pull/456"
108
+ ]
109
+ )
110
+ ```
111
+
112
+ **After marking PRs as ready**, always print the Polygraph session URL so the user can easily access the session overview. Call `show_session` and display:
113
+
114
+ ```
115
+ **Polygraph session:** POLYGRAPH_SESSION_URL
116
+ ```
117
+
118
+ Where `POLYGRAPH_SESSION_URL` is from `polygraphSessionUrl` in the response.
119
+
120
+ ## Associate Existing PRs
121
+
122
+ Use `associate_pr` to link pull requests that were created outside of Polygraph (e.g., manually or by CI) to the current session. This is useful when PRs already exist for the branches in the session and you want Polygraph to track them.
123
+
124
+ Provide either a `prUrl` to associate a specific PR, or a `branch` name plus `repo` to find and associate PRs for a source repository.
125
+
126
+ **Parameters:**
127
+
128
+ - `sessionId` (required): The Polygraph session ID
129
+ - `prUrl` (optional): URL of an existing pull request to associate
130
+ - `branch` (optional): Branch name to find and associate PRs for
131
+ - `repo` (optional): Source repository for branch-based association. Required when using `branch` in a multi-repo session.
132
+ - `description` (required): Must follow the Session Description Policy.
133
+
134
+ ```
135
+ associate_pr(
136
+ sessionId: "<session-id>",
137
+ prUrl: "https://github.com/org/repo/pull/123"
138
+ )
139
+ ```
140
+
141
+ Or by branch:
142
+
143
+ ```
144
+ associate_pr(
145
+ sessionId: "<session-id>",
146
+ repo: "org/repo",
147
+ branch: "feature/my-changes"
148
+ )
149
+ ```
150
+
151
+ **Returns** the list of PRs now associated with the session.
@@ -26,7 +26,7 @@ Invoke the CLI as `${POLYGRAPH_CLI:-polygraph}` in every command: when the sessi
26
26
 
27
27
  1. `${POLYGRAPH_CLI:-polygraph} session show --details <sessionId>` — metadata, description timeline, repositories, PRs. The description timeline often already summarizes goals and outcomes; mine it before reading transcripts.
28
28
  2. Duplicate-work check: if the metadata shows this session pursuing the SAME task as the current one (not merely related work) and it is unfinished or recently active, do NOT read the transcripts. Return the debrief section immediately, with `**DUPLICATE WORK IN FLIGHT**` as the first line after the heading, followed by the session's status and last activity, one line of evidence for the match, and what resuming it would restore. The parent halts and asks the user to choose between resuming that session and continuing the current one, so speed matters more than depth here.
29
- 3. `${POLYGRAPH_CLI:-polygraph} session logs -s <sessionId> --all --tail none > "$TMPDIR/<sessionId>-logs.txt" 2>&1` — the parent transcript plus every child transcript, rendered as plain text, in ONE call. Then read the file directly (with offsets for large files). Do NOT fetch `--json` and do NOT query the transcript with node/python one-liners — reading the rendered text is faster and you extract while reading.
29
+ 3. `${POLYGRAPH_CLI:-polygraph} session logs -s <sessionId> --all --tail none > "$TMPDIR/<sessionId>-logs.txt" 2>&1` — the parent transcript plus every child transcript, rendered as plain text, in ONE call. Then read the file directly (with offsets for large files). Do NOT fetch `--json` and do NOT query the transcript with node/python one-liners — reading the rendered text is faster and you extract while reading. **Coverage caveat:** `session logs --all` covers default-role children only — agents spawned with a `role` keep their transcripts local to the machine that ran them (viewable there via `polygraph agent attach --role <role>`); if the session used such agents, note in the debrief that their work is not visible in these logs.
30
30
  4. Write the debrief section (format below).
31
31
 
32
32
  Large transcripts: read the file in a few large chunks, prioritizing user prompts, assistant text and final messages, tool errors and failure events, and task notifications. Routine tool-use noise (file reads, searches) is safe to skim. Do not make repeated small queries against the transcript; each round trip costs more than reading a bigger chunk.