Summary
The inner dev session writes deferred-work ledger markdown directly. bmad-dev-auto's
step-04-review.md:69-74 instructs it to append a flat block to {deferred_work_file}:
- source_spec: `{spec_file}`
summary: <one sentence>
evidence: <why this is real>
and engine.py:2391-2396 reinforces it in the follow-up-review prompt the orchestrator sends
("append them to the deferred-work ledger as NEW entries only").
This sits awkwardly against deferredwork.py's own module docstring — "The orchestrator never
trusts an LLM to have edited it" — which is true of status flips and decision records, and not true
of entry creation. It is also the reason two recent bugs existed:
Every flat entry also costs a whole extra LLM migration session before it is sweepable
(sweep.py:714-720 — "One LLM session rewrites the legacy items into canonical DW entries").
Design sketch
Give the dev session a structured sink and let orchestrator Python render the ledger.
The channel already exists in two forms — neither needs an upstream fork:
- Spec frontmatter.
devcontract.synthesize_result (devcontract.py:197) already builds the
dev result dict entirely from spec frontmatter — dev sessions emit no result.json at all
(devcontract.py:1-10). A deferred: list there is parsed by the existing read_frontmatter.
- The generic result.json mixin.
adapters/generic.py:187-207 is available to any skill that
writes one; bmad-loop-sweep uses it today. bmad-dev-auto simply doesn't.
Either way the orchestrator calls deferredwork.append_entry per item, so entries arrive canonical
(### DW-<seq>) and pre-sanitized, and has_legacy/migration stops firing for new work.
bmad-dev-auto is upstream and not bundled here (install.py:62-70,
data/skills/README.md:21-23), but the orchestrator already overrides its ledger instructions by
prompt injection at engine.py:2391-2396 — so this is a prompt-contract plus renderer change on the
bmad-loop side, not a fork. An upstream change would be cleaner if Alex is open to it.
What this does NOT replace
A newline inside a JSON or YAML string value is still a real newline once parsed. Structured input
fixes syntax corruption (malformed blocks, broken indentation, boundary ambiguity), not value
content — so deferredwork's _one_line sanitizer from #305 remains load-bearing. What this buys is
making that chokepoint universal instead of partial, and deleting a migration session per flat
defer.
Scope note
Feature-sized: touches the dev contract, the flat-entry parser, migration, and legacy-ledger
handling. Filing for maintainer confirmation before any implementation, per CONTRIBUTING.
Backward compatibility is the main design question — flat appends must keep working for
already-installed bmad-dev-auto versions and for ledgers already containing them, so the renderer
is additive and parse_legacy/migration stay as the fallback path rather than being removed.
Summary
The inner dev session writes deferred-work ledger markdown directly.
bmad-dev-auto'sstep-04-review.md:69-74instructs it to append a flat block to{deferred_work_file}:and
engine.py:2391-2396reinforces it in the follow-up-review prompt the orchestrator sends("append them to the deferred-work ledger as NEW entries only").
This sits awkwardly against
deferredwork.py's own module docstring — "The orchestrator nevertrusts an LLM to have edited it" — which is true of status flips and decision records, and not true
of entry creation. It is also the reason two recent bugs existed:
the boundary between LLM-authored markdown and parsed markdown is guesswork.
The dev appender bypasses
append_entryentirely, so nothing normalizes what it emits.Every flat entry also costs a whole extra LLM migration session before it is sweepable
(
sweep.py:714-720— "One LLM session rewrites the legacy items into canonical DW entries").Design sketch
Give the dev session a structured sink and let orchestrator Python render the ledger.
The channel already exists in two forms — neither needs an upstream fork:
devcontract.synthesize_result(devcontract.py:197) already builds thedev result dict entirely from spec frontmatter — dev sessions emit no
result.jsonat all(
devcontract.py:1-10). Adeferred:list there is parsed by the existingread_frontmatter.adapters/generic.py:187-207is available to any skill thatwrites one;
bmad-loop-sweepuses it today.bmad-dev-autosimply doesn't.Either way the orchestrator calls
deferredwork.append_entryper item, so entries arrive canonical(
### DW-<seq>) and pre-sanitized, andhas_legacy/migration stops firing for new work.bmad-dev-autois upstream and not bundled here (install.py:62-70,data/skills/README.md:21-23), but the orchestrator already overrides its ledger instructions byprompt injection at
engine.py:2391-2396— so this is a prompt-contract plus renderer change on thebmad-loop side, not a fork. An upstream change would be cleaner if Alex is open to it.
What this does NOT replace
A newline inside a JSON or YAML string value is still a real newline once parsed. Structured input
fixes syntax corruption (malformed blocks, broken indentation, boundary ambiguity), not value
content — so
deferredwork's_one_linesanitizer from #305 remains load-bearing. What this buys ismaking that chokepoint universal instead of partial, and deleting a migration session per flat
defer.
Scope note
Feature-sized: touches the dev contract, the flat-entry parser, migration, and legacy-ledger
handling. Filing for maintainer confirmation before any implementation, per CONTRIBUTING.
Backward compatibility is the main design question — flat appends must keep working for
already-installed
bmad-dev-autoversions and for ledgers already containing them, so the rendereris additive and
parse_legacy/migration stay as the fallback path rather than being removed.