Duration Learning Loop Client

Drive the read-aloud duration loop by hand — forward pass through the API, backward pass watched in the database. Steps follow the app's Record-from-a-script flow.

Configuration

The token and the webhook key are held in these fields for this page load only; nothing is written to disk. Start node tools/learning-loop-client/serve.mjs for the database snapshot panel and the S3 PUT relay — without it the page still records, uploads and publishes.

Learning Loop Flow

0. Publish a script 1. Pick a role 2. Prompter 3. Record a take 4. Processing 5. Save or Publish 6. Learning 7. Prompter again

Step 0 is optional and has no app screen: it is the script-authoring side (validate, create, publish) that the backend annotates at publish time, and the only way to get a script whose durations come from the algorithm rather than the authored seconds. Steps 1–5 and 7 each match a screen in the app's Record-from-a-script flow, named below. Step 6 has no screen: the backward pass runs entirely in the backend, which is why this page reads the database for it. The 3/2/1 countdown named in step 3 is the receiver-side flow; the sender side uses three Rehearse/Record frames instead.

0. Publish a script optional — no app screen

Same three calls the authoring tools make, in order. The template below is the end-to-end test's document: one location, two characters, one shot each, one dialogue line each. Edit the lines to what you will actually say on camera; the second character's line is there for the wrong-words variant. Every reset mints a new manifest id, so the same page can publish as many scripts as you need.

What to look at now. Validate touches no table: the response's valid, errors and extracted synopsis are the whole result. Create writes spml_script (status = draft) and one spml_script_version holding the JSON; no character, shot or event rows exist yet, and the version's neutral_sum_ms is null. Publish is the forward pass's write: one transaction flips the status, fans the JSON out into spml_character_definition / spml_shot_definition / spml_event_definition, stamps every dialogue event's neutral_text_ms from the estimator, and rolls those into the shot and version neutral_sum_ms. The "Served shot durations" line above re-reads the shots after publish: a value that is not a round number of seconds is the algorithm's; the authored expectedDuration only shows when a sum is missing.

1. Pick a role 01 Choose a script → 05 Picking a role

What to look at now. Snapshot: a performance row appears with status = draft, learning_state = null, attempts = 0. No shot, no media and no calibration row yet — that row is born at the speaker's first stored prediction, not at sign-up.

Variants

Sweep

The reconciliation sweep has no HTTP surface, so there is no button for it. It runs inside content-service behind LEARNING_SWEEP_ENABLED (off by default), and its lane takes a Postgres advisory lock — a second content-service on the same database turns a tick into a silent no-op.

To watch it by hand: strand a shot the way the sweep's first duty selects it, enable the sweep, restart content-service, then snapshot.

Press the button to fill in the current ids.
What to look at now. After a tick, the stranded shot is back to status = ready and the parent's completed_shots is restored. Assert from the rows, never from the sweep's return value: every duty swallows its own exceptions, so a throw and a quiet hour look identical from outside.

Custom Request

API Calls Log

Database Snapshot

No snapshot yet.