@polygraph/codex-plugin 0.4.40 → 0.4.42
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/.codex-plugin/plugin.json +1 -1
- package/README.md +1 -0
- package/agents/polygraph-delegate-subagent.toml +9 -5
- package/agents/session-debrief.toml +24 -0
- package/bin/polygraph-codex-plugin.mjs +163 -60
- package/hooks/record-session-mapping.mjs +61 -8
- package/hooks/reinject-polygraph-context.mjs +40 -11
- package/package.json +1 -1
- package/skills/await-polygraph-ci/SKILL.md +1 -1
- package/skills/polygraph/SKILL.md +33 -202
- package/skills/polygraph/reference/publish-changes.md +151 -0
- package/skills/session-debrief/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ It detects your AI agent — Claude Code, Codex, OpenCode, and more — and inst
|
|
|
46
46
|
|
|
47
47
|
- **polygraph-init-subagent** — Discovers candidate repositories and initializes a Polygraph session
|
|
48
48
|
- **polygraph-delegate-subagent** — Delegates work to a child agent in another repository, polls for completion
|
|
49
|
+
- **session-debrief** — Analyzes the raw logs of past Polygraph sessions and returns a structured, rank-ordered debrief for the current task
|
|
49
50
|
|
|
50
51
|
## Development
|
|
51
52
|
|
|
@@ -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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name = "session-debrief"
|
|
2
|
+
description = "Analyze the raw logs of one or more past Polygraph sessions and return a structured, rank-ordered debrief for the current task. Launch as a background agent with a ranked list of relevant Polygraph session IDs/lines and a one-paragraph statement of the current task; it invokes the session-debrief skill, pulls parent and child transcripts via the polygraph CLI, and returns one consolidated debrief. Read-only with respect to the inspected sessions."
|
|
3
|
+
developer_instructions = '''
|
|
4
|
+
# Session Debrief Subagent
|
|
5
|
+
|
|
6
|
+
You produce debriefs of PAST Polygraph sessions so a parent agent working on a NEW task can decide what context is relevant. You run in the background and speed matters — the parent keeps working while it waits and folds your debrief in whenever it lands.
|
|
7
|
+
|
|
8
|
+
You are READ-ONLY with respect to the inspected sessions: never resume them, never spawn agents into them, never push branches, create PRs, or update their descriptions.
|
|
9
|
+
|
|
10
|
+
## Input Parameters (from Main Agent)
|
|
11
|
+
|
|
12
|
+
The main agent provides these in the prompt:
|
|
13
|
+
|
|
14
|
+
| Parameter | Description |
|
|
15
|
+
| ------------- | ------------------------------------------------------------------------------------------------ |
|
|
16
|
+
| `sessions` | A ranked list of relevant Polygraph sessions (IDs/lines, most relevant first, with optional titles/URLs) |
|
|
17
|
+
| `currentTask` | A one-paragraph statement of the task the parent is currently working on |
|
|
18
|
+
|
|
19
|
+
## What to do
|
|
20
|
+
|
|
21
|
+
Invoke the `session-debrief` skill and follow its procedure and output template exactly. Pass through the ranked session list and the current-task statement you received. The skill is the single source of truth for how to pull transcripts via the polygraph CLI, how to fan out across multiple sessions, and how to format each debrief section — do not reinvent or duplicate that procedure here.
|
|
22
|
+
|
|
23
|
+
Return the consolidated, rank-ordered debrief as your final message.
|
|
24
|
+
'''
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// source/codex/lib/installer.mjs
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
4
5
|
import {
|
|
5
6
|
cpSync,
|
|
6
7
|
existsSync,
|
|
@@ -35,10 +36,19 @@ function resolveUserHome(env = process.env) {
|
|
|
35
36
|
const userHome = env.HOME?.trim() || homedir();
|
|
36
37
|
return resolve(expandHome(userHome, env));
|
|
37
38
|
}
|
|
38
|
-
function
|
|
39
|
+
function getMarketplaceRoot(userHome) {
|
|
40
|
+
return join(userHome, ".polygraph", "codex-marketplace");
|
|
41
|
+
}
|
|
42
|
+
function getMarketplacePath(marketplaceRoot) {
|
|
43
|
+
return join(marketplaceRoot, ".agents", "plugins", "marketplace.json");
|
|
44
|
+
}
|
|
45
|
+
function getPluginInstallPath(marketplaceRoot) {
|
|
46
|
+
return join(marketplaceRoot, "plugins", PLUGIN_NAME);
|
|
47
|
+
}
|
|
48
|
+
function getLegacyMarketplacePath(userHome) {
|
|
39
49
|
return join(userHome, ".agents", "plugins", "marketplace.json");
|
|
40
50
|
}
|
|
41
|
-
function
|
|
51
|
+
function getLegacyPluginInstallPath(userHome) {
|
|
42
52
|
return join(userHome, ".agents", "plugins", PLUGIN_NAME);
|
|
43
53
|
}
|
|
44
54
|
function loadPackageMetadata(packageRoot) {
|
|
@@ -74,7 +84,9 @@ function loadPackageMetadata(packageRoot) {
|
|
|
74
84
|
function installPlugin({
|
|
75
85
|
packageRoot,
|
|
76
86
|
env = process.env,
|
|
77
|
-
force = false
|
|
87
|
+
force = false,
|
|
88
|
+
// Injectable so tests do not have to shell out to a real codex binary.
|
|
89
|
+
register = registerMarketplace
|
|
78
90
|
} = {}) {
|
|
79
91
|
if (!packageRoot) {
|
|
80
92
|
throw new Error("packageRoot is required");
|
|
@@ -83,8 +95,9 @@ function installPlugin({
|
|
|
83
95
|
const codexHome = resolveCodexHome(env);
|
|
84
96
|
const userHome = resolveUserHome(env);
|
|
85
97
|
const agentsPath = getAgentsPath(codexHome);
|
|
86
|
-
const
|
|
87
|
-
const
|
|
98
|
+
const marketplaceRoot = getMarketplaceRoot(userHome);
|
|
99
|
+
const marketplacePath = getMarketplacePath(marketplaceRoot);
|
|
100
|
+
const pluginPath = getPluginInstallPath(marketplaceRoot);
|
|
88
101
|
const installAlreadyPresent = existsSync(pluginPath);
|
|
89
102
|
let previousVersion = null;
|
|
90
103
|
if (installAlreadyPresent) {
|
|
@@ -117,15 +130,20 @@ function installPlugin({
|
|
|
117
130
|
copied = true;
|
|
118
131
|
}
|
|
119
132
|
const agentsChanged = installCodexAgents({ packageRoot, agentsPath });
|
|
120
|
-
const marketplaceChanged =
|
|
133
|
+
const marketplaceChanged = writeMarketplaceManifest({
|
|
121
134
|
marketplacePath,
|
|
122
|
-
|
|
123
|
-
|
|
135
|
+
marketplaceRoot,
|
|
136
|
+
pluginPath
|
|
124
137
|
});
|
|
138
|
+
const legacyCleanup = cleanLegacyMarketplaceEntry({ userHome });
|
|
139
|
+
const registration = register({ marketplaceRoot, env });
|
|
125
140
|
return {
|
|
126
141
|
ok: true,
|
|
127
142
|
action: "install",
|
|
128
143
|
plugin: PLUGIN_ID,
|
|
144
|
+
marketplaceName: MARKETPLACE_NAME,
|
|
145
|
+
marketplaceRoot,
|
|
146
|
+
marketplaceRegistered: registration.registered,
|
|
129
147
|
version,
|
|
130
148
|
codexHome,
|
|
131
149
|
agentsPath,
|
|
@@ -136,9 +154,68 @@ function installPlugin({
|
|
|
136
154
|
pluginUpdated: installAlreadyPresent && versionMismatch && !force,
|
|
137
155
|
previousVersion,
|
|
138
156
|
agentsChanged,
|
|
139
|
-
marketplaceChanged
|
|
157
|
+
marketplaceChanged,
|
|
158
|
+
legacyEntryRemoved: legacyCleanup.entryRemoved,
|
|
159
|
+
legacyMarketplaceRemoved: legacyCleanup.fileRemoved,
|
|
160
|
+
...registration.error ? { marketplaceRegistrationError: registration.error } : {}
|
|
140
161
|
};
|
|
141
162
|
}
|
|
163
|
+
function registerMarketplace({ marketplaceRoot, env = process.env }) {
|
|
164
|
+
const result = spawnSync(
|
|
165
|
+
"codex",
|
|
166
|
+
["plugin", "marketplace", "add", marketplaceRoot, "--json"],
|
|
167
|
+
{ encoding: "utf8", env }
|
|
168
|
+
);
|
|
169
|
+
if (result.error) {
|
|
170
|
+
return {
|
|
171
|
+
registered: false,
|
|
172
|
+
error: `Could not run \`codex\`: ${result.error.message}. Run \`codex plugin marketplace add ${marketplaceRoot}\` once codex is available.`
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (result.status !== 0) {
|
|
176
|
+
return {
|
|
177
|
+
registered: false,
|
|
178
|
+
error: `\`codex plugin marketplace add\` failed: ${(result.stderr || "").trim()}`
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return { registered: true };
|
|
182
|
+
}
|
|
183
|
+
function cleanLegacyMarketplaceEntry({ userHome }) {
|
|
184
|
+
const legacyPath = getLegacyMarketplacePath(userHome);
|
|
185
|
+
const unchanged = { entryRemoved: false, fileRemoved: false };
|
|
186
|
+
if (!existsSync(legacyPath)) {
|
|
187
|
+
return unchanged;
|
|
188
|
+
}
|
|
189
|
+
let marketplace;
|
|
190
|
+
try {
|
|
191
|
+
marketplace = readJsonFile(legacyPath);
|
|
192
|
+
} catch {
|
|
193
|
+
return unchanged;
|
|
194
|
+
}
|
|
195
|
+
if (!isPlainObject(marketplace) || !Array.isArray(marketplace.plugins)) {
|
|
196
|
+
return unchanged;
|
|
197
|
+
}
|
|
198
|
+
const remaining = marketplace.plugins.filter(
|
|
199
|
+
(plugin) => plugin?.name !== PLUGIN_NAME
|
|
200
|
+
);
|
|
201
|
+
if (remaining.length === marketplace.plugins.length) {
|
|
202
|
+
return unchanged;
|
|
203
|
+
}
|
|
204
|
+
if (remaining.length === 0 && marketplace.name === MARKETPLACE_NAME) {
|
|
205
|
+
rmSync(legacyPath, { force: true });
|
|
206
|
+
rmSync(getLegacyPluginInstallPath(userHome), {
|
|
207
|
+
recursive: true,
|
|
208
|
+
force: true
|
|
209
|
+
});
|
|
210
|
+
return { entryRemoved: true, fileRemoved: true };
|
|
211
|
+
}
|
|
212
|
+
writeJsonFile(legacyPath, { ...marketplace, plugins: remaining });
|
|
213
|
+
rmSync(getLegacyPluginInstallPath(userHome), {
|
|
214
|
+
recursive: true,
|
|
215
|
+
force: true
|
|
216
|
+
});
|
|
217
|
+
return { entryRemoved: true, fileRemoved: false };
|
|
218
|
+
}
|
|
142
219
|
function checkInstall({ packageRoot, env = process.env } = {}) {
|
|
143
220
|
let version = null;
|
|
144
221
|
if (packageRoot) {
|
|
@@ -147,29 +224,46 @@ function checkInstall({ packageRoot, env = process.env } = {}) {
|
|
|
147
224
|
const codexHome = resolveCodexHome(env);
|
|
148
225
|
const userHome = resolveUserHome(env);
|
|
149
226
|
const agentsPath = getAgentsPath(codexHome);
|
|
150
|
-
const
|
|
151
|
-
const
|
|
227
|
+
const marketplaceRoot = getMarketplaceRoot(userHome);
|
|
228
|
+
const marketplacePath = getMarketplacePath(marketplaceRoot);
|
|
229
|
+
const pluginPath = getPluginInstallPath(marketplaceRoot);
|
|
152
230
|
const pluginInstalled = isValidInstalledPluginDir(pluginPath);
|
|
153
231
|
const agentsInstalled = packageRoot ? areCodexAgentsInstalled({ packageRoot, agentsPath }) : hasDefaultCodexAgents(agentsPath);
|
|
154
232
|
const marketplaceConfigured = isPluginConfiguredInMarketplace({
|
|
155
233
|
marketplacePath,
|
|
156
|
-
|
|
234
|
+
marketplaceRoot,
|
|
157
235
|
pluginPath
|
|
158
236
|
});
|
|
159
|
-
const
|
|
237
|
+
const legacyMarketplacePresent = hasLegacyPluginEntry({ userHome });
|
|
238
|
+
const ok = pluginInstalled && agentsInstalled && marketplaceConfigured && !legacyMarketplacePresent;
|
|
160
239
|
return {
|
|
161
240
|
ok,
|
|
162
241
|
action: "check",
|
|
163
242
|
plugin: PLUGIN_ID,
|
|
243
|
+
marketplaceName: MARKETPLACE_NAME,
|
|
244
|
+
marketplaceRoot,
|
|
164
245
|
codexHome,
|
|
165
246
|
agentsPath,
|
|
166
247
|
pluginPath,
|
|
167
248
|
marketplacePath,
|
|
168
249
|
pluginInstalled,
|
|
169
250
|
agentsInstalled,
|
|
170
|
-
marketplaceConfigured
|
|
251
|
+
marketplaceConfigured,
|
|
252
|
+
legacyMarketplacePresent
|
|
171
253
|
};
|
|
172
254
|
}
|
|
255
|
+
function hasLegacyPluginEntry({ userHome }) {
|
|
256
|
+
const legacyPath = getLegacyMarketplacePath(userHome);
|
|
257
|
+
if (!existsSync(legacyPath)) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
const marketplace = readJsonFile(legacyPath);
|
|
262
|
+
return Array.isArray(marketplace?.plugins) && marketplace.plugins.some((plugin) => plugin?.name === PLUGIN_NAME);
|
|
263
|
+
} catch {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
173
267
|
function getPackagePayloadPaths(packageRoot, packageJson) {
|
|
174
268
|
const relativePaths = new Set(packageJson.files ?? []);
|
|
175
269
|
relativePaths.add("package.json");
|
|
@@ -234,44 +328,30 @@ function hasDefaultCodexAgents(agentsPath) {
|
|
|
234
328
|
(agentFile) => existsSync(join(agentsPath, agentFile))
|
|
235
329
|
);
|
|
236
330
|
}
|
|
237
|
-
function
|
|
331
|
+
function writeMarketplaceManifest({
|
|
238
332
|
marketplacePath,
|
|
239
|
-
|
|
240
|
-
|
|
333
|
+
marketplaceRoot,
|
|
334
|
+
pluginPath
|
|
241
335
|
}) {
|
|
242
|
-
const marketplace = readJsonFile(marketplacePath, {});
|
|
243
|
-
if (marketplace.plugins !== void 0 && !Array.isArray(marketplace.plugins)) {
|
|
244
|
-
throw new Error(`Expected plugins array in ${marketplacePath}`);
|
|
245
|
-
}
|
|
246
|
-
const marketplacePluginPath = toMarketplaceSourcePath(userHome, pluginPath);
|
|
247
|
-
const nextPluginEntry = {
|
|
248
|
-
name: PLUGIN_NAME,
|
|
249
|
-
source: {
|
|
250
|
-
source: "local",
|
|
251
|
-
path: marketplacePluginPath
|
|
252
|
-
},
|
|
253
|
-
policy: {
|
|
254
|
-
installation: "AVAILABLE",
|
|
255
|
-
authentication: "ON_INSTALL"
|
|
256
|
-
},
|
|
257
|
-
category: "Productivity"
|
|
258
|
-
};
|
|
259
|
-
const plugins = marketplace.plugins ?? [];
|
|
260
|
-
const existingIndex = plugins.findIndex(
|
|
261
|
-
(plugin) => plugin?.name === PLUGIN_NAME
|
|
262
|
-
);
|
|
263
|
-
const nextPlugins = existingIndex === -1 ? [...plugins, nextPluginEntry] : plugins.map(
|
|
264
|
-
(plugin, index) => index === existingIndex ? nextPluginEntry : plugin
|
|
265
|
-
);
|
|
266
336
|
const nextMarketplace = {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
337
|
+
name: MARKETPLACE_NAME,
|
|
338
|
+
interface: { displayName: MARKETPLACE_DISPLAY_NAME },
|
|
339
|
+
plugins: [
|
|
340
|
+
{
|
|
341
|
+
name: PLUGIN_NAME,
|
|
342
|
+
source: {
|
|
343
|
+
source: "local",
|
|
344
|
+
path: toMarketplaceSourcePath(marketplaceRoot, pluginPath)
|
|
345
|
+
},
|
|
346
|
+
policy: {
|
|
347
|
+
installation: "AVAILABLE",
|
|
348
|
+
authentication: "ON_INSTALL"
|
|
349
|
+
},
|
|
350
|
+
category: "Productivity"
|
|
351
|
+
}
|
|
352
|
+
]
|
|
274
353
|
};
|
|
354
|
+
const marketplace = readJsonFile(marketplacePath, null);
|
|
275
355
|
const changed = JSON.stringify(nextMarketplace) !== JSON.stringify(marketplace);
|
|
276
356
|
if (changed) {
|
|
277
357
|
writeJsonFile(marketplacePath, nextMarketplace);
|
|
@@ -280,13 +360,21 @@ function enablePluginInMarketplace({
|
|
|
280
360
|
}
|
|
281
361
|
function isPluginConfiguredInMarketplace({
|
|
282
362
|
marketplacePath,
|
|
283
|
-
|
|
363
|
+
marketplaceRoot,
|
|
284
364
|
pluginPath
|
|
285
365
|
}) {
|
|
286
366
|
if (!existsSync(marketplacePath)) {
|
|
287
367
|
return false;
|
|
288
368
|
}
|
|
289
|
-
|
|
369
|
+
let marketplace;
|
|
370
|
+
try {
|
|
371
|
+
marketplace = readJsonFile(marketplacePath);
|
|
372
|
+
} catch {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
if (marketplace?.name !== MARKETPLACE_NAME) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
290
378
|
if (!Array.isArray(marketplace.plugins)) {
|
|
291
379
|
return false;
|
|
292
380
|
}
|
|
@@ -296,7 +384,7 @@ function isPluginConfiguredInMarketplace({
|
|
|
296
384
|
if (!isPlainObject(pluginEntry?.source) || pluginEntry.source.source !== "local") {
|
|
297
385
|
return false;
|
|
298
386
|
}
|
|
299
|
-
const configuredPath = resolve(
|
|
387
|
+
const configuredPath = resolve(marketplaceRoot, pluginEntry.source.path);
|
|
300
388
|
return configuredPath === resolve(pluginPath);
|
|
301
389
|
}
|
|
302
390
|
function readJsonFile(path, fallbackValue) {
|
|
@@ -341,11 +429,11 @@ function expandHome(inputPath, env) {
|
|
|
341
429
|
}
|
|
342
430
|
return inputPath;
|
|
343
431
|
}
|
|
344
|
-
function toMarketplaceSourcePath(
|
|
345
|
-
const relativePath = relative(
|
|
432
|
+
function toMarketplaceSourcePath(marketplaceRoot, targetPath) {
|
|
433
|
+
const relativePath = relative(marketplaceRoot, targetPath);
|
|
346
434
|
if (relativePath === "" || relativePath === "." || relativePath.startsWith(`..${sep}`) || relativePath === "..") {
|
|
347
435
|
throw new Error(
|
|
348
|
-
`Expected plugin install path ${targetPath} to be inside ${
|
|
436
|
+
`Expected plugin install path ${targetPath} to be inside ${marketplaceRoot} so it can be referenced from the marketplace manifest`
|
|
349
437
|
);
|
|
350
438
|
}
|
|
351
439
|
return `./${relativePath.split(sep).join("/")}`;
|
|
@@ -360,8 +448,10 @@ var usage = `Usage:
|
|
|
360
448
|
npx @polygraph/codex-plugin install [--force] [--json]
|
|
361
449
|
npx @polygraph/codex-plugin check [--json]
|
|
362
450
|
|
|
363
|
-
The install command materializes the plugin payload
|
|
364
|
-
|
|
451
|
+
The install command materializes the plugin payload into the marketplace Polygraph
|
|
452
|
+
owns (~/.polygraph/codex-marketplace) and registers that marketplace with codex.
|
|
453
|
+
It does not write to the shared ~/.agents/plugins/marketplace.json. After running
|
|
454
|
+
install, run:
|
|
365
455
|
|
|
366
456
|
codex plugin add polygraph@polygraph-plugins
|
|
367
457
|
|
|
@@ -408,17 +498,30 @@ ${usage}`);
|
|
|
408
498
|
} else {
|
|
409
499
|
const pluginState = result.pluginInstalled ? "plugin files present" : "plugin files not present";
|
|
410
500
|
const agentsState = result.agentsInstalled ? "agents installed" : "agents not installed";
|
|
411
|
-
const marketplaceState = result.marketplaceConfigured ?
|
|
501
|
+
const marketplaceState = result.marketplaceConfigured ? `plugin published from the ${result.marketplaceName} marketplace` : `plugin not published from the ${result.marketplaceName} marketplace`;
|
|
502
|
+
const legacyState = result.legacyMarketplacePresent ? "; a stale entry remains in ~/.agents/plugins/marketplace.json (re-run install to clear it)" : "";
|
|
412
503
|
console.error(
|
|
413
|
-
`Polygraph Codex plugin check failed: ${pluginState}; ${agentsState}; ${marketplaceState}.`
|
|
504
|
+
`Polygraph Codex plugin check failed: ${pluginState}; ${agentsState}; ${marketplaceState}${legacyState}.`
|
|
414
505
|
);
|
|
415
506
|
}
|
|
416
507
|
} else {
|
|
417
508
|
console.log(`Materialized Polygraph Codex plugin ${result.version}.`);
|
|
418
509
|
console.log(`Plugin path: ${result.pluginPath}`);
|
|
419
510
|
console.log(`Agents: ${result.agentsPath}`);
|
|
420
|
-
console.log(`Marketplace: ${result.
|
|
421
|
-
|
|
511
|
+
console.log(`Marketplace: ${result.marketplaceName} (${result.marketplaceRoot})`);
|
|
512
|
+
if (result.legacyEntryRemoved) {
|
|
513
|
+
console.log(
|
|
514
|
+
result.legacyMarketplaceRemoved ? "Removed the obsolete ~/.agents/plugins/marketplace.json written by an earlier version." : "Removed the obsolete polygraph entry from ~/.agents/plugins/marketplace.json; your other plugins were left untouched."
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
if (result.marketplaceRegistered) {
|
|
518
|
+
console.log(`Next step: codex plugin add ${result.plugin}`);
|
|
519
|
+
} else {
|
|
520
|
+
console.warn(`Warning: ${result.marketplaceRegistrationError}`);
|
|
521
|
+
console.warn(
|
|
522
|
+
`Then run: codex plugin add ${result.plugin}`
|
|
523
|
+
);
|
|
524
|
+
}
|
|
422
525
|
}
|
|
423
526
|
if (command === "check" && !result.ok) {
|
|
424
527
|
process.exitCode = 1;
|
|
@@ -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
|
-
//
|
|
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
|
|
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
|
-
|
|
121
|
-
|
|
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
|
-
//
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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(
|
|
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 =
|
|
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
|
@@ -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
|
|
59
|
-
| `show_agent` | — | Poll
|
|
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
|
|
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
|
|
112
|
-
5. **Stop child agents** (if needed) - Use `stop_agent` to cancel an in-progress child agent. The
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
**
|
|
372
|
+
**CI status rules:**
|
|
446
373
|
|
|
447
|
-
- `
|
|
448
|
-
- `
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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.
|