taskchef 5.11.0 → 5.11.1
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/BACKLOG.md +5 -4
- package/README.md +10 -8
- package/SPEC.md +10 -8
- package/docs/delegation-design.md +11 -7
- package/hooks/hooks.json +1 -1
- package/index.js +1 -1
- package/package.json +1 -1
- package/skills/taskchef-delegate/SKILL.md +38 -14
- package/src/delegation.js +3 -4
- package/src/hook.js +40 -4
package/BACKLOG.md
CHANGED
|
@@ -50,7 +50,8 @@ clear data model before implementation.
|
|
|
50
50
|
`resolve_client_thread(clientThreadId) -> { status, threadId? }`. Returning a
|
|
51
51
|
reserved durable ID from `create_thread`, or emitting a materialization event
|
|
52
52
|
containing it, would also close the lifecycle gap.
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
- Replace bounded exact-marker discovery when Codex exposes one of these APIs.
|
|
54
|
+
The initial hook intentionally never links from `session_id`, because Codex
|
|
55
|
+
documents that subagent hooks use the parent session ID. Keep exact
|
|
56
|
+
correlation verification unless an official contract provides equivalent
|
|
57
|
+
guarantees.
|
package/README.md
CHANGED
|
@@ -140,9 +140,12 @@ Every delegated instruction begins with a unique
|
|
|
140
140
|
executor-ownership paragraph, a result-callback paragraph, and the assignment. The
|
|
141
141
|
valid HTML comment stays invisible in rendered Markdown.
|
|
142
142
|
TaskChef records the marked delegation before executor creation. If creation
|
|
143
|
-
does not return a durable thread ID, the
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
does not return a durable thread ID, the dispatcher performs bounded checks at
|
|
144
|
+
10 and 30 seconds and accepts only one recently created task whose structured
|
|
145
|
+
initial input contains the exact marker. The initial-prompt hook may wait for
|
|
146
|
+
that verified link, but never treats its session ID as executor identity:
|
|
147
|
+
subagent hooks can carry the parent session ID. If no unique match appears,
|
|
148
|
+
TaskChef leaves the entry unresolved instead of risking a wrong link.
|
|
146
149
|
|
|
147
150
|
### Ask for a live report
|
|
148
151
|
|
|
@@ -433,11 +436,10 @@ printf '%s\n' '{"id":"c0f010ff-84f2-4838-a69d-0ff1f5d721d7","project":"/workspac
|
|
|
433
436
|
taskchef task record --json
|
|
434
437
|
```
|
|
435
438
|
|
|
436
|
-
If a task has `threadId: null
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
the one-way transition from null to one unique thread ID:
|
|
439
|
+
If a task still has `threadId: null` after bounded resolution, direct manual
|
|
440
|
+
recovery may use the CLI operation below after verifying one exact structured
|
|
441
|
+
marker match. The same locked atomic logic permits only the one-way transition
|
|
442
|
+
from null to one unique thread ID:
|
|
441
443
|
|
|
442
444
|
```sh
|
|
443
445
|
taskchef task resolve c0f010ff-84f2-4838-a69d-0ff1f5d721d7 \
|
package/SPEC.md
CHANGED
|
@@ -22,8 +22,8 @@ latest useful semantic result, not a second lifecycle or event database.
|
|
|
22
22
|
4. It embeds a generated TaskChef UUID marker, executor-ownership sentence, and
|
|
23
23
|
result-callback instruction, then records the task with `threadId: null`.
|
|
24
24
|
5. It creates an independently openable Codex task. A durable returned ID is
|
|
25
|
-
resolved immediately; otherwise
|
|
26
|
-
|
|
25
|
+
resolved immediately; otherwise bounded recent-task checks accept exactly
|
|
26
|
+
one child whose structured initial input contains the exact marker.
|
|
27
27
|
6. It returns without waiting for executor work to complete.
|
|
28
28
|
7. Executors report `completed`, `needs_input`, or `failed` through MCP. Reports
|
|
29
29
|
filter old terminal entries, use one live metadata snapshot, and read only
|
|
@@ -217,15 +217,17 @@ For each assignment, `$taskchef-delegate`:
|
|
|
217
217
|
before executor creation
|
|
218
218
|
5. creates a real Codex task at the exact configured path
|
|
219
219
|
6. calls `resolve_task` when creation returns a durable thread ID; otherwise it
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
checks at 10 and 30 seconds and resolves only one recent task whose
|
|
221
|
+
structured initial input contains the exact marker
|
|
222
222
|
7. records executor-creation failure as a `failed` semantic result on the
|
|
223
223
|
already-existing entry
|
|
224
|
-
8.
|
|
224
|
+
8. returns after the bounded identity check without waiting for executor work.
|
|
225
225
|
|
|
226
226
|
Recording first closes the creation/hook race. On the exact marked prompt, the
|
|
227
|
-
hook
|
|
228
|
-
plus `working`.
|
|
227
|
+
hook waits for the separately verified canonical identity, then writes only the
|
|
228
|
+
initial turn plus `working`. It never persists the hook `session_id`, because a
|
|
229
|
+
subagent hook can receive its parent's session ID. On later prompts whose
|
|
230
|
+
session ID matches a recorded executor,
|
|
229
231
|
the same `UserPromptSubmit` hook reads the task history and supplies the current
|
|
230
232
|
turn ID as callback context without writing. No permission, tool, stop,
|
|
231
233
|
notification, or session hook writes task lifecycle state. A provisional
|
|
@@ -369,7 +371,7 @@ TaskChef does not include:
|
|
|
369
371
|
2. Project metadata routes an unambiguous request to the correct local project.
|
|
370
372
|
3. A delegation records `working` before creating a visible Codex task.
|
|
371
373
|
4. A durable creation resolves immediately; a provisional creation is linked
|
|
372
|
-
|
|
374
|
+
only after bounded discovery verifies one exact structured-marker match.
|
|
373
375
|
5. The dispatcher returns without waiting for execution.
|
|
374
376
|
6. Several independent assignments can create several entries, including
|
|
375
377
|
multiple entries for the same project.
|
|
@@ -27,8 +27,11 @@ sequenceDiagram
|
|
|
27
27
|
alt Durable ID returned
|
|
28
28
|
D->>M: resolve_task
|
|
29
29
|
else Provisional ID returned
|
|
30
|
+
D->>C: Bounded recent-task checks
|
|
31
|
+
D->>C: Read candidates and verify exact marker
|
|
32
|
+
D->>M: resolve_task(verified child ID)
|
|
30
33
|
C->>H: Initial UserPromptSubmit
|
|
31
|
-
H->>W:
|
|
34
|
+
H->>W: Wait for verified link; record initial turn
|
|
32
35
|
end
|
|
33
36
|
D-->>U: Return immediately
|
|
34
37
|
C->>M: report_result(needs_input | completed | failed)
|
|
@@ -37,8 +40,8 @@ sequenceDiagram
|
|
|
37
40
|
|
|
38
41
|
Recording happens before creation. This closes the only important race: when
|
|
39
42
|
the initial hook runs, the exact TaskChef marker already has an entry to update.
|
|
40
|
-
|
|
41
|
-
wakeup.
|
|
43
|
+
The dispatcher performs only the bounded 10/30-second identity checks. There is
|
|
44
|
+
no scheduler, daemon, indefinite polling, or dispatcher wakeup.
|
|
42
45
|
|
|
43
46
|
Every executor receives this ownership instruction unchanged:
|
|
44
47
|
|
|
@@ -49,8 +52,8 @@ Every executor receives this ownership instruction unchanged:
|
|
|
49
52
|
| Writer | Trigger and condition | Fields it owns |
|
|
50
53
|
| --- | --- | --- |
|
|
51
54
|
| Dispatcher via `record_task` | Before executor creation | New entry, `status: working`, null identity/result, server timestamps |
|
|
52
|
-
| Dispatcher via `resolve_task` | Creation
|
|
53
|
-
| Initial `UserPromptSubmit` hook | Prompt starts with the exact TaskChef marker and the
|
|
55
|
+
| Dispatcher via `resolve_task` | Creation returns a durable ID, or bounded discovery verifies one exact-marker child | `threadId` only |
|
|
56
|
+
| Initial `UserPromptSubmit` hook | Prompt starts with the exact TaskChef marker and the verified link exists | Initial `turnId`, `status: working`, `updatedAt`, `updatedBy: hook` |
|
|
54
57
|
| Follow-up `UserPromptSubmit` hook | Session ID exactly matches a recorded executor | Nothing; reads the snapshot and injects the current `turnId` for the MCP callback |
|
|
55
58
|
| Executor via `report_result` | Work has a semantic outcome | `status`, bounded `summary`, result `turnId`, `updatedAt`, `updatedBy: mcp` |
|
|
56
59
|
| Reporter | On explicit report request | Nothing; inferred live state is never persisted |
|
|
@@ -147,7 +150,8 @@ provide the stronger check whenever a targeted response is necessary.
|
|
|
147
150
|
## Permission and follow-up example
|
|
148
151
|
|
|
149
152
|
1. Delegation records one `working` entry with null identity.
|
|
150
|
-
2. The
|
|
153
|
+
2. The dispatcher verifies and resolves the child thread; the initial hook then
|
|
154
|
+
records its initial turn without trusting the inherited session ID.
|
|
151
155
|
3. The executor reaches a real product decision and calls `report_result` with
|
|
152
156
|
`needs_input` plus “Approve deployment to production.”
|
|
153
157
|
4. The user opens that executor and approves. The same hook reads the matching
|
|
@@ -168,7 +172,7 @@ live task state.
|
|
|
168
172
|
- lifecycle event types beyond `UserPromptSubmit`
|
|
169
173
|
- fork tracking or result merging
|
|
170
174
|
- SQLite
|
|
171
|
-
- polling, reconciliation schedules, daemons, and dispatcher wakeups
|
|
175
|
+
- indefinite polling, reconciliation schedules, daemons, and dispatcher wakeups
|
|
172
176
|
- durable report watermarks
|
|
173
177
|
- transcript or assistant-prose classification
|
|
174
178
|
- transport-authenticated caller thread/turn identity
|
package/hooks/hooks.json
CHANGED
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: taskchef-delegate
|
|
3
|
-
description: "Dispatch actionable requests through the per-user TaskChef workspace into independently openable Codex project tasks. Use automatically for actionable work received in the canonical TaskChef dispatcher workspace. From any other project, use only when the user explicitly asks to delegate or split separate work into Codex tasks; TaskChef-related subject matter alone is not delegation intent. Record before creation,
|
|
3
|
+
description: "Dispatch actionable requests through the per-user TaskChef workspace into independently openable Codex project tasks. Use automatically for actionable work received in the canonical TaskChef dispatcher workspace. From any other project, use only when the user explicitly asks to delegate or split separate work into Codex tasks; TaskChef-related subject matter alone is not delegation intent. Record before creation, resolve provisional identity by exact marker within a strict bound, and never wait for executor completion."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# TaskChef Delegate
|
|
@@ -32,11 +32,13 @@ explicitly requested benchmark artifact.
|
|
|
32
32
|
- Keep only `AGENTS.md`, `taskchef.json`, and `tasks.jsonl` in a dispatcher
|
|
33
33
|
workspace.
|
|
34
34
|
- Use real Codex tasks, never collaboration or subagent tools.
|
|
35
|
-
- Use only TaskChef's `UserPromptSubmit` hook:
|
|
36
|
-
provides read-only current-turn
|
|
35
|
+
- Use only TaskChef's `UserPromptSubmit` hook: after identity is separately
|
|
36
|
+
verified, it records the initial turn and provides read-only current-turn
|
|
37
|
+
context on follow-up prompts. Never infer
|
|
37
38
|
lifecycle state from hooks.
|
|
38
|
-
- Never use schedules, daemons,
|
|
39
|
-
|
|
39
|
+
- Never use schedules, daemons, background monitors, or indefinite polling.
|
|
40
|
+
The only permitted thread discovery is the bounded exact-marker resolution
|
|
41
|
+
after a provisional creation result.
|
|
40
42
|
- Never wait for delegated work after executor creation.
|
|
41
43
|
- Never collect transcripts or hidden reasoning.
|
|
42
44
|
|
|
@@ -88,13 +90,32 @@ explicitly requested benchmark artifact.
|
|
|
88
90
|
environment on its executor host, the marked instruction, and a short title.
|
|
89
91
|
8. If creation returns a durable `threadId`, call `resolve_task` immediately.
|
|
90
92
|
If creation returns only `clientThreadId`, `pendingWorktreeId`, or a `local:`
|
|
91
|
-
ID, retain it only for the created-thread directive
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
ID, retain it only for the created-thread directive and run this one bounded
|
|
94
|
+
identity-resolution sequence:
|
|
95
|
+
- Check immediately, then at 10 and 30 seconds after creation. At each
|
|
96
|
+
checkpoint, call the native recent-task listing once.
|
|
97
|
+
- Restrict candidates to Codex tasks created no earlier than five seconds
|
|
98
|
+
before `preparedAt`, on the returned host and selected project when those
|
|
99
|
+
fields are available. Prefer the exact title only as an ordering hint.
|
|
100
|
+
- Read each remaining bounded candidate at most once per checkpoint and
|
|
101
|
+
accept only the single task whose structured initial
|
|
102
|
+
`codexDelegation.input` begins with this outcome's exact marker.
|
|
103
|
+
- Call `resolve_task` only for that one verified durable child ID. Never use
|
|
104
|
+
`session_id`, a source/parent ID, title similarity, or a provisional ID as
|
|
105
|
+
identity proof.
|
|
106
|
+
- Stop at 30 seconds. Zero matches remain unresolved; multiple exact matches
|
|
107
|
+
are an ambiguity and also remain unresolved. Never guess or continue
|
|
108
|
+
polling.
|
|
109
|
+
|
|
110
|
+
The initial hook may wait for this canonical record transition so it can
|
|
111
|
+
inject the verified child ID and current turn ID. Codex documents that
|
|
112
|
+
subagent hooks receive the parent `session_id`, so the hook must never write
|
|
113
|
+
that field into `threadId`.
|
|
94
114
|
|
|
95
115
|
9. If executor creation fails, call `report_result` for the already-recorded
|
|
96
116
|
task with `failed`, null thread/turn IDs, and a concise creation error.
|
|
97
|
-
10. Emit the appropriate created-thread directive and return
|
|
117
|
+
10. Emit the appropriate created-thread directive and return after the bounded
|
|
118
|
+
identity step.
|
|
98
119
|
Treat a nullable record as preserved but not yet linked. Do not read an
|
|
99
120
|
executor for progress and never wait for executor work completion.
|
|
100
121
|
|
|
@@ -107,17 +128,20 @@ Keep the CLI for bootstrap, manual inspection, and recovery outside delegation.
|
|
|
107
128
|
|
|
108
129
|
## Later resolution
|
|
109
130
|
|
|
110
|
-
The trusted initial hook
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
131
|
+
The trusted initial hook never resolves identity from its `session_id`. It uses
|
|
132
|
+
only the child ID already stored by immediate or bounded exact-marker
|
|
133
|
+
resolution. For manual recovery, require one exact structured marker match,
|
|
134
|
+
then call `resolve_task` once. Never edit `tasks.jsonl` directly. Resolution is
|
|
135
|
+
an idempotent one-way transition from `threadId: null` to one unique durable
|
|
136
|
+
child thread ID.
|
|
114
137
|
|
|
115
138
|
## Legacy benchmark compatibility
|
|
116
139
|
|
|
117
140
|
The schema-v1 benchmark fixtures below describe the removed snapshot resolver
|
|
118
141
|
and remain only for historical-result validation. Do not use that resolver for
|
|
119
142
|
new delegation benchmarks. A future benchmark schema should measure record,
|
|
120
|
-
creation,
|
|
143
|
+
creation, bounded exact-marker resolution, and result callback without
|
|
144
|
+
indefinite polling. The
|
|
121
145
|
legacy fixture starts from
|
|
122
146
|
`<plugin-root>/assets/e2e-benchmark-example.json`. Capture one ISO start/end
|
|
123
147
|
interval for the parallel preparation/project-list operation, followed by
|
package/src/delegation.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// before the next major release.
|
|
3
|
+
// Bounded discovery parameters used when Codex returns only a provisional
|
|
4
|
+
// client ID. Candidate content still needs exact-marker verification.
|
|
6
5
|
export const THREAD_RESOLUTION_CHECKPOINTS_MS = Object.freeze([10_000, 30_000]);
|
|
7
6
|
export const THREAD_RESOLUTION_TIMEOUT_MS = 30_000;
|
|
8
7
|
export const THREAD_RESOLUTION_RECENT_LIMIT = 50;
|
|
@@ -310,7 +309,7 @@ export async function createAndRecordDelegation(input) {
|
|
|
310
309
|
|
|
311
310
|
return {
|
|
312
311
|
status: "recorded-unresolved",
|
|
313
|
-
reason: "awaiting-
|
|
312
|
+
reason: "awaiting-bounded-marker-resolution",
|
|
314
313
|
...prepared,
|
|
315
314
|
threadId: null,
|
|
316
315
|
provisional,
|
package/src/hook.js
CHANGED
|
@@ -2,6 +2,10 @@ import { parseTaskChefMarker } from "./delegation.js";
|
|
|
2
2
|
import { listTasks, startTaskFromHook } from "./workspace.js";
|
|
3
3
|
import { resolveWorkspacePath } from "./workspace-path.js";
|
|
4
4
|
|
|
5
|
+
// The final hook check trails the dispatcher's 30-second resolver checkpoint
|
|
6
|
+
// so a simultaneous final match is visible before the hook fails closed.
|
|
7
|
+
export const INITIAL_LINK_CHECKPOINTS_MS = Object.freeze([0, 250, 1_000, 3_000, 10_000, 32_000]);
|
|
8
|
+
|
|
5
9
|
function nonEmptyString(value) {
|
|
6
10
|
return typeof value === "string" && value.trim().length > 0
|
|
7
11
|
? value.trim()
|
|
@@ -24,27 +28,59 @@ function hookContext(taskId, threadId, turnId) {
|
|
|
24
28
|
};
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
async function waitForLinkedTask(root, taskId, {
|
|
32
|
+
checkpoints = INITIAL_LINK_CHECKPOINTS_MS,
|
|
33
|
+
listTaskSnapshots = listTasks,
|
|
34
|
+
waitImpl = (delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)),
|
|
35
|
+
} = {}) {
|
|
36
|
+
let previousCheckpoint = 0;
|
|
37
|
+
for (const checkpoint of checkpoints) {
|
|
38
|
+
if (!Number.isFinite(checkpoint) || checkpoint < previousCheckpoint) {
|
|
39
|
+
throw new Error("initial-link checkpoints must be finite, non-decreasing milliseconds");
|
|
40
|
+
}
|
|
41
|
+
const delay = checkpoint - previousCheckpoint;
|
|
42
|
+
if (delay > 0) await waitImpl(delay);
|
|
43
|
+
previousCheckpoint = checkpoint;
|
|
44
|
+
const task = (await listTaskSnapshots(root)).find((item) => item.id === taskId);
|
|
45
|
+
if (task?.threadId) return task;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
27
50
|
export async function handleInitialPromptHook(input, {
|
|
28
51
|
workspace = null,
|
|
29
52
|
resolveWorkspace = () => resolveWorkspacePath().workspace,
|
|
30
53
|
startTask = startTaskFromHook,
|
|
31
54
|
listTaskSnapshots = listTasks,
|
|
55
|
+
linkCheckpoints = INITIAL_LINK_CHECKPOINTS_MS,
|
|
56
|
+
waitImpl = (delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)),
|
|
32
57
|
} = {}) {
|
|
33
58
|
if (input?.hook_event_name !== "UserPromptSubmit") return { continue: true };
|
|
34
59
|
const taskId = parseTaskChefMarker(input.prompt);
|
|
35
60
|
const threadId = nonEmptyString(input.session_id);
|
|
36
61
|
const turnId = nonEmptyString(input.turn_id);
|
|
37
|
-
if (
|
|
62
|
+
if (turnId === null || (taskId === null && threadId === null)) {
|
|
38
63
|
if (taskId === null) return { continue: true };
|
|
39
64
|
return {
|
|
40
65
|
continue: true,
|
|
41
|
-
systemMessage: "TaskChef could not
|
|
66
|
+
systemMessage: "TaskChef could not prepare this initial task because the hook payload lacked a turn ID.",
|
|
42
67
|
};
|
|
43
68
|
}
|
|
44
69
|
if (taskId !== null) {
|
|
45
70
|
const root = workspace ?? resolveWorkspace();
|
|
46
|
-
await
|
|
47
|
-
|
|
71
|
+
const linked = await waitForLinkedTask(root, taskId, {
|
|
72
|
+
checkpoints: linkCheckpoints,
|
|
73
|
+
listTaskSnapshots,
|
|
74
|
+
waitImpl,
|
|
75
|
+
});
|
|
76
|
+
if (linked === null) {
|
|
77
|
+
return {
|
|
78
|
+
continue: true,
|
|
79
|
+
systemMessage: "TaskChef left this task unresolved because its durable child task ID could not be verified. Do not report a semantic result until the recorded identity is repaired.",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
await startTask(root, taskId, linked.threadId, turnId);
|
|
83
|
+
return hookContext(taskId, linked.threadId, turnId);
|
|
48
84
|
}
|
|
49
85
|
|
|
50
86
|
try {
|