simulated-execution
Run N backdated weeks of a program with no real LLO: synthetic visits, weekly stock and reviews.
SKILL.mdskills/simulated-execution/SKILL.md
GitHubSimulated execution
Runs a program's delivery for N simulated weeks after the award, with no real LLO involved. The
visits are synthetic, the week's stock moves through labs supply chain, labs saves one report run
per week, and each week gets a review an LLO and a program manager could act on. Everything is
dated in the past (lib/sim-calendar.ts), because labs has no clock to move.
In /ace:run this is the optional simulate_weeks step, run by agents/execution-manager.md
§ Simulated mode, Phase 9 mode: simulated only (spec
docs/superpowers/specs/2026-10-18-optional-labs-e2e-steps.md § G). It is OFF by default.
It never contacts anyone. No email, no Connect invite, no llo-onboarding, no Connect
opportunity mutation. The visits live on a labs-only opportunity filed under the ACE winner org,
never on the run's real Phase 4 opportunity.
The bar is what a viewer sees, not what the files say (owner, 2026-10-21, ace#3265). A
simulated delivery is done when labs' own pages show a supply chain working: stock leaving the
central store, workers holding phones between visits, handovers labs read off the visits, a map,
and a programme, partner and worker report to open. connect-fi/20261018-1440 passed every file
check while 44 of 814 phones moved and every worker sat at 0; simulated-execution-qa's
supply_in_motion and downstream_reports checks now fail that.
One synthetic programme holds everything. The labs-only opportunity is its own programme
(program_id = opportunity_id), and the whole supply chain (supply-chain-setup, -procure,
-fulfil, -operate) is built on THAT programme id, never on the run's Connect program. Labs
reads visits into a worker's stock only when the visits' opportunity and the supply chain are on
one registered synthetic programme (connect-labs supply_chain/scopes.py opportunity_problem);
across programmes a dispensing rule is refused ("opportunity 10168 belongs to programme 10168,
not 361", ace#3265). That is why this skill's Steps 1-3 run first, at Phase 9 S0b, before
supply-chain-setup: they create the programme the supply chain is built on.
Inputs
| Parameter | Used for | Resolver |
|---|---|---|
calendar |
week 0 + delivery weeks | explicit arg; default: run_state phases.execution-management.products.simulation.calendar (written by the agent's S0 from scripts/sim-calendar.ts) |
weeks |
N | explicit arg; default: run_state optional_steps.simulate_weeks |
winner_org_name |
the org the labs-only opportunity is filed under | explicit arg; default: the display name of connect_orgs.llo_org (selected_llo.org_slug) |
program_name |
the labs-only opportunity's program name | explicit arg; default: the PDD's program title |
template_workflow |
the semantic-snapshot report cloned for the weekly runs (the programme report) | explicit arg {workflow_id, program_id}; default: run_state phases.synthetic-data-and-workflows.products.synthetic.cascade.programme_report. Never cascade.worker_review for the weekly history: it is a drill-down template with no snapshot contract, and workflow_history_eligibility answers it template_not_saved_runs (ace#3263) |
partner_template, worker_template |
the partner (opportunity) report and the worker review cloned beside it, so a viewer can go programme → partner → worker | explicit args; default: the first role: opp_report dashboard of run_state products.synthetic.source.dashboards and products.synthetic.cascade.worker_review |
geography |
where the workers and stores are, so Map draws them | explicit arg {country, admin_area, centers: [{lat, lon, label}]}; default: the PDD's delivery area, geocoded once to its public coordinates (district or village centroids) and cited in the plan. Never invent a place the PDD does not name |
float_weeks |
how many weeks of handovers a worker carries | explicit arg; default: the PDD's stated worker stock, else 2 (a simulation assumption the plan and every review state as such) |
registry_eval_verdict |
the registry gate of scripts/run-labs-synthetic-generate.ts |
explicit arg (a local file); default: a local copy of the run's 7-synthetic/semantic-registry-author-eval_verdict.yaml |
pdd_targets |
what each week is judged against (ID, SIM, phone, wallet funded) | explicit arg; default: the PDD's Performance Measures section (run_state phases.idea-to-design.products.pdd) |
supply_chain |
whether each week also runs supply-chain-operate |
explicit arg; default: run_state optional_steps.supply_chain |
out_folder_id |
where the files go | explicit arg; default: the run's 9-execution-manager/ folder |
Outside a run, pass every parameter; nothing reads or writes run_state.yaml.
Products
9-execution-manager/simulated-execution_plan.yaml: the window, the synthetic-data manifest (whose persona ids are the setup plan's FLW usernames, so distributions map to workers 1:1), and the per-week enrolment the PDD implies. Written before any write.9-execution-manager/simulated-execution_issue-events.yaml(withsupply_chain): per commodity, itsissue_event, itsissue_events_sourceand the notederiveIssueEventsreturned, and per delivery week and worker the issue events and the quantity they give. What each week'sweek_activityis.9-execution-manager/simulated-week-<w>_review.md: one per delivery week, bysimulated-weekly-review.9-execution-manager/simulated-execution_summary.md: links every weekly review, the labs report run for each week, and the supply-chain programme pages.9-execution-manager/simulated-execution_supply-motion.yaml(withsupply_chain): labs'stock_flowandworker_stockreads as of the last delivery day andassessSupplyMotion's verdict (lib/sim-supply-motion.ts): whatsimulated-execution-qagates on.- run_state
phases.execution-management.products.simulation:synthetic_opportunity_id,program_id(= the opportunity id: labs files it as its own programme),workflow_id,reports: {programme, partner, worker}(each{workflow_id, url}),weekly_reviews[](the calendar was written by the agent's S0). Each id is written the moment its create call returns, because a re-run would otherwise create a second copy (ace#2412).
Process
Phase 9 runs this skill in two halves: Steps 1-3 at S0b, before supply-chain-setup, because
they create the programme the supply chain is built on; Steps 4-9 at S4, after week 0's
procurement and fulfilment.
Preconditions. Calendar present and valid (
assertSimCalendar);selected_llo.synthetic_winner: true. Any miss halts with[BLOCKER]naming it. (At S4, additionally, withsupply_chain,supply-chain-procure-qaandsupply-chain-fulfil-qabothpass: never start week 1 on a broken week 0.)Plan (first Products file). The manifest's
timelineisdeliveryWindow(calendar), and visit cadence follows the PDD's enrolment target spread over N weeks. Plant one under-performing worker so the reviews have something to act on. Three things the viewer depends on, each stated in the plan:- Geography. The manifest carries
geography(persona_centersat the plan'scenters, one per worker's area), so every visit has GPS and labs places each worker on Map from their visits. Without it Map draws nothing real (connect-fi's manifest had none). - Handovers at the PDD's rate. The issue field (the commodity's
issue_event.field, e.g.form.phone_screen.phone_obtained) is authored so that abouteligible_shareof beneficiaries reach a "handed over" answer by their later visit inside the window. Check the authored pool before generating: count the beneficiaries whose visits carry a handover answer and compare witheligible_share × beneficiaries with a later visit. A pool where nearly every answer isnot_yetsimulates a programme that never hands anything over (connect-fi: 40 of 400 adults in four weeks against a share of 0.55). - A stated quantity. Where the Deliver app records how many were handed over, author that
field too (a number), so the dispensing rule can read an exact
statedline rather than an estimatedprotocolone.
- Geography. The manifest carries
Create the labs-only opportunity (= the simulation programme), then generate the visits into it once, for the whole window (the C1 pattern of
demo-data-setup; ace#3263).synthetic_create_labs_only({label: "<opp> simulated delivery", gdrive_folder_id: 'pending-generate', org_name: <winner_org_name>, program_name, notes: "<sweep handle: opp/run id>", allowed_domains: ['@dimagi.com', '@dimagi-associate.com', '@dimagi-ai.com']}), withprogram_idUNSET so the opportunity is its own programme. Writeproducts.simulation.synthetic_opportunity_idANDproducts.simulation.program_id(the same id) the moment it returns (ace#2412). Without ACE's own domain inallowed_domains, ACE cannot read what it just created, and labs refuses to declare a runner on it.Generate with the plugin's own script, which reads the manifest off disk, so the archived manifest is the one labs received:
ACE_ROOT="${CLAUDE_PLUGIN_ROOT:-$(python3 -c "import json,os; d=json.load(open(os.path.expanduser('~/.claude/plugins/installed_plugins.json'))); print(d['plugins']['ace@ace'][0]['installPath'])")}" node "$ACE_ROOT/node_modules/tsx/dist/cli.mjs" "$ACE_ROOT/scripts/run-labs-synthetic-generate.ts" <plan's manifest> --opportunity-id <synthetic opp> --registry-eval-verdict <registry_eval_verdict>Record labs' returned
record_countsverbatim in the plan, never the manifest's authored totals: on connect-fi/20261018-1440 the generator kept 1,557 of 1,614 authored visits.
Generate once. Re-generating into an opportunity labs has already read gives the visits new ids, and the dispensing rule then hands every phone over twice; a second generation needs a fresh opportunity. Never generate against the run's real Phase 4 opportunity:
synthetic_generate_from_manifestregisters its target opportunity as synthetic.synthetic_clone_to_labs_onlyis not part of this step.Phase 9 now runs S1-S3 (
supply-chain-setup,-procure,-fulfil) withprogram_id=products.simulation.program_id, then returns here at Step 4.Issue events and top-ups (with
supply_chain; ace#3216, ace#3265). Read the generateduser_visits.jsonfrom the fixture folder the generator returned (drive_read_file), and for each commodity in the setup plan callderiveIssueEvents({commodity, issue_event, visits, calendar, seed: <manifest random_seed>})(lib/supply-chain-issue-events.ts). Then plan what each worker is ISSUED withplanWorkerTopUps({issues, weeks: N, float_weeks})(lib/sim-supply-motion.ts): each week the LLO issues that week's handovers plus what brings the worker back to their float (two weeks of their average handovers), so stock leaves the store every week and every worker holds phones between visits. Never withhold a top-up to plant a stockout: labs' dispensing rule takes every handover off the worker's stock from the visits, so a withheld top-up drives the ledger negative. The planted stockout is a worker's self-reported zero count (supply-chain-operateStep 5), which labs reads as variance against the ledger. Write the second Products file with both: per week and worker the issue events, their quantity, and the plannedtop_up.- Mirror-mode visits carry form identity, so a
form_answerissue event is read off the visits (source: form_answer_visits). Labs' de-novo stream merges every form into one schema and carries no form identity; for that case the helper generates the handovers from the PDD'seligible_share(source: simulated_from_eligibility), and the summary and every review say so. With noeligible_shareit throws, and the step halts with[BLOCKER]quoting it.
- Mirror-mode visits carry form identity, so a
The reports a viewer opens: programme, partner, worker. On the synthetic opportunity, clone each with
workflow_clone({source_workflow_id, source_program_id, target_opportunity_id: <synthetic opp>, linked: true, new_name: "<opp> simulated <role>"})and writeproducts.simulation.reports.<role>the moment each returns:- programme from
template_workflow(this is also the weekly history workflow,products.simulation.workflow_id); - partner from
partner_template(anindicator_opp_report: the LLO's own view with its workers' table); - worker from
worker_template(indicator_worker_review: the drill-down a worker row opens). A clone of it comes UNBOUND (registry.source: "disk", labs' on-diskkmcregistry), so bind it withworkflow_update_definition({patch: {registry_source: {registry_id, program_id}}})and re-read. Then create its one run (workflow_create_run, the window as its period) and point the programme and partner reports'config.worker_reviewat{workflow_id, run_id}of the clone; a clone keeps the SOURCE's, so worker rows would otherwise open Phase 7's workers (connect-fi rebuild, 2026-10-21). A linked clone keeps the registry binding and pipelines, and also the SOURCE cascade's opportunity list and description. So repoint each:workflow_update_opportunity_ids({workflow_id, opportunity_id: <synthetic opp>, opportunity_ids: [<synthetic opp>], expected_version}), withexpected_versionfromworkflow_get, then replace the inherited description (workflow_update_definition, the nextexpected_version) with one naming the simulated delivery. Without the repoint,workflow_preview_as_ofcaches and grades the source cascade's opportunities (connect-fi: 10163..10167). Thenworkflow_history_eligibilityon the programme report must say eligible; if not, halt with[BLOCKER].
- programme from
Save one run per week of the programme report (and of the partner report when
workflow_history_eligibilitysays it is eligible).workflow_preview_as_of({definition_id, opportunity_id, as_of: week1.end})first: ifcache.partial_cacheor every count is 0, callworkflow_ensure_visit_cacheand retry, never build history over a cold cache. Thenworkflow_rebuild_history({definition_id, opportunity_id, cadence: "weekly", start: week1.start, end: weekN.end, limit: N}); whiledoneis false call again withstart = next_startand the sameend; follow ajob_idwithjob_status. (The worker review keeps no saved runs; its one run was created in Step 5.)The weeks. With
supply_chain, in two passes, because labs reads the visits into the ledger on an hourly beat and there is no on-demand trigger over MCP:- Write every week (
supply-chain-operateinmode: write, w = 1..N, in order): transfers, the planned top-ups, counts. - Turn on the dispensing rule, then wait for labs to read the visits
(
supply-chain-operate§ Dispensing rule). Its stock reads are wrong until it has: a worker would show what they were issued and nothing handed over. - Read every week (
supply-chain-operateinmode: read, w = 1..N), then its QA. A failed week halts the loop. Then, for each week,simulated-weekly-reviewandsimulated-weekly-review-evalunless--no-evals.
- Write every week (
Is it moving? Read, as of the last delivery day,
supply_chain_stock_flow({program_id, item_id}),supply_chain_worker_stock({program_id, item_id, opportunity_id})and each store'ssupply_chain_supply_point_get, plus each week-endworker_stockfrom the week files. RunassessSupplyMotion(lib/sim-supply-motion.ts) and write the third Products file. A failing verdict is a defect to fix before the summary, not a residual to list: its problems name the cause in the order to check it.Summary (last Products file): link every weekly review, every report (programme, partner, worker) and the labs supply pages a viewer should open, each as
https://labs.connect.dimagi.com/supply/<page>/?program_id=<program_id>: Overview (overview/), Map (map/), Workers (workers/), Where it went (flow/), the order (orders/<id>/) and the tender (procurement/tenders/<id>/). Then writeproducts.simulation.weekly_reviewsandreports, dispatchsimulated-execution-qa, and capture the pages withoutput-preview-captureso the run page shows them.
MCP Tools Used
- connect-labs:
synthetic_create_labs_only,workflow_clone,workflow_get,workflow_update_opportunity_ids,workflow_update_definition,workflow_history_eligibility,workflow_preview_as_of,workflow_ensure_visit_cache,workflow_rebuild_history,workflow_create_run,job_status,workflow_history_runs,supply_chain_stock_flow,supply_chain_worker_stock,supply_chain_supply_point_get - ace-gdrive:
drive_create_file,drive_read_file(the fixtureuser_visits.json),update_yaml_file(merge: deep) - Scripts:
scripts/run-labs-synthetic-generate.ts(callssynthetic_generate_from_manifest) - Skills:
supply-chain-operate,simulated-weekly-review,output-preview-capture - Helpers:
lib/supply-chain-issue-events.ts(deriveIssueEvents),lib/sim-supply-motion.ts(planWorkerTopUps,assessSupplyMotion)
Existing objects
- The weekly runs are replaceable; the supply-chain records are not. Re-running Step 5
replaces only the runs
workflow_rebuild_historyitself made, with new run ids: relink the weekly reviews afterwards. Stock movements cannot be deleted (spec § F6); a re-run of a week replays bysource.ref. - Never simulate on a program ACE does not own, and never on the real Phase 4 opportunity.
Mode Behavior
- Auto: Steps 1-3 at S0b, Steps 4-9 at S4.
- Review: shows the Step 2 plan and waits before Step 3.
Dry-Run Behavior
Steps 1-2 and Step 5's eligibility read only. Write the plan; create nothing in labs. (Step 4 needs the generated visits, so a dry run writes no issue-events file.)
Change Log
| Date | Change | Author |
|---|---|---|
| 2026-10-21 | One synthetic programme holds visits AND supply chain (Steps 1-3 move to Phase 9 S0b, before supply-chain-setup); manifest carries geography and handovers at the PDD's share; workers carry a float (planWorkerTopUps); a dispensing rule reads the handovers (write-all, rule, read-all); programme + partner + worker reports; assessSupplyMotion gate; summary links the supply pages (owner audit of connect-fi/20261018-1440, ace#3265) |
ACE |
| 2026-10-19 | template_workflow defaults to cascade.programme_report (the worker review is never history-eligible); Step 3 creates the labs-only opportunity first and generates into it, never into the real Phase 4 opportunity; Step 4 repoints the linked clone at the synthetic opportunity (ace#3263) |
ACE |
| 2026-10-19 | Step 3a: per-worker, per-week issue events from each commodity's declared issue_event drive the week's distribution (week_activity), generated from the PDD's eligibility share because the synthetic stream carries no per-form visits (ace#3216) |
ACE |
| 2026-10-18 | Initial version (optional-track PR10): backdated N-week simulation on a labs-only copy under the ACE winner, weekly stock and saved report runs, one review per week | ACE |