Skip to content

fix: restore bundled ACP agent startup on Windows - #908

Merged
0xKT merged 8 commits into
mainfrom
fix/windows_agent_config_render
Oct 14, 2026
Merged

0xKT merged 8 commits into
mainfrom
fix/windows_agent_config_render

Conversation

@LivXue

@LivXue LivXue commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

Summary

Bundled ACP agents could fail to start on Windows when rendering configuration, reading stdin, or replacing the launcher process. Closing an ACP connection could also fail before releasing its process and transport because Windows has no os.killpg.

  • Keep all five bundled Windows launchers alive until their ACP child exits, forward the exit status, and apply the same behavior to new scaffolded launchers. POSIX retains exec.
  • Preserve live rendered configurations with the cross-platform PID probe, retain POSIX file permissions, and preserve seed-file line endings.
  • Read Windows stdin pipes through a daemon feeder with PeekNamedPipe and reads bounded by the available bytes.
  • End the live launcher's Windows process tree with bounded taskkill before releasing the transport. Leave an already-exited launcher's PID alone.
  • Preserve a completed session/new reply when EOF shutdown cancels its optional model-menu lookup. The first LiteLLM import can exceed the five-second grace even after fix(providers): keep model metadata lookups offline when requested #909 removed the metadata-network delay. Active-connection cancellation and cancellation during required session setup still propagate.
  • Run real stdio handshakes, all five launchers, command registration, and native teardown cases in Windows CI. The handshake now also runs with a fresh bytecode cache.

Type

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

Verification

Native Windows build 10.0.26300, Python 3.13.12, PYTHONUTF8=1. The final tree is based on the fetched main revision; all eight patches replayed unchanged. The last upstream update adds bug-report upload/receiver code and has no file overlap with this PR. The 19-case selection below was rerun on the final tree; the broader selection and controls predate that last unrelated update. Tests used isolated user/Raven homes, frozen dependencies, and verified imports from the tested checkout. Run-owned --basetemp and --junitxml output paths are omitted from the selections below.

  • uv run --frozen --no-sync pytest tests/test_acp_server.py::TestFrameLoop::test_session_creation_answers_when_eof_cancels_the_catalogue tests/test_acp_methods.py::TestConfigOptions::test_session_creation_preserves_cancellation_outside_shutdown_metadata tests/integration/test_acp_stdio_smoke.py tests/test_cli_agents_commands.py::test_register_pins_the_resolved_row_into_the_host_roster tests/test_subagent_acp.py::test_close_without_killpg_ends_the_tree_through_taskkill tests/test_subagent_acp.py::test_close_without_killpg_kills_nothing_by_the_pid_of_an_exited_launcher -m 'integration or not integration' -n 0 --tb=short: 19 passed, no skips after the final rebase. This includes both EOF timings, cancellation controls, both bytecode-cache states, all five real launchers, and real taskkill. One non-failing idle-ceiling warning was reported.
  • uv run --frozen --no-sync pytest tests/test_acp_server.py tests/test_acp_methods.py tests/test_acp_updates.py tests/test_acp_config_options.py tests/test_cli_acp_commands.py tests/test_config_product_render.py tests/test_utils_pid.py tests/test_agents_code_launcher.py tests/test_agents_design_launcher.py tests/test_agents_oncall_launcher.py tests/test_agents_ppt_launcher.py tests/test_agents_research_launcher.py tests/test_cli_agents_commands.py tests/test_subagent_acp.py tests/test_rpc_bootstrap.py tests/test_subagent_host_env.py tests/test_subagent_probe.py -n 0 --tb=short: 1284 passed, 58 failed, 10 skipped, 4 errors. The same selection on the rebased PR immediately before the EOF fix returned 1280 passed, the identical 58 failing and 4 error test IDs, and 10 skipped. No new failure/error IDs from the EOF fix.
  • All 62 failure/error IDs were also selected on main: 56 failed, 4 errored, and 2 passed. Those two passed in isolation on this PR too, and failed in the complete selection both before and after the EOF fix. The remaining failures include Windows path, shell/command, and symlink-privilege cases. The attempted complete main selection stalled in the pre-existing Windows stdin tests and was terminated; no complete main-suite result is claimed.
  • Before the EOF fix, uv run --frozen --no-sync pytest tests/integration/test_acp_stdio_smoke.py::test_a_whole_handshake_runs_in_one_process[cold_imports] -m integration -n 0 --tb=short failed with response IDs [1, 3]. A separate stack-capture probe measured 6.76 seconds in the first LiteLLM import; the handler awaited model.options while its worker imported LiteLLM and its dependencies.
  • uv run --frozen --no-sync pytest tests/test_acp_server.py::TestFrameLoop::test_session_creation_answers_when_eof_cancels_the_catalogue tests/test_acp_methods.py::TestConfigOptions::test_session_creation_preserves_cancellation_outside_shutdown_metadata -n 0 --tb=short: before the fix, 2 failed and 2 passed. Both EOF cases failed with the missing session reply. They pass with the fix and are included in the broader post-rebase selection above. Warm/cold real handshakes plus these four cases also passed as a six-case selection.
  • A mutant that treats every connection as closing makes the ordinary-cancellation control fail. Cancellation during required session setup still propagates.
  • uv run --frozen --no-sync ruff check raven evolver agents plugins-dist tests scripts docs-site and the corresponding ruff format --check: passed; 2266 files already formatted.
  • uv run --frozen --no-sync lint-imports: 10 contracts kept. uv run --frozen --no-sync ty check raven/acp/methods.py raven/acp/updates.py raven/acp_client/client.py raven/cli/acp_commands.py raven/config/product_render.py: passed.
  • uv run --frozen --no-sync pre-commit run --from-ref origin/main --to-ref HEAD: every applicable hook passed. The two final docstring edits also passed their file-scoped pre-commit run.
  • node node_modules/@commitlint/cli/cli.js --from origin/main --to HEAD --config commitlint.config.cjs, uv run --frozen --no-sync python scripts/check_commit_messages.py origin/main..HEAD, uv run --frozen --no-sync python scripts/check_large_files.py origin/main...HEAD, uv run --frozen --no-sync python scripts/check_source_language.py origin/main...HEAD, and git diff --check origin/main...HEAD: passed.
  • The PR body passed the repository body checker and an explicit ASCII scan.

The broader Windows selection remains red, so the relevant-tests checkbox is unchecked. This revision has no new Linux or full repository run, live LLM workload, rendering workload, or ACL audit. POSIX-only skips are not counted as Windows passes.

  • 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

At EOF, a completed session/new can omit optional configOptions when shutdown cancels a slow lookup; the sessionId and session modes are retained. Active connections keep their existing model-menu behavior. The handler grace remains five seconds.

Windows keeps the launcher present while its server runs, and connection close waits up to five seconds for taskkill. Idle Windows pipe input polls every 10 ms. Redirected files and text streams retain their existing reader path. POSIX retains exec and 0600 permissions; Windows rendered files inherit directory ACLs.

There is no configuration-format change. Previously generated agent folders retain their existing launchers; the scaffold change applies to new folders. Revert this PR to roll back the launcher, stdin, process-cleanup and EOF-response changes.

Related Issues

#909

@LivXue
LivXue requested review from 0xKT and gloryfromca October 11, 2026 10:55

@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.

I reviewed the full diff and surrounding ACP launcher/client and rendered-config code, the relevant history, AGENTS.md/CLAUDE.md and ACP context documentation, backward compatibility of the POSIX exec path, Windows process/config lifetime behavior, and whether the test edits weakened existing coverage. I found no concrete regression or project-rule violation.

Verification:

  • uv run --all-extras pytest tests/test_cli_acp_commands.py tests/test_config_product_render.py tests/test_agents_code_launcher.py tests/test_agents_design_launcher.py tests/test_agents_oncall_launcher.py tests/test_agents_ppt_launcher.py tests/test_agents_research_launcher.py tests/test_cli_agents_commands.py -x - 475 passed, 1 skipped (case-sensitive filesystem condition).
  • uv run --all-extras pytest -m integration tests/integration/test_acp_stdio_smoke.py -x - 11 passed.
  • git diff --check github/main...HEAD - passed.

The initial test invocation without --all-extras stopped during fixture setup because raven_everos was not installed; rerunning with the repository-declared extras resolved the environment and produced the results above.

@LivXue

LivXue commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

No blockers in the reviewed scope.

Scope: I reviewed the full PR diff (19 changed files), including tests and documentation, and traced affected callers and contracts at bd68335530c9. This review covers all changed areas, not only raven/agent.

Checked all five bundled launchers and the scaffold, the Windows child-wait path, POSIX exec behavior, native stdin pipe reads, EOF/error handling, stdout isolation, PID-based render cleanup, fd ownership, fchmod availability, and seed-file newlines.

Verification (native Windows, Python 3.12, isolated source snapshots; local JUnit-output options omitted below):

  • PR head, unit suites: uv run --no-sync --with truststore==0.10.4 pytest -o addopts= tests/test_cli_acp_commands.py tests/test_config_product_render.py tests/test_agents_code_launcher.py tests/test_agents_design_launcher.py tests/test_agents_oncall_launcher.py tests/test_agents_ppt_launcher.py tests/test_agents_research_launcher.py tests/test_cli_agents_commands.py -q --tb=short -> 464 passed, 1 failed, 3 errors, 8 skipped.
  • PR head, real stdio: uv run --no-sync --with truststore==0.10.4 pytest -o addopts= tests/integration/test_acp_stdio_smoke.py -q --tb=short -> 10 passed, 1 failed.
  • merge-base stdio control: uv run --no-sync --with truststore==0.10.4 pytest -o addopts= tests/integration/test_acp_stdio_smoke.py -q --tb=short -> 6 failed.
  • combined tree with fix(providers): keep model metadata lookups offline when requested #909: uv run --no-sync --with truststore==0.10.4 pytest -o addopts= tests/integration/test_acp_stdio_smoke.py -q --tb=short -> 11 passed.

Merge readiness: GitHub and git merge-tree --write-tree --name-only origin/main bd68335530c9623df1f25af75dd1e100d6b08eae report a conflict in tests/test_cli_agents_commands.py. Resolve it and rerun verification before merging. I recommend landing #909 first: the one remaining finite-input handshake failure at this head passed when combined with #909, and the unchanged stdio suite then passed 11/11, including all five bundled launchers. The unit residue is a POSIX mode assertion and three Windows symlink-privilege setup errors. Results above include the already-declared truststore 0.10.4 dependency; the initial environment did not have it. The combined tree is a local composition of the two heads, not an already-merged main revision.

git diff --check over the complete merge-base-to-head range passed. No new inline finding is being filed.

@LivXue

LivXue commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

Windows compatibility review: no new issue found in the inspected changes.

Reviewed the complete 19-file diff at head bd68335530c9623df1f25af75dd1e100d6b08eae against base 612696546702411e63d22f4b87d742d5e78b146b, including all five launchers, the scaffold, native stdin reading, rendered-config/PID lifetime, file-descriptor cleanup, permissions and line endings. Traced the platform guards and existing PID helper.

Native Windows / Python 3.13.12, detached temporary checkout with PYTHONUTF8=1, UV_NO_SYNC=1 and isolated user/Raven homes:

uv run pytest tests/test_config_product_render.py tests/test_cli_acp_commands.py tests/test_agents_code_launcher.py tests/test_agents_design_launcher.py tests/test_agents_oncall_launcher.py tests/test_agents_ppt_launcher.py tests/test_agents_research_launcher.py -n 0 -x --tb=short --basetemp "$reviewRunDir/pr908/tests"
# 397 passed, 7 skipped (POSIX modes/pipe transport).

uv run pytest tests/integration/test_acp_stdio_smoke.py -m integration -k bundled_launcher -n 0 -x --tb=short --basetemp "$runDir/pr908/integration-tests"
# 5 passed, 6 deselected.

uv run pytest tests/test_cli_agents_commands.py -k default_landing -n 0 -x --tb=short --basetemp "$runDir/pr908/scaffold-tests"
# 1 passed, 71 deselected.

The five real-launcher cases each answered two initialize requests with stdin kept open, retained the live rendered config during a sweep, and exited on EOF. Unit coverage also exercised native pipe reads and child-exit propagation.

Limits: synthetic credentials, memory service disabled, no LLM tasks or rendering workloads. The finite-input session/new handshake and the broader unit failures described in the existing review were not rerun in this selection; these results do not resolve that separate coverage gap or establish full Windows compatibility.

@0xKT

0xKT commented Oct 12, 2026

Copy link
Copy Markdown
Member

Merge conflict: @LivXue this PR no longer merges onto the current main. git merge-tree --write-tree of main fa29b27 and this head bd68335 exits 1 with one conflict:

  • tests/test_cli_agents_commands.py (content)

The reviewer's latest verdict on this head is No blockers. Once the conflict is resolved (rebase onto main, or merge main in) and the new head has a verdict, the board queues it again by itself.

@gloryfromca

Copy link
Copy Markdown
Member

Blocking: resolve the content conflict with current main in tests/test_cli_agents_commands.py before merging.

Confirmed against github/main at fa29b2744999: git merge-tree --write-tree HEAD github/main exits 1 on that file. The unchanged head remains internally green for the affected module: uv run --all-extras pytest tests/test_cli_agents_commands.py -x passed 72 tests. A rebased conflict resolution will be a new revision requiring its own verdict.

@LivXue
LivXue force-pushed the fix/windows_agent_config_render branch from bd68335 to 949778e Compare October 12, 2026 04:05

@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.

The rebase resolves the prior merge-conflict blocker. I reviewed the patch-to-patch delta, the current github/main...HEAD diff, the resolved test in context, current callers and compatibility assumptions, and retained the earlier full-scope review of project rules, architecture, history, and test strength. The resolution preserves main's Windows-safe permission-test guard while adding this PR's scaffold assertions; git range-diff shows no other semantic change to the patch.

Verification on the rebased head:

  • git merge-tree --write-tree HEAD github/main - passed.
  • git diff --check github/main...HEAD - passed.
  • Focused unit suites - 480 passed, 1 skipped for the documented case-sensitive-filesystem condition.
  • ACP stdio integration suite - 11 passed.

@LivXue

LivXue commented Oct 12, 2026

Copy link
Copy Markdown
Member Author

Windows re-review: the rebased launcher checks pass, but the previously reported finite-input handshake failure remains.

Scope: the complete 19-file diff at 949778ec4571e291a27cacde03abe9266c742bd2 against base/merge base fa29b274499968fbad02c74d31f3eee85c5dd2ef, including all five launchers, the scaffold, stdin handling, PID-based render cleanup, permissions and newlines. I found no additional Windows compatibility defect in those changes.

Code inspection: git range-diff confirms the rebase retains main's Windows/root permission-test guard and the scaffold assertions. The changed production files are byte-identical to the previously reviewed head bd68335530c9623df1f25af75dd1e100d6b08eae. The ACP server/methods and model-metadata files are also byte-identical between this PR's base and head.

Native Windows / Python 3.13.12 verification, in an isolated detached checkout after uv sync --frozen --extra dev, with PYTHONUTF8=1, UV_NO_SYNC=1 and isolated user/Raven/temp directories:

  • Unit selection: 409 passed, 8 skipped for the documented platform-specific permission/pipe checks.
  • Real stdio integration, across the selections below: 10 passed, 1 failed. All five launchers answered repeated initialize requests, retained their live rendered configs during a sweep, and exited on EOF.
  • Existing failure reproduced: test_a_whole_handshake_runs_in_one_process at tests/integration/test_acp_stdio_smoke.py:171 received response IDs [1, 3], missing session/new ID 2 after finite input/EOF. This confirms the earlier handshake discussion still applies at this head; the successful launcher cases do not resolve it.

Exact test selections ($R denotes this run's isolated directory):

$R = 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\26804879b5e54446be3d865cc00f408b'
uv run pytest tests/test_config_product_render.py tests/test_utils_pid.py tests/test_cli_acp_commands.py tests/test_agents_code_launcher.py tests/test_agents_design_launcher.py tests/test_agents_oncall_launcher.py tests/test_agents_ppt_launcher.py tests/test_agents_research_launcher.py tests/test_cli_agents_commands.py::test_the_default_landing_is_the_raven_home_agents_tree tests/test_cli_agents_commands.py::test_an_unwritable_target_refuses_cleanly -n 0 -x --tb=short --basetemp "$R/unit-pytest"
# 409 passed, 8 skipped

uv run pytest tests/integration/test_acp_stdio_smoke.py -m integration -k bundled_launcher -n 0 -x --tb=short --basetemp "$R/launchers-pytest"
# 5 passed, 6 deselected

uv run pytest tests/integration/test_acp_stdio_smoke.py -m integration -k 'not bundled_launcher' -n 0 -x --tb=short --basetemp "$R/finite-stdin-pytest"
# 3 passed, 1 failed, 5 deselected; -x stopped before the last two selected tests

uv run pytest tests/integration/test_acp_stdio_smoke.py::test_a_batch_of_requests_is_all_answered tests/integration/test_acp_stdio_smoke.py::test_closing_stdin_exits_cleanly -m integration -n 0 -x --tb=short --basetemp "$R/remaining-pytest"
# 2 passed

Limits: synthetic credentials and disabled memory service in launcher profiles; no LLM tasks or rendering workloads. Base comparison here was source inspection; a combined tree with #909 was not tested. This is not a full Windows compatibility pass.

@gloryfromca

Copy link
Copy Markdown
Member

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

The native Windows evidence changes my stance from clean to nonblocking: the finite-input handshake failure is real, but it is not introduced by this revision, and this change improves the merge-base behavior from six stdio failures to one. The author has assigned the remaining metadata-fetch delay to open PR #909, whose combined-tree run passes all 11 smoke tests, so I am carrying that as the named follow-up rather than blocking #908.

Local confirmation on Linux: uv run --all-extras pytest -m integration tests/integration/test_acp_stdio_smoke.py::test_a_whole_handshake_runs_in_one_process -x passed. The native Windows failure reported above is the relevant platform result.

@LivXue
LivXue force-pushed the fix/windows_agent_config_render branch from 949778e to 5ed268e Compare October 12, 2026 05:21
@LivXue
LivXue changed the base branch from main to fix/model_catalog_cache_only October 12, 2026 05:21

@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.

The target now contains the #909 metadata fix, resolving the previously named finite-input follow-up. I reviewed the complete diff against fix/model_catalog_cache_only, compared the production/test patch with the previously reviewed #908 revision, inspected the new Windows workflow in context, and rechecked callers, compatibility, project rules, architecture, and test strength. The prior patch is unchanged; the only new #908 delta is dedicated native Windows coverage for the full ACP stdio module.

Verification:

  • GitHub Windows ACP stdio job - passed all 11 tests on Windows/Python 3.13.
  • Focused local unit suites - 480 passed, 1 documented filesystem skip.
  • Local ACP stdio integration suite - 11 passed.
  • git merge-tree --write-tree HEAD github/fix/model_catalog_cache_only - passed.
  • git diff --check github/fix/model_catalog_cache_only...HEAD - passed.

@LivXue

LivXue commented Oct 12, 2026

Copy link
Copy Markdown
Member Author

Windows compatibility re-review: the previously reported finite-input handshake now passes at this stacked revision. No additional issue was found in the inspected changes.

Reviewed the complete 20-file diff at 5ed268e0c6d8253f17ae16bb754ab45c95e5d1b6 against base/merge base 78ed94ee153e9346f2803d69dc4288e9f911b3b4 (#909). The five launchers, scaffold, stdin feeder and rendered-config changes are unchanged from the prior review; the added Windows CI job uses Python 3.13, frozen dependencies and the full ACP stdio module. I also checked its workflow context and the now-included metadata fix.

Native Windows / Python 3.13.12, isolated detached checkout after uv sync --frozen --extra dev, with PYTHONUTF8=1, UV_NO_SYNC=1 and isolated user/Raven/temp directories:

$R = 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\b89bdffc4a4845fb988b4088f488acef/pr908'
uv run pytest tests/test_config_product_render.py tests/test_utils_pid.py tests/test_cli_acp_commands.py tests/test_agents_code_launcher.py tests/test_agents_design_launcher.py tests/test_agents_oncall_launcher.py tests/test_agents_ppt_launcher.py tests/test_agents_research_launcher.py tests/test_cli_agents_commands.py::test_the_default_landing_is_the_raven_home_agents_tree tests/test_cli_agents_commands.py::test_an_unwritable_target_refuses_cleanly -n 0 -x --tb=short --basetemp "$R/unit-pytest"
# 409 passed, 8 platform-related skips
uv run pytest tests/integration/test_acp_stdio_smoke.py -m integration -n 0 -x --tb=short --basetemp "$R/integration-pytest"
# 11 passed

The complete handshake now receives IDs 1, 2, 3, closing the failure reproduced on the previous head in this tested combination. All five real launchers also answered repeated initialize requests while stdin stayed open, retained their live rendered configs during a sweep, and exited cleanly on EOF. This result depends on the #909 base now included here.

Limits: synthetic credentials and disabled memory service in launcher profiles; no LLM tasks, rendering workloads or full repository suite. The skipped checks cover POSIX permissions/pipe transport, not passing Windows equivalents.

@LivXue
LivXue force-pushed the fix/windows_agent_config_render branch from 5ed268e to c89312c Compare October 12, 2026 08:39

@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: restore the stacked branch relationship so this PR cleanly contains only #908 over its declared #909 target.

GitHub currently reports this revision as non-mergeable (dirty) with 2,386 changed files because the head rebased #909 and #908 onto newer main history while fix/model_catalog_cache_only still points at 78ed94ee153e. git merge-tree --write-tree HEAD github/fix/model_catalog_cache_only confirms content conflicts in agents/raven-design/run.py, raven/config/product_render.py, tests/test_agents_design_launcher.py, and tests/test_config_product_render.py.

This late-round blocker meets all three required conditions: this revision introduced the history mismatch; every ordinary merge attempt reaches it; and the operator has no working merge path until the branch relationship is repaired. The logical three-patch series itself remains equivalent by git range-diff (apart from CI-commit author metadata), and uv run --all-extras pytest -m integration tests/integration/test_acp_stdio_smoke.py -x still passes all 11 tests.

@LivXue
LivXue force-pushed the fix/model_catalog_cache_only branch from 78ed94e to 38ebfd8 Compare October 12, 2026 09:12
@LivXue
LivXue force-pushed the fix/windows_agent_config_render branch from c89312c to 2ea3464 Compare October 12, 2026 09:12

@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.

The prior stacked-branch blocker is resolved: the target now points to the matching rebased fix/model_catalog_cache_only history, the 20-file PR diff is restored, and git merge-tree --write-tree HEAD github/fix/model_catalog_cache_only succeeds.

I rechecked the repository rules, full target diff, affected callers and contracts, history/range-diff, backward compatibility, test changes for weakening, and the launcher/stdin/render-lifetime architecture constraints. The range-diff shows no semantic change to the previously reviewed patches; only commit metadata changed.

Verification on 2ea34646a2fa:

  • git diff --check github/fix/model_catalog_cache_only...HEAD passed.
  • uv run --all-extras pytest -m integration tests/integration/test_acp_stdio_smoke.py -x passed: 11 tests.
  • The exact-head GitHub Actions Windows ACP stdio job passed, including ACP handshakes and bundled launchers.

@LivXue

LivXue commented Oct 12, 2026

Copy link
Copy Markdown
Member Author

Windows compatibility re-review: no new issue found in the inspected scope. One inherited Windows test failure remains.

Reviewed the complete 20-file PR diff at 2ea34646a2fad68fd3496a1675e946220cb37a6a; API base 38ebfd856d23fa0ca5fb333bbaf84c1a7d88e92f. Checked all five launchers and the scaffold, inherited stdin/EOF, process ownership, stale-render PID probing, UTF-8/CRLF seed handling, file-permission guards and Windows CI configuration.

Git checks: source tree identical to previously reviewed 5ed268e0c6d8253f17ae16bb754ab45c95e5d1b6; diff whitespace check passed and the merge-tree check against the recorded API base returned exit 0. The earlier branch-history blocker is resolved.

Native Windows / Python 3.13.12, detached checkout, PYTHONUTF8=1, UV_NO_SYNC=1, isolated user/Raven/temp directories. Reused a uv environment with matching pyproject.toml/uv.lock; PYTHONPATH selected this checkout and its bundled plugin roots, and the loaded Raven source path was asserted.

PR head:

uv run pytest 'tests/test_agents_code_launcher.py' 'tests/test_agents_design_launcher.py' 'tests/test_agents_oncall_launcher.py' 'tests/test_agents_ppt_launcher.py' 'tests/test_agents_research_launcher.py' 'tests/test_cli_acp_commands.py' 'tests/test_cli_agents_commands.py' 'tests/test_config_product_render.py' -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fa39f19b3fcb4d7daa4ad3b9472340f8\pr908\unit-pytest'
# 1 failed, 293 passed, 6 skipped in 30.45s; exit 1

PR head:

uv run pytest 'tests/integration/test_acp_stdio_smoke.py' -m integration -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fa39f19b3fcb4d7daa4ad3b9472340f8\pr908\integration-pytest'
# 11 passed in 52.60s; exit 0

Exact base control:

uv run pytest 'tests/test_cli_agents_commands.py::test_register_pins_the_resolved_row_into_the_host_roster' -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fa39f19b3fcb4d7daa4ad3b9472340f8\pr908\control-pytest'
# 1 failed in 8.58s; exit 1

Additional head coverage:

uv run pytest 'tests/test_config_product_render.py' 'tests/test_cli_agents_commands.py::test_the_default_landing_is_the_raven_home_agents_tree' -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\fa39f19b3fcb4d7daa4ad3b9472340f8\pr908\remainder-pytest'
# 127 passed, 1 skipped in 10.78s; exit 0

Reproduced on both head and exact base: the registration test assumes the stored command starts with an unquoted sys.executable. The unchanged Windows command formatter correctly adds surrounding double quotes, so startswith(sys.executable) is false. This is inherited, not a regression introduced by this PR. The 11 real stdio tests passed, including finite-input handshake responses and repeated requests through every bundled launcher.

Limits: The unit run stopped at an inherited assertion; the later render/scaffold selection passed separately. No live LLM task, installed-agent login, rendering workload, ACL-hardening audit or full repository suite was run. Four non-failing idle-ceiling warnings occurred in integration; strict idle checking was not enabled. This scoped result does not establish full Windows compatibility.

@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.

This revision fixes the inherited Windows-only assertion by decoding the stored launcher command with the same production helper used for spawning, then checking the complete argv. It also runs that regression test in the existing Windows ACP job. The change strengthens coverage without changing runtime behavior or weakening a test.

I reviewed the revision delta, repository rules, the command decoder contract and history, affected registration caller, cross-platform compatibility, test intent, and the unchanged launcher/stdin/render-lifetime architecture. The target merge-tree and diff whitespace checks pass.

Verification:

  • uv run --all-extras pytest tests/test_cli_agents_commands.py::test_register_pins_the_resolved_row_into_the_host_roster tests/integration/test_acp_stdio_smoke.py -m "integration or not integration" -n 0 -x --tb=short: 12 passed.
  • Exact-head GitHub Actions Windows ACP stdio: passed.
  • GitHub reports the head mergeable with the recorded target.

@LivXue

LivXue commented Oct 12, 2026

Copy link
Copy Markdown
Member Author

Windows compatibility re-review: the registration assertion is fixed; no new issue identified in the inspected changes. One intermittent EOF failure is recorded below.

Reviewed the complete 20-file diff at 69cb19f79e16e381fd046e2de22b5bd3a3bc7bf3 against base/merge base 38ebfd856d23fa0ca5fb333bbaf84c1a7d88e92f, including launchers, stdin/EOF handling, rendered-config lifetime and CI. Runtime code is unchanged from the previous review; this revision changes the registration test to verify the decoded interpreter, script and ACP arguments and adds it to Windows CI. Diff-whitespace and merge-tree checks passed.

Native Windows / Python 3.13.12, exact detached checkout, isolated user/Raven/temp directories, PYTHONUTF8=1 and UV_NO_SYNC=1; the existing uv environment has the same lockfile and imports were checked against this checkout:

uv run pytest 'tests/test_cli_agents_commands.py::test_register_pins_the_resolved_row_into_the_host_roster' 'tests/integration/test_acp_stdio_smoke.py' -m 'integration or not integration' -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\51c9a14534a54a89bcca3d83da323996\pr908\acp-pytest'
# First attempt: 4 passed, 1 failed; exit 1
uv run pytest 'tests/test_cli_agents_commands.py::test_register_pins_the_resolved_row_into_the_host_roster' 'tests/integration/test_acp_stdio_smoke.py' -m 'integration or not integration' -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\51c9a14534a54a89bcca3d83da323996\pr908\acp-isolated-pytest'
# Separate rerun: 12 passed; exit 0

The formerly failing registration case passed in both runs. In the first run, test_a_whole_handshake_runs_in_one_process received IDs 1 and 3 but not 2; its log reports one handler cancelled after the 5-second EOF grace period. The separate rerun passed the registration case and all 11 real stdio tests, including all five bundled launchers. This does not establish that the intermittent failure is resolved or caused by this test/CI-only revision. Non-failing idle-ceiling warnings occurred; strict idle checking was not enabled.

Limits: no full repository suite, live LLM workload, rendering task or ACL audit. This is a scoped Windows review, not a compatibility certification.

@LivXue
LivXue force-pushed the fix/model_catalog_cache_only branch from 38ebfd8 to 4ba33b8 Compare October 12, 2026 13:00
@LivXue
LivXue force-pushed the fix/windows_agent_config_render branch from 69cb19f to be55e81 Compare October 12, 2026 13:01

@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.

The rebased first three patches are semantically unchanged. The two new commits fix the Windows close path that previously called unavailable os.killpg: Windows now ends the launcher tree with bounded taskkill /T /F before the existing process cleanup, while POSIX retains the established process-group SIGKILL path. The added tests directly cover Windows fallback behavior, PID-reuse safety, and POSIX child cleanup; no test was weakened.

I checked the repository rules, actual target diff and range-diff history, close callers and process ownership contract, Windows/POSIX backward compatibility, the prior EOF evidence, test strength, and the launcher/stdin/render-lifetime architecture. The refreshed target merge-tree and diff whitespace checks pass.

Verification:

  • uv run --frozen --python 3.13 --no-sync pytest tests/test_subagent_acp.py -n 0 -x --tb=short: 312 passed.
  • Focused registration and ACP stdio suite: 12 passed.
  • Exact-head GitHub Actions Windows ACP stdio: passed.
  • The initial --all-extras attempt did not run tests because this host lacks the C compiler needed by the optional datrie dependency in the rebased target; the frozen CI-shaped runs above completed normally.

@LivXue

LivXue commented Oct 12, 2026

Copy link
Copy Markdown
Member Author

Windows compatibility re-review: one new Windows test defect reproduced.

Scope: the complete 22-file PR diff at head be55e81e308423caf0b31a8d2d6f931cede55e20, against API base/merge base 4ba33b8fe3b16fb6246d1dd911c66707b4d6c1af. Checked all five launchers, scaffold, native stdin/EOF, rendered-config lifetime, permissions/newlines, ACP process-tree teardown, tests and Windows CI.

P2 - Make the new Windows teardown fixture tolerate an already-absent attribute. tests/test_subagent_acp.py:3193 calls monkeypatch.delattr(os, "killpg") with its default raising=True. Native Windows has no os.killpg, so both new test_close_without_killpg_* tests raise AttributeError: killpg before reaching client.close(). The first has already launched a real child and also leaves pending-task diagnostics. This helper and both tests are new relative to the recorded base. Use raising=False (or guard the deletion), and ensure the launched client is closed in a finally block. Include these tests in native Windows coverage; the new CI job currently selects the stdio module and registration test only.

Native Windows, Python 3.13.12, isolated detached checkout and user/Raven/temp directories. Dependencies were synced with uv sync --frozen --extra dev; the reused uv environment matches this head's pyproject.toml and uv.lock. PYTHONPATH selected this checkout and its bundled plugin roots; the loaded Raven path was asserted. PYTHONUTF8=1, UV_NO_SYNC=1, UV_PROJECT_ENVIRONMENT=$R/venv. Below, $R = 'C:/Users/livxu/AppData/Local/Temp/raven-windows-review/7083af8abaec45c385fd83020f19e718'.

uv run pytest tests/test_subagent_acp.py -k close -n 0 -x --tb=short --basetemp "$R/checks/908-close/pytest" --junitxml "$R/checks/908-close/junit.xml"
# 1 passed, 1 failed; 307 deselected. Failure: AttributeError at line 3193.
uv run pytest tests/test_subagent_acp.py::test_close_without_killpg_kills_nothing_by_the_pid_of_an_exited_launcher -n 0 -x --tb=short --basetemp "$R/checks/908-second-close/pytest" --junitxml "$R/checks/908-second-close/junit.xml"
# 1 failed with the same AttributeError.
uv run pytest tests/test_config_product_render.py tests/test_utils_pid.py tests/test_cli_acp_commands.py tests/test_agents_code_launcher.py tests/test_agents_design_launcher.py tests/test_agents_oncall_launcher.py tests/test_agents_ppt_launcher.py tests/test_agents_research_launcher.py tests/test_cli_agents_commands.py::test_register_pins_the_resolved_row_into_the_host_roster tests/test_cli_agents_commands.py::test_the_default_landing_is_the_raven_home_agents_tree -n 0 -x --tb=short --basetemp "$R/checks/908-unit/pytest" --junitxml "$R/checks/908-unit/junit.xml"
# 410 passed, 7 skipped (POSIX modes/pipe-transport checks).
uv run pytest tests/integration/test_acp_stdio_smoke.py -m integration -n 0 -x --tb=short --basetemp "$R/checks/908-stdio/pytest" --junitxml "$R/checks/908-stdio/junit.xml"
# First run: 3 passed, 1 failed; handshake replies were [1, 3], missing session/new.
uv run pytest tests/integration/test_acp_stdio_smoke.py -m integration -n 0 -x --tb=short --basetemp "$R/checks/908-stdio-isolated/pytest" --junitxml "$R/checks/908-stdio-isolated/junit.xml"
# Separate rerun: 11 passed, including all five real bundled launchers.
uv run pytest C:/Users/livxu/AppData/Local/Temp/raven-windows-review/7083af8abaec45c385fd83020f19e718/test_native_acp_cleanup.py -s -n 0 -x --tb=short --basetemp "$R/checks/908-native-close/pytest" --junitxml "$R/checks/908-native-close/junit.xml"
# 1 passed; scratch probe, real taskkill, launcher and its worker both exited.

The scratch cleanup probe launched a Python launcher and worker from a path containing spaces and non-ASCII characters, held a handle to the worker, and verified both exited after the production AcpClient.close(), without mocking taskkill. This supports the runtime fix independently of the broken fixture.

The first stdio failure matches the previously recorded intermittent symptom; the separate green run does not establish that it is resolved. The stdin/server and smoke-test sources are unchanged from that reviewed revision; no new causal attribution is made. No LLM task, rendering workload or full repository suite was run.

@gloryfromca

Copy link
Copy Markdown
Member

Blocking: make the new Windows teardown fixture tolerate an absent os.killpg and run it in Windows CI.

The report holds and changes my stance from clean. MonkeyPatch.delattr(...) defaults to raising=True; simulating a platform where os.killpg is already absent reproduces AttributeError: killpg. The two targeted tests pass on Linux, but that does not refute the native Windows failure because Linux starts with the attribute present.

The production cleanup probe is useful evidence that the runtime fix works. The blocker is the newly added Windows-path coverage itself: use raising=False or guard the deletion, ensure a launched client is closed if fixture setup fails, and include these cases in the Windows job. The native run failed before either test exercised AcpClient.close(), while the current Windows workflow does not select this module.

@LivXue

LivXue commented Oct 13, 2026

Copy link
Copy Markdown
Member Author

Fixed the native teardown-test blocker in a1dcfd06ebccb1239fc00a0de3cdde305cb449a4.

The helper tolerates an already-absent os.killpg. On Windows it runs real taskkill and records its arguments and exit status; the live test completes an ACP handshake first and requires exit 0. Both launched clients are closed in finally, including fixture/assertion failures. Both regressions now run in the Windows ACP CI job. The existing POSIX simulation remains.

Native Windows build 10.0.26300, Python 3.13.12, PYTHONUTF8=1:

  • Before the fix, the exited-launcher regression failed with AttributeError: killpg at the shared helper.
  • uv run --frozen --no-sync pytest tests/test_subagent_acp.py -k close_without_killpg -n 0 -x --tb=short: 2 passed, 310 deselected, no skips.
  • uv run --frozen --no-sync pytest tests/test_cli_agents_commands.py::test_register_pins_the_resolved_row_into_the_host_roster tests/integration/test_acp_stdio_smoke.py -m 'integration or not integration' -n 0 -x --tb=short: 12 passed, no skips. A final combined run of these 14 cases also passed; the finite-input handshake emitted one non-failing idle-ceiling warning.
  • Independent native process probe: 1 passed; real taskkill stopped both an owned launcher and its worker, checked with a Windows process handle, under a path containing spaces and non-ASCII characters.

Complete-PR pre-commit, Ruff, import contracts, focused production type checks, commitlint and repository gates passed. Broader local dependency/type-check limitations and the earlier intermittent EOF symptom are disclosed in the updated description; this is not a full-suite or full-Windows-compatibility claim.

The original stacked base is preserved. Git HTTPS was unavailable, so the exact locally tested commit and tree were verified byte-for-byte on GitHub and the existing head branch was advanced through the Git database API with force=false. GitHub now reports the expected head. New CI is pending; no review thread was resolved.

@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.

The prior Windows-test blocker is resolved. The fixture now tolerates an already-absent os.killpg, keeps launched clients behind finally cleanup, exercises real taskkill on native Windows while retaining a POSIX simulation, and the Windows ACP job now selects both teardown regressions.

I reviewed the repository rules, revision delta, fixture and production teardown contracts, affected CI caller, Windows/POSIX compatibility, test strength, history, and the unchanged launcher/stdin/render-lifetime architecture. No test was weakened; the target merge-tree and diff whitespace checks pass.

Verification:

  • uv run --frozen --python 3.13 --no-sync pytest tests/test_subagent_acp.py -n 0 -x --tb=short: 312 passed.
  • Focused registration and ACP stdio suite: 12 passed.
  • Exact-head GitHub Actions Windows ACP stdio: passed, including the newly selected teardown tests.

@LivXue

LivXue commented Oct 13, 2026

Copy link
Copy Markdown
Member Author

Windows compatibility re-review: the previously reported teardown-test defect is fixed. No additional issue found in the inspected scope.

Reviewed the complete 22-file PR at a1dcfd06ebccb1239fc00a0de3cdde305cb449a4 against base/merge base 4ba33b8fe3b16fb6246d1dd911c66707b4d6c1af: all five launchers and the scaffold, stdin/EOF, command arguments, rendered-config/PID lifetime, permissions/newlines, ACP teardown, tests and Windows CI. The new revision changes only the teardown fixture/tests and their CI selection; production code is unchanged from the preceding reviewed head. The snapshot's 4,457 blobs matched the Git tree; the complete diff passed git diff --check.

Native Windows / Python 3.13.12, isolated source and user/Raven/temp directories, a fresh run-owned uv environment, PYTHONUTF8=1 and UV_NO_SYNC=1. Exact commands from the head snapshot:

$R = 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\2fe2e239804c4819b5a0992536a10bc3'
uv run pytest tests/test_subagent_acp.py -k close_without_killpg -n 0 -x --tb=short --basetemp "$R\checks\908-teardown\pytest" --junitxml "$R\checks\908-teardown\junit.xml"
# 2 passed, 310 deselected, no skips.
uv run pytest tests/test_cli_agents_commands.py::test_register_pins_the_resolved_row_into_the_host_roster tests/integration/test_acp_stdio_smoke.py -m 'integration or not integration' -n 0 -x --tb=short --basetemp "$R\checks\908-stdio\pytest" --junitxml "$R\checks\908-stdio\junit.xml"
# 12 passed, including all five real bundled launchers and finite-input handshake.
uv run pytest tests/test_config_product_render.py tests/test_utils_pid.py tests/test_cli_acp_commands.py tests/test_agents_code_launcher.py tests/test_agents_design_launcher.py tests/test_agents_oncall_launcher.py tests/test_agents_ppt_launcher.py tests/test_agents_research_launcher.py tests/test_cli_agents_commands.py::test_the_default_landing_is_the_raven_home_agents_tree -n 0 -x --tb=short --basetemp "$R\checks\908-unit\pytest" --junitxml "$R\checks\908-unit\junit.xml"
# 409 passed, 7 platform-related skips (POSIX modes/pipe transport).

The two teardown cases now run past fixture setup on native Windows. The live case completes an initialize handshake, checks the actual taskkill /T /F /PID invocation and zero exit status, and confirms the client/process closed; the exited-launcher case invokes no taskkill. Both clients have finally cleanup. This resolves the earlier native fixture finding at this head.

Limits: synthetic credentials, memory service disabled in launcher profiles, no live LLM/rendering workload, ACL audit or full repository suite. Three non-failing idle-ceiling warnings occurred in stdio tests. This passing run does not establish that the previous intermittent finite-input EOF symptom is eliminated, or establish full Windows compatibility.

0xKT pushed a commit that referenced this pull request Oct 13, 2026
)

## Summary

Creating an ACP session or opening the model picker could query
LiteLLM's provider metadata API even with `allow_fetch=False`. Unknown
Ollama models could delay `session/new` beyond the EOF cleanup grace
period and drop its reply.

Both context-window and output-ceiling queries now preserve the caller's
lookup policy. A cache-only lookup skips LiteLLM's `get_model_info` only
for the Ollama drivers (`ollama`, `ollama_chat`): on a table miss they
ask the serving daemon (`POST /api/show`), which is the request that
delayed `session/new`. Every other driver answers offline, by stripping
the routing prefix and reading the table, and that lookup stays on
cache-only paths: the table files most direct vendors under the bare
model name, so skipping it changed 203 of 413 measured Model Refs, for
example `openai/gpt-4o-mini` fell from its 128000-token window to the
200000 fallback, and `openai/gpt-4o` read OpenRouter's 4096-token output
ceiling instead of the vendor's 16384. huggingface stays skipped under
both policies, and fetch-enabled callers retain SDK lookup behavior.

Merge this PR before #908. The Windows stdin and launcher changes in
#908 are stacked on this metadata fix, and its Windows ACP stdio job
validates the complete combination.

## Type

- [x] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

At this head, rebased onto main, Linux, Python 3.12:

- `uv run --frozen --python 3.12 --all-extras pytest -q` with the proxy
variables unset: 3 failed, 29662 passed, 158 skipped. The same 3 fail on
main at the merge base on this machine (an npm on PATH, a root-only
permission check, a LibreOffice conversion); 0 introduced.
- `uv run --frozen --python 3.12 --all-extras pytest -n 0
tests/test_provider_rates.py tests/test_rpc_model.py
tests/test_acp_server.py tests/test_acp_methods.py
tests/test_acp_config_options.py tests/test_agent_loop_lazy_provider.py
tests/test_lazy_provider.py tests/test_token_wise_pricing.py
tests/test_rpc_session_init_bundle.py -q`: 609 passed.
- Cache-only `resolve_context_window` and `declared_max_output_tokens`
over 413 Model Refs (every provider-prefixed spelling of a bare-keyed
chat row in the bundled LiteLLM table, plus common ones), each tree in
its own empty home: this head and main answer identically for all 413,
in under 0.2 s per tree, so no lookup left the process.
- Removing the Ollama skip from the window lookup turns 8 tests red,
including the ACP EOF handshake regression; removing it from the ceiling
lookup turns 6 red. The new direct-vendor tests fail without the fix.
- `ruff check`, `ruff format --check`, `lint-imports`, `ty check` and
`deptry` over the Makefile targets, `commitlint`,
`scripts/check_commit_messages.py`, `scripts/check_large_files.py`,
`scripts/check_source_language.py` and `pre-commit run --from-ref
origin/main --to-ref HEAD`: passed.
- On the stacked #908 tree: `uv run --frozen --python 3.12 --all-extras
pytest tests/integration/test_acp_stdio_smoke.py -m integration -q -n
0`: 11 passed on Linux. #908's Windows ACP stdio job runs the same file
on windows-latest.

The metadata regressions cover uncached Ollama spellings, cached
aliases, offline normalization for direct vendors, both lookup policies,
real model.options handling, and all three ACP handshake replies after
EOF.

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

Configuration and protocol contracts are unchanged; the resolver
docstrings describe the lookup policy.

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

Cache-only lookups return what main returns for every Model Ref except
an uncached Ollama model, whose window and ceiling stay unknown rather
than being asked of the daemon; such a model keeps the configured
default, and a self-hosted model can set
agents.defaults.contextWindowTokens explicitly. Fetch-enabled lookup
behavior is preserved. The Ollama driver set mirrors LiteLLM 1.85.0's
`_get_model_info_helper`, whose only other branch that leaves the
process is huggingface's; a LiteLLM upgrade that adds one would need it
added there. Revert this change to roll back.

## Related Issues

#908
Base automatically changed from fix/model_catalog_cache_only to main October 13, 2026 06:42
Use platform-aware PID checks and permission handling for rendered configs.
Preserve supplied line endings in seeded workspace files.

Keep Windows launchers alive while their ACP child is running. Read stdin
without overlapped pipe transports or pending synchronous pipe reads that
can block native-module initialization. Preserve the POSIX exec path.
Run the real ACP stdio integration suite on Windows with Python 3.13 and
locked development dependencies. Cover all handshake replies after EOF and
repeated requests through each bundled launcher.
Windows has no os.killpg, so AcpClient.close raised AttributeError
before any of its teardown ran, leaving the connection marked closed
with its process, read loops and transport still in place. Now that the
launchers wait for their child, killing the launcher alone would also
leave raven acp running until its stdin closed.

Where killpg is missing, run taskkill /T /F on the launcher's pid while
it is still alive, as raven.ops.transport does, then Process.kill. Once
the launcher has exited its pid may name another process, so nothing is
killed by number. POSIX is unchanged.
Disabling the killpg branch of AcpClient.close left every ACP test
green: nothing checked that a child the launcher started dies with the
connection, which is why the client gives each server its own session.
A launcher that backgrounds a worker now has to lose it on close.
Tolerate an absent killpg attribute and always close launched clients. Record real taskkill results on Windows after an ACP handshake, retain the POSIX simulation, and run both regressions in Windows CI.
Return the created session without optional config options when shutdown
cancels a cold model catalogue lookup. Preserve cancellation on live
connections and during required session setup.

Cover both EOF timings, ordinary cancellation, and a fresh bytecode cache
in the real stdio handshake.
@LivXue
LivXue force-pushed the fix/windows_agent_config_render branch from a1dcfd0 to db1e2bb Compare October 13, 2026 10:06

@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.

The retarget to current main is clean: range-diff shows the six previously reviewed patches unchanged, and the branch merges without conflicts. The two new commits address the observed finite-input race by returning an already-created session without optional model metadata only when EOF shutdown has latched; required subscription/setup cancellation and ordinary live-connection cancellation still propagate. The launcher docstrings now describe the Windows wait behavior accurately.

I reviewed the repository rules, full main-target diff, callers and shutdown sequencing, patch history, Windows/POSIX backward compatibility, test strength, and the launcher/stdin/render-lifetime architecture. No test was weakened; merge-tree and diff whitespace checks pass.

Verification:

  • ACP methods, server, and client modules: 498 passed.
  • Focused registration and real stdio suite: 13 passed.
  • Exact-head GitHub Actions Windows ACP stdio: passed.

@LivXue

LivXue commented Oct 13, 2026

Copy link
Copy Markdown
Member Author

Windows compatibility re-review: the EOF/session-reply fix passes the focused native checks. No additional Windows compatibility issue found in the inspected changes.

Scope: all 26 PR files at db1e2bb69c069a77beec363a2c7dcf56d3898bd7, base/merge base ef3b4bf1b23c5d7039ff56206da50f36f5b74b7e. Rechecked all five launchers/scaffold, rendered-config/PID lifetime, stdin, teardown, CI and the new cancellation path. Previously inspected unchanged blobs were compared; all 4,466 snapshot blobs matched the head tree. The complete diff passed git diff --check.

Both EOF timings now return the created session even when shutdown cancels optional model discovery; ordinary cancellation and cancellation during required subscription setup still propagate. Real warm/cold-import handshakes, all five bundled launchers, command registration and native taskkill teardown passed at this head.

Native Windows / Python 3.13.12, fresh per-head environment from uv sync --frozen --extra dev --dev, UV_NO_SYNC=1, PYTHONUTF8=1, isolated user/Raven/temp directories, and imports verified against the exact-head snapshot. Commands from that snapshot:

$R = 'C:/Users/livxu/AppData/Local/Temp/raven-windows-review/d1d0b660deeb48e3a2e4d079a3316cbc'
uv run pytest tests/test_acp_methods.py tests/test_acp_server.py tests/test_cli_acp_commands.py -n 0 -x --tb=short --basetemp "$R/checks/908-eof/pytest" --junitxml "$R/checks/908-eof/junit.xml"
# 218 passed, 1 skipped (POSIX pipe transport).
uv run pytest tests/test_cli_agents_commands.py::test_register_pins_the_resolved_row_into_the_host_roster tests/integration/test_acp_stdio_smoke.py -m 'integration or not integration' -n 0 -x --tb=short --basetemp "$R/checks/908-stdio/pytest" --junitxml "$R/checks/908-stdio/junit.xml"
# 13 passed; 6 advisory idle-ceiling warnings.
uv run pytest tests/test_subagent_acp.py -k close_without_killpg -n 0 -x --tb=short --basetemp "$R/checks/908-teardown/pytest" --junitxml "$R/checks/908-teardown/junit.xml"
# 2 passed, 310 deselected.

Limits: synthetic credentials and disabled memory service in launcher profiles; no live LLM/rendering workload, Windows ACL audit or full repository suite. The author's broader Windows failures remain outside this focused selection. The passing deterministic EOF cases and warm/cold handshakes provide new evidence for the previous intermittent EOF symptom, without claiming exhaustive timing coverage.

0xKT pushed a commit that referenced this pull request Oct 14, 2026
…b key (#927)

## Summary

Two gaps in how Raven stops its cli sub-agents and what environment it
starts them with.

**Stopping a cli sub-agent on Windows.** A cli sub-agent's timeout,
cancel and stream-failure paths, and the `kimi` commands Raven runs to
explain a failed Kimi Code connect, ended the process group with
`os.killpg`, which Windows does not have. The `AttributeError` replaced
the timeout error or the cancellation, and the CLI was left running.
Where `killpg` is missing, the tree is now ended from the launcher's pid
with `taskkill /T /F` while the launcher is still alive, and the
launcher is then killed itself, the way `raven/ops/transport.py` already
ends a command on Windows. Once the launcher has exited its pid may name
another process, so nothing is killed by number. POSIX keeps the group
kill.

**A web key rotated through `settings.set`.** `settings.set` rewrites
`~/.raven/env` when a web credential changes, but a POSIX cli or acp
sub-agent reads that file only through the login-shell capture, which a
running Raven takes once and keeps. Every later spawn started with the
key the write had replaced, until a Connect, a Test, "Check again" or a
restart took the capture again. A mirrored key's write now takes the
capture again before the reply. Captures also run one at a time: a
refresh that read the shell before the write, a Connect's for instance,
could otherwise land after the write's own and put the replaced key
back, and readers that arrive while the first capture runs now wait for
it rather than each running the shell.

Windows is unchanged for keys. Nothing sources `~/.raven/env` there, and
a Windows sub-agent's environment is Raven's own with the registry's
PATH, so a key held only in config does not reach it; #912 states this
in the code. On POSIX the keys reach a sub-agent only through a `source`
line in the user's shell startup files, which the onboard wizard adds
only with the user's agreement, and a Windows delivery path would need a
consent step of its own, so this PR does not add one.

Not changed here: an acp sub-agent that is already connected keeps the
environment it was launched with until its next launch; a key written by
`raven onboard`, which runs as its own process, reaches a running
Raven's sub-agents at the next Connect, Test, "Check again" or restart;
and on Windows the wizard still prints the POSIX `source` line when
`$SHELL` is not bash or zsh.

## Type

- [x] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

Linux, Python 3.12, rebased onto main 182daca:

- `uv run --frozen --python 3.12 --all-extras pytest -q` with the proxy
variables unset: 3 failed, 29667 passed, 158 skipped. The same 3 fail on
main on this machine (an npm on PATH, a root-only permission check, a
LibreOffice conversion); 0 introduced.
- `uv run --frozen --python 3.12 --all-extras pytest
tests/test_subagent_third_party.py tests/test_subagent_kimi_code.py
tests/test_subagent_acp.py tests/test_subagent_host_env.py
tests/test_rpc_console.py tests/test_rpc_settings.py
tests/test_rpc_subagents.py -q`: 1080 passed.
- Windows stop: `tests/_windows_kill.py` leaves a Windows host as it is,
where the real `taskkill` runs, and elsewhere removes `os.killpg` and
stands in for `taskkill` by ending the process group it is given, the
launcher's tree here; either way it records each call. The launchers,
their workers and the Kimi stand-in are Python started on
`sys._base_executable`, because a Windows venv's `python.exe` is a
redirector that runs the base interpreter as its child and returns a pid
the script never sees. The tests have not run on a Windows host, and no
CI job runs these test files there.
- Windows stop, measured here: the six tests that use the helper pass.
With `os.killpg` removed for the whole session and a `taskkill` on PATH
that ends the group it is given, they pass again through that `taskkill`
(3 calls), and the POSIX-only Kimi test beside them is skipped. With
`sys.executable` replaced by a wrapper that runs Python as its child, as
a venv's `python.exe` does on Windows, both modes stay green; started on
that wrapper instead, the two cancel tests fail on the pid they compare.
- Windows stop, against the fix: the six fail with `AttributeError` on
main. Removing the alive check in the cli backend, the `taskkill` call
or the kill after it in either module, or the cli backend's handling of
a `taskkill` that times out, turns at least one of them red. Removing
the POSIX group kill from the Kimi Code commands turns only the new test
whose stand-in `kimi` starts a process of its own red (1 failed, 347
passed over the two test files).
- Rotated key:
`test_a_rotated_key_reaches_the_capture_the_next_spawn_reads` saves a
key twice through `settings.set` and reads the capture after each save,
through a real bash that sources the mirror; on main the second read
still returns the first key. Removing the refresh turns 12 tests red;
refreshing before the mirror is rewritten turns that test red. Two
existing tests that write a web key now stub the refresh, so they do not
run the host's login shell.
- Concurrency: three tests force the interleavings with events: a
refresh that read the shell first and lands after a later one, a second
reader that arrives while the first capture runs, and readers waiting on
a first capture that fails. Each failed in 3 of 3 runs on main and
passes with the lock; removing the lock from either capture path, or
either re-check inside the first one, turns one of them red.
- With `--cov=raven --cov=raven_everos --cov-branch` added to the full
run, `python scripts/coverage_gate.py diff --base-ref origin/main
--threshold 90`: 93.22% of the changed lines (55 of 59). The four left
are except clauses for a process group or a process that is already
gone.
- `ruff check`, `ruff format --check`, `lint-imports`, `ty check` and
`deptry` over the Makefile targets, `commitlint`, the three repository
gate scripts and `pre-commit run --from-ref origin/main --to-ref HEAD`:
passed.
- `git merge-file` of `raven/rpc/methods/console.py` with each open PR
that touches it (#883, #900, #911, #912, #923, #925), and of
`raven/agent/subagent/backends/env.py` with #912 in both orders: no
conflicts.

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

No user-facing docs describe either behavior.

## Risk

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

Saving a web key now waits for the user's login shell to start before
the reply, as Connect and Test already do; the capture's 15 s timeout
bounds it, and it can also wait for a capture already in flight. A
capture that fails keeps the previous one and does not fail the save. No
credential reaches a sub-agent that did not before; it is the same shell
reading the same file, read again. On Windows a stop now waits up to 5 s
for `taskkill`, and a worker that a launcher started and left behind
after exiting outlives the stop there, since Windows has no group to
address and an exited launcher's pid is not trusted. If `taskkill` is
missing, fails or does not answer within those 5 s, only the launcher is
killed, and a cli stop then waits for any worker still holding the
launcher's output pipes to exit. Revert this PR to roll back.

## Related Issues

#908, #912
@0xKT
0xKT merged commit 5cdf8c0 into main Oct 14, 2026
24 checks passed
@0xKT
0xKT deleted the fix/windows_agent_config_render branch October 14, 2026 03:06
0xKT pushed a commit that referenced this pull request Oct 14, 2026
… cache (#942)

## Summary

A long-running Raven host held on to three kinds of memory it no longer
used. Observed on one dev box on 2026-10-12:

- Two ACP agents, started on 2026-09-20 by a host that exited without
closing them, were still running 22 days later. They had been reparented
to init and held 523 MiB PSS between them.
- A gateway's glibc thread arenas had grown from 85 to 214 MiB over 18
days of light use.
- `SessionManager` kept every session it had ever loaded.

This PR cleans up all three automatically.

**1. ACP agents that outlive their host (Linux).** An ACP agent starts
in a session of its own, so it gets none of the signals sent to its
host's process group or terminal, and while the host lives only
`AcpClient.close` ends it. A host that exited without closing its
agents, or was killed, crashed or OOM-killed before its teardown, left
them running.

- The agent now starts through a one-line exec wrapper
(`raven/acp_client/leftovers.py`). It runs the host's own interpreter
with `-I -S`, arms `PR_SET_PDEATHSIG(SIGKILL)`, checks that its parent
is still the host, restores SIGPIPE and SIGXFSZ to their defaults and
execs the agent in place, with the same pid and group and the agent's
own argv. The agent's process then ends with the host.
- The signal reaches only the direct child, and an `npx` launcher's real
server is a grandchild. So every host also records the process groups it
launched, one file per host under `acp-launches/` in the config file's
directory. The first ACP launch in any process ends the groups recorded
by hosts that have since died. That sweep runs once per process on a
daemon thread. Every launch waits for it for at most 5 s, and once one
launch has waited that long the rest stop waiting for it.
- A group is ended only when all of the following hold:
- the record is of this version and comes from this boot and PID
namespace;
  - its host is gone, judged by pid and start time;
  - the group is not group 0, 1 or the sweeper's own;
  - its leader, if still running, has the start time on record;
- a current member still carries the dead host's mark,
`RAVEN_ACP_HOST=<host pid>-<host start ticks>`, which every launched
agent gets last in its environment.

A read that fails, of a record or of `/proc`, keeps the record and ends
nothing; a record that does not parse is deleted.

**2. Native heap trimming (Linux with glibc).** glibc keeps the memory
native code frees, such as PyMuPDF rasters and tokenizer tables, in its
arenas. `raven/utils/heap.py` checks every ten minutes and, once
anonymous memory stands 32 MiB above its lowest reading since the last
trim, runs `malloc_trim(0)` in a worker thread. The five long-running
hosts start it: `raven gateway`, `raven serve`, the TUI's RPC host,
`raven acp` and `raven a2a serve`.

**3. A bounded session cache.** `SessionManager._cache` becomes an LRU
with a soft limit of 64 sessions.

- A session leaves the cache only when nothing about it is unsaved. Its
message count must match what is on disk, and a fingerprint of its
metadata, consolidation mark and pending clarification must match the
one taken when it was last read or written. A metadata patch counts its
own keys as saved even when the caller set them on its copy first.
- A session that a caller still holds comes back as the same object.
- A reloaded session keeps the `updated_at` it was saved with.

`CONTEXT.md` gains two terms, **Leftover agent** and **Launch mark**.

Measured on this box on 2026-10-13:

- **PyMuPDF.** 112 pages rasterised three times in a 3-thread pool. In
two runs, anonymous memory was 279.8 and 315.3 MiB after
`store_shrink(100)`, and 93.5 and 84.6 MiB after `heap.trim()`.
- **Gateway.** An isolated gateway with a key-less config, run for 100
s. With the interval shortened to 45 s, one trim fired at t+47.7 s and
took anonymous memory from 293 to 273 MiB; with the default interval,
none fired. Final PSS was 321.6 against 328.6 MiB. Its boot check
launched 16 ACP agents through the wrapper: the five discovered agents
under `agents/`, which that config cannot start, and eleven presets,
eight of which completed the handshake (Claude Code, Codex, OpenCode,
Hermes Agent, OpenClaw, CodeBuddy, Qoder and Pi). Afterwards no marked
process and no launch record remained.
- **Killed host.** A stand-in host was SIGKILLed after launching through
`AcpClient`. On main, a direct agent, an `npx`-shaped launcher and its
grandchild all survive with ppid 1. On this branch, the direct agent and
the launcher end with the host, and the next host's first launch ends
the grandchild.
- **Discovered agents, with and without the wrapper.** Each of the five
under `agents/` was verified twice in a home holding only dummy keys (a
handshake calls no model): Raven-Design completed the handshake both
ways, and the other four stopped on the same missing key both ways.

The Windows regression fixtures use explicit executable filenames and
native project-path strings. The unreadable-transcript symlink fixture
skips only when Windows reports missing symlink privilege (WinError
1314); other creation errors still fail. These test corrections do not
extend the Linux-only cleanup mechanisms.

## Type

- [x] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

The original author's Linux measurements below precede the Windows
test-fixture follow-up. The native Windows results that follow were run
against the final follow-up tree.

- **Author's Linux full suite before the Windows test-fixture
follow-up:** `uv run --frozen --python 3.12 --all-extras pytest -q -p
no:cacheprovider` (repo addopts: `-n 4`, integration and e2e
deselected). Result: 3 failed, 30261 passed, 158 skipped, 2 xfailed. The
failures are the ones this box gives on main too:
`test_install_script.py::test_resolve_node_dir_answers_each_case_it_exists_for`,
`test_knowledge_parser_excel.py::test_a_legacy_workbook_is_converted_and_read`
(no LibreOffice) and
`test_subagent_node_runtime.py::test_what_cannot_be_read_names_nothing`
(run as root).
- **Directly affected files:** `tests/test_acp_leftovers.py`,
`tests/test_utils_heap.py`, `tests/test_session_manager.py`,
`tests/test_cli_{a2a,acp,gateway,serve,tui}_commands.py` and
`tests/test_subagent_host_env.py`, under `--idle-ceiling-strict` as CI
runs them. Result: 620 passed.
- **Neighbours:** `tests/test_rpc_session.py`,
`tests/test_session_{export,title,project_grouping}.py`,
`tests/test_defer_manager.py`, `tests/test_subagent_acp.py` and
`tests/test_acp_{ask_user,elicitation,remote}.py`. Result: 683 passed.
- **Lint and gates:** `make lint-python lint-imports lint-deps
lint-types check-large-files check-source-language check-commits`.
Result: all seven exit 0; import contracts 10 kept, 0 broken.
- **Mutation:** guards were deleted or inverted one at a time, out of
tree, and each such mutant failed the test written for it. They covered
the sweep's wait, bound, daemon flag and start failure; the version,
boot-time, PID namespace, liveness, leader and mark checks; the
`/proc/<pid>/stat` parse; the trimmer's reader and threshold; and the
cache's eviction order, fingerprint, `updated_at` restore and patch
rule.
- **Diff coverage:** 96.27% (387 of 402 executable changed lines) from
the directly affected and neighbouring files alone, against CI's 90%
gate. What stays uncovered is the arms for a `/proc` read or a `killpg`
that fails, and the import inside `raven a2a serve`, which only a
running server reaches.

**Native Windows test-fixture follow-up (2026-10-14):** Windows
10.0.26300, Python 3.13.12, `PYTHONUTF8=1`, a dedicated worktree and `uv
sync --frozen --python 3.13 --all-packages --extra dev`. The imported
Raven package was verified to come from that worktree. Pytest's isolated
homes and a separate caller `RAVEN_HOME` were used. JUnit output paths
are omitted from the command below.

```powershell
uv run --frozen --no-sync pytest tests/test_acp_leftovers.py tests/test_utils_heap.py tests/test_session_manager.py tests/test_session_project_grouping.py tests/test_subagent_host_env.py tests/test_cli_a2a_commands.py::test_serve_starts_the_heap_trimmer tests/test_cli_acp_commands.py::TestServe::test_the_heap_trimmer_starts_before_the_channel_is_served tests/test_cli_gateway_commands.py::test_run_starts_the_heap_trimmer_at_boot tests/test_cli_serve_commands.py::test_serve_starts_the_heap_trimmer_at_boot tests/test_cli_tui_commands.py::test_the_terminal_host_starts_the_heap_trimmer -k 'not test_acp_child_inherits_custom_raven_home and not test_acp_child_is_told_it_serves_as_a_subagent' -n 0 --idle-ceiling-strict --tb=short
```

Result: **229 passed, 73 skipped, 2 deselected**, exit 0 with the idle
ceiling enforced. Of the skips, 72 are existing Linux/glibc cases and
one is the measured lack of Windows symlink privilege. The three
corrected discovery/path cases failed before the corrections and pass in
this selection. Before the symlink guard, the same broader selection had
229 passes, 72 skips, two deselections and one WinError 1314 failure.

The two deselected real ACP child tests still depend on the native close
fix in #908. The broader CLI permission/signal limitations reported in
the Windows review are covered separately by #939. This follow-up claims
no full Windows suite pass, real ACP lifecycle validation, privileged
symlink run, Windows ACL audit, or new Linux run.

- `uv run --frozen --no-sync ruff check raven evolver agents
plugins-dist tests scripts docs-site` and the matching `ruff format
--check`: passed (2,272 files formatted); the final symlink edit also
passed both file-scoped checks.
- `uv run --frozen --no-sync lint-imports`: 10 contracts kept. `uv run
--frozen --no-sync ty check raven/acp_client/client.py
raven/acp_client/leftovers.py raven/cli/a2a_commands.py
raven/cli/acp_commands.py raven/cli/gateway_commands.py
raven/cli/serve_commands.py raven/cli/tui_commands.py
raven/session/manager.py raven/utils/heap.py`: passed.
- `uv run --frozen --no-sync pre-commit run --from-ref origin/main
--to-ref HEAD`: all applicable hooks passed.
- `node node_modules/@commitlint/cli/cli.js --from origin/main --to HEAD
--config commitlint.config.cjs`, `uv run --frozen --no-sync python
scripts/check_commit_messages.py origin/main..HEAD`, `uv run --frozen
--no-sync python scripts/check_large_files.py origin/main...HEAD`, `uv
run --frozen --no-sync python scripts/check_source_language.py
origin/main...HEAD`, and `git diff --check origin/main...HEAD`: passed.

The relevant-tests checkbox remains unchecked because the broader
Windows files still contain the disclosed limitations above.

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

No user-facing docs change, because no command, flag or setting does.
The two new terms are glossary entries in `CONTEXT.md`.

## Risk

User-visible behaviour changes:

- **An ACP agent that exists but cannot be executed now fails after the
spawn.** Examples are a bad interpreter line or a binary for the wrong
architecture. The wrapper writes `raven: cannot start <path>: <error>`
to the agent's stderr and exits 127, so the failure surfaces as a
connection that ends with that line in its stderr, where
`AcpClient.launch` used to raise `cannot start` at once. A missing
program still fails at launch, as before.
- **Agent environment.** Every ACP agent's environment carries
`RAVEN_ACP_HOST`. It is an identifier, not a secret; its parts are
world-readable in `/proc/<pid>/stat`.
- **Locale.** An agent started under a C or POSIX locale gets
`LC_CTYPE=C.UTF-8`, because the wrapper interpreter's PEP 538 coercion
carries through the exec.
- **Launch records.** Every ACP launch and close writes a small JSON
file under `acp-launches/` beside the config file, synchronously and
with an fsync.
- **First launches.** While dead hosts' records are swept, a process's
ACP launches wait for the sweep, 5 s at most in all: once one launch has
waited that long, the rest start without it. Normally the sweep takes
milliseconds.
- **Session timestamps.** A reloaded session's `updated_at` is the one
it was saved with, where it used to be the time of the load, so a save
that adds no message no longer moves it. Sentinel's defer timing and the
export header read it in memory, and the session list orders by the
saved value.

Left as today:

- Anything but Linux. #928 tracks macOS and Windows.
- A group whose surviving processes carry no readable mark: one started
with a fresh environment, another user's, or an undumpable one.
- An agent binary that is set-id or carries file capabilities: its exec
clears the parent-death signal, so only the sweep can end it, and only
where its environment is readable.
- Spawns other than ACP agents: the CLI sub-agent backend, background
exec and the sandbox executor.
- Leftovers created during one long-lived host's life. They wait for
another process's first ACP launch.
- A host started with another `--config` sweeps that directory's records
only.

Edge cases of the records directory:

- **Another PID namespace.** Its records are kept until a reboot, about
300 bytes per container start.
- **A directory shared between machines.** Another machine's record
older than this machine's boot is removed by this machine's sweep. That
host's agents would then not be swept if it died before its next launch
or close rewrote the file. Nothing is ever signalled across machines.

Edge cases of the session cache:

- A change made through a reference to a session that had already left
the cache, and then released unsaved, is lost, as it would be in a fresh
process.
- An in-memory undo whose save failed, followed by new messages back to
the same count, reads as clean. `save()` already mishandles that state,
so the residue predates this PR.

A group is signalled only when it passes all five checks above. A
crafted record cannot point a sweep at a live host's agents, because the
mark names the identity the liveness check reads.

- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes

Rollback is a revert. Nothing persisted changes shape: session files are
written exactly as before, and `acp-launches/` only holds what the next
sweep reads, so it can be deleted at any time.

## Related Issues

#928 (macOS and Windows; stays open)
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