Stage 3 of ACE Phase 7 (Plan B). Creates the two demonstrative workflows
on top of the synthetic data: an operational LLO weekly review (FLW KPI
scorecard + coaching-task spawning) and a meta-level program admin audit
(week-over-week review of the LLO's process). For each, the skill examines
the labs template registry for a good fit and either adapts the closest
template (workflow_create_from_template) or builds from scratch
(workflow_create, following the live workflow_authoring_guide). It then
wires up opp-specific config + KPIs from the manifest.
The output is two workflow IDs registered in labs, one or more
synthetic OCS coaching tasks attached to underperforming FLWs, and the
pipeline schemas populated with KPI fields. The polish step
(synthetic-workflow-polish) layers per-opp visual edits on top.
Either way: confirm the three strings match before you ship the
workflow — a blank-KPI render passes all create/run calls and only
surfaces in the saved-run screenshot.
-
Create the LLO weekly review workflow — via the path decided above.
ADAPT branch (a template fits):
mcp__connect-labs__workflow_create_from_template(
template_key: "<chosen key from list_templates, e.g. llo_weekly_review>",
opportunity_id: <synthetic.labs_opp_id>,
name: "<opp.yaml.display_name> — LLO Weekly Review" // optional
)
Capture the returned workflow_id. The response also lists the
pipelines created from pipeline_sources (the scaffold's
flw_kpi_aggregates pipeline); capture each pipeline_id for step 4.
Then continue to steps 3–4 to wire config + pipeline.
SCRATCH branch (no template fits / closest has a defect): author it
with workflow_create, following the workflow_authoring_guide you
fetched. Build the operational FLW-scorecard render_code, the config
(kpi_config from the manifest + coaching_task_template +
showSummaryCards/showFilters), and the pipeline_sources — honoring
the alias-consistency guardrail above.
mcp__connect-labs__workflow_create(
opportunity_id: <synthetic.labs_opp_id>,
name: "<opp.yaml.display_name> — LLO Weekly Review",
config: {
kpi_config: <manifest.kpi_config verbatim>,
coaching_task_template: { subject_template: "...", ocs_persona: "..." },
showSummaryCards: true, showFilters: true,
},
pipeline_sources: [ { pipeline_id: <linked/created>, alias: "<stable key>" } ],
render_code: "<authored per the guide; reads view.pipelines.<same alias>>",
)
workflow_create returns {workflow_id, render_code_version}. Because
the SCRATCH branch already authored config + render_code in this
call, skip step 3 (config is set) and run step 4 only if the
pipeline schema still needs its fields populated.
-
Wire the workflow's config from the manifest.
The SEED template ships config.kpi_config: [] and a placeholder
coaching_task_template. Both get filled now via:
mcp__connect-labs__workflow_update_definition(
workflow_id: <from step 2>,
opportunity_id: <synthetic.labs_opp_id>,
expected_version: 1, // first edit since template instantiation
patch: {
config: {
kpi_config: <manifest.kpi_config verbatim>,
coaching_task_template: {
subject_template: "Coaching feedback — week {week} for {flw_name}",
ocs_persona: "<from manifest.coaching_arcs[0].persona, default 'supportive_coach'>",
},
}
}
)
config shallow-merges, so this preserves showSummaryCards /
showFilters from the template default. On VERSION_CONFLICT,
re-fetch via workflow_get and retry once.
⚠️ kpi_config thresholds must match the scale the saved-run snapshot
RENDERS, not blindly the manifest totals (jjackson/ace#813). The render
highlights an underperformer via row[k.kpi] < k.threshold_underperform,
applied to whatever per-FLW numbers the deep-linked saved-run snapshot
shows. The manifest authors thresholds at full-window scale (e.g.
total_visits underperform <12, target ≥24 — sized for a 4-week
window). If the snapshot the deep-link renders is period-scoped (one
week ≈ total/weeks visits/FLW), every FLW falls below a full-window
threshold and the whole table highlights red as underperforming — including
the rockstar — so the "underperforming-only" filter becomes meaningless
(live on bednet-spot-check/20260630-1207: ~5 visits/FLW rendered against a
<12 threshold). Do not insert <manifest.kpi_config verbatim> without
first confirming the rendered scale:
- After the Week-N snapshot exists (step 6), read the per-FLW numbers the
saved-run deep-link actually renders — via
pipeline_preview on the
snapshot's run, or by reading the saved-run rows the screenshot shows.
- Compare them to the manifest's full-window per-FLW totals.
- Rendered ≈ full-window totals → the snapshot is NOT period-filtered
(the jjackson/ace#764 state, below in step 6); keep manifest-scale
thresholds as-is.
- Rendered ≈ per-period (weekly) counts → derive thresholds at that
scale (per-week ≈ full-window
threshold / manifest.timeline.weeks),
so only genuinely-struggling FLWs flag.
- Disclose the chosen scale + any derivation in the run summary.
Note the tension: this issue (#813) observed a period-scoped snapshot, while
the #764 note (step 6) records the rollup as full-window/not-period-filtered.
Which holds depends on live labs state, so verify per run rather than
hard-coding either assumption. The invariant is only: thresholds match the
number the screenshot shows.
-
Populate the pipeline schema.
The SEED template's pipeline schema ships with fields: [] because
real fields depend on the opp's form schema. Build the field list
from kpi_config:
fields = [
{
"name": kpi.kpi,
"path": kpi.field_path,
"aggregation": translate_kpi_aggregation(kpi.aggregation),
}
for kpi in manifest.kpi_config
]
Then call:
mcp__connect-labs__pipeline_update_schema(
pipeline_id: <from step 2>,
opportunity_id: <synthetic.labs_opp_id>,
expected_version: 1,
schema: { ...existing schema, fields: <built above> },
)
On VERSION_CONFLICT, re-fetch via pipeline_get and retry once.
Shared-path guardrail (jjackson/ace#595). Do NOT emit a bare
last/first/list aggregation field on the same field_path as
one or more count fields that carry a filter_path/filter_value on
that path. The labs pipeline SQL generator collapses the JSONB column
extraction for a shared path and the bare extraction wins, so the
filtered count fields after the first one silently compute 0
(not null — all-zero, so fields_all_null does NOT catch it). This
under-reports headline KPIs in the demo with no error surfaced (live on
malaria-rdt 20260531-0739: a last_visit_channel (last on
form.channel_type) zeroed the pmv_samples/public_phc_samples
filtered counts on the same path). When building fields: if two or
more fields share a field_path and at least one is a filter_path
count, drop the bare last/first/list on that path (derive that
value a different way, e.g. a separate filtered field or in render code).
Then assert it held: after the schema save, run pipeline_preview
(sample_size ≥ 10) and check that no filter_path count field on a
shared path is uniformly 0 across rows that have data — if one is, a
shared-path collision slipped through; surface a [WARN] and remove the
colliding bare aggregation. (Upstream fix tracked at jjackson/ace#595 —
give each field its own filtered extraction expression.)
-
Spawn coaching tasks.
This is the authoritative path for coaching arcs. synthetic-data-generate
deliberately strips coaching_arcs from the manifest it sends to
synthetic_generate_from_manifest (that in-generate Task path 500s —
jjackson/ace#594), so the arcs are created here via the standalone
task_create_synthetic atom, which is reliable. Do NOT move arc creation
back into the generate call until #594 is fixed upstream.
For each entry in manifest.coaching_arcs:
mcp__connect-labs__task_create_synthetic(
opportunity_id: <synthetic.labs_opp_id>,
assigned_to: <coaching_arcs[].flw_id>,
subject: "<coaching_task_template.subject_template formatted with the arc>",
ocs_conversation: <coaching_arcs[].transcript verbatim>,
status: "completed",
)
transcript is the array of {role: bot|flw, text, ts} the
manifest's narrative-plan authored. Capture each task's returned ID
for the run summary.
Per-arc try/except: if one task creation fails (e.g.
assigned_to doesn't match a real FLW the synthetic data minted),
record the failure in the run summary and continue with the next
arc. Other tasks proceed.
-
Suitability check.
The seeded workflow's render_code is opp-agnostic. Decide whether
synthetic-workflow-polish (Stage 3.2) can apply surgical patches
on top, or whether a full L2-mode rewrite is needed. Heuristic:
scaffold_unsuitable: false (default) — the manifest's
flw_personas[].archetype matches the standard rockstar/steady/
struggling/new_hire mix, and the KPIs are per-FLW aggregates.
Polish skill will do workflow_patch_render_code edits.
scaffold_unsuitable: true — the manifest's KPIs are not
per-FLW aggregates (e.g., a focus-group archetype with
facilitation-quality KPIs that aren't worker-rollable), or the
archetype mix is exotic. Polish skill will rewrite the render
code from scratch (workflow_update_render_code).
For the canonical atomic-visit archetype with per-FLW KPIs, this is
always false. Record the flag in step 8.
-
Create the program admin audit workflow — via the path decided above.
ADAPT branch (a template fits — usually program_admin_report, the
live registry key for the cross-opp SOP-compliance rollup; confirm the
exact key from list_templates rather than assuming):
mcp__connect-labs__workflow_create_from_template(
template_key: "<chosen audit key from list_templates>",
opportunity_id: <synthetic.labs_opp_id>,
name: "<opp.yaml.display_name> — Program Admin Audit"
)
SCRATCH branch (no audit template fits): author with
workflow_create per the workflow_authoring_guide — a meta-level
workflow whose render reads the watched LLO weekly review's saved-run
snapshots (it has no pipeline of its own).
Either way, wire the watched workflow:
mcp__connect-labs__workflow_update_definition(
workflow_id: <from step 7>,
opportunity_id: <synthetic.labs_opp_id>,
expected_version: 1,
patch: {
config: { watched_workflow_id: <llo_weekly_review_id from step 2> }
}
)
The audit workflow renders a week-over-week compliance dashboard from
its snapshot's watched_summary. Note: this config wiring is
definition-level metadata only — the build_snapshot hook does NOT read
watched sources from the config. The functional requirement is the
per-run initial_state set in step 8b (and the hook's key is
workflow_definition_id, whereas the config field is watched_workflow_id
/ watched_sources[].workflow_id — don't assume they're the same name).
-
Saved-runs progression — Week 1 + Week 2 snapshots.
Plan B's Task 3.1 calls for a Week 1 + Week 2 saved-runs progression
to make the demo show week-over-week deltas. The connect-labs MCP
shipped workflow_create_run + a fixed workflow_save_snapshot
(with opportunity_id scope) to support this loop programmatically.
Derive Week 1 and Week 2 windows from the manifest's timeline:
start = manifest.timeline.start_date
week_1_end = start + 7 days
week_2_start = start + 7 days
week_2_end = start + 14 days
Then for each week, do create-then-snapshot against the LLO weekly
review workflow. The audit's rollup is NOT free — despite the old
"it just reads the LLO review's snapshots" assumption, the audit's
watched_summary is built by a server-side hook that fires only when an
audit run is snapshotted, and reads its inputs from that run's own
state. Step 8b below creates it. (Root-caused live 2026-05-31 against
workflow 3448 — jjackson/ace#596.)
# Week 1
r1 = mcp__connect-labs__workflow_create_run(
definition_id: <llo_weekly_review_id>,
opportunity_id: <synthetic.labs_opp_id>,
period_start: <start>,
period_end: <week_1_end>,
)
mcp__connect-labs__workflow_save_snapshot(
run_id: r1.run_id,
opportunity_id: <synthetic.labs_opp_id>, # required since labs PR #168
snapshot_name: "Week 1",
captured_at: "<week_1_end>T23:59:59Z",
)
# Week 2 — same shape with bumped dates
Capture both run IDs and snapshot timestamps for the run summary.
⚠️ The two LLO-review snapshots currently render IDENTICAL (jjackson/ace#764).
The flw_kpis rollup aggregates all of the opportunity's visits and is
NOT period-filtered by the run's period_start/period_end (the labs
pipeline groups by completed_at, and save_run_snapshot freezes a
non-period-scoped aggregate). So Week 1 and Week 2 snapshots show the same
numbers. This is a labs-side root cause (not local; the pipeline must thread
period_start/period_end into flw_kpis as a visit_date filter).
Until it lands, do NOT market the two snapshots as week-distinct in the
walkthrough or polish narrative. Either (a) present them as "the same review,
run on a recurring cadence" without asserting week-over-week deltas, or
(b) point the "recurring rhythm" scene at a page that IS genuinely distinct
per period (the program_admin_audit window aggregate) rather than the
identical LLO-review weekly snapshots. synthetic-workflow-polish must keep
any week-referencing narrative conditional on real per-period difference —
see its SKILL.md note.
Per-week try/except: if Week 1 succeeds but Week 2's create or
snapshot fails (e.g. transient labs error), surface the partial
completion in the run summary and continue. Operator can re-run
/ace:step synthetic-workflow-seed to retry — but note that re-runs
create NEW workflows + duplicate runs (no idempotency on labs side
yet); operator should use workflow_delete to retire stale ones
before retrying.
Why Week 1 + Week 2 specifically: the program admin audit
workflow renders week-over-week LLO-process compliance from
multiple snapshots. Two weeks is the minimum for a "trend"; more
weeks add noise to the demo without meaningful narrative gain.
Operators wanting a longer run can edit the manifest's timeline to
N weeks, then call workflow_create_run + workflow_save_snapshot
N times via direct MCP calls.
8b. Snapshot the program admin audit run (REQUIRED — without this the
audit renders "0 opportunities watched" + an empty WINDOW AGGREGATE).
9. Write the run summary to
7-synthetic/synthetic-workflow-seed.md via drive_create_file
(find-or-update — re-runs overwrite). Body:
This skill is permanent. Saved-runs creation (step 8) was deferred to a
labs PR at first ship; that gap closed in 0.13.64 once labs shipped
workflow_create_run + the workflow_save_snapshot opp-scope fix
(connect-labs PR #168). No remaining [WAITING ON LABS] items.