# Programming Inference

> Transform raw Programming/Movement Source Sheet rows into Rubicon's programming write operations (create_node, patch_node, create_edge, patch_edge), applying conjugate-method coaching conventions and resolving movement IDs via movement_catalog_search. Use this as stage 2 of the Rubicon programming pipeline, after sheet-read and before preview.

- Skill: `joelhooks/programming-inference` (Agent Skill)
- Install (CLI): `npx skillmds@latest add joelhooks/programming-inference`
- Raw SKILL.md: https://api.skillmd.com/api/skills/joelhooks/programming-inference/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: joelhooks (https://skillmd.com/u/joelhooks)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/joelhooks/programming-inference

---


# Programming Inference

Stage 2 of 4 in the Rubicon coach programming pipeline:

`sheet-read` → `programming-inference` → `preview` → `publish`

Input: raw rows from the Programming Source Sheet and/or Movement Source
Sheet (from the `sheet-read` skill). Output: a batch of Rubicon programming
**write operations** — nothing is sent to Rubicon yet. That happens in
`preview` (dry run) and `publish` (real write), which come after this skill.

## Operation shapes

These are the real operation shapes accepted by Rubicon's Coach MCP Surface
write tools (`programming_write_dry_run` / `programming_write_publish`).
Copy them exactly — do not invent additional fields or rename these.

### `create_node`

```ts
{
  kind: "create_node",
  nodeId: string,
  type:
    | "program"
    | "track"
    | "session"
    | "wod"
    | "block"
    | "movement"
    | "movement_variant"
    | "benchmark"
    | "collection",
  title: string,
  status: "draft" | "published" | "archived",
  format:
    | "for_load"
    | "for_reps"
    | "for_time"
    | "rounds_reps"
    | "emom"
    | "amrap"
    | "interval"
    | "checklist"
    | "text"
    | null,
  scoreFormat:
    | "load"
    | "reps"
    | "time"
    | "rounds_reps"
    | "calories"
    | "distance"
    | "points"
    | "checkbox"
    | "text"
    | null,
  payload: unknown,
  sessionDate: string | null, // ISO date, or null
  slug: string,
  sourceId: string,
  sourceTable: string,
  trackId: string,
  weekNumber: number,
}
```

### `patch_node`

```ts
{
  kind: "patch_node",
  nodeId: string,
  patch: Partial<Omit<CreateNodeOp, "kind" | "type" | "nodeId">>,
}
```

### `create_edge`

```ts
{
  kind: "create_edge",
  edgeId: string,
  parentNodeId: string,
  childNodeId: string,
  position: number,
  role:
    | "contains"
    | "variant_of"
    | "source_movement"
    | "canonical_movement"
    | "max_effort"
    | "dynamic_effort"
    | "repetition_effort"
    | "accessory"
    | "conditioning"
    | "warm_up"
    | "activation"
    | "individual_prep"
    | "skill"
    | "finisher"
    | "recovery"
    | "test",
  metadata: unknown,
}
```

### `patch_edge`

```ts
{
  kind: "patch_edge",
  edgeId: string,
  patch: Partial<Omit<CreateEdgeOp, "kind" | "edgeId">>,
}
```

## Batching rule

**Max 25 operations per write call.** This is enforced server-side by the
Coach MCP Surface, not a suggestion. If a sheet import produces more than 25
operations, split it into multiple sequential batches (e.g. by week, by
session, or by track), and run each batch through its own
preview → publish cycle rather than trying to push everything at once.
Tell the coach how you split it and why.

## Resolving movement IDs — never guess

Before building a `create_edge` with role `source_movement`,
`canonical_movement`, `max_effort`, `dynamic_effort`, `repetition_effort`,
`accessory`, `conditioning`, `warm_up`, `activation`, `individual_prep`,
`skill`, `finisher`, `recovery`, or `test` that points at a movement, call
the `movement_catalog_search` MCP tool (Coach MCP Surface) to resolve the
canonical movement `nodeId`. This tool may be new / still being built in
parallel with this plugin — reference it by name and treat "tool not found"
as a signal to stop and tell the coach, not as license to fabricate a
`nodeId`. Never guess or hand-roll a movement `nodeId` from the sheet text.

Typical flow per movement mentioned in a sheet row:

1. Extract the movement name/variant as written in the sheet.
2. Call `movement_catalog_search` with that name (and any qualifiers like
   equipment or variant).
3. If there's a single clear match, use its `nodeId` for the edge.
4. If there are multiple plausible matches or no match, surface the
   ambiguity to the coach instead of picking one — a wrong movement mapping
   silently corrupts the programming graph.

## Conjugate-method coaching conventions

This section encodes how Chris's conjugate-method programming (Max Effort /
Dynamic Effort / Repetition Effort — standard, publicly documented Westside
Barbell / conjugate-system terminology, not proprietary or secret) maps onto
Rubicon's edge roles. This is intentionally public: unlike sheet identity
and credentials, coaching methodology is not something this plugin treats as
sensitive.

**Role mapping**

- `max_effort` — the max-effort (ME) exercise for the session: a near-max
  single (or low-rep top set) on a rotating main lift variation. Map the
  session's ME movement to an edge with role `max_effort` from the
  session/block node to the resolved movement node.
- `dynamic_effort` — the dynamic-effort (DE) work: sub-maximal loads moved
  at maximal bar speed, typically across banded/chained wave sets. Map to
  role `dynamic_effort`.
- `repetition_effort` — repetition-effort (RE) / hypertrophy-style accessory
  work taken to or near failure at moderate loads. Map to role
  `repetition_effort`.
- `accessory`, `warm_up`, `activation`, `individual_prep`, `skill`,
  `finisher`, `recovery`, `conditioning`, `test` — use these for exactly
  what they say; don't overload `accessory` for things that are really
  warm-up or conditioning work just because it's convenient.

**DE wave progression convention**

Dynamic Effort work is typically organized in short waves (commonly ~3
weeks) with a deliberate week-over-week progression across the wave:

- Week 1 → Week 3: intensity (percentage of max used for the dynamic sets)
  trends **up** (~+5% bar weight per week in Chris's programming).
- Week 1 → Week 3: target bar speed trends **down** slightly as a natural
  consequence of rising intensity — the athlete is still moving the bar as
  fast as possible for the load, but the load itself is heavier (Chris's
  velocity targets: 0.9 / 0.8 / 0.7 m/s).
- Week 1 → Week 3: set volume holds then drops slightly (12×2, 12×2, 10×2).
- The start cadence does **not** tighten across the wave. Chris currently
  prefers a constant 45-second start cadence; his older squat waves
  progressed 30s → 40s → 45s (looser, not tighter, as load rises). Speed
  pulls always run on 45-second starts.

When a sheet row encodes a wave number/position (e.g. "DE Wk 2 of 3"),
reflect that in `weekNumber` on the relevant node and make sure
percentage/interval values in `payload` are internally consistent with this
progression direction. If the sheet's numbers contradict this convention
(e.g. intensity going down week-over-week inside a DE wave), don't silently
"correct" it — flag the discrepancy to the coach; the sheet is the source of
truth for actual numbers, this convention is only a sanity check.

### Chris's confirmed conventions

Source: Chris's 8 Loom walkthroughs plus one Slack addendum, 2026-07-11.
Transcripts and per-video analyses (with timestamped quotes) are archived in
the rubicon repo at `imports/looms/2026-07-11/`. Everything below is stated
by Chris on camera or in Slack — nothing here is invented.

**Weekly template**

- Monday / Tuesday / Thursday / Friday are the heavy primary days: two
  upper-body and two lower-body days.
- Wednesday / Saturday are supplemental days: Olympic-lift technique work
  (light, position-first — ideally not another heavy exposure; even PVC
  work counts), gymnastics skill work, aerobic-flush intervals (rower,
  SkiErg, Echo Bike), occasional strongman/odd-object work (sleds, yokes,
  sandbags), and therapeutic movements (Turkish get-ups, ATG split-squat
  series, foot/ankle work).

**Session ordering**

1. General warm-up: 3–5 minutes of simple pulse-raising, then joint prep
   for the day's lifts (full ranges, not technical).
2. Specific warm-up: on primary days, 3 movement-specific activation
   exercises interleaved with progressive build-up sets, ending ~1–2
   build-up sets short of the first working weight.
3. Primary piece (ME, DE, or RE) immediately after the warm-up.
   - DE lower days always have **two** primaries: speed squat, then
     speed pulls immediately after.
   - RE upper primaries are followed by a pull/pull-up slot (strict, not
     kipping; a pull-up variation appears ~2–3 weeks out of 4; the slot
     may host chest-to-bar or muscle-up development for CrossFit athletes).
4. Accessories.
5. Finisher (including the week's midline work).

**Max-effort (ME) 1RM protocol**

- Default 7×1 building to the day's target: ~60%, 70%, 80%, 86–87%, 93%,
  97%, 100%.
- Lower-body clock: 0:00, 2:30, 5:00, 7:30, 10:00, 13:00, 16:30. Upper-body
  clock (less rest): 0:00, 2:00, 4:00, 6:00, 9:00, 12:00, 15:00.
- Athlete-dependent: stop at 6 sets on a bad day; allow an 8th for newer
  athletes establishing a max.
- Band tension generally 20–40%. Set a target the athlete can hit — no
  planned misses. Safety pins/spotters set up before lifting.

**Dynamic-effort (DE) lower**

- 3-week waves: 0.9 / 0.8 / 0.7 m/s, ~+5% bar weight per week,
  12×2 / 12×2 / 10×2, same scheme for speed squat and speed pull.
- Week 1 loading guidance: ~40% bar weight plus band tension.
- 45-second start cadence (see wave-progression section above).
- Bands/chains a majority of the time, with occasional straight-weight
  waves for pure bar-speed practice.
- Rotate the variation after each 3-week wave; pick DE variations that
  offset the week's ME pattern (e.g. Manta Ray back squat against a banded
  front squat ME) to avoid pattern overload. A practiced DE movement may
  later be tested as an ME, optionally after a week away.
- Chris's percentages run deliberately **below** published Westside
  recommendations — his athletes don't hit the literature numbers. Don't
  "correct" sheet loads upward toward Westside tables.

**Dynamic-effort bench (Slack addendum, 2026-07-11)**

- Bench is usually trained with the repetition-effort method, but DE bench
  cycles occur: 9×3, 9×3, 9×3 (sets/reps constant across all three weeks),
  on the minute, at percentages slightly lower than DE squat/pull.
- Grip rotation: week 1 wide, week 2 narrow, week 3 normal — or within a
  single session, 3 sets wide, 3 middle, 3 narrow.

**Repetition-effort (RE)**

- Primary RE is **not** to failure — failure work belongs in the finisher.
- Default progression: week 1 = 8×8, one constant load, sets every 2
  minutes, starting ~50–60% of 1RM (closer to the middle); week 2 = 8×8
  heavier; week 3 = 8×6 heavier again (48 total reps) — progressive
  overload inside the microcycle.
- RE spans a heavy 5-rep effort through a 20-rep max; don't hard-code one
  rep range as "the" RE range.

**Accessory sequencing**

- Accessories come after primaries, sequenced by "small fish feed medium
  fish, medium fish feed big fish" (e.g. rotator cuff → scaps → lats).
- First accessory complements the primary by shifting emphasis (heavy
  below-parallel front squat → posterior-chain RDL), second slot is
  single-leg work at 8–12 reps (step-ups, reverse lunges, split squats),
  third slot is usually higher-rep and loads the body differently (e.g.
  open-chain reverse hyper after closed-chain RDL).
- Upper-body accessories always cover back/lats and generally hit triceps
  hard; chest is already covered by the primary; delts/biceps sprinkled in.

**Finisher**

- Strategies: repetition effort to failure, very-high-rep (~100–200)
  super-low-intensity band/bodyweight flush work for joint-capsule blood
  flow, and isometrics (e.g. 3 sets single-leg hip-bridge iso; planks and
  plank marching count).
- Midline work lives here, split into three categories — erectors,
  abdominals, obliques — distributed across the week so all three get an
  effective dose. GHD hip extensions classify as posterior-chain midline.
- Upper-body-day finishers bias rear delts and rotator cuff to offset
  bench-pattern overload.

**Programming workflow (sheet mechanics)**

- Chris starts in the Conjugate Monthly tab (red lines = 3-week wave
  boundaries), uses it to audit trained vs. neglected patterns, then
  transfers into Conjugate Daily and adds per-lift bullet points.
- Primary and accessory pieces generally share the same 3-week format.
- Warm-ups (general and specific) are programmed **last**, after all
  primary/accessory/finisher details are entered.

### Open items — resolve from the sheet, not by asking

The sheet is the source of truth and Chris writes explicit numbers on it
every week. Do not ask him to recite values the sheet archive already
records (his spoken recall can contradict his own written sheets — see
the DE cadence correction above). Derive these from sheet/prod history
and record the answer here with a receipt:

- Exact DE bench percentages (read off the next DE bench cycle).
- DE lower week-1 loading and band range (transcript garbles
  "15, 20 to 40" — actual loads are on the sheet).
- Accessory set counts and the third-slot rep range (prescriptions like
  "4x6 - OT2:15-2:30" appear verbatim in blocks).
- Whether finishers follow the 3-week wave (diff finisher blocks across
  a wave).
- Whether constant 45s DE cadence supersedes 30/40/45 (compare recent
  vs. older waves).
- ME set 4 86 vs. 87: moot — encode 86–87%, midpoint, round to plate
  increments. 1% at set 4 is below barbell resolution.

### Resolved intent rules (Joel, verified against transcripts where noted)

1. **The denominator — differs by method.**
   - **ME:** percentages reference **that day's target weight**, not a
     stored 1RM. The transcript is explicit: "we set a target so you
     can see we have a hundred percent target there... set five 93
     percent... and then the target weight would be the final set"
     (max-effort Loom [00:00]). Sets 1–6 scale to the target; set 7 IS
     the target. Prefill math: `set_pct × day_target`.
   - **RE:** percent of 1RM — "roughly between 50 and 60 of your one
     rep max" (repetition-effort Loom [00:00]).
   - **DE:** percent of 1RM — bar-weight recommendations are "based on
     your one rep max" (dynamic-effort Loom [04:01]).
2. **Autoregulation: mark the miss and move on.** No in-session redo,
   no plan mutation. The program is built to avoid failure and ride the
   edge of effort; misses get logged as failed and the session
   continues. Transcript support: ME builds abort early when the day
   isn't there ("sometimes it's not going right so you finish on the
   sixth set"), and RE week 1 is deliberately set so "you don't want to
   miss your set." App implication: record failed sets, don't
   auto-adjust the prescription.
3. **After week 3: start a new wave (rotate the movement).** No
   programmed deload week. Optionally the coach tests the trained
   movement in week 4 or 5 — sometimes after a week away from it
   ("almost like a little deload week from the movement") — but it's
   explicitly coach's choice, "not always... just kind of depends"
   (dynamic-effort Loom [02:01]; also "after the three weeks we switch
   it up" [06:05]). The RE "four format" is transcription garble: only
   3 weeks exist; "two to three weeks out of four" [02:03] describes
   how often the post-RE slot is a pull-up, not a 4-week wave.

4. **ME day-target: a conversation, not a formula (Joel 2026-07-12).**
   The target is set by coach–athlete chat, anchored to recent
   performance on that variation ("around where we were") and adjusted
   by same-day feel — a "lift for the day," biased toward NOT failing.
   "There's always tomorrow" is the operating vibe. App implication:
   prefill a *suggestion* from the athlete's last exposure to the
   variation, but the target field is athlete-owned and editable —
   never auto-escalate it, and never treat a conservative target as an
   error.
5. **DE/RE denominator: "the most obvious 1RM," resolved along the
   movement graph (Joel 2026-07-12).** The reference 1RM for a
   percentage falls back from the specific variation (with mods, bands,
   specialty bars) toward the core lift until a known max exists —
   e.g. manta-ray back squat w/ bands resolves up to back-squat 1RM if
   the variation has never been tested. App implication: implement
   denominator lookup as a walk over movement-family edges (rubicon's
   `movementFamily` data) from specific → core, taking the nearest
   ancestor with a recorded 1RM. A stale core-lift 1RM makes every
   prefill run hot for an athlete trading strength for engine —
   recency-weight or flag old maxes rather than blindly using all-time
   PRs.

All questions from https://rubicon-loom-followups-2026-07-11.wzrrd.sh/
are now resolved. Band-tension measurement basis (top/bottom/vs. what)
remains parked until the app ever recommends band setups — it currently
doesn't.

## Handoff

Once the operation batch (≤25 ops) is built, hand off to the `preview`
skill to run `programming_write_dry_run` before anything is written.

