Skip to content

feat(*): let a dag decision node choose which of its branches run - #1001

Merged
LivXue merged 14 commits into
mainfrom
feat/dag_conditional_branches
Oct 19, 2026
Merged

LivXue merged 14 commits into
mainfrom
feat/dag_conditional_branches

Conversation

@LivXue

@LivXue LivXue commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

Summary

A sub-agent DAG node can now decide which of its downstream branches run. Before this, a background run could not turn on a result: every node ran, and choosing a path meant the host winding the run down and starting another.

How it works:

  • A node that declares routes (route name to condition) is a decision node; select is "one" (the default) or "many". Raven's per-node judge, the existing verdict call, picks the route or routes the node's answer satisfies. The node's own text never selects a route. A route that cannot be determined fails the node, with no fallback route.
  • A downstream node carries when, a guard over <node>.<route> and <node>.done atoms joined with not, and, or and parentheses, evaluated with three-valued logic: a route of a decision node that was never executed is unknown. Every decision node in a node's depends_on must appear in its when; such graphs are refused at submission otherwise, along with guards that can never hold and a route named done in any letter case. Validation keeps negation parity and the surrounding boolean expression: a double-negated done and a disjunction with a satisfiable arm are accepted, without expanding disjunctions into combinations.
  • A node that does not run is not_executed, with a reason: when_false, when_unknown or upstream_not_executed. A node whose dependencies all went unexecuted is not executed either; a failed or skipped dependency still skips its dependents first.
  • A merge node reads a missing upstream as a fixed sentence, <This task is not executed and the output is missing, please go on without this output.>, written outside every untrusted-content fence, plus a note listing the inputs that may be missing. A file reference to the files of a node that may not execute is refused at submission however its path is spelled: @nodes/, an absolute path into the node root, or a workdir path that lands there, symlinks followed. Windows case, separator and drive-relative aliases are recognized, and the advice keeps the declared node ID.
  • Node judgement is always on: the subagentDag.verdictEnabled switch is retired. Config migration 14 removes it with a notice, and drops it again on every later load should an older build sharing the config write it back. raven playbook run now judges its nodes too, on the same subagentDag settings and a pinned verdictModel's own provider; with nobody there to answer, a node judged not to have accomplished its task fails on the judge's reason. A host with no judge at all refuses graphs that declare routes.

Where it shows:

  • Run events, the manifest, dag.get, tasks.list and dag.run_completed carry reason, selected_routes and route_evidence per node, and routes, select and when from the graph. The instance registry's DAG-node rows (and InstanceRow) carry the outcome too, so a client that reloads before the run finalizes shows what a branch decided. TaskCounts.not_executed is a new counter.
  • TUI: a not-executed node shows a hollow marker and its reason in words; a decision node shows its selection on the node's second line, and the guard and route evidence in the detail and in /dag <node>. The Agents Overlay and the live agents strip leave not-executed nodes out, as the backend's agent lists do.
  • Web: the desk's tasks tab names not-executed steps and shows the same fields.
  • Playbooks pass the three fields through, namespace guard atoms with the run prefix, and keep them through compilation.
  • The run_subagent_dag schema, the DAG orchestration skill and the docs site (English and Chinese) describe routes, guards and the merge rule; CHANGELOG has the entries.

A graph with no routes and no when behaves exactly as before: the same judge requests, events, records, summary and finish text. route_evidence is a quote the judge picks from the sub-agent's output; it is cut at 500 characters, keeps a lone surrogate from a malformed escape as the escape's own text, and never enters model-facing text (finish text, dag_status, exception reports). When a judge answer does not report a usable route, the judge is asked again at most twice, each time with one correction. The shared instance registry drops its cache when a write fails on the data rather than the disk, so a row the file cannot hold fails its own write and no other.

Two Windows CI steps run the alias and finish-text regressions; the latter exposes the installed Git cat executable required by the existing fixture.

Type

  • Fix
  • Feature
  • Docs
  • CI / tooling
  • Refactor
  • Other

Verification

Linux, Python 3.12.13, on this revision rebased onto the current main ($CHECKS is a temporary evidence directory):

uv run --frozen --no-sync pytest -q -n 4
# 23 failed, 33229 passed, 162 skipped, 1 xfailed, 3 errors
uv run --frozen --no-sync pytest $(git diff --name-only origin/main...HEAD -- tests/) -q -n 4 --idle-ceiling-strict --cov=raven --cov-branch --cov-report=json:$CHECKS/coverage.json
# the 20 test files this branch changes: 2622 passed, 1 skipped (a POSIX permission case that does not apply to root)
uv run --frozen --no-sync python scripts/coverage_gate.py --coverage-json "$CHECKS/coverage.json" diff --base-ref origin/main --head HEAD --threshold 90
# Diff coverage: 99.48% (764/768 executable changed lines), passed the 90.00% threshold

The full run's 23 failures and 3 errors are 25 tests. 24 of them fail the same way on the current main in this environment: the venv lacks nltk and datrie (16), a proxy is set in the environment (5), this box has its own npm in /usr/bin (1 install-script case), LibreOffice writes no .xls here (1), and the run is root (1). The 25th, test_a_cli_run_left_on_the_machine_when_this_computer_stops_is_picked_up_by_the_next_start, failed with its teardown in the full run and in one of two runs of its file, and passed in five runs on its own: it reads the remote-run record the moment its pid file appears, a race in code this branch does not change (#840 makes that test wait for both). The four uncovered changed lines are the gateway's _verdict_provider body, whose helper is tested on its own; a guard atom over a node that did not complete, which the runner never evaluates because it reads a guard only once every dependency completed or was not executed; and the cut of an over-long terminal output.

Ruff check and format, ty check over the changed modules, the import contracts, deptry raven, commitlint, and scripts/check_commit_messages.py, check_large_files.py and check_source_language.py over this branch's commits pass. ui-web: vitest run src/features/tasks/BoardCard.test.tsx scripts/gates passes 50 files and 244 tests, and tsc --noEmit is clean. In Chromium a not-executed title whose first word is wider than the room beside its status now breaks that word instead of starting under a blank line, and every other tasks-board card renders pixel-identical, English and Chinese, light and dark. Firefox still starts such a title under a blank line and cuts it without an ellipsis. CI's TUI checks covers ui-tui.

Mutation checks: removing any one of these makes its own test fail: the every-load strip of the retired switch, the quote's escape before the cut, the registry's cache drop, the node-root check in the tool, the runner and the replan path, the physical path through a symlink, the CLI's judge, its settings and its no-answer failure, and the pinned judge provider. A model reading only the DAG skill's catch-all paragraph answers that without a catch-all route the judge may pick the nearest route or fail the node; an earlier wording led it to answer that the graph always stops.

A live run on openrouter/z-ai/glm-5.3-flash, two graphs with synthetic messages, selected the expected route at each decision node, left the unselected branch not_executed with when_false, and put the fixed sentence outside the fences. Earlier revisions of this change passed the native Windows selections (aliases, finish text, guards; Windows build 26300, Python 3.13.12). The final revision has not been rerun on native Windows or macOS.

  • Relevant tests pass locally
  • Relevant lint / type checks pass locally
  • User-facing docs or screenshots are updated when needed

Risk

  • Security impact considered
  • Backward compatibility considered
  • Rollback path is clear for risky changes

Security: sub-agent text stays fenced or out of model-facing text on every new path. The routes block in the judge request sits after the last untrusted fence and is labelled as the graph author's; the missing-output sentence is written outside every fence; route_evidence is bounded and reaches records, events, the registry and the UIs only. In the TUI, route evidence is drawn through the same path as a node's error text; neither strips terminal control sequences, which this PR leaves as it is.

Behaviour changes: a config that set subagentDag.verdictEnabled: false now gets judged nodes; migration 14 removes the key and says so once. raven playbook run judges every node, one more model call per node, and a node judged not to have accomplished its task fails there instead of completing. New wire fields are optional except TaskCounts.not_executed, which is always present (0 when nothing branched). InstanceRow gains three optional fields, and a decision node's registry row now also stores its selection and up to 500 characters of route evidence.

Conditional-artifact validation matches node IDs using the session namespace's case-insensitive identity on every host, including macOS. This conservatively rejects case-only spellings even on case-sensitive filesystems. Separator and drive normalization remain native, and unconditional file resolution keeps its existing behaviour.

Rollback: revert the squash commit. A config already stamped 14 loads unchanged on the previous build, since the stamp only gates migrations; a user who had set verdictEnabled: false can set it again there, and a later build drops it again on load.

Related Issues

Related, found while building this and not changed here: #998, #999, #1000. Follow-ups from its review, not changed here: #1008.

@LivXue
LivXue requested review from 0xKT and gloryfromca October 18, 2026 09:56

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: valid double-negated done guards are rejected as impossible.

I found one inline correctness issue. I covered the repository rules and canonical DAG terminology, the full diff, the parser/validator/scheduler/judge/storage path, RPC and both UI callers, playbook compilation, the single-commit history, backward compatibility for non-branching DAGs and retired config migration, and test changes; I did not find weakened tests or an architecture-boundary violation.

Verification: uv run pytest tests/test_subagent_dag_guard.py tests/test_subagent_dag_core.py tests/test_subagent_dag_runner.py tests/test_subagent_dag_verdict.py tests/test_subagent_dag_control_tools.py tests/test_rpc_dag.py tests/test_rpc_tasks.py tests/test_rpc_schema_match.py tests/test_playbook_executor.py tests/test_playbook_validate.py tests/test_unified_playbook.py -q (1681 passed); changed TUI suites (341 passed); changed web suites (471 passed).

Comment thread raven/agent/subagent/dag_guard.py Outdated
@LivXue

LivXue commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

Windows compatibility review: inspected the full 94-file diff and relevant callers at head 213d68eecd19423350a269dfb46d79eae8e2ffef, against base f4317bd9ed551a30aa735e143f8b84a98e3d74d6. Verification used isolated exact-SHA archives on native Windows.

  1. [P2] Recognize Windows aliases before rejecting references to conditional artifacts. dag_graph.py:1021 normalizes with posixpath and the caller compares IDs case-sensitively. On the default Windows filesystem, @nodes/LEFT.out.md and @nodes/.\left.out.md resolve to left.out.md, but neither is recognized as conditional node left. Reproduced: with r choosing right, left guarded by r.left, and a merge depending on both with when: r.done, validation accepts both aliases; the real runner leaves left unexecuted and fails the merge with "not a file it can read". This happens for ref, ref_path, and file inputs. The canonical spelling is rejected correctly, and {{ left.output }} completes with the missing-output sentence. Normalize separator/case equivalence consistently with the file backend, or reject these aliases consistently, before this check. The unchanged resolver accepts all six unconditional alias controls at both base and head; the missed conditional check is new here.

  2. [P2] Build expected output paths using Path joins in the new finish-text tests. test_subagent_dag_runner.py:10465 appends /triage.out.md to a native Path, creating a mixed-separator expectation. Reproduced: test_the_finish_text_counts_and_explains_the_branches_that_were_not_taken runs the graph successfully but fails its text assertion: expected nodes/triage.out.md, actual nodes\triage.out.md. This persists after making the already-installed Git cat.exe available to the test process. Use str(nodes / 'triage.out.md') (and the corresponding joins in the other newly added finish-text expectations). This finding concerns new test portability; the production path formatting already existed at base.

Verification: 343 selected added Python cases passed, plus 158 guard/verdict cases and 5 branch-config cases. The external native-filesystem probe passed 16 assertions (including six confirmations of finding 1); its six unconditional controls also passed at base. Changed TUI suites: 372 passed; changed Web suites: 704 passed. Both npm run type-check checks passed. git diff --check passed.

Limitations: the wider Python run stopped after 238 passes / 2 skips at test_a_row_pinning_a_gone_raven_interpreter_follows_this_install; the same failure reproduces at base, so it is inherited. Four added foreground cases were excluded from the 343-case run after the initial run hit the existing cat dependency; the targeted retry above isolates finding 2. Python tests used the repository-required PYTHONUTF8=1 (disabling it is rejected by the existing conftest guard on this cp936 host). Node was v25.9.0, outside TUI Vitest 5's declared supported version range despite these checks passing. No live model, UNC share, interactive terminal, or real-browser layout testing.

Frontend verification commands

From the respective isolated head archive's ui-tui / ui-web directories, with temporary USERPROFILE, RAVEN_HOME, TEMP and TMP:

# Setup, both projects: successful; lifecycle scripts disabled during installation.
npm ci --ignore-scripts --no-audit --no-fund
# TUI: 372 passed. npm's pretest also built the local Hermes Ink package.
npm test -- src/__tests__/agentsOverlay.test.tsx src/__tests__/dagCommand.test.ts src/__tests__/dagNodePoll.test.tsx src/__tests__/dagPanel.test.tsx src/__tests__/dagRun.test.ts src/__tests__/dagStatus.test.ts src/__tests__/liveAgentsStore.test.ts src/__tests__/liveAgentsStrip.test.ts --maxWorkers=2 --reporter=json --outputFile 'C:/Users/livxu/AppData/Local/Temp/raven-windows-review/fc077797d3174e3b89f5749c21a12dcd/checks/1001-tui/vitest.json'
# Web: 704 passed.
npm test -- src/features/dag/nodes.test.ts src/features/desk/DeskPalette.test.tsx src/features/desk/DeskSurface.test.tsx src/features/desk/store.test.ts src/features/tasks/BoardCard.test.tsx src/features/tasks/NodeRecord.test.tsx src/features/tasks/TasksPage.test.tsx src/features/tasks/live.test.ts src/features/tasks/source.test.ts src/features/tasks/store.test.ts src/features/transcript/TranscriptPage.test.tsx src/features/transcript/store.test.ts --maxWorkers=2 --reporter=json --outputFile 'C:/Users/livxu/AppData/Local/Temp/raven-windows-review/fc077797d3174e3b89f5749c21a12dcd/checks/1001-web/vitest.json'
# Both projects: exit 0.
npm run type-check

Python used 3.13.12, an isolated environment prepared with uv sync --frozen --python 3.13 --all-packages --extra dev, then UV_NO_SYNC=1, PYTHONUTF8=1, and temporary profile/state directories. review_changed_tests is a scratch collection filter selecting added test function names from the PR diff. The alias probe uses the real validator, runner and LocalFileBackend, a scripted judge and an echo backend, under a path containing a space and non-ASCII characters. For finish-cat only, the process PATH was prefixed with C:\Program Files\Git\usr\bin.

Exact verification commands
# pure: 238 passed, 1 failed, 0 errors, 2 skipped; exit 1
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\pr1001\source\head
uv run pytest tests/test_subagent_dag_guard.py tests/test_subagent_dag_verdict.py tests/test_config_loader.py tests/test_config_raven_sections.py -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\checks\1001-pure\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\checks\1001-pure\junit.xml'
# base-config: 0 passed, 1 failed, 0 errors, 0 skipped; exit 1
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\pr1001\source\base
uv run pytest 'tests/test_config_loader.py::test_a_row_pinning_a_gone_raven_interpreter_follows_this_install' -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\checks\1001-base-config\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\checks\1001-base-config\junit.xml'
# config-branch: 5 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\pr1001\source\head
uv run pytest tests/test_config_loader.py tests/test_config_raven_sections.py -k 'verdict or subagent_dag' -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\checks\1001-config-branch\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\checks\1001-config-branch\junit.xml'
# added: 308 passed, 1 failed, 0 errors, 0 skipped; exit 1
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\pr1001\source\head
uv run pytest tests/test_playbook_executor.py tests/test_playbook_validate.py tests/test_rpc_dag.py tests/test_rpc_instances.py tests/test_rpc_playbooks.py tests/test_rpc_schema_match.py tests/test_rpc_spine.py tests/test_rpc_subagent_calls.py tests/test_rpc_tasks.py tests/test_subagent_dag_control_tools.py tests/test_subagent_dag_core.py tests/test_subagent_dag_runner.py tests/test_subagent_third_party.py tests/test_unified_playbook.py -p review_changed_tests -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\checks\1001-added\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\checks\1001-added\junit.xml'
# added-rest: 343 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\pr1001\source\head
uv run pytest tests/test_playbook_executor.py tests/test_playbook_validate.py tests/test_rpc_dag.py tests/test_rpc_instances.py tests/test_rpc_playbooks.py tests/test_rpc_schema_match.py tests/test_rpc_spine.py tests/test_rpc_subagent_calls.py tests/test_rpc_tasks.py tests/test_subagent_dag_control_tools.py tests/test_subagent_dag_core.py tests/test_subagent_dag_runner.py tests/test_subagent_third_party.py tests/test_unified_playbook.py -p review_changed_tests -k 'not test_the_finish_text and not test_branch_e2e_one_tool_call' -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\checks\1001-added-rest\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\checks\1001-added-rest\junit.xml'
# finish-cat: 0 passed, 1 failed, 0 errors, 0 skipped; exit 1
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\pr1001\source\head
uv run pytest 'tests/test_subagent_dag_runner.py::test_the_finish_text_counts_and_explains_the_branches_that_were_not_taken' -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\checks\1001-finish-cat\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\checks\1001-finish-cat\junit.xml'
# alias-final: 16 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\pr1001\source\head
uv run pytest 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\test_windows_branch_alias.py' -s --asyncio-mode=auto -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\checks\1001-alias-final\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\checks\1001-alias-final\junit.xml'
# base-alias: 6 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:/Users/livxu/AppData/Local/Temp/raven-windows-review/fc077797d3174e3b89f5749c21a12dcd/pr1001/source/base
uv run pytest 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\test_windows_branch_alias.py' -k unconditional_alias_control -s --asyncio-mode=auto -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\checks\1001-base-alias\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fc077797d3174e3b89f5749c21a12dcd\checks\1001-base-alias\junit.xml'

@LivXue

LivXue commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

Addressed both findings in the Windows review:

  • 48be00ba1110: conditional-artifact lookup uses native path/case normalization while retaining the declared node ID in its advice. It covers ref, ref_path and file inputs; unconditional aliases still work.
  • 840b9f49adfa: all ten expected paths across the four new foreground/finish-text cases use Path joins. Windows CI now runs the alias and finish-text regressions, with the existing Git cat fixture made available on PATH.

Native Windows build 26300 / Python 3.13.12: the initial selection reproduced 15 failures / 9 passes; the final focused selection passes 34 tests (669 deselected, no skips), and 16 external native cases pass. The broader Windows selection has 1686 passes and 16 failures; every remaining failing ID also fails on the unmodified original PR head. The final Linux selection passes 1702 tests with no skips. Removing any of the three normalization/mapping components makes the regression fail; source was restored byte-identically and the focused tests passed again.

Full-PR diff coverage is 97.55% (716/734). Lint/type/import/commit/source/asset checks pass; the dependency gate passes in Linux with the declared channel extras installed. The PR description records exact commands and the native baseline limitations. The separate double-negated guard finding remains open; I did not resolve any review thread. New-head CI is pending and will be checked separately.

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: the existing double-negation guard rejection remains reproducible on this head.

I reviewed the two-commit delta from 213d68eecd19 and found no new blocker in the Windows path normalization, platform-native finish-text expectations, or CI coverage. The open finding at dag_guard.py remains unchanged: the same not not decide.done graph still fails validation on this revision, so that thread continues to block.

Coverage for this revision included the delta, its local-file/path callers, the existing guard validation path, commit history, backward compatibility on non-Windows hosts, test changes, and the repository rules/architecture already covered in my first full review. I found no weakened tests. Verification: uv run pytest tests/test_subagent_dag_guard.py tests/test_subagent_dag_core.py tests/test_subagent_dag_runner.py -q (766 passed), plus the direct double-negation reproduction (still rejected).

@LivXue

LivXue commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

Windows compatibility re-review at head 840b9f49adfa825a7f66b5a49532463c05f53762, base f4317bd9ed551a30aa735e143f8b84a98e3d74d6. Rechecked the full 95-file change using the previously inspected full patches and the complete new delta: 91 patches are identical; CI, graph validation and two test files changed. Traced the current local resolver, validator, renderer and runner. Exact-head blobs and the complete local/GitHub diff were verified.

[P2] A same-drive relative alias still bypasses the conditional-artifact check (reproduced on Windows). At dag_graph.py:1022, normpath/normcase leave C:left.out.md drive-relative, so the extracted key is c:left, not left. With node artifacts on C:, the unchanged LocalFileBackend.abspath joins that spelling to the node directory and reads the actual left.out.md. The existing lexical confinement check accepts it.

Native reproduction: r selects right; left depends on r with when: r.left; merge depends on both with when: r.done. A merge reference {{ ref:@nodes/C:left.out.md }} passes validation, then the runner records left=not_executed, merge=failed with "not a file it can read". ref_path and {"file": "@nodes/C:left.out.md"} inputs reproduce the same result. This is an additional alias case for the earlier finding: the reported uppercase and backslash variants are fixed. Reject drive-qualified @nodes/ paths as non-relative, or match their resolved local target before deciding whether the node may be missing; retain the placeholder remedy. The unconditional drive-relative controls pass at both exact base and head, so drive-relative resolution is inherited; the missed conditional check belongs to this PR.

Verification on native Windows / Python 3.13.12:

  • 34 repository path/finish-text tests passed, 669 deselected, including all four corrected foreground cases. Git's existing cat.exe was exposed on this test process's PATH.
  • The external probe completed 22 assertions: 16 corrected-path/placeholder controls, 3 successful unconditional drive-relative cases, and 3 confirmations of the remaining failure above. All 9 unconditional controls passed at exact base.
  • The original bug-observing probe now stops at the correct validation rejection of @nodes/LEFT.out.md (3 passed, 1 expected reversal); the updated controls verify all original cases.

The separate double-negated guard finding remains in the existing discussion and is not duplicated here.

Setup: isolated exact-SHA archives and uv environment via uv sync --frozen --python 3.13 --all-packages --extra dev; UV_NO_SYNC=1, PYTHONUTF8=1, temporary profile/OAuth/state directories and verified imports. Native probes used the real validator, runner and LocalFileBackend with an echo backend and scripted judge, under a space/non-ASCII path. git diff --check passed. No live model, UNC share, sandbox, macOS/Linux or full-suite run. Unchanged frontend patches were compared with the prior review; frontend tests were not rerun.

Exact verification commands
# focused: 34 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\d600058f8f1348f7b0ccc38bebd9753b\pr1001\source\head
uv run pytest tests/test_subagent_dag_core.py tests/test_subagent_dag_runner.py -k 'at_nodes or file_input_aimed or test_the_finish_text or test_branch_e2e_one_tool_call' -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\d600058f8f1348f7b0ccc38bebd9753b\checks\1001-focused\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\d600058f8f1348f7b0ccc38bebd9753b\checks\1001-focused\junit.xml'
# alias-original: 3 passed, 1 failed, 0 errors, 0 skipped; exit 1
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\d600058f8f1348f7b0ccc38bebd9753b\pr1001\source\head
uv run pytest -c pyproject.toml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\d600058f8f1348f7b0ccc38bebd9753b\test_windows_branch_alias_original.py' -s -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\d600058f8f1348f7b0ccc38bebd9753b\checks\1001-alias-original\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\d600058f8f1348f7b0ccc38bebd9753b\checks\1001-alias-original\junit.xml'
# alias-current: 22 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\d600058f8f1348f7b0ccc38bebd9753b\pr1001\source\head
uv run pytest -c pyproject.toml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\d600058f8f1348f7b0ccc38bebd9753b\test_windows_branch_alias_current.py' -s -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\d600058f8f1348f7b0ccc38bebd9753b\checks\1001-alias-current\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\d600058f8f1348f7b0ccc38bebd9753b\checks\1001-alias-current\junit.xml'
# base-alias: 9 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\d600058f8f1348f7b0ccc38bebd9753b\pr1001\source\base
uv run pytest -c pyproject.toml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\d600058f8f1348f7b0ccc38bebd9753b\test_windows_branch_alias_current.py' -k unconditional -s -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\d600058f8f1348f7b0ccc38bebd9753b\checks\1001-base-alias\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\d600058f8f1348f7b0ccc38bebd9753b\checks\1001-base-alias\junit.xml'

@LivXue

LivXue commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

Addressed the same-drive relative alias finding in 0313297466ee.

Native splitdrive now separates the drive before the existing path/case normalization and canonical-node lookup. A drive-relative suffix is matched conservatively before dispatch roots are known; the actual file resolver and unconditional reads are unchanged. Rejections retain the declared node ID and {{ left.output }} remedy.

Native Windows build 26300 / Python 3.13.12: the new filesystem matrix reproduced 9 failures / 21 passes before the fix. Three external runner probes independently reproduced the accepted reference followed by left=not_executed and merge=failed; the three unconditional drive controls completed. After the fix, the repository selection passes 43 tests (669 deselected, no skips/errors) and all 22 external cases pass, including the original aliases, unconditional reads and missing-output merge control.

Linux Python 3.12.12: 1711 related tests pass with no skips/errors. Full-PR diff coverage is 97.55% (717/735). Lint/type/import/dependency, full-PR pre-commit and commit/source/asset checks pass. The PR description records exact commands and keeps the earlier broad native failures separate; that broad native selection was not rerun for this follow-up. The generic double-negation finding remains open, and no review thread was resolved. New-head CI will be checked separately.

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; suggestions only, and they are marked inline.

I reviewed the 0313297466ee delta and found no concrete regression in the drive-relative conditional-artifact handling or its tests. The existing double-negation validation issue remains a worthwhile follow-up, but at this fifth round it does not meet the blocker bar: it is introduced here, yet is low-reachability and has the direct workaround not not X -> X. I withdrew that objection and resolved my thread.

Coverage included the new diff and commit history, local path resolution/validation callers, Windows versus POSIX behavior, backward compatibility, test changes, and the repository rules and architecture covered in the original full review. No weakened tests found. Verification: uv run pytest tests/test_subagent_dag_core.py -q (363 passed).

@LivXue

LivXue commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

Windows compatibility re-review at head 0313297466eea72425f154158b5b0a28e124e9de, base f4317bd9ed551a30aa735e143f8b84a98e3d74d6. Rechecked the full 95-file change using the previously inspected patches and complete latest delta: 93 patches are identical; graph validation and its filesystem matrix changed. Traced the current validator, local resolver, renderer and runner, and refreshed all discussion. Exact-head blobs and the complete local/GitHub diff were verified.

The reported same-drive relative alias failure is fixed in the tested native scenarios; no additional Windows compatibility issue found in the inspected scope. Re-running the previous reproduction now rejects @nodes/C:left.out.md before dispatch with the canonical {{ left.output }} advice. The updated assertions verify that rejection for ref, ref_path and file inputs, while unconditional drive-relative reads and the missing-output merge control still complete. The actual file resolver is unchanged.

Native Windows / Python 3.13.12: 43 repository path/finish-text tests passed, 669 deselected, and 22 external native controls passed under a space/non-ASCII path. All four foreground/finish-text cases passed with the existing Git cat.exe exposed only to the test process. The unchanged old bug-observing probe recorded 16 passes then stopped at the correct rejection; that expected reversal is not a repository-test failure.

The generic double-negation limitation remains documented in the existing follow-up; its reviewer closed the thread, and this Windows review does not refile it.

Setup: isolated exact-SHA archive and uv environment via uv sync --frozen --python 3.13 --all-packages --extra dev; UV_NO_SYNC=1, PYTHONUTF8=1, isolated profile/OAuth/temp paths and verified imports. Probes use the real validator, runner and LocalFileBackend with a scripted judge and echo backend. git diff --check passed; tested source blobs remained unchanged. No new full-suite, frontend/browser, Linux/macOS, live-model, absolute-path, UNC-share, ACL or symlink verification; unchanged frontend patches were compared with the earlier full review.

Exact verification commands
# focused: 43 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\7ec6ce43ce1a4efe9b15878b7d4000d6\pr1001\source\head
uv run pytest tests/test_subagent_dag_core.py tests/test_subagent_dag_runner.py -k 'at_nodes or file_input_aimed or test_the_finish_text or test_branch_e2e_one_tool_call' -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\7ec6ce43ce1a4efe9b15878b7d4000d6\checks\1001-focused\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\7ec6ce43ce1a4efe9b15878b7d4000d6\checks\1001-focused\junit.xml'
# alias-previous: 16 passed, 1 failed, 0 errors, 0 skipped; exit 1
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\7ec6ce43ce1a4efe9b15878b7d4000d6\pr1001\source\head
uv run pytest -c pyproject.toml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\7ec6ce43ce1a4efe9b15878b7d4000d6\test_windows_branch_alias_previous.py' -s -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\7ec6ce43ce1a4efe9b15878b7d4000d6\checks\1001-alias-previous\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\7ec6ce43ce1a4efe9b15878b7d4000d6\checks\1001-alias-previous\junit.xml'
# alias-fixed: 22 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\7ec6ce43ce1a4efe9b15878b7d4000d6\pr1001\source\head
uv run pytest -c pyproject.toml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\7ec6ce43ce1a4efe9b15878b7d4000d6\test_windows_branch_alias_fixed.py' -s -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\7ec6ce43ce1a4efe9b15878b7d4000d6\checks\1001-alias-fixed\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\7ec6ce43ce1a4efe9b15878b7d4000d6\checks\1001-alias-fixed\junit.xml'

@LivXue
LivXue force-pushed the feat/dag_conditional_branches branch from 0313297 to 7340bef Compare October 18, 2026 15:51

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; this can merge as far as I am concerned.

Re-reviewed the rebased revision against current github/main and inspected the complete new delta, callers, history, compatibility, tests, and architecture contracts. The reserved-node identity change now matches conditional artifact paths with the session's case-folding rule, and the Boolean-context walker fixes the previously named double-negation follow-up while preserving valid or arms. I found no new plain error or blocker.

Verification: uv run pytest tests/test_subagent_dag_guard.py tests/test_subagent_dag_core.py -q passed (463 tests); a generated check of 927 depth-bounded Boolean guards found zero satisfiable expressions falsely rejected; git diff --check github/main...HEAD passed; exact-head TUI CI passed. The full Python CI matrix was still pending when reviewed. Tests were strengthened rather than weakened.

Covered the applicable AGENTS.md, CLAUDE.md, CONTEXT-MAP.md/CONTEXT.md rules, the PR diff and rebase range-diff, production callers, backward compatibility, and the DAG validation/runtime boundary. Patch-risk assessment: merge, human review required because this changes an authored public DAG contract; impact moderate, regression likelihood low, protection strong, recovery easy, confidence high. The assessment JSON validated against the patch-risk schema.

@LivXue

LivXue commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

Completed the pending native Windows verification for 7340bef492c7c778b81d8a5b99bd1ad32f32a54c after the rebase and reserved-ID/Boolean-context changes.

Windows build 26300 / Python 3.13.12, isolated profile/OAuth/temp state:

  • 72 repository path, identity, finish-text and Boolean regression cases passed (669 deselected).
  • The complete guard test file passed 71 tests.
  • 30 external native cases passed using the real validator, LocalFileBackend and runner with an echo backend/scripted judge. They include same-drive/case/separator references, unconditional controls, the missing-output merge, six satisfiable Boolean execution cases and two impossible-guard refusals.

All three selections had zero failures, errors or skips. Source imports were verified outside the checkout with PYTHONPATH cleared; Git cat was exposed to the test environment for the existing fixture. git diff --check passed and source stayed unchanged. Exact commands are in the updated PR description. No code commit or push was needed, and no review thread was resolved.

The earlier 16-failure broad native selection was not rerun. No new full-repository, frontend/browser, live-model, macOS, UNC-share, ACL or symlink validation is claimed. Current-head CI was observed at 21 passing checks and 2 intentional skips.

@LivXue

LivXue commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

No additional Windows compatibility issue found in the inspected scope.

Reviewed head 7340bef492c7c778b81d8a5b99bd1ad32f32a54c, base a2d391ea4c6dea0351b230333caef1ff6d8e08a0. Rechecked the complete 95-file comparison against the previous full inspection: 86 normalized patches are identical; the complete nine-file delta was inspected, including validation, tests, documentation/skill text and the TUI test formatting. Current Windows CI retains both DAG steps and the upstream diagnostics steps. Exact-head source blobs, local/GitHub diff agreement and git diff --check passed.

Code inspection: conditional artifact lookup now uses the same folded node identity as namespace reservation while retaining native separator/drive normalization and canonical-ID advice. The Boolean walker preserves negation and disjunction context. Traced both checks into graph validation, the existing resolver/renderer and runner guard evaluation.

Fresh native Windows verification, Python 3.13.12. This separately isolated rerun corroborates the native verification report:

  • 72 focused repository cases passed, 669 deselected: conditional references through all three entry points, case/separator/drive-relative aliases, canonical advice, valid/invalid Boolean guards and all four foreground/finish-text cases.
  • All 71 guard-suite tests passed.
  • 26 external native controls passed using the real validator, runner and LocalFileBackend under a path containing spaces/non-ASCII characters, with an echo backend and scripted judge. The prior alias refusals, unconditional reads and missing-output merge still work. New runner controls complete double-negated and satisfiable-disjunction guards, while a false guard leaves the merge not_executed with when_false.

The previous double-negation follow-up is corrected in these tested scenarios.

Setup: uv sync --frozen --python 3.13 --all-packages --extra dev; isolated snapshot/environment/profile/OAuth/temp paths, UV_NO_SYNC=1, PYTHONUTF8=1, and verified imports. Git's cat.exe was exposed only in the test process PATH for the existing finish-text fixture. Tested source blobs remained unchanged.

Limits: one guard case emitted an advisory idle-ceiling warning; strict idle mode was not enabled. No new full-suite, frontend/browser, live-model, Linux/macOS, absolute-path, UNC-share, ACL or symlink verification was performed.

Exact verification commands
# focused: 72 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\ab5372c6053347c693aeec4e4b0449c9\pr1001\source\head
uv run pytest tests/test_subagent_dag_core.py tests/test_subagent_dag_runner.py -k 'at_nodes or file_input_aimed or test_the_finish_text or test_branch_e2e_one_tool_call or satisfiable_guards_survive or impossible_guards_are_still_rejected' -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\ab5372c6053347c693aeec4e4b0449c9\checks\1001-focused\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\ab5372c6053347c693aeec4e4b0449c9\checks\1001-focused\junit.xml'
# guards: 71 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\ab5372c6053347c693aeec4e4b0449c9\pr1001\source\head
uv run pytest tests/test_subagent_dag_guard.py -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\ab5372c6053347c693aeec4e4b0449c9\checks\1001-guards\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\ab5372c6053347c693aeec4e4b0449c9\checks\1001-guards\junit.xml'
# native: 26 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\ab5372c6053347c693aeec4e4b0449c9\pr1001\source\head
uv run pytest -c pyproject.toml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\ab5372c6053347c693aeec4e4b0449c9\test_windows_branch_alias_fixed.py' 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\ab5372c6053347c693aeec4e4b0449c9\test_windows_branch_guard.py' -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\ab5372c6053347c693aeec4e4b0449c9\checks\1001-native\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\ab5372c6053347c693aeec4e4b0449c9\checks\1001-native\junit.xml'

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; this can merge as far as I am concerned.

Re-reviewed the rebased revision against current github/main. Range-diff confirms the seven previously reviewed commits are unchanged in substance; I inspected all seven new commits and their production callers. The config cleanup, UTF-8-safe route evidence, physical node-root path validation, CLI playbook judging, documentation corrections, and not-executed card layout fix are consistent with the existing contracts. I found no new plain error or blocker.

Verification: the focused Python suites for config, verdicts, registry persistence, DAG validation/execution, and CLI playbooks passed (1,021 tests). The targeted web card suite passed (28 tests). git diff --check passed. Exact-head page, TUI, Windows ACP, trajectory, lint, docs, bridge, repository, and packaging CI checks passed; the sharded Python and UI RPC jobs were still pending when reviewed. Tests were strengthened rather than weakened.

Covered the applicable AGENTS.md, CLAUDE.md, CONTEXT-MAP.md/CONTEXT.md rules, the full PR comparison and rebase history, changed callers and callees, backward compatibility, persistence/config behavior, filesystem alias boundaries, and CLI/runtime architecture. Patch-risk assessment: merge, human review required because the change affects public DAG/config behavior and persisted registry data; impact moderate, regression likelihood low, protection strong, recovery easy, confidence high. The assessment JSON validated against the patch-risk schema.

Handsome-wzw
Handsome-wzw previously approved these changes Oct 19, 2026
LivXue added 12 commits October 19, 2026 06:32
A node that declares routes is a decision node: Raven's per-node judge,
the existing verdict call, picks the route or routes the node's answer
satisfies, and a downstream node guarded with `when` runs only on its
route. Guards combine `<node>.<route>` and `<node>.done` atoms with not,
and, or, using three-valued logic. A node left out is not_executed with
a reason, and a merge node reads a missing upstream as a fixed sentence
written outside every untrusted fence.

Graphs with no routes and no `when` behave as before. The
subagentDag.verdictEnabled switch is retired by config migration 14.
Use native path and case normalization before matching a file reference to
a conditional node, and retain the declared node ID in rejection advice.
Verify the aliases against LocalFileBackend and actual filesystem reads.
Build expected artifact paths with Path joins in all four new foreground
finish-text cases. Run the alias and finish-text regressions in Windows CI,
making the installed Git cat executable available to the existing fixture.
Match the relative suffix of a native drive-qualified path before looking
up a conditional node. Preserve the declared ID and missing-output advice.

Extend the real-filesystem matrix across all three reference entry points,
including drive prefixes combined with case and separator aliases.
Match conditional artifact references using the session node identity on every host. Preserve native path normalization, canonical node advice and unconditional file reads.
Carry negation parity through conjunctions and reject a disjunction only when every arm is impossible. Preserve three-valued runtime evaluation and bounded validation without distributing route combinations.
…loor has run

Floor 14 stripped subagentDag.verdictEnabled only from a config stamped
below it. The build before this one reads a stamp of 14 as current,
keeps it and accepts the key, so a rollback, or a second install sharing
the home, could write the key back, and the next load of this build then
refused the whole config in load_raven_config. The strip now holds on
every load, as the retired sessionTitle and sentinel keys already do;
the notice is still given once, when the floor runs.
…egistry

An unpaired escape in the judge's JSON decodes to a lone surrogate. The
route quote kept it, the instance registry failed to encode it on flush,
and since the cache already held the row, every later registry write in
the process failed the same way, for every session, until a restart.
The run's manifest could not be written either.

The quote now keeps such a character as the escape it was written with,
before the 500-character cut. The registry also drops its cache when a
flush fails on the data rather than the disk, so a row the file cannot
hold fails its own write and no other.
… may not run

Rule 8 read only the @nodes/ spelling. The history root around the node
root is a reference root of its own, so an absolute path, as a finish
text prints it, or a path out of the workdir reached the same artifacts
and was accepted; the reader then failed at render on the missing file,
and the run reported that instead of the branch that was not taken.

Such a reference is now resolved the way the confinement check resolves
it, through one shared helper, and held to the same rule when it lands
in the node root the renderer reads. The tool, the runner and the
replan path pass that root to validation.
raven playbook run built its graph tool with no judge, so a playbook
that declares routes was refused before anything ran, while the
command's own compose prompt invites a branch. The tool now judges each
node as a conversation does, on the user's subagentDag settings and on
a pinned verdictModel's own provider, resolved by one helper both hosts
call. Nobody on the command line can answer a node that falls short, so
such a node fails on its judge's reason instead of waiting.
…fely

The skill said a single-select decision with no catch-all route fails
the node whenever its answer fits none of the conditions. The judge is
held to the declared routes, so it may instead pick the nearest one and
send the graph down a branch the answer does not support. The paragraph
now says both.
Retiring subagentDag.verdictEnabled changes what an upgraded build does,
so its entry moves from Added to Changed, beside the other behaviour
changes, and the test that pins it follows it.
The status copy floated into the title leaves its first line about
78px. A first word wider than that moved below the float, so the title
started under a blank line and its second line, ellipsis included, fell
outside the clamp. The not-executed title may now break a word where
that room ends; every other status keeps its title as it was.

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; this can merge as far as I am concerned.

Reviewed the rebased head against both the previously reviewed revision and the current GitHub base. Thirteen commit patches are identical; the remaining patch differs only in a CI comment updated for the Mirasim Windows job, with no executable change. I found no new defect and no weakened test.

Coverage included AGENTS.md and the routed domain context, the full github/main...HEAD diff, normalized history, relevant DAG/RPC/playbook/UI callers, backward compatibility, test integrity, and the existing architecture boundaries. The patch-risk assessment recommends merge; its human-review workflow label reflects the broad persistent-state and public-contract surface, not a blocking issue.

Verification on exact head: 63 focused DAG tests passed; the complete ui-web suite passed (4,104 tests); git diff --check passed; and GitHub's Windows ACP, all four Python unit shards, TUI, RPC-contract, lint, repository, page, bridge, wheel, installer, and trajectory jobs passed. Coverage aggregation was still pending when checked. My only review thread is already resolved.

@LivXue
LivXue merged commit 1b9cbf8 into main Oct 19, 2026
24 checks passed
@LivXue
LivXue deleted the feat/dag_conditional_branches branch October 19, 2026 07:17
yudongyouqing added a commit to yudongyouqing/Raven that referenced this pull request Oct 19, 2026
The pin from EverMind-AI#1001 asserted the silence this branch removes -- a cascade
skip on the pass that meets a replan got no event. It now expects that
frame, under a name and docstring that say the exit publishes it.
0xKT pushed a commit that referenced this pull request Oct 19, 2026
#1002)

## Summary

- A node the cascade skipped, or a stop cancelled, on the pass that
meets a replan got no `dag_node_updated` frame and no registry row until
the run finalized: that pass breaks at the replan before the sweep could
take it, so a live panel kept drawing the node as running.
- The replan exit now publishes `skipped` and `cancelled` alongside
`not_executed`, through `_publish_terminal` -- the helper #1001
introduced for exactly this stamping. One definition covers every pass,
and the end stamp is hard-assigned there: the helper's docstring
sentence and the exit's comment now say so instead of naming the replan
pass as announced by nobody.
- The first revision of this branch put a second copy of that publish
inside `_apply_replan`, stamped with `setdefault`, which kept an earlier
attempt's end on a node cancelled out of a retry and wrote a duration
that ran backwards (a start after its end). That copy is withdrawn in
favor of the shared helper; the regression test drives the
retry-cancelled-on-the-replan-pass case under a strictly increasing
counter clock, so the ordering it asserts cannot race the real clock's
millisecond resolution.
- #1001's pin test asserted the silence this branch removes; it now
expects the cascade skip's frame, under a name that says the exit
publishes it.

## Type

- [x] Fix

## Verification

- [x] Relevant tests pass locally: `uv run pytest
tests/test_subagent_dag_runner.py
tests/test_subagent_dag_adjudication.py tests/test_subagent_dag_core.py
tests/test_subagent_dag_control_tools.py tests/test_rpc_dag.py -q` ->
916 passed, after rebasing onto #1001
- [x] The three new tests fail on the merge base (no frames for the
settled nodes, and 1020 > 1010 under the counter clock) and pass on this
head
- [x] Relevant lint / type checks pass locally: `ruff check` and `ruff
format --check` on both files; `make lint-types` -> clean
- [x] User-facing docs or screenshots are updated when needed: no
user-facing surface changes; the frames carry statuses consumers already
read at finalization

## Risk

- [x] Security impact considered: announcement only; no value or path
crosses that did not before
- [x] Backward compatibility considered: a replan pass now emits one
extra `dag_node_updated` frame per cascade-skipped or stop-cancelled
node, which is the fix itself; the manifest's end stamps on that pass
are re-assigned rather than kept, so no duration runs backwards
- [x] Rollback path is clear: plain revert restores the silent exit

## Related Issues

N/A (adopts `_publish_terminal` from #1001)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants