diff --git a/.wiki/concepts/delegate-task-negative-trigger-fp.md b/.wiki/concepts/delegate-task-negative-trigger-fp.md index fbbcc4f..f7866ac 100644 --- a/.wiki/concepts/delegate-task-negative-trigger-fp.md +++ b/.wiki/concepts/delegate-task-negative-trigger-fp.md @@ -1,7 +1,7 @@ --- title: delegate-task — literal negative triggers beat abstract carve-outs type: concept -updated: 2026-06-09 +updated: 2026-06-17 --- # delegate-task — literal negative triggers beat abstract carve-outs @@ -57,3 +57,7 @@ skill's domain, an abstract "does NOT apply when…" clause is too weak. Put the colliding negative phrase** in the description with an explicit **→ ** route. Literal beats abstract under the 1%-rule. See also [[tdd-criteria-design]] for another "make the bright line literal, not a judgement call" pattern. + +See [[session-inbox-monitor-received-msg-fp]] for the next clause: a literal+routed negative +still fails if its **route target isn't installed** — the carve-out then has no real competitor +and the nearest in-domain skill wins anyway. diff --git a/.wiki/concepts/session-inbox-monitor-received-msg-fp.md b/.wiki/concepts/session-inbox-monitor-received-msg-fp.md new file mode 100644 index 0000000..69b1850 --- /dev/null +++ b/.wiki/concepts/session-inbox-monitor-received-msg-fp.md @@ -0,0 +1,71 @@ +--- +title: session-inbox-monitor — a routed negative only competes if its sibling is installed +type: concept +tags: [skill-triggers, false-positive, trigger-discrimination, test-trigger] +updated: 2026-06-17 +--- + +# session-inbox-monitor — a routed negative only competes if its sibling is installed + +Sibling of [[delegate-task-negative-trigger-fp]]. Same failure family (a skill +false-positive-fires on a phrase its description tries to exclude), but a **distinct +mechanism** — and it stays **open** as of this writing (follow-up task +`session-inbox-monitor-received-msg-fp`, not yet fixed). + +## Symptom + +In the `session-inbox-monitor-test-trigger` run (2026-06-17, clean session, 7 unprimed +clean-context subagents), the negative phrase **«В .claude-inbox пришло сообщение от другой +Claude-сессии. Прочитай его и ответь отправителю.»** (N1, RU) routed to +**`session-inbox-monitor`** — a false-positive. The skill is about *raising the monitor*, not +*handling a received message*; the latter belongs to inter-session-peer-discipline / +the CLAUDE.md inter-session rule. + +The English twin of the same scenario (N3, «A message arrived in my inbox … handle it and +reply») and the multi-machine-backend negative (N2) both routed to `none` cleanly, citing the +carve-out. So the FP is **borderline / non-deterministic**, not a hard miss: pos 4/4, neg 2/3. + +## Root cause + +The description *does* carry a literal, routed carve-out — +`NOT for how to handle a received message (→ inter-session-peer-discipline)` — which is exactly +the fix shape [[delegate-task-negative-trigger-fp]] prescribes. The new twist: + +**The route target `inter-session-peer-discipline` is not an installed skill.** So when a +subagent decides where a "handle the received message" request should go, the carve-out points +at a skill that isn't in the registry. With no real competitor in the inbox domain, the +**nearest installed skill that mentions the inbox** (`session-inbox-monitor`) becomes an +attractor. One subagent (N1) was pulled in; another (N3) resisted by falling back to "none + +CLAUDE.md rule." Hence the non-determinism. + +**Mitigating property:** the FP self-corrects on body-load. Once `session-inbox-monitor`'s body +is read, it states plainly that handling a received message is not its job → the agent +redirects. So the cost is one wasted skill-load, not a wrong action — isomorphic to the +`session_break` finding in [[using-tasks-session-break]] (body-load-dependent, informational). + +## Status — open + +No fix shipped. Options under the follow-up task (decide, not necessarily implement): + +- **(a) Harden the description** — surface `NOT for handling a received message` earlier / + more prominently. Cheap, but a description edit forces a re-run of `-test-trigger` and feeds + back into `-review`. +- **(b) Install the sibling** — give the carve-out a real target by installing + `inter-session-peer-discipline` (likely the true root, but it's a separate skill that may not + exist yet, with its own promotion ledger). +- **(c) Accept as informational** — like `session_break`: low harm + body-load self-correct. + +## Reusable principle + +[[delegate-task-negative-trigger-fp]] established: *make the negative literal and routed, not +abstract.* This case adds the next clause: + +> **A routed negative competes only if its route target is installed.** A carve-out +> `→ ` is dead weight when `` isn't in the registry — the request +> has nowhere to go, so the nearest installed skill in that domain wins by default. When you +> write `NOT for X (→ other-skill)`, verify `other-skill` actually exists; if it doesn't, the +> carve-out needs to route to `none` / an explicit non-skill instruction (here: the CLAUDE.md +> inter-session rule), or the sibling must be promoted alongside. + +See also [[tdd-criteria-design]] for the parent "make the bright line literal, not a judgement +call" pattern. diff --git a/.wiki/index.md b/.wiki/index.md index b2a6396..deb0ef9 100644 --- a/.wiki/index.md +++ b/.wiki/index.md @@ -48,6 +48,7 @@ Catalog of all wiki pages. One line per page, organized by type. Updated on ever - [using-tasks-status-archival.md](concepts/using-tasks-status-archival.md) — `using-tasks` v1.3.0 done-task archival rule (≥10 🟢 → `.tasks/archive/YYYY-MM.md`) fixes STATUS.md bloat; documents why `tasks_get_status` (single-task, by slug) / `tasks_aggregate` (cross-project cache) can't replace the orientation board-read, so the literal task instruction was not followed - [delegate-task-negative-trigger-fp.md](concepts/delegate-task-negative-trigger-fp.md) — `delegate-task` v0.2.1 FP fix: «создать задачу себе» stem-matched the «создать задачу на агента» positive trigger; abstract "does NOT apply when doing the work yourself" carve-out loses to literal stem-match under the 1%-rule → made the negative literal + routed (→ using-tasks). Verified pos 5/5, neg 4/5 (was 0/5) - [using-markitdown-cli-migration.md](concepts/using-markitdown-cli-migration.md) — `using-markitdown` v1.0.0→v1.0.1 (PATCH): rewrote from the Docker-based `mcp__markitdown__convert_to_markdown` MCP tool to the native `markitdown` CLI (0.1.6, on PATH); dropped the host→container `file://` mount caveat; container decommission is by image ancestor (`--filter ancestor=markitdown-mcp:latest`), not by the non-existent name `markitdown-mcp` +- [session-inbox-monitor-received-msg-fp.md](concepts/session-inbox-monitor-received-msg-fp.md) — sibling of [[delegate-task-negative-trigger-fp]]: `session-inbox-monitor` FP-fires on RU «обработай полученное письмо» (N1) because its literal+routed carve-out points at `inter-session-peer-discipline`, which **isn't installed** → no competitor, nearest inbox-skill wins. Borderline (neg 2/3, EN twin clean), body-load self-corrects. **Open** (follow-up task). New principle: *a routed negative competes only if its route target is installed* - [task-format-design.md](concepts/task-format-design.md) — new `task-format` skill v0.1.0: public reference for the on-disk `.tasks/STATUS.md` block format the poller parses (header regex, status emoji, `**Weight:**` / `**Notify:**` / `**Requirements:**`); ships with `factory` where the internal wiki/MCP-source can't reach; distinct from [[delegate-task]] (MCP-tool delegation) and [[using-tasks]] (board mechanics); RED 3-baseline / GREEN 2-verify per writing-skills; ground truth = `status-md.ts` + `claim.ts` + `fleet-router.js` ## Packages diff --git a/.wiki/log.md b/.wiki/log.md index d80f73a..9075811 100644 --- a/.wiki/log.md +++ b/.wiki/log.md @@ -76,3 +76,4 @@ Parseable: `grep "^## \[" .wiki/log.md | tail -20`. ## [2026-06-09] decision | delegate-task-review-weight — `delegate-task` 0.2.2→0.2.3 (PATCH): Step 5 (paired `-review` task) now sets an explicit `weight`, inherited from the impl-task with a `needs-claude` floor (impl `needs-human`→review `needs-human`; `needs-claude`→`needs-claude`; `cheap-ok`→`needs-claude`). Root cause of commit `c0af151` ("add Weight: needs-claude to 4 review tasks — reconciler was skipping them"): the authoring skill omitted `weight` on review tasks, making them invisible to fleet routing. Floor (not pure inheritance) chosen to stay internally consistent with the skill's own "What NOT to do" bullet that forbids `cheap-ok` for review tasks — a `cheap-ok` impl would otherwise propagate a forbidden `cheap-ok` review. Added a What-NOT-to-do bullet against weightless review tasks. Concept page concepts/delegate-task-review-weight.md + index. TDD N/A (markdown policy artifact). ## [2026-06-11] decision | task-format — new skill v0.1.0: public reference for the `.tasks/STATUS.md` task-block format the autonomous poller parses. Motivation: the field rules (`**Weight:**` capability/cost tier, `**Notify:** /` inbox target, header regex, status emoji) lived only in internal sources (`projects-meta-mcp/src/lib/status-md.ts` parser + `status-md-writer.ts` + `.common/.wiki/concepts/agents-task-runner-ops.md`); skills ship with `factory` to external users, the wiki/MCP-source don't. Scope kept distinct from delegate-task (creates tasks for others via `tasks_create`, the tool emits the format) and using-tasks (board claim/close mechanics) — task-format is the byte-level field reference for hand-edited blocks. Ground truth verified against source: header `/^##\s+(\S+)\s+\[([^\]]+)\]\s+—\s+(.+)$/u`; Weight ∈ {cheap-ok, needs-claude, needs-human}; claim gate excludes only `needs-human` (`claim.ts`), but a *missing* Weight finds no backend tier (`fleet-router.js` resolveBackend) → poller parks to 🔵 blocked, so Weight is operatively required for pickup. TDD per writing-skills: RED = 3 baseline subagents w/o skill (2/3 used `###`/bullet headers the parser can't recognize, 2/3 omitted Weight inventing `risk`/`tier`/`claimable-by`, 2/3 put notify in prose, 1/3 used 🟢 for ready); GREEN = 2 fresh subagents w/ skill, both parser-valid incl. correct `needs-human` for the critical-infra scenario; REFACTOR = no new loopholes. Reference skill ~900 words (loads only when authoring a task block). Concept page concepts/task-format-design.md + index. Not yet installed to `~/.claude/skills/` or added to hermes mapping — deferred follow-up (mirrors using-system-snapshot deployment-scaffold note). ## [2026-06-09] decision | using-markitdown-cli-migration — `using-markitdown` 1.0.0→1.0.1 (PATCH): rewrote the skill from the Docker-based `mcp__markitdown__convert_to_markdown` MCP tool to the native `markitdown` CLI (v0.1.6, on PATH). Tool block now `markitdown ` → stdout (or `-o file`); removed the whole "Docker-mount caveat (READ FIRST)" section (host→container `file://` translation + `[Errno 2] /c:/Users/...` symptom are gone — CLI sees the full host FS). Updated the ingest pattern (use `-o` straight into `.wiki/raw/`), the gotchas table (`command not found` → check `markitdown --version`, install `pip install markitdown[all]`; dropped the MCP "tool not available / ToolSearch" row), and the contrast-table header (CLI, not MCP). Description frontmatter (the WHEN-to-use triggers) left unchanged. Container decommission: the task's literal `docker stop/rm markitdown-mcp` had no target — no container is named that; the MCP spawns anonymously-named containers from `markitdown-mcp:latest` per session (3 had piled up). Removed all by image ancestor (`docker rm -f $(docker ps -aq --filter "ancestor=markitdown-mcp:latest")`), verified none remain. Left the `mcpServers.markitdown` entry in `~/.claude.json` untouched (out of scope; a container will respawn next session until it's deregistered — flagged as a follow-up). Concept page concepts/using-markitdown-cli-migration.md + index. TDD N/A (markdown skill). +## [2026-06-17] decision | session-inbox-monitor-received-msg-fp — finding from `session-inbox-monitor-test-trigger` (VERDICT PASS, clean session, 7 unprimed clean-context subagents: pos 4/4 incl. CLAUDE.md-line P4, neg 2/3). The 1 FP: RU «обработай полученное письмо из инбокса» (N1) routed to `session-inbox-monitor`; the EN twin (N3) and the multi-machine-backend negative (N2) routed to `none` cleanly. Root cause = a new dimension on top of [[delegate-task-negative-trigger-fp]]: the carve-out is already literal+routed (`NOT for handling a received message → inter-session-peer-discipline`), but the route target `inter-session-peer-discipline` is **not installed** → no real competitor, so the nearest in-domain skill (session-inbox-monitor) wins by default; non-deterministic, self-corrects on body-load (cost = one wasted skill-load, not a wrong action; isomorphic to [[using-tasks-session-break]] session_break). New page concepts/session-inbox-monitor-received-msg-fp.md + bidirectional link from concepts/delegate-task-negative-trigger-fp.md + index. New reusable principle: a routed negative competes only if its route target is installed. Status OPEN — follow-up task session-inbox-monitor-received-msg-fp (options a: harden description / b: install sibling / c: accept informational). Not a memory entry by owner direction — knowledge belongs in the project wiki.