Repository navigation
feat(framework): a task identity file linking delivery to its backlog item #649
Description
Outcome
A delivery folder declares where it comes from and which step produced which file, without repeating anything the backlog item already holds.
Scope
- Includes:
metadata.jsonin the task folder, carrying aunit_id— the identity of the unit of work, distinct from therun_idof a session in feat(framework): an aidd-telemetry plugin whose hooks journal every session #620, one upward link, and a journal of the steps that ran with the files each produced. - Includes: written by the skills that create and advance the folder, at step boundaries — a handful of writes over a task's life, human-readable and correctable.
- Excludes: status of any kind. The artefact's frontmatter owns it, and a step with an end time is finished by deduction.
- Excludes: the folder's file listing.
producedis provenance, not an inventory. - Excludes: tokens, cost, model, duration. Those come from telemetry.
- Excludes: any downward link. Readers index the folders and group by the upward one.
The file
{
"schema_version": 1,
"unit_id": "01J9X4M2K7QRVB",
"task_id": "2026_08_14_telemetry-v1",
"backlog": "ai-driven-dev/framework#617",
"branch": "feat/telemetry-v1",
"pull_request": "ai-driven-dev/framework#651",
"opened_at": "2026-08-14T08:40:12Z",
"closed_at": null,
"steps": [
{ "skill": "aidd-pm:04-spec", "from": "...", "to": "...", "produced": ["spec.md"] },
{ "skill": "aidd-dev:01-plan", "from": "...", "to": "...", "produced": ["plan.md", "phase-1.md"] },
{ "skill": "aidd-dev:08-debug", "from": "...", "to": "...", "produced": [] }
]
}steps is a journal, not a checklist: entries repeat, and arrive out of order. Three debug runs mid-implementation give three entries. The flow is read afterwards, never constrained beforehand.
One field, one support
backlog points at the artefact on whatever support it lives: an issue reference where the backlog lives with the ticket provider, a project-relative path where it lives in Markdown. This is what persistence.md already prescribes — "Use native fields when supported; otherwise use explicit ids or project-relative paths" — and its companion rule is why nothing is copied: "Never mirror one Story across supports."
Neither the type of work nor the originating ticket is repeated here. The backlog artefact carries them in type, work_kind and source.
A folder with no backlog artefact is a normal state: backlog is null, and only then does the file carry its own source and type.
Done When
- From a run file, the chain to the epic is walkable with no missing edge.
- No field in the file duplicates one that exists on the backlog artefact, asserted field by field.
- A story delivered in two folders produces two files pointing at it, and the story is unchanged.
- A folder created without a backlog artefact is valid and readable.
- Three invocations of the same skill produce three step entries, not one.
- Nothing writes a downward link.
Completion Evidence
A real feature whose cost, sessions, steps, produced files and originating ticket are reachable from one another in both directions of reading.
Relations
| Field | Value |
|---|---|
| parent | #648 |
| depends_on | #620 |
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Projects
- StatusShow more project fieldsDone
Activity
blafourcade commentedon Aug 18, 2026
Each step must carry the
run_id. Time is not enough.The join from a delivery folder to a session is
task_id, and it is exact. The join from a step to the session that ran it was left to timestamps, and that fails on the case this project hits every day.Two sessions can work the same task at once — a background agent while you work, two worktrees on one feature. A step recorded from 10:04 to 10:12 overlaps both. Time cannot say which session ran it, so any cost split across steps is a guess wearing a decimal point.
Two smaller reasons point the same way:
nowIso()drops milliseconds, so two steps in the same second are indistinguishable; and a reader would have to trust that two files written by two different programs agree on an instant.The rule: time is never used to choose a session, only to split inside one.
task_idrun_idrun_id+ time windowTimezones are not a concern here and never were:
toISOString()emits UTC with aZ, so every timestamp already shares one clock.This may not be a file the skills write at all
The steps in this issue are declared: a skill is expected to record its own boundary. The run journal started that way too, with a pointer the planning skills wrote, and it was replaced by observation for two reasons that apply here unchanged — it put a measurement concern inside skills whose concern is something else, and a declaration can silently stop happening while everything still looks fine.
A skill start is observable: a recorded
PostToolUsepayload carriestool_name: "Skill"andtool_input: { skill }. The hook already listens on that event for attachment.So the question this issue should answer before it is built: are steps observed by the hook, or declared by the skills? #663 is measuring exactly that. If steps are observable,
metadata.jsonkeeps its upward link and its identity, and stops carrying a journal that something else records more reliably.blafourcade commentedon Aug 18, 2026
Confirmed placement:
metadata.jsonlives in each task folder,aidd_docs/tasks/<yyyy_mm>/<task_id>/metadata.json, besideplan.md. That is what this issue already says; recording it here because the end-to-end diagram omitted both the location and therun_idon each step, and the omission was mine.Each step entry carries its
run_id— the reason is on the comment above: two sessions can work the same task in the same time window, so a timestamp cannot say which one ran a step.And a scope reduction that follows from #663: since
PreToolUseandPostToolUseboth fire for theSkilltool with the real name, step boundaries are observed rather than declared. If the hook records them, this file keeps its identity and its upward link to the backlog, and stops carrying a journal that something else records more reliably — the same correction already made twice in this layer.blafourcade commentedon Aug 28, 2026
Moved to the front, second after #661
A product decision taken 2026-08-28 puts this immediately behind #661.
Once a person can be resolved across tools and machines, the next thing a team view needs is the link outward: which backlog item a session's work belonged to. That is what turns "tokens per skill" — which the local layer already reports for one person — into pull requests, features and bugs per epic. Without it, an aggregated view shows a bigger version of what one machine already shows, which is not a reason for anyone to look at it.
It stays distinct from #720, deliberately. #720 groups the tasks the journal already records against each other; this one links a task to the item it delivers. Neither implies the other, and #720 is the cheaper half.
Note for whoever picks this up: the journal already records a task declaration from a tool's own arguments (
task_declared, with the path the tool named), on claude-code, cursor, codex and copilot. The path is there; what is missing is the identity file that says which backlog item that path is delivering.feat(telemetry): a task folder can declare the backlog item it delivers
blafourcade commentedon Sep 2, 2026
Delivered by #706, squash-merged into
nextas 627408f.blafourcade commentedon Sep 12, 2026
Closed as a rescope.
The delivered contract is
backlog-link.json: one upward backlog reference plus writing provenance. The task folder itself is the identity.The omitted
metadata.jsonfields are deliberate: step and file provenance already belong to the journal; branch and pull request are derivable from Git and the forge. Duplicating them would create a second source of truth.aidd telemetry reportreads the link asby_backlog.