One platform, two ways in. The science lives in the engine and library; shared product logic lives once in services; Fox (people) and Machines (AI agents) are thin surfaces on top. The work is nine numbered workstreams; each item is a ticket in the Cortex Delivery board. The goal running through all of it: thinner surfaces, a fatter shared middle, every capability built once.
Waiting on you live · labelled needs:david
Sequence & dependencies
live lanes
9/11 done
11/19 done
starts once Surveys reaches its executor (3.5)
Build alongside the spine; only the job-store swap (2.3) waits (for 1.3 + 3.5).
Nothing blocks these; they run whenever there is a slot.
Computed from the tickets this run — every item currently Ready, In Progress or In Review.
7.3 analysis blocks · 8.2 v6.1 · 8.6/8.7 drivers + Likert — need Matt's rulings (8.4).
Gantt · effort & sequence T-shirt sizes · live
The work — by workstream active shown · done in Landed
Objective: Stand up the guardrails and coordination so the build runs safely, in parallel, without rotting.
Needs: Unlocks the whole critical path. These are live lanes, not board tickets.
Objective: Give the platform a real memory — one production database for accounts, keys, assets and results.
Needs: Store ruled (D-021, production). 2.3 waits on 1.3 + 3.5. · epic #647
staff_role → unlocks the 2.7 admin write-UI (RT-J #7 prerequisite).Objective: Prove the build-once model — move the first real feature into shared services, thin on both surfaces.
Needs: Needs the 1.1 gate + 1.3 job seam; not the data platform. Spec 3.1 ruled. · epic #648
studies/_shared/jsnum.py, each annotated with the client line it mirrors and the re-baseline that retires it; #947's register lists them.select_participants and stamp_provenance as shared functions that each executor calls in place of reading client-chosen member_refs; each executor keeps its own order of operations. The pipeline (the study kernel) is not this ticket: it is filed at re-baseline R2 when the goldens are re-cut anyway.POST /v1/studies/{type}/runs taking a population pin, an audience definition, a study configuration and parameters, served by the same executors; the v1 job kinds untouched until each client runner retires (#931); the Machines tool (#613) can use v2.Objective: Let an AI agent commission a survey end to end — the first Machines proof of concept, internal only.
Needs: Branches off the spine once Surveys reaches its executor (3.5). Delta ruled (D-022). · epic #649
Objective: Close the honesty and cleanup gaps in the Fox app so demos and pilots are trustworthy.
Needs: No gate — runs anytime, full-auto to verified PRs. · epic #650
engine-photograph files in 04-sales-demo/david/workspace/data/baked/ (bulk_cache.json, subject_profiles.json, competitor_profiles.json, creative_review_camden.json, survey_camden.json, creative_review.json) either regenerated from the live service at the current pin over the new population, with MANIFEST.json provenance updated and the bake_*.py scripts pointed at the service, or removed with the screens that read them shown an honest empty state. The three fixture-content files stay.Objective: Build the shared plumbing both surfaces rely on — the AI gateway, accounts, a real release path.
Needs: 6.1 rides the Surveys path; 6.3 is independent and unlocks retiring the old VM. · epic #651
sample_cap (250) and study_min_sizes from 04-sales-demo/david/workspace/data.json become service-side parameters with defaults in app/config.py, returned to the client through the existing config or status route, and overridable per study submission within a service-enforced ceiling; the service executors read them from one place (app/survey/sampler.py SAMPLE_CAP, sensitivity's 80).03-bis-service/app/gateway/: the task registry grouped into features with a default model per feature; policy resolution platform → feature → task → surface → account; an asynchronous OpenRouter client that sends models fallbacks and provider preferences and reads the answering model, generation id and cost; a global in-flight cap with per-feature allowances and a bounded queue; POST /v1/llm/batch fan-out in input order; one provenance row per call with model_requested, model_reported, provider_response_id, config_hash, version_observable = true. Design: docs/coordination/scopes/621_LLM_GATEWAY_2026-09-05.md §3.survey/voicing.py's voice_quote_for_participant and analyst_read_for_study call the gateway's in-process function instead of provider.complete, with the model pinned (VOICING_MODEL) and the tasks marked pinned in the registry.generate_market_insights, read_news_cultural_snapshot and read_socials_cultural_snapshot registered with class search, model perplexity/sonar, running as llm_task jobs with the poll route (45 s class does not fit the sync request budget); Fox's /api/llm forwards them under the llm_via_gateway flag.survey/executor.py:215) and the interview executor's per-participant calls dispatched through the gateway's batch fan-out with the feature's parallelism, results assembled in input order.TASK_ENDPOINT_CLASS generated from GET /v1/llm/tasks and pinned by a test.POST /v1/llm/ingest/{describe-image|scrape|youtube} on the service; the three Pages functions become forwarders with the server-held key and a typed 503 when unconfigured.generate-image forwards to POST /v1/llm/images (stays app-triggered, loses its private key); genericCompletion removed from functions/api/llm.js; its two callers (legacy-inputs-panel.ts, inputs-flows.ts) get registered tasks.gateway.model_policy (append-only, audit row per change), PUT /v1/admin/llm-policy, and the AC admin console's LLM and costs panel showing the effective policy and its config_hash; the engine domain read-only there.--role api and --role worker; the API serves requests and never runs a study or loads a population beyond the profiles sidecar for the count; one worker claims job rows with a lease on the SQLite store; the fatal box verify checks both containers; in Fox, the async overall budgets (120 s single, 180 s bulk in engineAsync.ts) replaced by poll-until-done with a per-poll timeout and an elapsed indicator.work.jobs on Postgres with SELECT ... FOR UPDATE SKIP LOCKED, N workers configurable, the lease reaper across workers, the SQLite store retired.api_keys.expires_at and a sweeper; a keys:mint scope that can only issue short-lived humans-surface keys; the Pages proxy mints one per workspace session bound to the Access-authenticated email and the claimed active account (recorded as a claim until memberships exist), and attaches it to every engine and study call; keyed twins on /v1 for the m2, m3 and m4 analyse routes and their async forms; a flag per route with rollback to the keyless path; a humans-surface default budget.app/policy.py: limits, budgets, caps and model policy in one in-process, hashed snapshot keyed by surface and account, reloaded on change, built first as a facade over config.py so there is one authority; #960's caps join it; config.py keeps deployment settings only.d1_import.py (2,462 lines, the largest module in the service) moves to a tools/ package the app does not import, with its job kind kept through a thin adapter or removed after the migration; cache_rewarm runs under the worker role only.Objective: Grow the product by pulling each study type into shared services, reusing the Surveys machinery.
Needs: Needs the Surveys machinery (3). 7.3 also needs Matt (8.4). · epic #652
results_hash equality (or the STOP) is recorded on #630 as a checklist row with the run date, the service build, the population id and hash.CohortDetailScreen.tsx (and wherever a service count replaces a client estimate), a guard: if the service count contradicts the client's own estimate by a wide margin (for example the service says 0 where the estimate says thousands), the UI shows both numbers with an honest label instead of silently replacing the estimate; Big-Five boundary filters allow a small tolerance (one to two per cent; #902 measured 2651 versus 2652) so the guard does not fire spuriously.rebuild/src/logic/clustering.ts (pickClusterCount, doClustering, autoClusterAudience, backfillMissingClusters) ported to a service job kind (audience_cluster or a step of the selection module) as a photograph: same algorithm, same seed handling, golden-locked to a frozen oracle from the client, with a known_defects list. The Fox screens that cluster (ClusterIntoPersonasDialog, CohortDetailScreen, focus-group and interview configs) call it behind a flag.Objective: Engine & science — the 100k population, the capability workshop, and Matt's handover items.
Needs: 8.1 is urgent and runs now; most rows are Matt-gated. · epic #653
data/population.json regenerated from the subsample's rows (same rows as the service's profile photograph), HOSTED_POPULATION_ID, HOSTED_POPULATION_HASH and HOSTED_POPULATION_VERSION in rebuild/src/logic/engine.ts moved, getAudiencePopulationId() in population.ts moved, the three hard-coded 10,000 denominators found in SAMPLING_100K_OPTIONS_2026-09-04.md made data-driven, the frozen-file re-baseline (--write-baseline with David's own intent sentence), and the catalogue cases that pin known answers (TC-AUD-26's 5,053) re-pinned deliberately.uk_100k_v6 (a one-off seeded draw raked to the census marginals or the blessed marginal design, verified against the 100k with spike S2's protocol; never stratified_persona_indices, never even-stride); (2) the Σ_pop rebuild against the subsample with a condition-number assert as a gate (Matt: the last wrong Σ_pop was near-singular at 4.8e7 against 45); (3) the favourability-head build script, which is not in the repo (question 4 on the Matt list).bislib/engine/m4/narratives.py (_call_anthropic), moved out: the service owns narrative generation through its provider seam (the compat-M2 provider and budgets), the library's M4 becomes fully deterministic, and the facade signature drops the narrative side effect. Segment narratives keep their current wording via the same prompts, now on the service.docs/coordination/BRIEF_MATT_SAMPLED_VECTORISED_SERVING_2026-09-04.md: the data layout and the array contract (memory-mapped population, the statistics artefacts, how a sample is addressed), the sampler specification, the parity and tolerance policy with the σ protocol as its first step, the engine-to-service contract, the migration path and re-baseline plan, effort and sequencing, and the split between what Matt builds and what we build. Slice 1 is the array-in facade (react and analyse_population taking arrays, not List[HSV]).02-bis-lib/tests/parity_v5/evidence/ with the numbers.app/populations/ owning identity (id, hash, version), storage (curated, bespoke, subsample), the profiles photograph and the caps; registry.py, bespoke_registry.py, the resolver and records, and the profiles build in audiences/count.py fold in; cache_rewarm's keyset providers use it.Objective: Security baseline, dependency debt, and pre-customer licensing validation.
Needs: 9.1 sweep can start now (Codex); 9.2 has David's go. · epic #654
service status check (workflow service-ci, job name service) added to ruleset 18742520's required checks, and .github/protection-baseline.json updated to match so protection-drift stays green. Consider verify_v5 (bis-v5-verify) in the same change.Objective: Operational, board and CI/bookkeeping tickets — outside the 1-9 delivery spine.
Needs: No delivery dependency; kept homed for the audit trail, not a workstream of work.