taskchef 5.11.2 → 6.0.0

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,178 +1,77 @@
1
1
  # Delegation and result design
2
2
 
3
- TaskChef is deliberately smaller than a workflow engine. It records one useful
4
- snapshot per delegated task, links the executor identity once, accepts semantic
5
- results from the executor, and performs cheap freshness checks when reporting.
6
-
7
- ## Minimal workflow
8
-
9
- ```mermaid
10
- sequenceDiagram
11
- participant U as User
12
- participant D as Delegate skill
13
- participant M as TaskChef MCP
14
- participant C as Codex executor
15
- participant H as Initial hook
16
- participant W as tasks.jsonl
17
-
18
- U->>D: Delegate work
19
- par Prepare routing
20
- D->>M: prepare_dispatch
21
- and
22
- D->>C: List native projects
23
- end
24
- D->>M: record_task(threadId: null)
25
- M->>W: Append working task under lock
26
- D->>C: Create task with exact marker
27
- alt Durable ID returned
28
- D->>M: resolve_task
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)
33
- C->>H: Initial UserPromptSubmit
34
- H->>W: Wait for verified link; record initial turn
35
- end
36
- D-->>U: Return immediately
37
- C->>M: report_result(needs_input | completed | failed)
38
- M->>W: Replace latest semantic snapshot under lock
39
- ```
40
-
41
- Recording happens before creation. This closes the only important race: when
42
- the initial hook runs, the exact TaskChef marker already has an entry to update.
43
- The dispatcher performs only the bounded 10/30-second identity checks. There is
44
- no scheduler, daemon, indefinite polling, or dispatcher wakeup.
45
-
46
- Every executor receives this ownership instruction unchanged:
47
-
48
- > This task owns the delegated assignment. Execute it in this task; do not re-dispatch it merely because it concerns TaskChef or a configured project. Explicit requests to delegate separate work remain valid.
49
-
50
- ## Who writes what
51
-
52
- | Writer | Trigger and condition | Fields it owns |
53
- | --- | --- | --- |
54
- | Dispatcher via `record_task` | Before executor creation | New entry, `status: working`, null identity/result, server timestamps |
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` |
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 |
58
- | Executor via `report_result` | Work has a semantic outcome | `status`, bounded `summary`, result `turnId`, `updatedAt`, `updatedBy: mcp` |
59
- | Reporter | On explicit report request | Nothing; inferred live state is never persisted |
60
-
61
- The hook does not write needs-input, completed, or failed. Its follow-up path is
62
- read-only and exists only so the executor can report the current turn. A native permission
63
- request is live Codex state; it is not a TaskChef semantic result. The executor
64
- uses `needs_input` only when it truly requires a user decision or information.
65
-
66
- ## Task snapshot
67
-
68
- Schema version 3 retains the delegation fields and adds:
69
-
70
- - `status`: `working`, `needs_input`, `completed`, or `failed`
71
- - `summary`: null while working, otherwise a concise result capped at 2,000 characters
72
- - `turnId`: initial or latest reported turn; linked MCP results require it, and
73
- only a pre-thread creation failure may report null
74
- - `updatedAt`: server-side timestamp
75
- - `updatedBy`: `dispatcher`, `hook`, or `mcp`
76
-
77
- Schema versions 1 and 2 remain readable and normalize to nullable result fields.
78
- There is no result-event file: each callback replaces the latest snapshot on the
79
- same JSONL line.
80
- Result instructions forbid secrets, transcripts, and raw command output; the
81
- server also caps the stored summary at 2,000 characters.
82
-
83
- ## Locking and conflicts
84
-
85
- All configuration, identity, and result writes use the existing cross-process
86
- workspace lock. A writer acquires the lock, rereads and validates the complete
87
- JSONL file, changes one exact task, and publishes a complete replacement with
88
- an atomic rename. Concurrent writers therefore cannot create partial JSON,
89
- duplicate entries, or lose changes to different tasks. Sequential callbacks for
90
- the same task use last accepted write wins; normal executor turns are already
91
- sequential.
92
-
93
- SQLite is postponed because this file-level write volume is tiny and the
94
- existing lock provides the property users need. SQLite becomes worthwhile only
95
- if TaskChef later adds high-frequency event history or many continuous writers.
96
-
97
- ## Result trust
98
-
99
- The MCP server does not receive an independently authenticated caller task ID
100
- from the model transport. It validates that the supplied task exists and that
101
- the supplied durable thread ID exactly matches the recorded thread. The turn ID
102
- is stored as evidence but remains model-supplied. A trusted plugin install,
103
- local-only MCP server, bounded summary, and exact task/thread match are the
104
- current trust boundary.
105
-
106
- This is sufficient for a lightweight personal dispatcher, but not a
107
- multi-tenant authorization boundary. Transport-authenticated caller identity is
108
- postponed until Codex exposes it.
109
-
110
- ## Fresh reporting without reading every task
111
-
112
- A stored result is cached evidence, not permanent truth. Overview reports:
113
-
114
- 1. Load `tasks.jsonl` once.
115
- 2. Always consider working, needs-input, unresolved, and legacy entries.
116
- 3. Consider completed or failed entries updated in the last seven days.
117
- 4. Take one recent-thread metadata snapshot for all selected tasks. Include an
118
- older terminal task in an overview when the snapshot shows it is active or
119
- awaiting native approval.
120
- 5. A null-thread/null-turn `failed` snapshot written by MCP is a fresh creation
121
- failure and needs no live task lookup because no executor exists.
122
- 6. Treat only `updatedBy: mcp` as a semantic cache. Dispatcher- and hook-written
123
- `working` snapshots require a targeted live read; an inactive task with no
124
- callback has an unknown outcome.
125
- 7. In a broad overview, use an MCP result directly when identity is certain and
126
- the task is inactive; do not fan out detailed reads over idle terminal tasks
127
- solely because their timestamps are newer.
128
- 8. Active or awaiting-approval metadata overrides the cached result directly.
129
- For a focused task, title, or project report, read each selected inactive
130
- task at most once when matched metadata is newer than the callback by any
131
- amount. Batch targeted immediate reads, at most eight tasks per call, also
132
- for a missing callback, uncertain or contradictory state, or an explicitly
133
- fully-live request.
134
- 9. If an anomaly triggers a detailed read, compare its latest structured turn
135
- ID and native turn state with stored `turnId`. A newer turn without a
136
- callback makes the cache stale. An interrupted or cancelled callback turn
137
- cannot prove completion.
138
-
139
- An explicit task, title, or project report bypasses the seven-day overview
140
- filter. Old terminal tasks skipped from an overview are counted so the user
141
- knows history was intentionally omitted.
142
-
143
- The cheap operation is the single list/metadata snapshot, not one read per
144
- historical task. It is sufficient to expose active and native-approval state for
145
- many recent tasks at once. It does not prove completion; semantic outcomes come
146
- from MCP callbacks. Detailed thread reads are the exceptional fallback.
147
- Timestamps are a pragmatic anomaly filter; turn IDs and native turn state
148
- provide the stronger check whenever a targeted response is necessary.
149
-
150
- ## Permission and follow-up example
151
-
152
- 1. Delegation records one `working` entry with null identity.
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.
155
- 3. The executor reaches a real product decision and calls `report_result` with
156
- `needs_input` plus “Approve deployment to production.”
157
- 4. The user opens that executor and approves. The same hook reads the matching
158
- task and injects the new turn ID without changing the stored snapshot. Until
159
- the final callback, a report sees newer/active live metadata and labels the
160
- cached needs-input result stale.
161
- 5. The executor finishes and calls `report_result` with `completed`, the new
162
- turn ID, and a concise outcome. The same JSONL line now contains the completed
163
- snapshot.
164
-
165
- If step 3 were merely Codex asking for filesystem or command approval, no MCP
166
- callback would be written. Reporting would show “awaiting native approval” from
167
- live task state.
168
-
169
- ## Explicitly postponed
170
-
171
- - append-only result or transition events
172
- - lifecycle event types beyond `UserPromptSubmit`
173
- - fork tracking or result merging
174
- - SQLite
175
- - indefinite polling, reconciliation schedules, daemons, and dispatcher wakeups
176
- - durable report watermarks
177
- - transcript or assistant-prose classification
178
- - transport-authenticated caller thread/turn identity
3
+ TaskChef stores a durable local task history while Codex owns execution. The
4
+ dispatcher records intent, the executor registers its own identity, and only
5
+ the executor reports semantic outcomes.
6
+
7
+ ## Lifecycle
8
+
9
+ 1. `prepare_dispatch` returns a fresh task UUID, exact marker, timestamp, and
10
+ configured projects.
11
+ 2. The dispatcher calls `record_task` with the complete marked instruction and
12
+ `threadId: null` before native creation.
13
+ 3. The dispatcher creates the Codex task and returns immediately. A durable or
14
+ provisional creation result is never treated as authority to link the
15
+ record.
16
+ 4. As its first TaskChef action, the executor reads its own durable native
17
+ thread ID and calls `link_task(taskId, threadId)`.
18
+ 5. Before ending a turn with a semantic outcome, the executor reads that exact
19
+ thread, takes the current turn ID, and calls `report_result`.
20
+
21
+ There is no task listing, candidate read, marker search, wait, retry loop,
22
+ transcript read, or hook in the dispatch path. The existing filesystem watcher
23
+ notices the atomic `link_task` rewrite and immediately refreshes the dashboard.
24
+
25
+ ## Identity guarantees
26
+
27
+ The exact marker correlates the child instruction with the pre-created record.
28
+ The executor must use its native current thread ID, never the delegation
29
+ `sourceThreadId`, parent task, inherited session identity, title, or provisional
30
+ client ID.
31
+
32
+ `link_task` runs under the workspace lock. It permits one atomic
33
+ `null`-to-durable transition, rejects malformed or provisional IDs, rejects a
34
+ thread already owned by another TaskChef task, rejects a different retry, and
35
+ returns the existing snapshot for an identical retry.
36
+
37
+ Custom MCP does not currently authenticate the calling Codex task. The thread
38
+ ID is therefore a cooperative assertion inside TaskChef's local single-user
39
+ trust boundary. The design prevents accidental parent/child confusion but does
40
+ not claim resistance to a deliberately forged local MCP call.
41
+
42
+ ## Failure and retry behavior
43
+
44
+ If native creation fails after recording, the dispatcher writes one terminal
45
+ `failed` result with null thread and turn IDs and a bounded summary. If the
46
+ executor is interrupted, cancelled, cannot see `link_task`, or gets a rejected
47
+ link, the record remains `working` with `threadId: null`. That visible
48
+ link-pending state is retryable on a later turn. TaskChef never guesses or
49
+ recovers identity through the dashboard.
50
+
51
+ ## Result freshness
52
+
53
+ Linked results require an exact thread-ID match and a non-null current turn ID.
54
+ For self-linked schema 4 journeys, the native time-ordered Codex UUID must be
55
+ strictly newer for every changed result; exact same-turn retries remain safe.
56
+ Identical `report_result` retries are safe. A follow-up or resumed executor must
57
+ read its exact task again and report the new turn ID; reusing the initial turn
58
+ cannot establish freshness.
59
+
60
+ The latest snapshot fields are `status`, bounded `summary`, `turnId`,
61
+ `updatedAt`, and `updatedBy`. Historical `updatedBy: hook` values remain
62
+ readable, but new installations contain no hook and new writes use `dispatcher`
63
+ or `mcp`.
64
+
65
+ ## Legacy recovery
66
+
67
+ `taskchef task resolve` is retained only for unresolved schema 1-3 records.
68
+ Operators must establish one exact marker match and one unique durable child
69
+ ID. Schema 4 self-linking records reject manual resolution. History is read
70
+ compatibly and is not eagerly rewritten.
71
+
72
+ ## Dashboard and reports
73
+
74
+ The dashboard deep link uses only the stored self-linked child ID. File watcher
75
+ events surface linking and results without user interaction. Reporting may
76
+ compare current native metadata with cached semantic results, but it never
77
+ writes inferred lifecycle state.
package/index.js CHANGED
@@ -9,6 +9,7 @@ export {
9
9
  filterTasks,
10
10
  importProjects,
11
11
  initializeWorkspace,
12
+ linkTask,
12
13
  listProjects,
13
14
  prepareDispatch,
14
15
  readConfig,
@@ -17,7 +18,6 @@ export {
17
18
  recordTask,
18
19
  reportTaskResult,
19
20
  resolveTask,
20
- startTaskFromHook,
21
21
  requireSafeId,
22
22
  removeProject,
23
23
  validateConfig,
@@ -31,20 +31,14 @@ export {
31
31
 
32
32
  export {
33
33
  EXECUTOR_OWNERSHIP_PARAGRAPH,
34
+ EXECUTOR_LINK_PARAGRAPH,
34
35
  EXECUTOR_RESULT_PARAGRAPH,
35
- THREAD_RESOLUTION_CHECKPOINTS_MS,
36
- THREAD_RESOLUTION_CLOCK_SKEW_MS,
37
- THREAD_RESOLUTION_RECENT_LIMIT,
38
- THREAD_RESOLUTION_TIMEOUT_MS,
39
36
  createAndRecordDelegation,
40
- filterThreadCandidates,
41
- hasExactTaskChefMarker,
42
37
  isProvisionalThreadId,
43
- listThreadEntries,
38
+ normalizeCodexThreadId,
44
39
  normalizeDurableThreadId,
45
40
  parseTaskChefMarker,
46
41
  prepareDelegation,
47
- structuredDelegatedInputs,
48
42
  taskChefMarker,
49
43
  } from "./src/delegation.js";
50
44
 
@@ -69,5 +63,3 @@ export {
69
63
  } from "./src/dashboard.js";
70
64
 
71
65
  export { createTaskChefMcpServer } from "./src/mcp.js";
72
-
73
- export { INITIAL_LINK_CHECKPOINTS_MS, handleInitialPromptHook } from "./src/hook.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskchef",
3
- "version": "5.11.2",
3
+ "version": "6.0.0",
4
4
  "description": "A non-blocking interactive dispatcher for visible Codex tasks.",
5
5
  "license": "MIT",
6
6
  "author": "Favo Yang",
@@ -24,7 +24,6 @@
24
24
  "BACKLOG.md",
25
25
  "bin",
26
26
  "docs/delegation-design.md",
27
- "hooks",
28
27
  "index.js",
29
28
  "mcp",
30
29
  "SPEC.md",