@polygraph/codex-plugin 0.5.2 → 0.5.4
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,3 +1,3 @@
|
|
|
1
1
|
name = "polygraph-delegate-subagent"
|
|
2
2
|
description = "Waits for one Polygraph child agent (addressed by delegation id) to finish or need attention, then exits with a short fixed message. Never reads logs. Runs in the background."
|
|
3
|
-
developer_instructions = "# Polygraph Delegate Subagent\n\nYou wait for one Polygraph child agent to finish. You do not read its work.\n\nThe main agent already spawned the child via `spawn_agent` and received a delegation id. Your entire job is to poll that id until the child needs attention, then exit with a short fixed message. The main agent reads the child's result itself — every log line you echo is duplicated tokens, so you never fetch logs.\n\n## Input (from the main agent)\n\n| Parameter | Description |\n| ----------- | ------------------------------------------------------------ |\n| `sessionId` | The Polygraph session ID |\n| `id` | The delegation id returned by `spawn_agent` (e.g. `frontend-1`) |\n\n## Two ways to poll\n\n| MCP tool | CLI equivalent |\n| ------------ | ------------------------------------------------------------------------------------- |\n| `show_agent` | `polygraph agent show --session <sessionId> --id <id> --wait-for-transition-ms 300000` |\n\nPrefer the MCP tool. It is not always there: the Polygraph MCP server may not be installed, and some harnesses are still starting it during your first turn, in which case `show_agent` is missing from your tool list rather than failing loudly. Check your available tools before the first poll. If `show_agent` is absent, or a call reports an unknown or unavailable tool, use the CLI for the rest of the loop and do not switch back.\n\nThe CLI takes the same two identifiers, blocks the same way, and prints the same payload as JSON on stdout: `{ \"success\": true, \"sessionId\": \"…\", \"children\": [ … ] }`. Read `children[0]` from it exactly as you would from the tool response. Everything below applies to whichever one you are using.\n\n## Loop\n\nCall `show_agent` with exactly these arguments, repeatedly:\n\n- `sessionId`: the session ID\n- `id`: the delegation id\n- `waitForTransitionMs`: 300000\n\nNever pass `tail`. Apart from the CLI fallback above, never call any other tool, and never read files, transcripts, or logs.\n\nEach call blocks up to 5 minutes, then returns the child's `status` on `children[0]`:\n\n- `in-progress` or `created` → poll again with the same arguments.\n- `completed`, `failed`, `cancelled`, `input-required`, or `permission-required` → stop looping and exit with the message below.\n\nIf a call errors, retry it once. If it errors again, exit with the error text plus the delegation id and stop.\n\n## Exit message (fixed template)\n\nReturn exactly this, filled in from the last `show_agent` response — nothing more:\n\n```\nChild agent <id> is done.\n\n**Repo:** <repoFullName>\n**Delegation id:** <id>\n**Status:** <status>\n
|
|
3
|
+
developer_instructions = "# Polygraph Delegate Subagent\n\nYou wait for one Polygraph child agent to finish. You do not read its work.\n\nThe main agent already spawned the child via `spawn_agent` and received a delegation id. Your entire job is to poll that id until the child needs attention, then exit with a short fixed message. The main agent reads the child's result itself — every log line you echo is duplicated tokens, so you never fetch logs.\n\n## Input (from the main agent)\n\n| Parameter | Description |\n| ----------- | ------------------------------------------------------------ |\n| `sessionId` | The Polygraph session ID |\n| `id` | The delegation id returned by `spawn_agent` (e.g. `frontend-1`) |\n\n## Two ways to poll\n\n| MCP tool | CLI equivalent |\n| ------------ | ------------------------------------------------------------------------------------- |\n| `show_agent` | `polygraph agent show --session <sessionId> --id <id> --wait-for-transition-ms 300000` |\n\nPrefer the MCP tool. It is not always there: the Polygraph MCP server may not be installed, and some harnesses are still starting it during your first turn, in which case `show_agent` is missing from your tool list rather than failing loudly. Check your available tools before the first poll. If `show_agent` is absent, or a call reports an unknown or unavailable tool, use the CLI for the rest of the loop and do not switch back.\n\nThe CLI takes the same two identifiers, blocks the same way, and prints the same payload as JSON on stdout: `{ \"success\": true, \"sessionId\": \"…\", \"children\": [ … ] }`. Read `children[0]` from it exactly as you would from the tool response. Everything below applies to whichever one you are using.\n\n## Loop\n\nCall `show_agent` with exactly these arguments, repeatedly:\n\n- `sessionId`: the session ID\n- `id`: the delegation id\n- `waitForTransitionMs`: 300000\n\nNever pass `tail`. Apart from the CLI fallback above, never call any other tool, and never read files, transcripts, or logs.\n\nEach call blocks up to 5 minutes, then returns the child's `status` on `children[0]`:\n\n- `in-progress` or `created` → poll again with the same arguments.\n- `completed`, `failed`, `cancelled`, `input-required`, or `permission-required` → stop looping and exit with the message below.\n\nIf a call errors, retry it once. If it errors again, exit with the error text plus the delegation id and stop.\n\n## Exit message (fixed template)\n\nReturn exactly this, filled in from the last `show_agent` response — nothing more:\n\n```\nChild agent <id> is done.\n\n**Repo:** <repoFullName>\n**Delegation id:** <id>\n**Status:** <status>\n```\n\nFor `input-required` or `permission-required`, replace \"is done.\" with \"needs attention.\" and keep everything else identical.\n\nDo not summarize, quote, or describe the child's work. Do not include log lines. The main agent reads the result itself via `show_agent`, and chooses how to read it. Never tell it which call to make."
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
// holds selectedUrl, used to build the session URL.
|
|
21
21
|
//
|
|
22
22
|
// Outside a Polygraph session (no matching sidecar) the hook is a silent no-op.
|
|
23
|
+
// Speculative captures (implicit session context, not explicit Polygraph sessions)
|
|
24
|
+
// are also treated as no-op and not injected as context.
|
|
23
25
|
|
|
24
26
|
import {
|
|
25
27
|
appendFileSync,
|
|
@@ -94,7 +96,9 @@ function sessionsRoot(root) {
|
|
|
94
96
|
|
|
95
97
|
// Scan the immediate subdirectories of `baseDir`; for each, `candidatePath`
|
|
96
98
|
// maps the subdirectory name to a candidate sidecar file. Returns the first
|
|
97
|
-
// parsed match, or null.
|
|
99
|
+
// parsed match that is not a speculative sidecar, or null.
|
|
100
|
+
// Speculative sidecars (target === 'speculative') are skipped; only regular
|
|
101
|
+
// sidecars (target === 'regular' or missing target) are returned.
|
|
98
102
|
function scanForSidecar(baseDir, candidatePath) {
|
|
99
103
|
if (!existsSync(baseDir)) return null;
|
|
100
104
|
|
|
@@ -109,7 +113,13 @@ function scanForSidecar(baseDir, candidatePath) {
|
|
|
109
113
|
if (!entry.isDirectory()) continue;
|
|
110
114
|
const candidate = candidatePath(entry.name);
|
|
111
115
|
if (existsSync(candidate)) {
|
|
112
|
-
|
|
116
|
+
const sidecar = readJson(candidate);
|
|
117
|
+
if (sidecar) {
|
|
118
|
+
// Skip speculative sidecars; only return regular ones (missing or 'regular' target).
|
|
119
|
+
if (sidecar.target !== 'speculative') {
|
|
120
|
+
return sidecar;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
113
123
|
}
|
|
114
124
|
}
|
|
115
125
|
return null;
|
|
@@ -118,7 +128,7 @@ function scanForSidecar(baseDir, candidatePath) {
|
|
|
118
128
|
// Find the sidecar that maps an agent session id to a Polygraph session.
|
|
119
129
|
// Checks the new per-session layout first, then falls back to the legacy
|
|
120
130
|
// shared sidecars directory. Returns the parsed sidecar object, or null when
|
|
121
|
-
// none matches.
|
|
131
|
+
// none matches or only speculative sidecars are found.
|
|
122
132
|
export function findSidecar(agentSessionId, root = polygraphRoot()) {
|
|
123
133
|
if (!agentSessionId) return null;
|
|
124
134
|
|
package/package.json
CHANGED
|
@@ -304,6 +304,7 @@ If the session has a description timeline, also display:
|
|
|
304
304
|
|
|
305
305
|
1. **Read each result once** — when a poller exits, read that child with a single unwaited `show_agent(sessionId, id)`; `result.text` is the child's final message. Only reach for an explicit `tail` if that is not enough.
|
|
306
306
|
1. **State the output in every brief** — children are told to be concise, so the instruction must say what to return: the shape, a cap where one makes sense, and the exact token for "nothing to report". See [`reference/delegation.md`](reference/delegation.md).
|
|
307
|
+
1. **Batch the spawn and the read** — send several repos in one spawn call and collect their results in one read. Identical work rides a single instruction; differing work goes in a spec file with an entry per repo, sharing only what is genuinely shared. See [`reference/delegation.md`](reference/delegation.md).
|
|
307
308
|
1. **Poll child status before proceeding** — Always verify child agents have reached a terminal `child.status` (`'completed'`, `'failed'`, or `'cancelled'`) before pushing branches or creating PRs
|
|
308
309
|
1. **Link PRs in descriptions** - Reference related PRs in each PR body
|
|
309
310
|
1. **Keep PRs as drafts** until all repos are ready
|
|
@@ -30,7 +30,29 @@ spawn_agent(
|
|
|
30
30
|
|
|
31
31
|
Write the instruction as if to a competent engineer who cannot see your conversation: state the goal, the constraints, what "done" looks like, and what to report back. The child has its own repo and its own context; it inherits nothing from yours.
|
|
32
32
|
|
|
33
|
-
Delegate to several repos in parallel
|
|
33
|
+
Delegate to several repos in parallel before waiting on any of them, and prefer one call over N. There are three shapes, the same three `polygraph agent spawn` takes:
|
|
34
|
+
|
|
35
|
+
- **One repo** — `repo` with an `instruction`.
|
|
36
|
+
- **Several repos, one task** — `repos` with one `instruction`, run verbatim in each.
|
|
37
|
+
- **Several repos, different tasks** — `specFile`, a JSON file you write, whose path you pass.
|
|
38
|
+
|
|
39
|
+
Batching pays because the shared text is transmitted once rather than once per repo, and every sibling starts from a byte-identical prompt prefix that can be served from cache. Each id still gets its own poller.
|
|
40
|
+
|
|
41
|
+
The spec file is the many-to-many form. Each entry names a repo and carries its own `instruction`, and optionally its own `context`, `role`, `agent`, and `model`. Whatever the entries share goes in `sharedInstruction` / `sharedContext`, which are prepended to each entry's own text with a blank line between — so the common brief stays one identical prefix and only the per-repo part varies. An entry may carry no `instruction` of its own and rely entirely on the shared one. Being a file, it also carries a brief too large to pass as an argument, and it is straightforward to generate programmatically.
|
|
42
|
+
|
|
43
|
+
```jsonc
|
|
44
|
+
{
|
|
45
|
+
"sharedInstruction": "Brief every child gets, verbatim.",
|
|
46
|
+
"sharedContext": "Optional background every child gets.",
|
|
47
|
+
"agents": [
|
|
48
|
+
{ "repo": "api", "instruction": "Add the expand parameter to the endpoint." },
|
|
49
|
+
{ "repo": "people", "instruction": "Consume it in the client.", "context": "This repo still pins the old SDK." },
|
|
50
|
+
{ "repo": "planets", "role": "reviewer", "agent": "codex", "instruction": "Review the change against the shared brief." }
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
One qualifier is load-bearing: share only what is genuinely shared. Flattening several different tasks into one `sharedInstruction` to look efficient produces worse work, and the round trips to repair it cost more than the batch saved. Work that differs belongs in each entry's own `instruction`, which is what the file is for.
|
|
34
56
|
|
|
35
57
|
**Own-repo rule.** With the default role, `repo` must be a repository other than the one you are working in — never delegate into your own repo with the default role; work on it directly (ordinary local subagents are fine for that). Delegating into your own repo IS allowed with an explicit non-default `role`, because each (repo, role) pair is a separate agent slot and the child then runs alongside your own default-role work without colliding with it.
|
|
36
58
|
|
|
@@ -75,6 +97,8 @@ show_agent(sessionId: "<sessionId>", id: "<id>")
|
|
|
75
97
|
|
|
76
98
|
One-off unwaited reads like this are cheap and expected inline. It is the *waiting* that belongs in a subagent, not the reading.
|
|
77
99
|
|
|
100
|
+
When several pollers have exited, a **batch read** collects their results in one unwaited call: pass the list of ids and correlate each result by its delegation id. Batch when you spawned children together and have nothing to do until they all finish. Read one at a time when you act on each result as it lands, or only one child is in flight.
|
|
101
|
+
|
|
78
102
|
## When the result is not enough
|
|
79
103
|
|
|
80
104
|
Only if `result.text` is missing, truncated, or the child failed in a way you cannot explain from it:
|