MVP-0 domain model and review loop¶
This explanation documents the MVP-0 Creator/Reviewer loop and the domain objects it persists. It focuses on the current implementation, workspace artifacts, and the convergence rules used by the loop engine.
Domain model snapshot¶
The MVP-0 domain model centers on a workspace with assets that accumulate candidates and review iterations.
EpisodeWorkspaceis the root aggregate. It stores anepisode_id,root_dir, and collections ofAsset,Chapter, andTrackrecords. It is serialized tostate.jsonfor persistence. Invariants enforce unique asset and track ids plus strictly increasing chapter start times.Assetgroups all draft material for a singleasset_id(for exampledescription) and keeps:candidates(Candidateobjects) andreviews(ReviewIterationobjects).selected_candidate_idwhen the loop converges.kind(optional) when the asset id matches a knownAssetKind.Invariants:
asset_idvalues are unique per workspace, review iterations are strictly increasing, andselected_candidate_idmust refer to an existing candidate.
Candidatecaptures a single draft withcontent,format,created_at, and provenance metadata. Candidates are bound to anasset_idand get a UUID.ReviewIterationrecords the reviewer verdict (ReviewVerdict) and optionalReviewIssuelist. Each issue carries a severity (IssueSeverity), message, and optional code/field.ReviewVerdict.okcannot be combined withIssueSeverity.errorissues. Reviews also carry areviewerlabel, optionalsummary, andprovenanceentries to tag automated runs.ReviewVerdictis the tri-state verdict used by the loop engine:ok,changes_requested, orneeds_human.
Workspace layout and stored artifacts¶
Workspaces are rooted at an episode directory and follow the layout defined by EpisodeWorkspaceLayout. The CLI demo
creates a ./demo_workspace* root, while helpers in workspace_store also support ./episodes/<episode_id>/. The
review loop mostly writes under copy/, while the workspace state lives at the root.
<workspace>/
episode.yaml
state.json
transcript/
transcript.txt
chapters.txt
chunks/
chunk_0001.txt
chunk_0001.json
auphonic/
downloads/
outputs/
summaries/
chunks/chunk_0001.summary.json
episode/episode_summary.json
episode/episode_summary.md
episode/episode_summary.html
copy/
candidates/<asset_id>/candidate_<uuid>.json + .md/.html
reviews/<asset_id>/iteration_01.json (or iteration_01.<reviewer>.json)
protocol/<asset_id>/iteration_01.json
protocol/<asset_id>/state.json
selected/<asset_id>.md/.html/.txt
provenance/<kind>/<ref>.json
Key persisted artifacts:
episode.yaml: input metadata such as transcript/chapters paths.state.json: serializedEpisodeWorkspacesnapshot.copy/candidates/...: JSON plus rendered text for eachCandidate.copy/reviews/...: reviewer outputs per iteration.copy/protocol/...: loop protocol state (LoopProtocolState) plus per-iteration JSON envelopes (LoopProtocolIteration).copy/selected/...: selected final draft when converged.summaries/...: chunk and episode summaries written by the stub summarizer.transcript/chunks/...: transcript chunk text and metadata.auphonic/...: reserved for Auphonic downloads/outputs.
Review loop flow and convergence rules¶
The loop is driven by run_review_loop_engine and follows a strict Creator/Reviewer cadence. It persists a
LoopProtocolState with a LoopDecision that can lock terminal outcomes to prevent replays from rewriting history.
Start from an optional existing protocol state; if it is terminal and the outcome is locked, the engine returns without re-running.
For each iteration up to
max_iterations:The creator receives
CreatorInput(including the prior candidate/review) and returns aCandidateplus adoneflag.The reviewer receives the new candidate and returns a
ReviewIteration.The engine writes protocol JSON (
LoopProtocolIteration) for the iteration and evaluates convergence.
The engine writes a protocol
state.jsonsnapshot for the loop.
Convergence is determined by the following rules:
ReviewVerdict.ok+ creatordone=Trueterminates with outcomeconverged.If
iteration == max_iterationswithout convergence, the loop ends withneeds_humanand reasoniteration_limit.ReviewVerdict.needs_humanis recorded on the iteration but does not terminate the loop on its own.
Fake runner usage¶
The CLI podcast review command exposes a --fake-runner flag for the MVP-0 loop. When enabled, it uses the
FakeCreatorRunner and FakeReviewerRunner to emit scripted replies (including deterministic IDs and timestamps).
The fake runners can also mutate files via a mutate_files map in their JSON replies, which is useful for tests and
demo workspaces.
Example invocation:
podcast review --fake-runner --asset-id description --max-iterations 2
References¶
Domain model:
src/podcast_pipeline/domain/models.pyWorkspace layout/store:
src/podcast_pipeline/workspace_store.pyReview loop engine:
src/podcast_pipeline/review_loop_engine.pyFake runners:
src/podcast_pipeline/agent_runners.pyCLI + demo entrypoint:
src/podcast_pipeline/entrypoints/cli.py,src/podcast_pipeline/entrypoints/draft_demo.pyTests:
tests/test_domain_models.py,tests/test_review_loop_engine.py,tests/test_fake_agent_runners.py,tests/test_cli_draft_fake_runner.py,tests/test_workspace_store.py,tests/test_e2e_description_converges.py