@evoclock/pi-agentic-driver 0.9.2 → 0.9.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.
- package/PROVENANCE.md +9 -0
- package/README.md +178 -73
- package/extensions/tasks.ts +570 -0
- package/janus/README.md +196 -0
- package/janus/janus/audit.ts +213 -0
- package/janus/janus/budget.ts +101 -0
- package/janus/janus/budget_breaker.ts +105 -0
- package/janus/janus/config.ts +332 -0
- package/janus/janus/errors.ts +90 -0
- package/janus/janus/evaluate.ts +230 -0
- package/janus/janus/keychain.ts +119 -0
- package/janus/janus/providers.ts +544 -0
- package/janus/janus/schema.ts +344 -0
- package/janus/janus/server.ts +267 -0
- package/janus/janus/single_flight.ts +99 -0
- package/janus/janus/verdict.ts +192 -0
- package/janus/package-lock.json +616 -0
- package/janus/package.json +29 -0
- package/janus/tests/accounting.test.ts +229 -0
- package/janus/tests/allowlist.test.ts +97 -0
- package/janus/tests/config.test.ts +151 -0
- package/janus/tests/errors.test.ts +147 -0
- package/janus/tests/evaluate_pipeline.test.ts +358 -0
- package/janus/tests/helpers.ts +217 -0
- package/janus/tests/providers.test.ts +292 -0
- package/janus/tests/redaction_budget.test.ts +227 -0
- package/janus/tests/schema.test.ts +356 -0
- package/janus/tests/server.test.ts +288 -0
- package/janus/tests/single_flight.test.ts +84 -0
- package/janus/tsconfig.json +19 -0
- package/janus/user.janus.plist +51 -0
- package/package.json +7 -2
- package/scripts/enforcement/router_schemas_pi.js +63 -13
- package/scripts/enforcement/session_tasks_core_pi.js +605 -0
- package/scripts/enforcement/session_tasks_validate_pi.js +235 -0
- package/skills/tasks/SKILL.md +110 -0
- package/templates/AGENTS.md +11 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Julen Gamboa <j.a.r.gamboa@gmail.com>
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
+
|
|
4
|
+
// `tasks validate` (rule 30): a read-only setup-time validator in the shape
|
|
5
|
+
// of nudge_validate_pi.js and router_validate_pi.js. It loads everything
|
|
6
|
+
// exactly as the runtime would, checks it, and exits 0 only when the tasks
|
|
7
|
+
// surface is operational — otherwise nonzero with a machine-readable error
|
|
8
|
+
// list. It never mutates state, never writes the board, never promotes.
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
11
|
+
import { join, resolve } from "node:path";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { pathToFileURL } from "node:url";
|
|
14
|
+
import {
|
|
15
|
+
SESSION_TASKS_SCHEMA,
|
|
16
|
+
resolveTaskListId, tasksMirrorPath, loadSnapshotFile,
|
|
17
|
+
validateSnapshot, validateSessionTask,
|
|
18
|
+
promotionIdempotencyKey, findExistingPromotedCard, resolveCallerOrigin, hasTasksCapability,
|
|
19
|
+
TASKS_CAPABILITY_ENV,
|
|
20
|
+
} from "./session_tasks_core_pi.js";
|
|
21
|
+
import { validateBoard } from "./task_board_core_pi.js";
|
|
22
|
+
|
|
23
|
+
function readJsonIfExists(path) {
|
|
24
|
+
if (!existsSync(path)) return null;
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readTasksConfig(repoRoot) {
|
|
33
|
+
const candidates = [
|
|
34
|
+
join(repoRoot, ".agentic-driver", "tasks.json"),
|
|
35
|
+
join(homedir(), ".config", "agentic-driver", "tasks.json"),
|
|
36
|
+
];
|
|
37
|
+
for (const candidate of candidates) {
|
|
38
|
+
const value = readJsonIfExists(candidate);
|
|
39
|
+
if (value !== null) return { path: candidate, value };
|
|
40
|
+
}
|
|
41
|
+
return { path: null, value: null };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Validate the tasks surface. Pure observation: no writes, no promotion.
|
|
46
|
+
* Returns { valid, operational, checks, errors, warnings }.
|
|
47
|
+
*/
|
|
48
|
+
export function validateTasksSetup({
|
|
49
|
+
repoRoot = process.cwd(),
|
|
50
|
+
session = null,
|
|
51
|
+
boardPath = null,
|
|
52
|
+
tasksRoot = null,
|
|
53
|
+
capabilityEnv = process.env,
|
|
54
|
+
resolveOrigin = null,
|
|
55
|
+
} = {}) {
|
|
56
|
+
const errors = [];
|
|
57
|
+
const warnings = [];
|
|
58
|
+
const checks = {};
|
|
59
|
+
const root = typeof repoRoot === "string" && repoRoot !== "" ? resolve(repoRoot) : process.cwd();
|
|
60
|
+
|
|
61
|
+
// 1. taskListId resolution (the same chain the runtime uses).
|
|
62
|
+
const taskListId = resolveTaskListId({ session });
|
|
63
|
+
checks.taskListId = taskListId ? { ok: true, taskListId } : { ok: false, reason: "no PI_TASK_LIST_ID, CLAUDE_CODE_TASK_LIST_ID, or session id" };
|
|
64
|
+
if (!taskListId) errors.push("taskListId could not be resolved (PI_TASK_LIST_ID, then CLAUDE_CODE_TASK_LIST_ID, then the session id)");
|
|
65
|
+
|
|
66
|
+
// 2. Session snapshot shape (picc-tasks mirror file).
|
|
67
|
+
let snapshot = null;
|
|
68
|
+
if (taskListId) {
|
|
69
|
+
const mirrorPath = tasksMirrorPath(taskListId, { tasksRoot });
|
|
70
|
+
checks.snapshot = { ok: true, path: mirrorPath, present: existsSync(mirrorPath) };
|
|
71
|
+
if (existsSync(mirrorPath)) {
|
|
72
|
+
snapshot = loadSnapshotFile(mirrorPath);
|
|
73
|
+
if (snapshot === null) {
|
|
74
|
+
checks.snapshot = { ok: false, path: mirrorPath, present: true, reason: "the snapshot does not satisfy agentic-driver.session-tasks.v1" };
|
|
75
|
+
errors.push(`session snapshot at ${mirrorPath} does not satisfy ${SESSION_TASKS_SCHEMA}`);
|
|
76
|
+
} else {
|
|
77
|
+
const ids = snapshot.tasks.map((task) => task.id);
|
|
78
|
+
if (new Set(ids).size !== ids.length) {
|
|
79
|
+
errors.push("session snapshot has duplicate task ids");
|
|
80
|
+
}
|
|
81
|
+
const hwmMax = snapshot.tasks.reduce((max, task) => {
|
|
82
|
+
const n = Number.parseInt(task.id, 10);
|
|
83
|
+
return Number.isNaN(n) ? max : Math.max(max, n);
|
|
84
|
+
}, 0);
|
|
85
|
+
if (snapshot.highWaterMark < hwmMax) {
|
|
86
|
+
errors.push(`highWaterMark ${snapshot.highWaterMark} is below the largest task id ${hwmMax}`);
|
|
87
|
+
}
|
|
88
|
+
for (const task of snapshot.tasks) {
|
|
89
|
+
if (!validateSessionTask(task)) {
|
|
90
|
+
errors.push(`task ${task.id} is outside the closed session-task shape`);
|
|
91
|
+
}
|
|
92
|
+
if (task.metadata?.origin !== undefined && task.metadata?.origin !== null
|
|
93
|
+
&& !["coordinator", "worker", "legacy-unknown"].includes(task.metadata.origin)) {
|
|
94
|
+
errors.push(`task ${task.id} metadata.origin "${task.metadata.origin}" is outside the closed origin values`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
warnings.push(`no session snapshot present at ${mirrorPath} (a fresh session has none; not an error)`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 3. Board availability + validity (TASKS.md first; board.md never).
|
|
104
|
+
let resolvedBoardPath = boardPath;
|
|
105
|
+
if (!resolvedBoardPath) {
|
|
106
|
+
// TASKS.md first; board.md is a derived projection and is never the
|
|
107
|
+
// promotion target. When only a projection exists, the canonical path
|
|
108
|
+
// is reported so the error names the right fix.
|
|
109
|
+
const canonical = join(root, "TASKS.md");
|
|
110
|
+
resolvedBoardPath = existsSync(canonical) ? canonical : canonical;
|
|
111
|
+
}
|
|
112
|
+
if (resolvedBoardPath.endsWith("board.md")) {
|
|
113
|
+
checks.board = { ok: false, path: resolvedBoardPath, reason: "board.md is a projection; it is never the promotion target" };
|
|
114
|
+
errors.push("board.md must never be the promotion target; the canonical TASKS.md is required");
|
|
115
|
+
} else if (existsSync(resolvedBoardPath)) {
|
|
116
|
+
const markdown = readFileSync(resolvedBoardPath, "utf8");
|
|
117
|
+
const validated = validateBoard(markdown);
|
|
118
|
+
checks.board = { ok: validated.ok, path: resolvedBoardPath, cards: validated.cards.length };
|
|
119
|
+
if (!validated.ok) errors.push(...validated.errors.map((e) => `board invalid: ${e}`));
|
|
120
|
+
// 4. Linkage integrity: every promotedCardId on session tasks must
|
|
121
|
+
// resolve to a card whose importedId points back at the task.
|
|
122
|
+
if (snapshot && taskListId) {
|
|
123
|
+
const brokenLinks = [];
|
|
124
|
+
for (const task of snapshot.tasks) {
|
|
125
|
+
const cardLink = task.metadata?.promotedCardId ?? task.metadata?.promotedcardid;
|
|
126
|
+
if (typeof cardLink !== "string" || cardLink === "") continue;
|
|
127
|
+
const card = validated.cards.find((c) => c.cardId === cardLink);
|
|
128
|
+
if (!card) { brokenLinks.push(`task ${task.id} -> card ${cardLink}: card not found`); continue; }
|
|
129
|
+
const expectedImported = findExistingPromotedCard(markdown, promotionIdempotencyKey(taskListId, task.id));
|
|
130
|
+
if (!expectedImported || expectedImported.cardId !== cardLink) {
|
|
131
|
+
brokenLinks.push(`task ${task.id} -> card ${cardLink}: card importedId does not link back`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
checks.linkage = { ok: brokenLinks.length === 0, brokenLinks };
|
|
135
|
+
errors.push(...brokenLinks);
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
checks.board = { ok: false, path: resolvedBoardPath, reason: "no TASKS.md in this workspace" };
|
|
139
|
+
warnings.push("no TASKS.md found for the workspace; promotion is unavailable until a board exists (a write tool call bootstraps one)");
|
|
140
|
+
}
|
|
141
|
+
// 5. Promotion target configuration.
|
|
142
|
+
const config = readTasksConfig(root);
|
|
143
|
+
if (config.value === null) {
|
|
144
|
+
checks.promotionConfig = { ok: true, path: null, defaults: true };
|
|
145
|
+
checks.promotionConfig.defaults = true;
|
|
146
|
+
} else {
|
|
147
|
+
const value = config.value;
|
|
148
|
+
const problems = [];
|
|
149
|
+
if (value.schema !== undefined && value.schema !== "agentic-driver.tasks.v1") {
|
|
150
|
+
problems.push(`schema "${value.schema}" is not recognized`);
|
|
151
|
+
}
|
|
152
|
+
if (value.promotion !== undefined) {
|
|
153
|
+
const p = value.promotion;
|
|
154
|
+
if (p === null || typeof p !== "object" || Array.isArray(p)) {
|
|
155
|
+
problems.push("promotion must be an object");
|
|
156
|
+
} else {
|
|
157
|
+
for (const [key, val] of Object.entries(p)) {
|
|
158
|
+
if (key === "completedTasks" && !["skip", "promote-as-done"].includes(val)) {
|
|
159
|
+
problems.push(`promotion.completedTasks "${val}" must be skip or promote-as-done`);
|
|
160
|
+
}
|
|
161
|
+
if (key === "batchAtSessionEnd" && !["offered", "off"].includes(val)) {
|
|
162
|
+
problems.push(`promotion.batchAtSessionEnd "${val}" must be offered or off`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
checks.promotionConfig = { ok: problems.length === 0, path: config.path, problems };
|
|
168
|
+
errors.push(...problems.map((p) => `promotion config: ${p}`));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 6. Origin provenance + worker capability (observation only).
|
|
172
|
+
const origin = typeof resolveOrigin === "function" ? resolveOrigin() : resolveCallerOrigin();
|
|
173
|
+
checks.origin = { ok: true, origin: origin.origin, proven: origin.proven, reason: origin.reason };
|
|
174
|
+
checks.capability = {
|
|
175
|
+
ok: true,
|
|
176
|
+
loaded: hasTasksCapability({ env: capabilityEnv }),
|
|
177
|
+
marker: TASKS_CAPABILITY_ENV,
|
|
178
|
+
};
|
|
179
|
+
if (!checks.capability.loaded) {
|
|
180
|
+
warnings.push(`tasks skill capability not declared (${TASKS_CAPABILITY_ENV}); session tools still work, worker briefs must set the marker explicitly`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const valid = errors.length === 0;
|
|
184
|
+
// Operational means ready end-to-end: valid state AND a resolvable board
|
|
185
|
+
// AND linkage clean. A missing snapshot is tolerated (fresh session).
|
|
186
|
+
const operational = valid
|
|
187
|
+
&& Boolean(taskListId)
|
|
188
|
+
&& Boolean(checks.board?.ok);
|
|
189
|
+
return { valid, operational, checks, errors, warnings };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function formatTasksValidateReport(report) {
|
|
193
|
+
const lines = [];
|
|
194
|
+
lines.push("tasks validate — unified session tasks + promotion (v1)");
|
|
195
|
+
for (const [name, check] of Object.entries(report.checks)) {
|
|
196
|
+
const ok = check.ok ? "ok" : "FAIL";
|
|
197
|
+
const detail = check.reason ?? check.taskListId ?? check.path ?? check.origin ?? "";
|
|
198
|
+
lines.push(` [${ok}] ${name}${detail ? `: ${detail}` : ""}`);
|
|
199
|
+
}
|
|
200
|
+
for (const warning of report.warnings) lines.push(` [warn] ${warning}`);
|
|
201
|
+
for (const error of report.errors) lines.push(` [error] ${error}`);
|
|
202
|
+
lines.push(`result: ${report.operational ? "operational" : "not operational"}`);
|
|
203
|
+
return lines.join("\n");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function isEntrypoint() {
|
|
207
|
+
try {
|
|
208
|
+
return pathToFileURL(process.argv[1] ?? "").href === import.meta.url;
|
|
209
|
+
} catch {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (isEntrypoint()) {
|
|
215
|
+
const args = process.argv.slice(2);
|
|
216
|
+
const take = (flag) => {
|
|
217
|
+
const idx = args.indexOf(flag);
|
|
218
|
+
return idx >= 0 ? args[idx + 1] : null;
|
|
219
|
+
};
|
|
220
|
+
const report = validateTasksSetup({
|
|
221
|
+
repoRoot: take("--repo-root") ?? process.cwd(),
|
|
222
|
+
session: take("--session"),
|
|
223
|
+
boardPath: take("--board"),
|
|
224
|
+
tasksRoot: take("--tasks-root"),
|
|
225
|
+
});
|
|
226
|
+
process.stdout.write(`${formatTasksValidateReport(report)}\n`);
|
|
227
|
+
if (!report.valid || !report.operational) {
|
|
228
|
+
process.stderr.write(`${JSON.stringify({
|
|
229
|
+
valid: report.valid, operational: report.operational,
|
|
230
|
+
errors: report.errors, warnings: report.warnings,
|
|
231
|
+
}, null, 2)}\n`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
process.exit(0);
|
|
235
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# tasks — unified session task list + board promotion (v1)
|
|
2
|
+
|
|
3
|
+
Owner-facing skill for the pi-agentic-driver session task list and
|
|
4
|
+
owner-initiated promotion to the durable `TASKS.md` board.
|
|
5
|
+
|
|
6
|
+
## Prior art and credit
|
|
7
|
+
|
|
8
|
+
The session task tools (`TaskCreate`, `TaskGet`, `TaskList`, `TaskUpdate`)
|
|
9
|
+
reimplement the documented behavior of
|
|
10
|
+
[`@ladbabynpm/picc-tasks`](https://www.npmjs.com/package/@ladbabynpm/picc-tasks)
|
|
11
|
+
v0.2.0 (MIT, Copyright (c) 2026 Ladbaby) as prior art: the same tool names,
|
|
12
|
+
the same storage model (session JSONL snapshot entries plus a disk mirror at
|
|
13
|
+
`~/.pi/tasks/{taskListId}/tasks.json`), and the same result text. No code is
|
|
14
|
+
copied; this is a clean AGPL-3.0-only reimplementation to the same behavior
|
|
15
|
+
spec. picc-tasks is an independent project by its author, used as reference;
|
|
16
|
+
it is not affiliated with, nor does it endorse, pi-agentic-driver.
|
|
17
|
+
|
|
18
|
+
## What this skill owns
|
|
19
|
+
|
|
20
|
+
1. The **session task list** — working memory for the current session. Every
|
|
21
|
+
task carries `metadata.origin` (`coordinator` or `worker`, with
|
|
22
|
+
`legacy-unknown` for unprovable runtime identity).
|
|
23
|
+
2. **Promotion** — `TaskPromote`, an explicit owner-initiated action that
|
|
24
|
+
turns selected session tasks into `TASKS.md` cards through the trusted
|
|
25
|
+
board writer (`scripts/enforcement/task_board_core_pi.js`). The skill is
|
|
26
|
+
a writer CLIENT: it never serializes or writes board bytes itself, and it
|
|
27
|
+
never creates a second authority store.
|
|
28
|
+
|
|
29
|
+
## Promotion rules (non-negotiable)
|
|
30
|
+
|
|
31
|
+
- **Owner-initiated only.** Promotion is never automatic, never a
|
|
32
|
+
session-shutdown hook, and never worker-authorized.
|
|
33
|
+
- **Fresh authority.** Every promotion requires the owner's actual
|
|
34
|
+
instruction quoted verbatim (`quotedInstruction`) supplied through the
|
|
35
|
+
owner-facing tool call. A digest alone is not accepted.
|
|
36
|
+
- **Preview before mutation.** The preview shows the target repository root
|
|
37
|
+
and canonical `TASKS.md` path, title/description, specification,
|
|
38
|
+
definitionOfDone, stoppingPoint, scopePaths, lane, dependencies,
|
|
39
|
+
session-qualified source identity, and the resulting idempotency key.
|
|
40
|
+
Missing required board fields are errors — owner-authorized fields are
|
|
41
|
+
never invented from task text.
|
|
42
|
+
- **Workers cannot promote.** Worker origin is observed (never trusted) and
|
|
43
|
+
recorded as audit metadata; a proven worker pane is refused. Unproven
|
|
44
|
+
identity is recorded as `legacy-unknown` and still requires fresh owner
|
|
45
|
+
authority.
|
|
46
|
+
- **Completed tasks are skipped by default** in v1. Surviving
|
|
47
|
+
pending/in_progress tasks default to the `backlog` lane so they remain
|
|
48
|
+
dispatchable. `in-progress` lane placement does not make a card
|
|
49
|
+
dispatchable by itself.
|
|
50
|
+
|
|
51
|
+
## Idempotency
|
|
52
|
+
|
|
53
|
+
The promotion key binds `taskListId` + `taskId`
|
|
54
|
+
(`p1-<sha256(taskListId + NUL + taskId).hex32>`): slash-free,
|
|
55
|
+
collision-resistant across sessions. Retrying the same promotion returns the
|
|
56
|
+
existing card and link; another session's same numeric task id never
|
|
57
|
+
collides. The forward link is stored on the card as `importedId`; the
|
|
58
|
+
reverse link (`metadata.promotedCardId`) is set on the session task after
|
|
59
|
+
the writer confirms the write.
|
|
60
|
+
|
|
61
|
+
## Batch promotion
|
|
62
|
+
|
|
63
|
+
A batch is a sequence of independent writer calls. **Atomicity is not
|
|
64
|
+
claimed.** Partial success is reported per task and reconciled
|
|
65
|
+
deterministically using the idempotency key and the card↔session link.
|
|
66
|
+
Dependency edges are mapped to writer-minted card IDs only after all cards
|
|
67
|
+
in the batch exist; edges pointing outside the batch are dropped and
|
|
68
|
+
recorded, never written dangling.
|
|
69
|
+
|
|
70
|
+
## Worker loading contract
|
|
71
|
+
|
|
72
|
+
The tasks tools ship in the package, so the session tools are present in any
|
|
73
|
+
driver-equipped workspace — but presence is not authorization. The
|
|
74
|
+
`AGENTIC_DRIVER_TASKS_CAPABILITY` marker is the per-session loading contract:
|
|
75
|
+
worker briefs set it when the coordinator intends the worker to plan with the
|
|
76
|
+
session tools, and `TaskPromote` commit **enforces** it — a session without
|
|
77
|
+
the marker cannot commit a promotion regardless of what authority text it
|
|
78
|
+
supplies. The marker never substitutes for the owner confirmation boundary;
|
|
79
|
+
it is an additional fail-closed gate on the authority-bearing surface.
|
|
80
|
+
Spawned workers receive briefs through prompts (Herdr `agent start` exposes
|
|
81
|
+
no environment mechanism), so the marker must be carried in the worker brief
|
|
82
|
+
text and exported by the worker's own shell setup; a worker that cannot set
|
|
83
|
+
it cannot promote, which is the intended default.
|
|
84
|
+
|
|
85
|
+
## Migration from picc-tasks
|
|
86
|
+
|
|
87
|
+
The driver skill reads the existing picc-tasks mirror file on first run; the
|
|
88
|
+
shape is identical, so snapshots replay as-is. Tasks missing
|
|
89
|
+
`metadata.origin` are NEVER inferred: there is no trusted runtime source for
|
|
90
|
+
a legacy task's creation context, so they backfill to `legacy-unknown` audit
|
|
91
|
+
metadata on import. Existing `worker` origin values are preserved unchanged.
|
|
92
|
+
|
|
93
|
+
**Rollback is not symmetric.** The file layout is preserved (re-installing
|
|
94
|
+
picc-tasks finds its data intact), but custom session entries and
|
|
95
|
+
UI/reminder behavior changes are retained by the driver skill and are not
|
|
96
|
+
reverted by re-installing picc-tasks.
|
|
97
|
+
|
|
98
|
+
## Validation (rule 30)
|
|
99
|
+
|
|
100
|
+
`node scripts/enforcement/session_tasks_validate_pi.js --repo-root <root>`
|
|
101
|
+
is read-only. It validates session snapshot shape, taskListId resolution,
|
|
102
|
+
promotion metadata, linkage integrity, board availability (canonical
|
|
103
|
+
`TASKS.md` first; `board.md` never), and promotion target configuration.
|
|
104
|
+
Exit 0 only when ready; nonzero with a machine-readable error list
|
|
105
|
+
otherwise. It never mutates state, never writes the board, never promotes.
|
|
106
|
+
|
|
107
|
+
## Out of scope in v1
|
|
108
|
+
|
|
109
|
+
No Obsidian work. No automatic worker proposal store. No session-end prompt.
|
|
110
|
+
No completed-task promotion. No Vogelkop writer-authority changes.
|
package/templates/AGENTS.md
CHANGED
|
@@ -35,6 +35,17 @@ acceptance result, and exact next step in the existing task and report. Create
|
|
|
35
35
|
a follow-up task only when no existing task represents the remaining work. The
|
|
36
36
|
next session or worker resumes that task instead of rediscovering scope.
|
|
37
37
|
|
|
38
|
+
Session task tools (`TaskCreate`, `TaskGet`, `TaskList`, `TaskUpdate`) are
|
|
39
|
+
available when the tasks capability is loaded for the session — declared by
|
|
40
|
+
the `AGENTIC_DRIVER_TASKS_CAPABILITY` marker in the worker brief. Package
|
|
41
|
+
presence is not the loading contract; a worker without the marker must not
|
|
42
|
+
assume the session tools exist. Worker task lists are session-scoped:
|
|
43
|
+
promotion to the durable board (`TaskPromote`) is owner-initiated only and is
|
|
44
|
+
refused in worker sessions — the commit path additionally requires the
|
|
45
|
+
capability marker, so a session that cannot declare it can never promote. A
|
|
46
|
+
worker that believes a task deserves a board card states that in its report
|
|
47
|
+
text; it never promotes and never invents an authority record.
|
|
48
|
+
|
|
38
49
|
Every worker report should state:
|
|
39
50
|
|
|
40
51
|
- repository and revision;
|