akm-cli
Version:
akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur
109 lines (97 loc) • 6.33 kB
Markdown
# 0005 — The D8 result-vocabulary re-code and its legacy read mapping
## Context
Before P1b's D8 vocabulary re-code, a task's result `target.kind` used
`"prompt"` for a prepared command/agent-LLM result and one shared
`"command"` for every native (shell/script) result, which conflated two
materially different execution shapes under one label and mislabeled an
LLM-routed dispatch as a literal "prompt". D8 renamed the written
vocabulary; the harder problem was that `task_history` is a durable,
already-populated table — existing rows on disk still carry the OLD labels,
and `readTaskHistory` has to keep returning a coherent shape for both
generations of rows forever.
## Decision
Moved verbatim from `src/tasks/run/task-result.ts`'s module header:
> `TaskRunResult` — the shape every dispatch arm returns — plus the small
> cluster of helpers that build one directly: `preparedResultTarget` (the D8
> result-vocabulary projection of a freshly prepared execution),
> `finishDisabledTask` (the disabled-task short-circuit), and
> `exitCodeForStatus` (the OS-scheduler exit-code mapping). `RunTaskOptions`
> — the public options bag `runTask()`, `load-task.ts`, and every dispatch
> arm read from — lives here too, alongside the other public-surface types
> the compat shim (`src/tasks/runner.ts`) re-exports.
>
> D8 (§5.3, §6 F-2): `preparedResultTarget`'s prepared-command arm now
> returns `{kind:"command", engine}` (formerly `{kind:"prompt", engine}`);
> its native arm now returns the bare `{kind:"shell"}` / `{kind:"script"}`
> (formerly one shared `{kind:"command"}`) — the arm-specific `cmd` is
> added by `run-native-task.ts` once it has actually built the argv,
> mirroring the pre-P1b shape where the bare disabled-task projection never
> carried `cmd` either.
And from `src/tasks/run/task-history.ts`'s module header (the WRITE half —
the READ half's mapping rule is kept in the code itself, not moved; see
below):
> The `task_history` read/write boundary: `appendHistory` (write) and
> `readTaskHistory` / `taskHistoryRowToResult` (read).
>
> D8 (spec §5.3, §6 F-2) result-vocabulary re-code, implemented entirely at
> this read/write boundary. WRITE: every row `appendHistory` writes now
> carries `targetVocab: 2` in its metadata, and the new target_kind strings
> ("command" for a prepared command/agent-LLM result, "shell", "script",
> "workflow" unchanged).
## Consequences
- Every NEW row written after D8 is unambiguous — `targetVocab: 2` plus the
new `target_kind` strings mean a reader never has to guess which
generation a row belongs to.
- Every OLD row (written before D8, no `targetVocab` marker at all) must
keep reading correctly forever — this is not a migration that runs once
and finishes; there is no "convert task_history in place" step, because
the table is an append-only historical log, not authoring state. The
read-side mapping rule is therefore a PERMANENT part of the codebase
(`docs/plans/specs/p4-deletions-closeout.md` row B-51: "it reads old rows
forever. Deleting it is a review-blocking violation.").
- The exact legacy mapping — kept in `src/tasks/run/task-history.ts` itself
as a short invariant comment, not summarized here, because a maintainer
reading `taskHistoryRowToResult` needs it right there — is: a legacy row
(no `targetVocab` marker) maps `"prompt"` → `{kind:"command", engine}`,
`"command"` → `{kind:"shell"}`, `"workflow"` unchanged, and anything else
(including the new vocabulary's own strings written WITHOUT a marker,
which no production writer ever does) → `"unknown"`. The P0-pinned null
fallbacks survive: a workflow row's `ref` falls back to `""`, the
command/prompt arm's `engine` falls back to `null`.
- `SAFE_TASK_ATTEMPT_ERROR_CODES` (`src/tasks/run/attempt-lifecycle.ts`) is a
related but separate allowlist — it decides which error CODES are safe to
surface verbatim in a `detail.error` column, not which result-kind
strings are legal. Do not conflate the two when reading either file.
- **Removed 2026-08-28** (review finding against this phase's own close-out
spec, `docs/plans/specs/p4-deletions-closeout.md`): `finishDisabledTask`
and `preparedResultTarget`, quoted above as part of the moved essay, are no
longer in `src/tasks/run/task-result.ts`. `finishDisabledTask`'s only
caller — `run-task.ts`'s `shouldSkipUnactivatedTask` — was deleted by the
SAME phase's P4-N6 (task source v4 has no document-level `enabled` to skip
at fire time), which left `finishDisabledTask` fully dead; `preparedResultTarget`
was in turn `finishDisabledTask`'s only caller, so it went dead with it.
Deleting them also retired the `"disabled"` `TaskRunStatus` member
`finishDisabledTask` was the sole producer of, and the now-unreachable
`result.status === "disabled"` branch in `src/commands/tasks/tasks.ts`.
This does not touch the D8 vocabulary or the legacy read mapping this ADR
is about — every OTHER dispatch arm still builds its own
`TaskRunResult.target` per the Decision above, and `task-history.ts`'s read
side is unchanged.
- **Superseded 2026-09-01**: the "PERMANENT part of the codebase" ruling
above (row B-51) is overturned. `task_history` is DB-owned data, and the
legacy->current remap is deterministic and total, so it belongs in a
one-time schema migration, not three permanently-recurring read-side
branches (the mapping had been independently re-implemented at
`src/tasks/run/task-history.ts`, `src/commands/health/improve-metrics.ts`'s
`isAgentTaskHistoryRow`, and `src/commands/health/windows.ts`). State
migration `025-task-history-vocabulary-backfill`
(`src/core/state/migrations.ts`) now rewrites every legacy row's
`target_kind` (and stamps `targetVocab: 2`) the first time a pre-migration
state.db is opened by this or a later release; all three read sites were
deleted and now read `target_kind` directly in the current vocabulary.
## Provenance
- Source: `src/tasks/run/task-result.ts` module header;
`src/tasks/run/task-history.ts` module header (WRITE half only — the READ
half's mapping rule stays in the code as a permanent invariant).
- Spec: `docs/plans/specs/p1b-model-extraction.md` §5.3, §6 F-2.
- Extracted: P4 (`docs/plans/specs/p4-deletions-closeout.md` §4.2), 2026-08-27.