@polygraph/codex-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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygraph",
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
  "author": {
6
6
  "name": "Narwhal Technologies Inc",
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
@@ -16,13 +16,14 @@ The main agent provides these parameters in the prompt:
16
16
  | `sessionId` | The Polygraph session ID |
17
17
  | `repo` | Repository to delegate to (e.g., `org/repo-name`) |
18
18
  | `instruction` | The task instruction for the child agent |
19
+ | `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. |
19
20
  | `context` | (Optional) Additional context to pass to the child agent |
20
21
 
21
22
  ## Delegating work
22
23
 
23
- 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.
24
+ 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.
24
25
 
25
- `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.
26
+ `repo` must be a repository other than the one the parent agent is working in — never delegate into the parent's own repo.
26
27
 
27
28
  **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.
28
29
 
@@ -31,6 +32,7 @@ spawn_agent(
31
32
  sessionId: "<sessionId>",
32
33
  repo: "<repo>",
33
34
  instruction: "<instruction>",
35
+ role: "<role, if any>",
34
36
  context: "<context>"
35
37
  )
36
38
  ```
@@ -45,6 +47,7 @@ Call `show_agent` in a loop, passing `waitForTransitionMs: 50000` on every call:
45
47
  show_agent(
46
48
  sessionId: "<sessionId>",
47
49
  repo: "<repo>",
50
+ role: "<role, if any>",
48
51
  waitForTransitionMs: 50000
49
52
  )
50
53
  ```
@@ -61,7 +64,7 @@ After calling `spawn_agent`, parse the structured JSON response:
61
64
 
62
65
  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.
63
66
 
64
- The response's `children[]` array has a single entry the child for your repo. On it, inspect:
67
+ 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:
65
68
 
66
69
  - `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.
67
70
  - `child.inputRequiredQuestion` — populated only when `child.status === 'input-required'`; contains the verbatim question the child agent has asked the parent.
@@ -75,7 +78,7 @@ State machine:
75
78
  - Read `child.inputRequiredQuestion`.
76
79
  - Surface this question verbatim to the parent/user: "The child agent in `{child.repoFullName}` needs input: {child.inputRequiredQuestion}".
77
80
  - Wait for the parent/user to supply an answer.
78
- - Call `spawn_agent` again with the same `repo` and `instruction: <the answer>` — the orchestrator routes it to the active task automatically.
81
+ - 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.
79
82
  - Resume polling.
80
83
 
81
84
  <!-- Claude and Codex parents handle permission gates via the native MCP elicitation dialog
@@ -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
 
@@ -5,15 +5,27 @@
5
5
  // same script ships in both plugin artifacts.
6
6
  //
7
7
  // File contract (must match the Polygraph CLI reader exactly):
8
+ // <sessionsRoot>/<POLYGRAPH_SESSION_ID>/sidecars/mapping-<agentType>-<agentSessionId>.json
9
+ // where sessionsRoot = $POLYGRAPH_ROOT, else `globalRoot` from
10
+ // ~/.polygraph/config.json, else ~/.polygraph/sessions
11
+ // Legacy fallback, used ONLY when <sessionsRoot>/<POLYGRAPH_SESSION_ID>
12
+ // does not exist (for real sessions nothing new is written here):
8
13
  // ~/.polygraph/sidecars/<POLYGRAPH_SESSION_ID>/mapping-<agentType>-<agentSessionId>.json
9
14
  //
15
+ // The session folder is a trustworthy location for this parent-transcript
16
+ // binding because the Polygraph CLI's child-agent sandboxes exclude the
17
+ // session root — children cannot write there. The CLI reads mappings from
18
+ // the session folder first, with the flat dir as a read-only fallback.
19
+ //
10
20
  // Behaviour:
11
21
  // - Silent no-op when POLYGRAPH_SESSION_ID is unset.
12
22
  // - Silent no-op when POLYGRAPH_CHILD_AGENT is set (child agents must not
13
23
  // register themselves as parents).
14
24
  // - Atomic write: write to <path>.tmp-<pid>, then rename over final path.
15
- // - Refresh: when a valid prior mapping for the same session already exists,
16
- // preserve its firstSeenAt and only update lastSeenAt + mutable fields.
25
+ // - Refresh: when a valid prior mapping for the same session already exists
26
+ // (checked in the new location first, then the legacy flat dir), preserve
27
+ // its firstSeenAt and only update lastSeenAt + mutable fields — so
28
+ // migrating a mapping from the legacy dir keeps firstSeenAt continuity.
17
29
  // - All failures are silently swallowed; never writes to stdout (Claude Code
18
30
  // injects hook stdout into the model context); never exits non-zero.
19
31
 
@@ -90,9 +102,35 @@ function sanitizeFilename(str) {
90
102
  return str.replace(/[^A-Za-z0-9._-]/g, '_');
91
103
  }
92
104
 
105
+ // Resolve the root directory that holds per-session folders:
106
+ // $POLYGRAPH_ROOT, else `globalRoot` from ~/.polygraph/config.json, else
107
+ // ~/.polygraph/sessions. Must match the Polygraph CLI's own resolution.
108
+ export function sessionsRoot(home = process.env.HOME?.trim() || homedir()) {
109
+ const fromEnv = process.env.POLYGRAPH_ROOT?.trim();
110
+ if (fromEnv) return fromEnv;
111
+
112
+ try {
113
+ const config = tryParseJson(
114
+ readFileSync(join(home, '.polygraph', 'config.json'), 'utf8')
115
+ );
116
+ if (typeof config?.globalRoot === 'string' && config.globalRoot.trim()) {
117
+ return config.globalRoot.trim();
118
+ }
119
+ } catch {
120
+ // no config — use the default
121
+ }
122
+
123
+ return join(home, '.polygraph', 'sessions');
124
+ }
125
+
93
126
  /**
94
127
  * Write (or refresh) the agent-capture mapping file.
95
128
  *
129
+ * Written into the session folder (`<sessionsRoot>/<sessionId>/sidecars/`)
130
+ * when the session directory exists; only when it does not exist does the
131
+ * write fall back to the legacy flat `~/.polygraph/sidecars/<sessionId>/`
132
+ * dir — for real sessions nothing new lands under the flat dir.
133
+ *
96
134
  * @param {object} opts
97
135
  * @param {string} opts.agentType 'claude' | 'codex'
98
136
  * @param {string} opts.agentSessionId The harness's own session id.
@@ -106,19 +144,33 @@ export function writeCaptureMapping(
106
144
  { agentType, agentSessionId, polygraphSessionId, cwd, transcriptPath, pid },
107
145
  home = process.env.HOME?.trim() || homedir()
108
146
  ) {
109
- const sidecarDir = join(home, '.polygraph', 'sidecars', polygraphSessionId);
110
- mkdirSync(sidecarDir, { recursive: true });
111
-
112
147
  const filenamePart = sanitizeFilename(`${agentType}-${agentSessionId}`);
113
- const finalPath = join(sidecarDir, `mapping-${filenamePart}.json`);
148
+ const fileName = `mapping-${filenamePart}.json`;
149
+
150
+ const sessionDir = join(sessionsRoot(home), polygraphSessionId);
151
+ const sessionSidecarDir = join(sessionDir, 'sidecars');
152
+ const legacyDir = join(home, '.polygraph', 'sidecars', polygraphSessionId);
153
+
154
+ // New location when the session directory exists; legacy flat dir only
155
+ // when it does not.
156
+ const targetDir = existsSync(sessionDir) ? sessionSidecarDir : legacyDir;
157
+ mkdirSync(targetDir, { recursive: true });
158
+
159
+ const finalPath = join(targetDir, fileName);
114
160
  const tmpPath = `${finalPath}.tmp-${process.pid}`;
115
161
 
116
162
  const now = Date.now();
117
163
 
118
164
  // Refresh semantics: preserve firstSeenAt from a valid prior mapping.
165
+ // Check the new location first, then the legacy flat dir — this keeps
166
+ // firstSeenAt continuity when migrating a mapping from the legacy dir.
119
167
  let firstSeenAt = now;
120
- if (existsSync(finalPath)) {
121
- const existing = tryParseJson(readFileSync(finalPath, 'utf8'));
168
+ for (const candidate of [
169
+ join(sessionSidecarDir, fileName),
170
+ join(legacyDir, fileName),
171
+ ]) {
172
+ if (!existsSync(candidate)) continue;
173
+ const existing = tryParseJson(readFileSync(candidate, 'utf8'));
122
174
  if (
123
175
  existing !== null &&
124
176
  existing.version === 1 &&
@@ -127,6 +179,7 @@ export function writeCaptureMapping(
127
179
  Number.isFinite(existing.firstSeenAt)
128
180
  ) {
129
181
  firstSeenAt = existing.firstSeenAt;
182
+ break;
130
183
  }
131
184
  }
132
185
 
@@ -8,9 +8,12 @@
8
8
  // into the model.
9
9
  //
10
10
  // Everything is read from local Polygraph state — no network calls:
11
+ // <sessionsRoot>/<polygraphSessionId>/sidecars/parent-<agentSessionId>.json
12
+ // (sessionsRoot = $POLYGRAPH_ROOT or ~/.polygraph/sessions) maps this
13
+ // agent session id -> Polygraph session id (the "parent log sidecar"
14
+ // the CLI uses to stream parent-agent activity to the UI). Falls back
15
+ // to the legacy location for sessions created by older CLIs:
11
16
  // ~/.polygraph/sidecars/<polygraphSessionId>/parent-<agentSessionId>.json
12
- // maps this agent session id -> Polygraph session id (the "parent log
13
- // sidecar" the CLI uses to stream parent-agent activity to the UI).
14
17
  // ~/.polygraph/sessions/<polygraphSessionId>/session/session.json
15
18
  // holds the session's repos, agentType, and orgId.
16
19
  // ~/.polygraph/config.json
@@ -83,25 +86,28 @@ function readJson(file) {
83
86
  }
84
87
  }
85
88
 
86
- // Find the sidecar that maps an agent session id to a Polygraph session.
87
- // Returns the parsed sidecar object, or null when none matches.
88
- export function findSidecar(agentSessionId, root = polygraphRoot()) {
89
- if (!agentSessionId) return null;
89
+ // Resolve the root directory that holds per-session folders. Overridable via
90
+ // POLYGRAPH_ROOT (must match the Polygraph CLI's own resolution).
91
+ function sessionsRoot(root) {
92
+ return process.env.POLYGRAPH_ROOT?.trim() || path.join(root, 'sessions');
93
+ }
90
94
 
91
- const sidecarsDir = path.join(root, 'sidecars');
92
- if (!existsSync(sidecarsDir)) return null;
95
+ // Scan the immediate subdirectories of `baseDir`; for each, `candidatePath`
96
+ // maps the subdirectory name to a candidate sidecar file. Returns the first
97
+ // parsed match, or null.
98
+ function scanForSidecar(baseDir, candidatePath) {
99
+ if (!existsSync(baseDir)) return null;
93
100
 
94
- const fileName = `parent-${agentSessionId}.json`;
95
101
  let entries;
96
102
  try {
97
- entries = readdirSync(sidecarsDir, { withFileTypes: true });
103
+ entries = readdirSync(baseDir, { withFileTypes: true });
98
104
  } catch {
99
105
  return null;
100
106
  }
101
107
 
102
108
  for (const entry of entries) {
103
109
  if (!entry.isDirectory()) continue;
104
- const candidate = path.join(sidecarsDir, entry.name, fileName);
110
+ const candidate = candidatePath(entry.name);
105
111
  if (existsSync(candidate)) {
106
112
  return readJson(candidate);
107
113
  }
@@ -109,6 +115,29 @@ export function findSidecar(agentSessionId, root = polygraphRoot()) {
109
115
  return null;
110
116
  }
111
117
 
118
+ // Find the sidecar that maps an agent session id to a Polygraph session.
119
+ // Checks the new per-session layout first, then falls back to the legacy
120
+ // shared sidecars directory. Returns the parsed sidecar object, or null when
121
+ // none matches.
122
+ export function findSidecar(agentSessionId, root = polygraphRoot()) {
123
+ if (!agentSessionId) return null;
124
+
125
+ const fileName = `parent-${agentSessionId}.json`;
126
+
127
+ // New layout: <sessionsRoot>/<sessionId>/sidecars/parent-<agentSessionId>.json
128
+ const sessionsDir = sessionsRoot(root);
129
+ const fromSessions = scanForSidecar(sessionsDir, (sessionId) =>
130
+ path.join(sessionsDir, sessionId, 'sidecars', fileName)
131
+ );
132
+ if (fromSessions) return fromSessions;
133
+
134
+ // Legacy layout: <root>/sidecars/<sessionId>/parent-<agentSessionId>.json
135
+ const legacyDir = path.join(root, 'sidecars');
136
+ return scanForSidecar(legacyDir, (sessionId) =>
137
+ path.join(legacyDir, sessionId, fileName)
138
+ );
139
+ }
140
+
112
141
  // Build the context block for a Polygraph session, or null when the agent is
113
142
  // not running inside a Polygraph session.
114
143
  export function buildPolygraphContext(agentSessionId, root = polygraphRoot()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polygraph/codex-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,
@@ -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.
@@ -165,7 +165,7 @@ For each repo with `ciStatus: FAILED`, branch on the PR's `ci` object from `show
165
165
  show_agent(sessionId: "<session-id>", repo: "frontend")
166
166
  ```
167
167
 
168
- Poll until the child agent's status indicates completion. Use the `tail` parameter to retrieve recent output lines containing the investigation results.
168
+ 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.
169
169
 
170
170
  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.
171
171
 
@@ -55,9 +55,9 @@ Polygraph functionality is available via both MCP tools and CLI commands. Use wh
55
55
  | --- | --- | --- |
56
56
  | `list_repos` | `polygraph repo list` | Discover candidate repositories. Candidate entries do not include repository descriptions; use `semanticQuery` for natural-language discovery. |
57
57
  | `start_session` | `polygraph session start --repo <ids>` | Initialize a Polygraph session with selected repositories |
58
- | `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. |
59
- | `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'`. |
60
- | `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. |
58
+ | `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. |
59
+ | `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". |
60
+ | `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. |
61
61
  | `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. |
62
62
  | `create_pr` | — | Create draft PRs with session metadata linking related PRs |
63
63
  | `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. |
@@ -106,10 +106,10 @@ After logging in (or if logged in but no org is selected), use `polygraph accoun
106
106
  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.
107
107
 
108
108
  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.
109
- 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.
109
+ 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.
110
110
 
111
- 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.
112
- 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.
111
+ 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.
112
+ 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.
113
113
  6. **Push branches** - Use `push_branch` after making commits. A required `description` must follow the Session Description Policy.
114
114
  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.
115
115
  8. **Create draft PRs** - Use `create_pr` to create linked draft PRs. Always pass `description` following the Session Description Policy.
@@ -252,6 +252,14 @@ polygraph session search --sha a1b2c3d
252
252
  - Multiple sessions may match a sha. They come back newest first — pick the most relevant one and report the others if they matter.
253
253
  - **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.
254
254
 
255
+ ## Agent roles
256
+
257
+ A repository in a session can host multiple child agents at once, distinguished by **role**:
258
+
259
+ - **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.
260
+ - **Default role.** An omitted `role` means the default role: `spawn_agent` without `role` starts or follows up with the repo's default-role agent.
261
+ - **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>`.
262
+
255
263
  ## Simple tasks (fire-and-forget)
256
264
 
257
265
  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.
@@ -268,6 +276,7 @@ spawn_agent(
268
276
  - sessionId: "<session-id>"
269
277
  - repo: "<org/repo-name>"
270
278
  - instruction: "<the task instruction>"
279
+ - role: "<optional role>"
271
280
  - context: "<optional context>"
272
281
 
273
282
  Call the Polygraph MCP spawn_agent for the repo, then poll show_agent via chained waitForTransitionMs long-poll calls until terminal. Return a structured summary with repo, status, session ID, and result text.
@@ -275,8 +284,8 @@ spawn_agent(
275
284
  )
276
285
  ```
277
286
 
278
- 2. Delegate to multiple repos in parallel by launching multiple `polygraph-delegate-subagent` instances before waiting for results — one delegation per repo at a time.
279
- 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).
287
+ 2. Delegate to multiple repos in parallel by launching multiple `polygraph-delegate-subagent` instances before waiting for results — one delegation per (repo, role) at a time.
288
+ 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).
280
289
  4. Collect completed results with `wait_agent` when the main flow needs them, then continue to `push_branch` + `create_pr`.
281
290
 
282
291
  In rare cases where you need to check the raw child agent status directly (e.g., debugging a stuck subagent), you may call the Polygraph MCP `show_agent` as a one-off tool call. Do NOT use this for regular polling — that belongs inside `polygraph-delegate-subagent`.
@@ -287,13 +296,13 @@ Use Simple when the task is well-defined and the child will not need clarificati
287
296
 
288
297
  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.
289
298
 
290
- 1. Call `spawn_agent` with the initial `instruction`. Parse the response:
299
+ 1. Call `spawn_agent` with the initial `instruction` (and optionally `role`). Parse the response:
291
300
 
292
301
  ```json
293
302
  { "taskId": "…", "message": "…", "status": "delegated" }
294
303
  ```
295
304
 
296
- 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:
305
+ 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:
297
306
 
298
307
  - `child.status` — one of `'created'`, `'in-progress'`, `'input-required'`, `'permission-required'`, `'completed'`, `'failed'`, `'cancelled'` (British double-L on `'cancelled'`).
299
308
  - `child.inputRequiredQuestion` — populated only when `child.status === 'input-required'`.
@@ -303,13 +312,13 @@ Use this pattern when the child may need clarification, the task is exploratory,
303
312
  Drive the state machine:
304
313
 
305
314
  - `child.status === 'in-progress'` or `'created'` — continue polling.
306
- - `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.
315
+ - `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.
307
316
  - `child.status === 'completed'` — read `child.lastOutputLines`, proceed to `push_branch` + `create_pr`.
308
317
  - `child.status === 'failed'` — read `child.lastOutputLines`, surface the failure.
309
318
  - `child.status === 'cancelled'` — the child was stopped via `stop_agent`; see below.
310
319
  - `child.status === 'permission-required'` — the child is waiting on a permission decision; see "Handling permission requests" below.
311
320
 
312
- 3. To abort mid-flight, call `stop_agent` with `{ sessionId, repo }`. The response is:
321
+ 3. To abort mid-flight, call `stop_agent` with `{ sessionId, repo, role? }`. The response is:
313
322
 
314
323
  ```json
315
324
  {
@@ -321,7 +330,7 @@ Use this pattern when the child may need clarification, the task is exploratory,
321
330
  }
322
331
  ```
323
332
 
324
- 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.
333
+ 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.
325
334
 
326
335
  Use Multi-turn when the child may need clarification, the task is exploratory, or interactive collaboration is desired. Otherwise use Simple.
327
336
 
@@ -340,26 +349,11 @@ If you call `allow_agent` while the dialog is already open, you create a race: t
340
349
 
341
350
  The `allow_agent` and `deny_agent` tools exist for parents whose MCP clients do NOT advertise elicitation capability (opencode TUI today). They are not part of your flow.
342
351
 
343
- ### 2. Push Branches
352
+ ### 2. Publish Changes (Push Branches, Create PRs, Mark Ready)
344
353
 
345
- Once work is complete in a repository, push the branch using `push_branch`. This must be done before creating a PR.
354
+ 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).
346
355
 
347
- `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.
348
-
349
- **Parameters:**
350
-
351
- - `sessionId` (required): The Polygraph session ID
352
- - `repo` (required): Repository name or repository ID to push from
353
- - `branch` (required): Branch name to push to remote
354
- - `description` (required): A session description is required. Must follow the Session Description Policy.
355
-
356
- ```
357
- push_branch(
358
- sessionId: "<session-id>",
359
- repo: "org/repo-name",
360
- branch: "polygraph/ad5fa-add-user-preferences"
361
- )
362
- ```
356
+ **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.
363
357
 
364
358
  ### Session Description Policy
365
359
 
@@ -367,121 +361,18 @@ push_branch(
367
361
 
368
362
  **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`).
369
363
 
370
- ### 3. Create Draft PRs
371
-
372
- 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.
373
-
374
- **Parameters:**
375
-
376
- - `sessionId` (required): The Polygraph session ID
377
- - `prs` (required): Array of PR specifications, each containing:
378
- - `owner` (required): GitHub repository owner
379
- - `repo` (required): GitHub repository name
380
- - `title` (required): PR title
381
- - `body` (required): PR description (session metadata is appended automatically)
382
- - `branch` (required): Branch name that was pushed
383
- - `targetRepository` (optional): Target GitHub repository for fork PR creation or registration, as `owner/repo`. Omit for same-repository PRs.
384
- - `description` (required): Must follow the Session Description Policy.
385
-
386
- **PR title format (applies to parent and child agents):**
364
+ ### 3. Get Current Polygraph Session
387
365
 
388
- - 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>`).
389
- - 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.
390
-
391
- ```
392
- create_pr(
393
- sessionId: "<session-id>",
394
- prs: [
395
- {
396
- owner: "org",
397
- repo: "frontend",
398
- title: "feat: Add user preferences UI",
399
- body: "Part of multi-repo user preferences feature",
400
- branch: "polygraph/ad5fa-add-user-preferences"
401
- },
402
- {
403
- owner: "org",
404
- repo: "backend",
405
- title: "feat: Add user preferences API",
406
- body: "Part of multi-repo user preferences feature",
407
- branch: "polygraph/ad5fa-add-user-preferences"
408
- }
409
- ]
410
- )
411
- ```
412
-
413
- 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:
414
-
415
- ```
416
- create_pr(
417
- sessionId: "<session-id>",
418
- prs: [
419
- {
420
- owner: "contributor",
421
- repo: "frontend-fork",
422
- targetRepository: "org/frontend",
423
- title: "feat: Add user preferences UI",
424
- body: "Part of multi-repo user preferences feature",
425
- branch: "polygraph/ad5fa-add-user-preferences"
426
- }
427
- ]
428
- )
429
- ```
430
-
431
- **After creating PRs**, always print the Polygraph session URL:
432
-
433
- ```
434
- **Polygraph session:** POLYGRAPH_SESSION_URL
435
- ```
436
-
437
- ### 4. Get Current Polygraph Session
438
-
439
- 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.
366
+ 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`.
440
367
 
441
368
  **Parameters:**
442
369
 
443
370
  - `sessionId` (required): The Polygraph session ID
444
371
 
445
- **Returns:**
372
+ **CI status rules:**
446
373
 
447
- - `session.sessionId`: The session ID
448
- - `session.polygraphSessionUrl`: URL to the Polygraph session UI
449
- - `session.description`: DescriptionItem[] timeline describing the session.
450
- - `session.agentSessionId`: The agent CLI session ID — captured automatically by the MCP server (null if no agent has run yet).
451
- - `session.linkedReferences`: Array of references linked to this session
452
- - Session repository entries: Array of connected repositories, each with:
453
- - `id`: Repository ID
454
- - `name`: Repository name
455
- - `defaultBranch`: Default branch (e.g., `main`)
456
- - `vcsConfiguration.repositoryFullName`: Full repo name (e.g., `org/repo`)
457
- - `vcsConfiguration.provider`: VCS provider (e.g., `GITHUB`)
458
- - description field: AI-generated description of what this repository does (may be null)
459
- - `initiator`: Whether this repository initiated the session
460
- - `session.dependencyGraph`: Graph of repository dependency `edges`
461
- - `session.pullRequests[]`: Array of PRs, each with:
462
- - `url`: PR URL
463
- - `branch`: Branch name
464
- - `baseBranch`: Target branch
465
- - `title`: PR title
466
- - `status`: One of `DRAFT`, `OPEN`, `MERGED`, `CLOSED`
467
- - `repoId`: Associated repository ID
468
- - `relatedPRs`: Array of related PR URLs across repos
469
- - `session.ciStatus`: CI pipeline status keyed by PR ID, each containing:
470
- - `status`: One of `SUCCEEDED`, `FAILED`, `IN_PROGRESS`, `NOT_STARTED` (null if no CIPE and no external CI)
471
- - `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
472
- - `completedAt`: Epoch millis timestamp, set only when the CIPE has completed (null otherwise)
473
- - `selfHealingStatus`: The self-healing fix status string from Nx Cloud's AI fix feature (null if no AI fix exists)
474
- - `externalCIRuns`: Array of external CI runs (present when no CIPE but external CI data exists, e.g., GitHub Actions). Each run contains:
475
- - `runId`: GitHub Actions run ID
476
- - `name`: Workflow name
477
- - `status`: Run status (`completed`, `in_progress`, `queued`)
478
- - `conclusion`: Run conclusion (`success`, `failure`, `cancelled`, `timed_out`, or null)
479
- - `url`: GitHub Actions run URL
480
- - `jobs`: Array of jobs in the run, each with:
481
- - `jobId`: Job ID (use with `get_ci_logs`)
482
- - `name`: Job name
483
- - `status`: Job status
484
- - `conclusion`: Job conclusion (or null)
374
+ - `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.
375
+ - 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`.
485
376
 
486
377
  ```
487
378
  show_session(sessionId: "<session-id>")
@@ -528,67 +419,7 @@ link_reference({
528
419
 
529
420
  The canonical MCP parameters are `{ sessionId, reference }`. There is no unlink command.
530
421
 
531
- ### 5. Mark PRs Ready
532
-
533
- Once all changes are verified and ready to merge, use `mark_pr_ready` to transition PRs from DRAFT to OPEN status.
534
-
535
- **Parameters:**
536
-
537
- - `sessionId` (required): The Polygraph session ID
538
- - `prUrls` (required): Array of PR URLs to mark as ready for review
539
-
540
- ```
541
- mark_pr_ready(
542
- sessionId: "<session-id>",
543
- prUrls: [
544
- "https://github.com/org/frontend/pull/123",
545
- "https://github.com/org/backend/pull/456"
546
- ]
547
- )
548
- ```
549
-
550
- **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:
551
-
552
- ```
553
- **Polygraph session:** POLYGRAPH_SESSION_URL
554
- ```
555
-
556
- Where `POLYGRAPH_SESSION_URL` is from `polygraphSessionUrl` in the response.
557
-
558
- ### 6. Associate Existing PRs
559
-
560
- 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.
561
-
562
- Provide either a `prUrl` to associate a specific PR, or a `branch` name plus `repo` to find and associate PRs for a source repository.
563
-
564
- **Parameters:**
565
-
566
- - `sessionId` (required): The Polygraph session ID
567
- - `prUrl` (optional): URL of an existing pull request to associate
568
- - `branch` (optional): Branch name to find and associate PRs for
569
- - `repo` (optional): Source repository for branch-based association. Required when using `branch` in a multi-repo session.
570
- - `description` (required): Must follow the Session Description Policy.
571
-
572
- ```
573
- associate_pr(
574
- sessionId: "<session-id>",
575
- prUrl: "https://github.com/org/repo/pull/123"
576
- )
577
- ```
578
-
579
- Or by branch:
580
-
581
- ```
582
- associate_pr(
583
- sessionId: "<session-id>",
584
- repo: "org/repo",
585
- branch: "feature/my-changes"
586
- )
587
- ```
588
-
589
- **Returns** the list of PRs now associated with the session.
590
-
591
- ### 7. Add Repositories to a Session
422
+ ### 4. Add Repositories to a Session
592
423
 
593
424
  Use `add_repo` to add repositories to an existing Polygraph session after it has already started.
594
425
 
@@ -608,7 +439,7 @@ add_repo(
608
439
  )
609
440
  ```
610
441
 
611
- ### 8. Archive Session
442
+ ### 5. Archive Session
612
443
 
613
444
  **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.
614
445
 
@@ -722,7 +553,7 @@ If the session has a description timeline, also display:
722
553
 
723
554
  1. **NEVER call the Polygraph MCP `spawn_agent` or `show_agent` directly for routine delegation**. These MUST run inside `polygraph-delegate-subagent`.
724
555
 
725
- 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.
556
+ 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.
726
557
  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.
727
558
 
728
559
  1. **Respect the sandbox** — When a command fails with a sandbox denial (`EPERM` binding a port, blocked host, denied write), stop instead of retrying and point the user to the options in "Sandboxing in Polygraph Sessions": commit harness sandbox settings to the repo, or toggle sandboxing via `polygraph config`.
@@ -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.