@jaybeeuu/agent-cortex 1.39.0 → 1.41.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.
- package/.agents/skills/style-versioning/SKILL.md +1 -1
- package/.github/workflows/ci.yml +47 -14
- package/AGENTS.md +35 -8
- package/CHANGELOG.md +60 -0
- package/README.md +174 -97
- package/agents/README.md +14 -11
- package/bin/agent-cortex.mjs +55 -10
- package/bin/installers/claude.mjs +674 -174
- package/bin/installers/copilot.mjs +13 -18
- package/bin/installers/pi.mjs +293 -51
- package/claude/settings.json +4 -0
- package/{claude → claude-extras}/scripts/statusline-command.sh +32 -4
- package/docs/claude-hooks.md +15 -3
- package/docs/ideas/ban-content-assertion-tests.md +73 -0
- package/docs/ideas/bead-progress-notes-for-session-resumption.md +75 -0
- package/docs/ideas/classify-beads-via-deterministic-script-with-agent-fallback.md +70 -0
- package/docs/ideas/ctx-alias-version-and-update-commands.md +65 -0
- package/docs/ideas/ctx-feedback-command-for-capturing-ideas-from-any-machine.md +67 -0
- package/docs/ideas/deployment-beads-for-post-merge-pipelines.md +36 -0
- package/docs/ideas/design-and-delivery-as-separate-bead-trees/design-and-delivery-as-separate-bead-trees.md +99 -0
- package/docs/ideas/enable-signed-commit-enforcement-changesets-bot-commits-must-be-signed.md +33 -0
- package/docs/ideas/extension-manifests.md +55 -14
- package/docs/ideas/improve-pi-system-prompt.md +5 -2
- package/docs/ideas/map-every-cortex-tool-to-a-real-pi-equivalent.md +95 -0
- package/docs/ideas/record-idea-implementation-questions.md +68 -0
- package/docs/ideas/self-flushing-session.md +72 -0
- package/docs/ideas/state-in-beads-not-md.md +69 -0
- package/docs/ideas/task-session-context.local.md +95 -0
- package/docs/ideas/test-data-creation-helpers.md +92 -0
- package/docs/ideas/test-planning-stage-bead-in-feature-planning.md +68 -0
- package/docs/ideas/worktree-housekeeping-for-closed-beads.md +39 -0
- package/docs/technical-direction/harness-config.md +161 -0
- package/docs/technical-direction/migration-execution-labels.md +60 -23
- package/extensions/agent-guardrails/README.md +45 -0
- package/extensions/agent-guardrails/guardrails.test.ts +42 -0
- package/extensions/agent-guardrails/guardrails.ts +66 -0
- package/extensions/agent-guardrails/index.ts +22 -0
- package/extensions/agent-guardrails/node_modules/.bin/pi +21 -0
- package/extensions/agent-guardrails/node_modules/.bin/tsc +21 -0
- package/extensions/agent-guardrails/node_modules/.bin/tsserver +21 -0
- package/extensions/agent-guardrails/node_modules/.bin/tsx +21 -0
- package/extensions/agent-guardrails/package.json +19 -0
- package/extensions/agent-guardrails/tsconfig.json +12 -0
- package/extensions/agent-modes/README.md +12 -5
- package/extensions/agent-modes/discover.test.ts +30 -7
- package/extensions/agent-modes/discover.ts +1 -1
- package/extensions/agent-modes/index.test.ts +208 -0
- package/extensions/agent-modes/index.ts +23 -6
- package/extensions/agent-modes/package.json +1 -1
- package/extensions/skill-stats/index.test.ts +3 -2
- package/lib/cli.mjs +30 -12
- package/lib/pi-packages.mjs +169 -0
- package/package.json +8 -4
- package/pi/settings.json +23 -12
- package/plugin.json +1 -1
- package/scripts/build-copilot-agents.mjs +1 -1
- package/scripts/check-no-sync.mjs +49 -0
- package/scripts/lib/compose-agent.mjs +32 -14
- package/scripts/lib/fs.mjs +31 -0
- package/skills/engineering/grill-with-docs/SKILL.md +1 -1
- package/skills/planning/classify-bead/REFERENCE.md +85 -0
- package/skills/planning/classify-bead/SKILL.md +32 -36
- package/skills/planning/classify-bead/scripts/classify-bead.mjs +292 -0
- package/skills/planning/create-task/SKILL.md +19 -10
- package/skills/planning/write-a-prd/SKILL.md +2 -2
- package/skills/style/style-code/REFERENCE.md +32 -2
- package/skills/style/style-code/SKILL.md +5 -21
- package/skills/style/style-tests/SKILL.md +69 -17
- package/skills/workflow/hitl-collab/SKILL.md +1 -1
- package/skills/workflow/run-pipeline-stage/SKILL.md +13 -1
- package/skills/workflow/run-pipeline-stage/prompts/stage-runner.md +12 -0
- package/test/classify-bead.test.mjs +277 -0
- package/test/claude-installer.test.mjs +199 -139
- package/test/cli.test.mjs +95 -41
- package/test/copilot-installer.test.mjs +92 -82
- package/test/fs.test.mjs +76 -0
- package/test/helpers/fake-claude.mjs +152 -0
- package/test/installer.test.mjs +542 -57
- package/test/pi-installer.test.mjs +457 -102
- package/test/pi-packages.test.mjs +295 -0
- package/token-map.README.md +23 -12
- package/token-map.json +8 -7
- package/.claude-plugin/marketplace.json +0 -16
- package/claude/.claude-plugin/plugin.json +0 -11
- package/claude/agents/plan.md +0 -58
- package/claude/agents/ralph-plan.md +0 -121
- package/claude/agents/ralph.md +0 -193
- package/claude/agents/strategy.md +0 -111
- package/claude/hooks/scripts/notify.mjs +0 -54
- package/claude/hooks.json +0 -38
- /package/{claude → claude-extras}/.mcp.json +0 -0
package/pi/settings.json
CHANGED
|
@@ -4,26 +4,37 @@
|
|
|
4
4
|
"maxNameLength": 40,
|
|
5
5
|
"maxSummaryLength": 100
|
|
6
6
|
},
|
|
7
|
-
"lastChangelogVersion": "0.
|
|
7
|
+
"lastChangelogVersion": "0.87.1",
|
|
8
8
|
"packages": [
|
|
9
|
-
|
|
9
|
+
{
|
|
10
|
+
"source": "../../src/agent-cortex",
|
|
11
|
+
"skills": []
|
|
12
|
+
},
|
|
10
13
|
"npm:pi-web-access@0.10.7",
|
|
11
14
|
"npm:@getpipher/vision",
|
|
12
15
|
"npm:@hypabolic/pi-hypa",
|
|
13
|
-
"npm:context-mode"
|
|
16
|
+
"npm:context-mode",
|
|
17
|
+
"npm:pi-questions"
|
|
14
18
|
],
|
|
15
19
|
"defaultProvider": "opencode-go",
|
|
16
|
-
"defaultModel": "
|
|
20
|
+
"defaultModel": "deepseek-v4.1-flash",
|
|
17
21
|
"theme": "dark",
|
|
18
22
|
"defaultThinkingLevel": "high",
|
|
19
23
|
"enabledModels": [
|
|
20
|
-
"opencode/
|
|
21
|
-
"opencode-go/qwen3.7-
|
|
22
|
-
"opencode/kimi-k2.7-code",
|
|
23
|
-
"opencode/mimo-v2.5-free",
|
|
24
|
-
"opencode-go/mimo-v2.5",
|
|
25
|
-
"opencode/deepseek-v4-flash-free",
|
|
24
|
+
"opencode/big-pickle",
|
|
25
|
+
"opencode-go/qwen3.7-plus",
|
|
26
26
|
"opencode-go/deepseek-v4-flash",
|
|
27
|
-
"opencode/
|
|
28
|
-
|
|
27
|
+
"opencode-go/kimi-k3",
|
|
28
|
+
"opencode/deepseek-v4-flash",
|
|
29
|
+
"opencode/mimo-v2.5-free",
|
|
30
|
+
"opencode-go/deepseek-v4.1-flash"
|
|
31
|
+
],
|
|
32
|
+
"compaction": {
|
|
33
|
+
"enabled": true
|
|
34
|
+
},
|
|
35
|
+
"steeringMode": "all",
|
|
36
|
+
"followUpMode": "all",
|
|
37
|
+
"tuiMode": "regular",
|
|
38
|
+
"treeFilterMode": "default",
|
|
39
|
+
"hideThinkingBlock": true
|
|
29
40
|
}
|
package/plugin.json
CHANGED
|
@@ -27,4 +27,4 @@ import { installCopilot } from "../bin/installers/copilot.mjs";
|
|
|
27
27
|
|
|
28
28
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
29
29
|
|
|
30
|
-
installCopilot({ root: ROOT, output: join(ROOT, "agents") });
|
|
30
|
+
await installCopilot({ root: ROOT, output: join(ROOT, "agents") });
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Repo convention: never block the main thread — no `*Sync` functions
|
|
2
|
+
// (fs/child_process/...) in tooling code. Runs via `pnpm lint` (CI) and
|
|
3
|
+
// locally. Scans the toolchain and test dirs: bin/, scripts/, lib/, hooks/,
|
|
4
|
+
// test/. Skill-owned scripts (skills/*/scripts) and extensions are separate
|
|
5
|
+
// follow-up surfaces tracked outside this check.
|
|
6
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
7
|
+
import { join, relative } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
const ROOT = join(fileURLToPath(new URL(".", import.meta.url)), "..");
|
|
11
|
+
const SCAN_DIRS = ["bin", "scripts", "lib", "hooks", "test"];
|
|
12
|
+
const EXTENSIONS = new Set([".mjs", ".js", ".ts"]);
|
|
13
|
+
const SELF = "scripts/check-no-sync.mjs";
|
|
14
|
+
|
|
15
|
+
const SYNC_CALL = /\b\w*Sync\s*\(/;
|
|
16
|
+
|
|
17
|
+
/** Recursively collect files under dir, skipping this script and node_modules. */
|
|
18
|
+
async function collect(dir) {
|
|
19
|
+
const out = [];
|
|
20
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
21
|
+
for (const e of entries) {
|
|
22
|
+
if (e.name === "node_modules") continue;
|
|
23
|
+
const full = join(dir, e.name);
|
|
24
|
+
if (e.isDirectory()) out.push(...(await collect(full)));
|
|
25
|
+
else if (EXTENSIONS.has(e.name.slice(e.name.lastIndexOf(".")))) out.push(full);
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const offenders = [];
|
|
31
|
+
for (const dir of SCAN_DIRS) {
|
|
32
|
+
const abs = join(ROOT, dir);
|
|
33
|
+
for (const file of await collect(abs)) {
|
|
34
|
+
const rel = relative(ROOT, file);
|
|
35
|
+
if (rel === SELF) continue;
|
|
36
|
+
const src = await readFile(file, "utf-8");
|
|
37
|
+
const hits = src.split("\n").map((line, i) => ({ line: i + 1, text: line })).filter((l) => SYNC_CALL.test(l.text));
|
|
38
|
+
if (hits.length) offenders.push({ rel, hits });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (offenders.length) {
|
|
43
|
+
console.error("✗ Never block the main thread — *Sync function calls found:");
|
|
44
|
+
for (const { rel, hits } of offenders) {
|
|
45
|
+
for (const { line, text } of hits) console.error(` ${rel}:${line}: ${text.trim()}`);
|
|
46
|
+
}
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
console.log("✓ no *Sync calls in tooling code (bin/, scripts/, lib/, hooks/, test/)");
|
|
@@ -29,9 +29,19 @@
|
|
|
29
29
|
//
|
|
30
30
|
// Zero dependencies so it runs on the CI Node (20) and local Node alike.
|
|
31
31
|
|
|
32
|
-
import {
|
|
32
|
+
import { readFile, stat } from "node:fs/promises";
|
|
33
33
|
import { join } from "node:path";
|
|
34
34
|
|
|
35
|
+
/** True when a path exists (stat succeeds). */
|
|
36
|
+
async function pathExists(p) {
|
|
37
|
+
try {
|
|
38
|
+
await stat(p);
|
|
39
|
+
return true;
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
// ─── Token substitution ──────────────────────────────────────────────────────
|
|
36
46
|
|
|
37
47
|
const TOOL_RE = /\{\{TOOL:([^}]+)\}\}/g;
|
|
@@ -92,22 +102,30 @@ export function substituteTokens(content, harness, tokenMap, options = {}) {
|
|
|
92
102
|
* @param {string} harness Harness id: "copilot" | "claude" | "pi"
|
|
93
103
|
* @returns {{ name: string, description: string, tools: string[], model?: string, argumentHint?: string, body: string }}
|
|
94
104
|
*/
|
|
95
|
-
export function composeAgent(root, name, harness, options = {}) {
|
|
105
|
+
export async function composeAgent(root, name, harness, options = {}) {
|
|
96
106
|
const { dropNullTools = false, pluginRoot, resolveRelativePaths = false, warn = noop } = options;
|
|
97
107
|
const agentDir = join(root, "agents", name);
|
|
98
|
-
const frontmatter = parseFrontmatter(join(agentDir, harness, "frontmatter.json"), name, harness);
|
|
99
|
-
const tokenMap = loadTokenMap(root);
|
|
108
|
+
const frontmatter = await parseFrontmatter(join(agentDir, harness, "frontmatter.json"), name, harness);
|
|
109
|
+
const tokenMap = await loadTokenMap(root);
|
|
100
110
|
|
|
101
|
-
let body =
|
|
111
|
+
let body = await readFile(join(agentDir, "agent.md"), "utf-8");
|
|
102
112
|
|
|
103
|
-
// 1. {{SECTION:name}} → <harness>/<name>.md (may itself contain TOOL/PATH
|
|
104
|
-
|
|
113
|
+
// 1. {{SECTION:name}} → <harness>/<name>.md (may itself contain TOOL/PATH
|
|
114
|
+
// tokens). Single pass over the original body — inserted section content
|
|
115
|
+
// is never rescanned (same semantics as the sync replace()).
|
|
116
|
+
const sectionRe = /\{\{SECTION:([^}]+)\}\}/g;
|
|
117
|
+
let out = "";
|
|
118
|
+
let last = 0;
|
|
119
|
+
for (let m = sectionRe.exec(body); m; m = sectionRe.exec(body)) {
|
|
120
|
+
const section = m[1];
|
|
105
121
|
const sectionPath = join(agentDir, harness, `${section}.md`);
|
|
106
|
-
if (!
|
|
122
|
+
if (!(await pathExists(sectionPath))) {
|
|
107
123
|
throw new Error(`agent "${name}": missing section "${section}" for harness "${harness}" (${sectionPath})`);
|
|
108
124
|
}
|
|
109
|
-
|
|
110
|
-
|
|
125
|
+
out += body.slice(last, m.index) + (await readFile(sectionPath, "utf-8")).trim();
|
|
126
|
+
last = m.index + m[0].length;
|
|
127
|
+
}
|
|
128
|
+
body = out + body.slice(last);
|
|
111
129
|
|
|
112
130
|
// 2. + 3. TOOL/PATH substitution (contextualised for error messages)
|
|
113
131
|
body = substituteTokens(body, harness, tokenMap, {
|
|
@@ -127,8 +145,8 @@ const REQUIRED_FIELDS = ["name", "description", "tools"];
|
|
|
127
145
|
const KNOWN_OPTIONAL_FIELDS = ["model", "argumentHint"];
|
|
128
146
|
|
|
129
147
|
/** Parse and schema-check a harness frontmatter.json (strict, per agents/README.md). */
|
|
130
|
-
function parseFrontmatter(path, name, harness) {
|
|
131
|
-
const raw =
|
|
148
|
+
async function parseFrontmatter(path, name, harness) {
|
|
149
|
+
const raw = await readFile(path, "utf-8");
|
|
132
150
|
let fm;
|
|
133
151
|
try {
|
|
134
152
|
fm = JSON.parse(raw);
|
|
@@ -225,11 +243,11 @@ export function translateToolList(tools, tokenMap, harness, { warn = noop } = {}
|
|
|
225
243
|
// ─── Token map ───────────────────────────────────────────────────────────────
|
|
226
244
|
|
|
227
245
|
/** Load token-map.json from the package root; missing/incomplete maps hard-fail. */
|
|
228
|
-
export function loadTokenMap(root) {
|
|
246
|
+
export async function loadTokenMap(root) {
|
|
229
247
|
const path = join(root, "token-map.json");
|
|
230
248
|
let raw;
|
|
231
249
|
try {
|
|
232
|
-
raw =
|
|
250
|
+
raw = await readFile(path, "utf-8");
|
|
233
251
|
} catch {
|
|
234
252
|
throw new Error(`missing ${path} — the composer requires token-map.json`);
|
|
235
253
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Shared async filesystem predicates. The install-time generators all need the
|
|
2
|
+
// same "does this path exist and is it a file/directory?" probe, and the repo
|
|
3
|
+
// convention is async-only (no *Sync) in tooling code — see scripts/check-no-sync.mjs.
|
|
4
|
+
//
|
|
5
|
+
// Both probes are stat-based and resolve `false` on any error (missing path,
|
|
6
|
+
// permission denied, broken symlink), so callers can branch on existence
|
|
7
|
+
// without a separate try/catch:
|
|
8
|
+
//
|
|
9
|
+
// if (await isFile(join(dir, "agent.md"))) { … }
|
|
10
|
+
//
|
|
11
|
+
// Zero dependencies so it runs on the CI Node (20) and local Node alike.
|
|
12
|
+
|
|
13
|
+
import { stat } from "node:fs/promises";
|
|
14
|
+
|
|
15
|
+
/** True when `p` exists and is a regular file; false on any error. */
|
|
16
|
+
export async function isFile(p) {
|
|
17
|
+
try {
|
|
18
|
+
return (await stat(p)).isFile();
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** True when `p` exists and is a directory; false on any error. */
|
|
25
|
+
export async function isDirectory(p) {
|
|
26
|
+
try {
|
|
27
|
+
return (await stat(p)).isDirectory();
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: grill-with-docs
|
|
3
|
-
description: Grilling session that challenges your plan against the existing domain model, sharpens terminology, and updates documentation (CONTEXT.md, ADRs) inline as decisions crystallise. Use when the user wants to stress-test a plan against their project's language and documented decisions: "grill my plan", "check this against our docs".
|
|
3
|
+
description: "Grilling session that challenges your plan against the existing domain model, sharpens terminology, and updates documentation (CONTEXT.md, ADRs) inline as decisions crystallise. Use when the user wants to stress-test a plan against their project's language and documented decisions: \"grill my plan\", \"check this against our docs\"."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Grill With Docs
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# classify-bead reference
|
|
2
|
+
|
|
3
|
+
Detail behind `SKILL.md`. The executable `scripts/classify-bead.mjs` is the source of
|
|
4
|
+
truth for the deterministic tiers; this document mirrors it for verification.
|
|
5
|
+
|
|
6
|
+
## Tier precedence
|
|
7
|
+
|
|
8
|
+
| Tier | `source` | Evidence | Applies the label? |
|
|
9
|
+
|---|---|---|---|
|
|
10
|
+
| 1 | `label` | An `implementation-type:afk` or `implementation-type:hitl` label is already present | no — it is already there |
|
|
11
|
+
| 2 | `type-field` | The legacy `## Type` section in the bead body declares `AFK`/`HITL` (block or inline form, case-insensitive) | yes |
|
|
12
|
+
| 3 | `heuristic` | A conservative natural-language signal (below) | yes |
|
|
13
|
+
| 4 | *(none)* | Nothing deterministic matched → `escalate: true`; a small/cheap model applies the rubric | after the model decides |
|
|
14
|
+
|
|
15
|
+
When both `implementation-type` labels are present, AFK wins — matching the
|
|
16
|
+
pre-existing skill wording, which checks `implementation-type:afk` first.
|
|
17
|
+
|
|
18
|
+
The classifier runs `bd show <id> --json` and `bd tag <id> <label>`; it never
|
|
19
|
+
re-reads prose from the rendered `bd show` output.
|
|
20
|
+
|
|
21
|
+
## Heuristic signals
|
|
22
|
+
|
|
23
|
+
Heuristics only fire on explicit phrasing. They are checked in this order,
|
|
24
|
+
mirroring the rubric's "a human is required if any criterion is true" rule.
|
|
25
|
+
|
|
26
|
+
HITL (human required):
|
|
27
|
+
|
|
28
|
+
- `requires human`, `human sign-off|approval|judgement|decision`
|
|
29
|
+
- `manual step|action|intervention`
|
|
30
|
+
- `visually review|inspect|verify`
|
|
31
|
+
- `stakeholder sign-off|approval`
|
|
32
|
+
- `credentials|secrets setup|rotation|provisioning`
|
|
33
|
+
- `provisioning` within 40 characters of `infrastructure|infra|server|cluster`
|
|
34
|
+
|
|
35
|
+
AFK (explicitly no human):
|
|
36
|
+
|
|
37
|
+
- `no human action|input|review|intervention|judgement`
|
|
38
|
+
|
|
39
|
+
AFK (machine-checkable acceptance):
|
|
40
|
+
|
|
41
|
+
- `machine-checkable`
|
|
42
|
+
- `automated test|tests|check|checks|verification`
|
|
43
|
+
|
|
44
|
+
Anything else escalates. Widening this list trades model calls for
|
|
45
|
+
misclassification risk; only add phrasing that maps directly onto a rubric
|
|
46
|
+
criterion.
|
|
47
|
+
|
|
48
|
+
Matching is literal, not semantic, and the patterns are not negation-aware —
|
|
49
|
+
"no human approval is required" still matches the HITL tier. The error direction
|
|
50
|
+
is toward HITL, which is visible and correctable, so prefer unambiguous phrasing
|
|
51
|
+
over more patterns.
|
|
52
|
+
|
|
53
|
+
## CLI
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
node classify-bead.mjs <bead-id> [--dry-run] # resolve (and tag) one bead
|
|
57
|
+
node classify-bead.mjs --audit [--limit <n>] # measure the split over recent beads
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Run from the target project's directory — `bd` resolves its beads DB from cwd.
|
|
61
|
+
JSON goes to stdout:
|
|
62
|
+
|
|
63
|
+
- Classification: `{ id, classification, source, reason, escalate, applied, label }`
|
|
64
|
+
- Audit: `{ sampled, bySource, deterministic, needsRubric, deterministicRatio }`
|
|
65
|
+
|
|
66
|
+
Exit codes: `0` success (including `escalate`, which is a valid outcome), `2`
|
|
67
|
+
usage error, `1` `bd` failure. `BD_PATH` overrides the `bd` executable.
|
|
68
|
+
|
|
69
|
+
## Measured split
|
|
70
|
+
|
|
71
|
+
This is the "measure first" number that sizes the win over spawning a subagent.
|
|
72
|
+
Reproduce it against the repo's beads DB:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
node <skill-scripts>/classify-bead.mjs --audit --limit 100
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Measured 2026-09-18 on `agent-cortex`: 78 of the newest 100 beads resolved
|
|
79
|
+
deterministically (77 from an existing label, 1 from a heuristic) and 22 needed
|
|
80
|
+
the rubric. That is the share of classifications that skip the model entirely
|
|
81
|
+
when the caller runs the classifier before spawning a subagent. The sample is
|
|
82
|
+
the newest 100 beads, so the ratio moves as beads are created — re-run the
|
|
83
|
+
command rather than quoting this snapshot. The deterministic rate is higher on
|
|
84
|
+
backlogs labelled at planning time; freshly created beads with neither a label
|
|
85
|
+
nor a `## Type` field lean on the heuristic tier.
|
|
@@ -5,7 +5,7 @@ description: Classify a bead as AFK or HITL and apply the implementation-type la
|
|
|
5
5
|
|
|
6
6
|
# Classify Bead
|
|
7
7
|
|
|
8
|
-
Determine whether a bead requires human action (HITL) or can be completed autonomously by an agent (AFK), then persist the result as an `implementation-type` label.
|
|
8
|
+
Determine whether a bead requires human action (HITL) or can be completed autonomously by an agent (AFK), then persist the result as an `implementation-type` label. A deterministic classifier settles the common cases; only genuine rubric judgement reaches a model.
|
|
9
9
|
|
|
10
10
|
## When to use
|
|
11
11
|
|
|
@@ -21,71 +21,67 @@ Determine whether a bead requires human action (HITL) or can be completed autono
|
|
|
21
21
|
|
|
22
22
|
## Invocation
|
|
23
23
|
|
|
24
|
-
Run
|
|
24
|
+
Run the deterministic classifier first. It resolves the existing label, the legacy `## Type` field, and explicit heuristic signals without a model call:
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
```bash
|
|
27
|
+
# Run from the TARGET project's directory — bd resolves its beads DB from cwd.
|
|
28
|
+
node <skill-scripts>/classify-bead.mjs <bead-id>
|
|
29
|
+
```
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
- Output carries `"classification": "afk" | "hitl"` → done. The classifier applied the label; report the classification. Do not spawn a subagent.
|
|
32
|
+
- Output carries `"escalate": true` → no deterministic signal. Spawn this skill as a background subagent via {{TOOL:task}} (a small, cheap model) with the bead ID and the `bd prime` output; the subagent applies the rubric below and tags the bead.
|
|
33
|
+
- `--dry-run` resolves without applying the label. `--audit [--limit <n>]` reports the deterministic-vs-rubric split over recent beads.
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
1. **Check for an existing label.**
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
bd label list <id>
|
|
36
|
-
```
|
|
35
|
+
`<skill-scripts>` is the absolute path to this skill's own `scripts/` directory, resolved from wherever this skill was loaded — it varies by harness and install location, so never hardcode one harness's path. Only run inline when no task-spawning capability exists in the current environment.
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
- `implementation-type:hitl` present → classification is **HITL**. Stop here.
|
|
37
|
+
**Callers should run the classifier themselves before spawning this subagent.** If `implementation-type` is already present, or the classifier resolves the bead, there is no subagent call to make.
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
## Workflow
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
bd show <id>
|
|
45
|
-
```
|
|
41
|
+
1. **Check for an existing label.** `bd label list <id>` — `implementation-type:afk` present → **AFK**; `implementation-type:hitl` present → **HITL**. Stop.
|
|
46
42
|
|
|
47
|
-
|
|
43
|
+
2. **Run the deterministic classifier.** `node <skill-scripts>/classify-bead.mjs <id>`. A resolved classification is already tagged; stop and report it.
|
|
48
44
|
|
|
49
|
-
3. **
|
|
45
|
+
3. **Apply the rubric** — only when the classifier reports `escalate: true`. Read the full bead body:
|
|
50
46
|
|
|
51
|
-
**HITL** —
|
|
47
|
+
**HITL** — a human is required if at least one of the following holds:
|
|
52
48
|
- The outcome cannot be verified by an agent (e.g. visual review, stakeholder sign-off, UX judgement call).
|
|
53
49
|
- A manual action only a human can perform is required (e.g. credential setup, secrets management, external service configuration, infrastructure provisioning outside the codebase).
|
|
54
50
|
- A decision must be made that the agent cannot make unilaterally (e.g. architectural choice between equally valid options, regulatory or legal sign-off).
|
|
55
51
|
|
|
56
|
-
**AFK** — the agent can implement, verify, and complete the task autonomously, and all acceptance criteria are machine-checkable.
|
|
57
|
-
|
|
58
|
-
4. **Apply the label.**
|
|
52
|
+
**AFK** — otherwise: the agent can implement, verify, and complete the task autonomously, and all acceptance criteria are machine-checkable.
|
|
59
53
|
|
|
60
|
-
|
|
61
|
-
bd tag <id> implementation-type:afk # or implementation-type:hitl
|
|
62
|
-
```
|
|
54
|
+
4. **Apply the label.** `bd tag <id> implementation-type:afk` (or `:hitl`). The classifier does this for the deterministic tiers; do it yourself only after step 3.
|
|
63
55
|
|
|
64
56
|
5. **Return the classification.** Report `AFK` or `HITL` so the calling skill or agent can act on it.
|
|
65
57
|
|
|
58
|
+
Tier precedence (label → `## Type` → heuristics → rubric), the heuristic signal list, the CLI contract, and the measured deterministic split are in `REFERENCE.md`.
|
|
59
|
+
|
|
66
60
|
## Red Flags
|
|
67
61
|
|
|
68
|
-
- Classifying a bead without the caller-side `bd label list` check — you either overwrite a recorded decision or waste
|
|
62
|
+
- Classifying a bead without the caller-side `bd label list` check — you either overwrite a recorded decision or waste a call re-reading a label that already exists.
|
|
63
|
+
- Spawning the rubric subagent when the classifier returned a classification — the deterministic tier is the point.
|
|
69
64
|
- Defaulting to HITL for a complex task whose acceptance criteria are machine-checkable — complexity alone is not a criterion.
|
|
70
|
-
- Choosing a class without reading the bead body when
|
|
65
|
+
- Choosing a class without reading the bead body when the classifier escalated.
|
|
71
66
|
|
|
72
67
|
## Common Rationalizations
|
|
73
68
|
|
|
74
69
|
| Rationalization | Rebuttal |
|
|
75
|
-
|---|---|
|
|
76
70
|
| "This task is complex, so it must be HITL" | Complexity is not a criterion. HITL applies only when a human is genuinely required to complete or verify. |
|
|
77
71
|
| "I'll mark it HITL to be safe" | HITL consumes scarce human attention. Prefer AFK — ralph's review gates still catch agent mistakes. |
|
|
78
|
-
| "The label is probably already there, I'll skip the check" | One `bd label list` command settles it
|
|
72
|
+
| "The label is probably already there, I'll skip the check" | One `bd label list` command settles it, and the classifier checks it too. |
|
|
73
|
+
| "Heuristics are good enough for this one" | The classifier uses conservative, explicit signals only. If it escalated, read the body. |
|
|
79
74
|
|
|
80
75
|
## Philosophy / rationale
|
|
81
76
|
|
|
82
|
-
- Classification gates automation against human attention: AFK beads feed ralph's autonomous pipeline, HITL beads
|
|
83
|
-
- The rubric is deterministic, so
|
|
77
|
+
- Classification gates automation against human attention: AFK beads feed ralph's autonomous pipeline, HITL beads route to a person. Prefer AFK because human time is the scarce resource, and later review gates still catch agent mistakes.
|
|
78
|
+
- The rubric is deterministic, so most classifications are a lookup. The executable encodes that lookup once, and the model is reserved for beads whose evidence is genuinely ambiguous.
|
|
84
79
|
|
|
85
80
|
## Verification checklist
|
|
86
81
|
|
|
87
82
|
- [ ] `bd label list <id>` was checked before any other step.
|
|
88
|
-
- [ ]
|
|
89
|
-
- [ ]
|
|
90
|
-
- [ ]
|
|
91
|
-
- [ ]
|
|
83
|
+
- [ ] The deterministic classifier ran before any rubric subagent.
|
|
84
|
+
- [ ] A rubric subagent ran only when the classifier reported `escalate: true`.
|
|
85
|
+
- [ ] The full bead body was read before classifying from first principles.
|
|
86
|
+
- [ ] `bd tag <id> implementation-type:<afk|hitl>` is present afterwards.
|
|
87
|
+
- [ ] The calling skill or agent received the classification in the report.
|