Skip to main content

debug-orchestration

Diagnose and fix nondeterminism errors in the duroxide orchestration. Covers the replay model, common violations, and how to audit orchestration code for determinism.

Quellinformationen

Repository
microsoft/PilotSwarm
Letzte Quellaktivität
7. September 2026 um 17:59
Erkannte Sprache von SKILL.md
Englisch
Sterne
14
Forks
7

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
debug-orchestration
description
Diagnose and fix nondeterminism errors in the duroxide orchestration. Covers the replay model, common violations, and how to audit orchestration code for determinism.
# Debug Orchestration Nondeterminism If the issue is about live worker routing, session-affined activity placement, hydration/dehydration traces, or cross-pod runtime evidence rather than replay-safe code shape, use the sibling skill [`.github/skills/investigate-duroxide-runtime/SKILL.md`](../investigate-duroxide-runtime/SKILL.md). Duroxide replays the orchestration generator from the beginning on every new event. The generator must produce the **exact same sequence of yielded actions** during replay as during original execution. A mismatch causes: ``` nondeterministic: custom status mismatch: action=CallActivity { ... } vs event=CustomStatusUpdated { ... } ``` ## How replay works 1. Duroxide stores every action (yield) result in a history log. 2. On replay, it feeds stored results back into the generator at each `yield`. 3. It compares each yielded action descriptor with the stored history event. 4. If they don't match → **nondeterminism error** → orchestration fails. `setCustomStatus()` is **also recorded** in history as `CustomStatusUpdated`. It's fire-and-forget (no yield), but its position relative to yields matters. ## Diagnosis steps 1. **Read the error message carefully.** It tells you: - What the orchestration **tried to do** (`action=CallActivity { name: "X" }`) - What the history **expected** (`event=CustomStatusUpdated { ... }`) 2. **Find the divergence point.** The orchestration took a different code path during replay. Look for: - `Date.now()` used in a condition before a yield or `setCustomStatus()` - `Math.random()` or `crypto.randomUUID()` in control flow - Any I/O (fetch, fs, database) done directly in the generator - `setCustomStatus()` that moved relative to a yield 3. **Trace the yield sequence.** Number every `yield` and `setCustomStatus()` call in `orchestration.ts`. During replay, the Nth yield must match the Nth history event. ## Common bugs and fixes | Bug | Fix | |-----|-----| | `Date.now()` in condition before yield | Use `yield ctx.utcNow()` — deterministic, replay-safe | | `Math.random()` for IDs | Use `yield ctx.newGuid()` | | Direct I/O in generator | Wrap in an activity (`ctx.scheduleActivity()`) | | `setTimeout` / `setInterval` | Use `yield ctx.scheduleTimer(ms)` | | `setCustomStatus()` order changed | Ensure it appears at the same position relative to yields | | Redeployed with changed yields | Freeze the released handler and register a new orchestration version. Deploys never reset the database. | ## Deterministic alternatives | Non-deterministic | Deterministic equivalent | |-------------------|--------------------------| | `Date.now()` | `yield ctx.utcNow()` | | `new Date()` | `yield ctx.utcNow()` (returns ms, wrap with `new Date(ms)` if needed) | | `Math.random()` | `yield ctx.newGuid()` | | `crypto.randomUUID()` | `yield ctx.newGuid()` | | `setTimeout(fn, ms)` | `yield ctx.scheduleTimer(ms)` | | `fetch()` / any I/O | `yield ctx.scheduleActivity("name", input)` | ## The `setCustomStatus` trap `setCustomStatus()` doesn't require a yield, but duroxide records it as a `CustomStatusUpdated` event in the history. This means: ```typescript // Original execution: condition is false → no activity yield → setCustomStatus if (Date.now() > threshold) { // ← non-deterministic! yield someActivity(); // recorded as CallActivity } setStatus("idle"); // recorded as CustomStatusUpdated // Replay: condition is NOW true → activity yield → mismatch! // Replay expects CustomStatusUpdated but gets CallActivity ``` The fix: use `yield ctx.utcNow()` for any time-based branching. ## Runaway / leaky deployments A very common cause of nondeterminism is **old workers from a previous deployment still running** in the same cluster, connected to the same database. This happens when: - A previous deployment used a different Kubernetes namespace (e.g. `copilot-sdk` vs `copilot-runtime`) and was never cleaned up. - A rolling update left old ReplicaSet pods running alongside new ones during the transition. - Manual `kubectl run` or port-forward sessions left orphaned pods polling the same database. Old workers run **old orchestration code** but process orchestrations that were started (or replayed) by the **new code**. The yield sequences differ → nondeterminism error. ### Diagnosis ```bash # Check ALL namespaces for worker pods — not just the expected one kubectl get pods --all-namespaces -l app.kubernetes.io/component=worker --no-headers ``` If you see pods in an unexpected namespace or from a different ReplicaSet, that's the culprit. ### Fix ```bash # Delete the old deployment and namespace kubectl delete deployment copilot-runtime-worker -n <old-namespace> kubectl delete namespace <old-namespace> --wait=false # Reset the database to clear any tainted history NODE_TLS_REJECT_UNAUTHORIZED=0 node --env-file=.env.remote scripts/db-reset.js --yes # Restart current workers kubectl rollout restart deployment/copilot-runtime-worker -n copilot-runtime ``` ## After fixing 1. **Reset the database** — existing orchestrations have stale history: ```bash NODE_TLS_REJECT_UNAUTHORIZED=0 node --env-file=.env.remote scripts/db-reset.js --yes ``` 2. **Redeploy** — or use the full deploy script: ```bash ./scripts/deploy-aks.sh ``` ## Key files - [src/orchestration.ts](../../../src/orchestration.ts) — the orchestration generator (audit this for determinism) - [src/session-proxy.ts](../../../src/session-proxy.ts) — activity definitions (safe — activities run outside replay) - [scripts/deploy-aks.sh](../../../scripts/deploy-aks.sh) — deploy with DB reset - [scripts/db-reset.js](../../../scripts/db-reset.js) — standalone DB reset
Auf GitHub ansehen