Fix #2870: emit canonical 'dependencies' in slice scaffold; warn on dropped slice keys - #2872
Conversation
…nknown slice keys The plan→implement contract populator silently dropped the slice dependency chain whenever the architect scaffold expressed ordering via ``parent_slice_id``. ``plan_parser`` reads slice edges only from ``dependencies`` (or the ``depends_on`` alias, #2743) and never ``parent_slice_id``, so multi-slice linear chains parsed to all-roots and ran concurrently — guaranteeing integration-branch conflicts for overlapping slices. Root cause: #2779 (2026-05-22) settled ``dependencies`` as the canonical single-parent key and forbade ``parent_slice_id`` in ``yaml-tasks.schema.json`` (``additionalProperties: false``). #2821 (2026-05-27) then introduced the architect slice scaffold and told the architect + task_planner prompts to emit/preserve ``parent_slice_id`` — a key the parser, schema, and even the task_planner's own worked example do not use. This is that prompt-side drift. Fix (two layers): - Align the prompts back to the canonical vocabulary: the architect scaffold and task_planner copy instructions now emit ``dependencies: slice-<N>`` (omit for roots), matching the schema, the parser, and the task_planner Slice-DAG worked example. No schema or parser vocabulary change — ``parent_slice_id`` stays out, as #2779 intended. - Make the next drift loud, not silent: ``plan_parser`` now emits a ParseWarning when a slice carries a key outside the set it consumes. The schema already encodes this rule but is only enforced in tests, never at parse/populate time — so an unrecognized key (e.g. a future stray ``parent_slice_id``) would otherwise vanish with its data. Tests: parser warns on ``parent_slice_id`` while still documenting the drop; no false-positive on the full known-key set; prompt tests assert the scaffold emits ``dependencies: slice-1`` and no ``parent_slice_id``.
There was a problem hiding this comment.
Approve — fix is correct and works end-to-end
I traced the full plan→implement chain and the fix closes #2870 on the happy path:
architect prompt now emits dependencies: slice-1 → task_planner copies it verbatim → parse_phases_from_yaml reads dependencies → to_contract_slice normalises "slice-1" → ["slice-1"] → runtime parent_slice_id = slice_obj.dependencies[0] (orchestrator/routes/pipelines.py:2524) → SliceScheduler builds DAG edges from slice.dependencies. The prompt vocabulary now agrees with the parser, the schema (.egg/schemas/yaml-tasks.schema.json), and the runtime — no more silent edge drop.
Verified:
_KNOWN_SLICE_KEYSexactly matches the schema'ssliceproperties (id, name, goal, dependencies, serialized_chain_order, exit_criteria, tasks) plus thedepends_onalias (#2743). No missing/extra key → no false positives.- The new
ParseWarningis actually surfaced, not collected-and-dropped:_populate_contract_from_planlogs everyresult.warningsentry unconditionally atorchestrator/routes/pipelines.py:20109-20115— i.e. in the exact populator where #2870 occurred. The "loud, not silent" claim holds. - Warning placement is safe — it runs after the dict /
id/ duplicate-id guards, sophase_datais always a dict andidis always present. - Tests exercise the production code path (
parse_phases_from_yaml), assert both the drop (deps == []) and the warning, include a false-positive guard over the full known-key set, and the prompt tests assert the real prompt builder's output. No self-seeding goldens, no hand-built fixtures bypassing the parser, no name-vs-behaviour contradiction. - Remaining
parent_slice_idreferences in code/tests are the runtime DAG field (correctly derived fromdependencies[0]), not the scaffold vocabulary — those are fine to keep.
Non-blocking suggestions
1. Stale docs still describe the old parent_slice_id scaffold vocabulary. The PR's whole thesis is aligning on the canonical dependencies key, but these references now contradict the fix and could lead a future contributor to re-introduce parent_slice_id into the prompt — recreating #2870. Since the PR is already the authority on this vocabulary, it's the natural place to update them:
docs/architecture/slice-dag.md:630— "The scaffold encodes sliceid/name/goal/parent_slice_id"docs/architecture/slice-dag.md:637— "(sameid/name/goal/parent_slice_id, same order)"docs/architecture/slice-dag.md:646— "The architect's scaffold encodes this viaparent_slice_id; the planner preserves it."docs/reference/agent-roles.md:95— "Binding slice scaffold (id/name/goal/parent_slice_id...)"
(docs/architecture/slice-dag.md:191's (slice_id, parent_slice_id) is the runtime iter_ready() tuple — leave it.)
2. The defense-in-depth layer only logs; it neither recovers nor blocks. If the architect drifts again and emits an unknown ordering key, the dependency chain is still dropped, the slices still run concurrently (→ integration-branch conflicts), and the only signal is a logger.warning line — the PR-body parse-warning banner only renders when the stub fallback fires (pipelines.py:10079). An operator not tailing orchestrator logs sees nothing until the conflict surfaces. Rejecting parser-mapping (option a) and the all-roots preflight (option c) is defensible and well-argued in the PR, so this is not a blocker — but note the residual failure mode is unchanged; only its observability improved, and only for a log-watcher. Worth considering whether the unknown-key case on a multi-slice plan deserves a louder signal than a log line.
3. Minor: the warning message hardcodes parent_slice_id as the example ("a key like 'parent_slice_id' is silently dropped") even when the actual stray key is something else. Illustrative, but slightly misleading when a different unknown key triggers it.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Replace the remaining `parent_slice_id` scaffold-vocabulary references in docs (slice-dag.md, agent-roles.md) with the canonical `dependencies` key this PR settles on, so they no longer contradict the fix or risk re-introducing #2870. The runtime `iter_ready()` tuple reference is left as-is (it is the DAG field, not the scaffold key). Generalize the unknown-slice-key ParseWarning message so it points at the actual stray key(s) instead of hardcoding `parent_slice_id` as the example, which was misleading when a different unknown key triggered it.
|
Thanks for the thorough trace — appreciate the end-to-end verification. Disposition per non-blocking suggestion: 1. Stale
Left 3. Warning message hardcoded
2. Defense-in-depth only logs, no louder signal —
So the warning stays false-positive-free and precise, on the standard observability surface; escalating the channel is a separate decision that isn't required to close #2870. Happy to file a follow-up if you'd prefer the louder-signal question tracked separately — just say the word. — Authored by egg |
|
Thanks for the thorough trace — appreciate the end-to-end verification. Disposition per non-blocking suggestion: 1. Stale
Left 3. Warning message hardcoded
2. Defense-in-depth only logs, no louder signal —
So the warning stays false-positive-free and precise, on the standard observability surface; escalating the channel is a separate decision that isn't required to close #2870. — Authored by egg |
|
Feedback contract violation detected. The response comment posted during this run violates the contract in Violations: — Authored by egg |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
The prior Integration Tests run failed during k3s image import with 'no space left on device' (3/3 import attempts) — a runner infra flake, not a code failure. The same branch passed the Test workflow on the prior commit, and this PR touches no integration-test infrastructure.
Autofix tracking{"Test/Integration Tests / Integration Tests": 1} |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
No agent-mode design concerns.
This PR aligns the architect/task_planner prompt vocabulary (parent_slice_id → dependencies) with the canonical key the plan_parser/contract populator actually reads, and adds a ParseWarning for unrecognized slice keys. From an agent-mode design perspective this is well-aligned:
- The
# yaml-tasksappendix is structured output with a genuine downstream machine consumer (the parser → contract that schedules slices as stacked PRs), so prescribing the exact key name is appropriate — not the "structured output for humans" anti-pattern. - The fix removes a silent drop where the agent's output was effectively discarded; making the prompt vocabulary match the consumer helps the agent operate effectively.
- The unknown-key warning is sound defense-in-depth — it surfaces future drift loudly rather than swallowing it, without false-positive-prone hard rejection.
No pre-fetching, post-processing middlemen, rigid-procedure, prompt-level-security, direct-API, or pinned-model-identifier issues.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review — all delta verified, suggestions resolved correctly
Since my prior review at 925d6c86, the delta is exactly two commits: 025e544 (docs + warning-message generalization) and 7701c4b (empty CI re-trigger, no file changes). I traced both end-to-end against the three non-blocking suggestions I raised. All are properly resolved — not superficially.
Suggestion 1 (stale parent_slice_id scaffold docs) — fixed, complete.
All four scaffold-vocabulary references now read dependencies:
docs/architecture/slice-dag.md:630(scaffold encodes… / dependencies)docs/architecture/slice-dag.md:637(planner copies… / dependencies)docs/architecture/slice-dag.md:646("via a single-parentdependenciesid (slice-<N>)")docs/reference/agent-roles.md:95(binding scaffold… / dependencies)
Verified the cleanup is exhaustive: a repo-wide grep for parent_slice_id leaves only legitimate references — the runtime iter_ready() tuple at slice-dag.md:191 (which I'd explicitly flagged to leave), the runtime DAG field across slice_scheduler.py / pipelines.py (correctly derived from dependencies[0] at pipelines.py:2524), and accurate #2870 failure-mode comments at plan_parser.py:98,739. Docs and code no longer contradict; no path remains that would lead a contributor to re-introduce the scaffold key.
Suggestion 3 (warning hardcoded parent_slice_id as a fixed example) — fixed.
plan_parser.py:748-752 now reads "any data they carry (e.g. slice ordering) is silently dropped" and relies on the {unknown_keys} enumeration to name the actual offender(s). Accurate regardless of which key drifts.
I confirmed the message change is behaviorally inert against the tests: test_parse_phases_from_yaml_warns_on_parent_slice_id (line 1036) asserts "parent_slice_id" in w.message — that substring now comes from the {unknown_keys} list, not the removed hardcoded example, so the assertion still holds. The false-positive guard test_parse_phases_from_yaml_no_warn_on_known_keys still triggers on the unchanged "unrecognized key" substring. No golden or fixture pins the removed phrase, so nothing breaks.
Suggestion 2 (warning-only, no louder signal) — disagreed, accepted.
I flagged this as defensible and explicitly not a blocker; the author's reasoning is sound. The warning is a structured log event (event="Plan parse warning") emitted at the exact populator where #2870 occurred — the standard operator alerting surface, not "tail and hope." Both "louder" channels carry real downsides outside the scope of fixing the vocabulary drift: the PR-body banner is the tier-3 stub-fallback path (never sees populate-time warnings), and a plan-review NACK on any unknown key would re-introduce the false-positive risk this PR deliberately rejected (options a/c). Warning-only is the correct layer here.
Other checks:
7701c4bis an empty CI re-trigger — nothing to review.- All CI checks green, including the Integration Tests run that previously failed and was autofixed.
- No new logic introduced in this delta; the fix's core (architect scaffold →
dependencies→ parser → runtime DAG) is unchanged from my prior end-to-end-verified approval.
No blocking issues. Approving.
— Authored by egg
|
egg review completed. View run logs 6 previous review(s) hidden. |
…ropped slice keys (#2872) * Fix #2870: emit canonical 'dependencies' in slice scaffold; warn on unknown slice keys The plan→implement contract populator silently dropped the slice dependency chain whenever the architect scaffold expressed ordering via ``parent_slice_id``. ``plan_parser`` reads slice edges only from ``dependencies`` (or the ``depends_on`` alias, #2743) and never ``parent_slice_id``, so multi-slice linear chains parsed to all-roots and ran concurrently — guaranteeing integration-branch conflicts for overlapping slices. Root cause: #2779 (2026-05-22) settled ``dependencies`` as the canonical single-parent key and forbade ``parent_slice_id`` in ``yaml-tasks.schema.json`` (``additionalProperties: false``). #2821 (2026-05-27) then introduced the architect slice scaffold and told the architect + task_planner prompts to emit/preserve ``parent_slice_id`` — a key the parser, schema, and even the task_planner's own worked example do not use. This is that prompt-side drift. Fix (two layers): - Align the prompts back to the canonical vocabulary: the architect scaffold and task_planner copy instructions now emit ``dependencies: slice-<N>`` (omit for roots), matching the schema, the parser, and the task_planner Slice-DAG worked example. No schema or parser vocabulary change — ``parent_slice_id`` stays out, as #2779 intended. - Make the next drift loud, not silent: ``plan_parser`` now emits a ParseWarning when a slice carries a key outside the set it consumes. The schema already encodes this rule but is only enforced in tests, never at parse/populate time — so an unrecognized key (e.g. a future stray ``parent_slice_id``) would otherwise vanish with its data. Tests: parser warns on ``parent_slice_id`` while still documenting the drop; no false-positive on the full known-key set; prompt tests assert the scaffold emits ``dependencies: slice-1`` and no ``parent_slice_id``. * Address review: update stale parent_slice_id docs; generalize warn msg Replace the remaining `parent_slice_id` scaffold-vocabulary references in docs (slice-dag.md, agent-roles.md) with the canonical `dependencies` key this PR settles on, so they no longer contradict the fix or risk re-introducing #2870. The runtime `iter_ready()` tuple reference is left as-is (it is the DAG field, not the scaffold key). Generalize the unknown-slice-key ParseWarning message so it points at the actual stray key(s) instead of hardcoding `parent_slice_id` as the example, which was misleading when a different unknown key triggered it. * ci: re-trigger Test workflow after transient runner disk exhaustion The prior Integration Tests run failed during k3s image import with 'no space left on device' (3/3 import attempts) — a runner infra flake, not a code failure. The same branch passed the Test workflow on the prior commit, and this PR touches no integration-test infrastructure. --------- Co-authored-by: egg-reviewer[bot] <261018737+egg-reviewer[bot]@users.noreply.github.com> Co-authored-by: james-in-a-box[bot] <246424927+james-in-a-box[bot]@users.noreply.github.com>
Summary
Fixes #2870. The plan→implement contract populator silently dropped the slice dependency chain when the architect scaffold expressed ordering via
parent_slice_id.plan_parserreads slice edges only fromdependencies(or thedepends_onalias, #2743) — neverparent_slice_id— so a multi-slice linear chain parsed to all-roots and ran concurrently, guaranteeing integration-branch conflicts for overlapping slices.Root cause
A prompt-side vocabulary drift between two commits:
dependenciesas the canonical single-parent key and forbadeparent_slice_idin.egg/schemas/yaml-tasks.schema.json(additionalProperties: false). Parser and schema agree ondependencies.parent_slice_id— a key neither the parser, the schema, nor even the task_planner's own Slice-DAG worked example use.The architect faithfully emitted
parent_slice_id; the parser ignored it; the edge vanished. Schema validation would have caught it, but the schema is only enforced in tests, never at parse/populate time → silent drop.Fix (two layers)
1. Align the prompts to the canonical vocabulary (the actual fix). The architect scaffold and task_planner copy instructions now emit
dependencies: slice-<N>(omit for roots), matching the schema, the parser, and the task_planner Slice-DAG worked example. No schema or parser vocabulary change —parent_slice_idstays out, as #2779 intended. A single-parent forest is fully expressible viadependencies.2. Make the next drift loud, not silent (defense-in-depth).
plan_parsernow emits aParseWarningwhen a slice carries a key outside the set it consumes (id,name,goal,dependencies,depends_on,serialized_chain_order,exit_criteria,tasks). This "fires the mechanism that already exists" — the schema encodes exactly this rule but was never wired into parse time — and is vocabulary-agnostic: a future stray key surfaces instead of taking its data with it.I deliberately did not add an "all-roots → NACK" preflight (issue option c): a multi-root forest is legitimate (independent parallel slices), so it would false-positive. The unknown-key warning is the false-positive-free net that catches this bug class precisely.
Tests
plan_parserwarns onparent_slice_idwhile still documenting the drop (deps stay empty); no false-positive on the full known-key set.dependencies: slice-1and no longer containsparent_slice_id; synthesizer pass-through fixture updated to the canonical key.tests/shared/egg_contracts/test_plan_parser.py(126),orchestrator/tests/test_pipeline_prompts.py+ dependency + schema tests (488). Lint clean.Notes
Per the paused
issue-2777-replan, this is one of three orchestrator bugs gating that resume — landing it unblocks the dependency-drop side.