Conversation
…gating Resolves the two Phase 3 Step 9 review deferrals. ## C2 — dedicated `blocked_on_qwen` status Previously, Qwen-3.6 unavailability was mapped to `blocked_on_permission`. Semantic mismatch: there's no permission to grant; the LLM service is just down. Now: - New status `blocked_on_qwen` in `_VALID_TRANSITIONS` (running → blocked_on_qwen → running | aborted) - `_run_agent` Qwen exception handler transitions to `blocked_on_qwen` - Daemon's `_daemon_one_tick` adds a new branch: when status=blocked_on_qwen, probe Qwen with a tiny call. If alive → transition to running and respawn. No user interaction needed. User benefit: when Qwen flickers (PM2 restart, OOM kill, etc.), agents auto-recover within one daemon tick without needing manual /resume. ## M4 — read_paths runtime enforcement Previously, `PermissionManifest.read_paths` was declared at approval time but never enforced at runtime. Now: - `Action` dataclass: NEW fields `reads_path: bool` + `read_path: str` - `permission_gate` checks `action.read_path` against `agent_grants.read_paths ∪ global_grants.read_paths` (parallel to write_paths logic) - `_NEXT_ACTION_SYSTEM_PROMPT` updated: LLM now emits `reads_path` + `read_path` fields. Single action can read AND write (e.g. read template.md, write output.md) - `_qwen_next_action` parser populates the new Action fields Side fix: `permission_gate` now expands `~` on BOTH sides (action path and grant glob) so `~/Documents/x` matches `~/Documents/**` correctly. Was a latent bug for write_paths too — symmetric improvement. ## Tests 6 new tests in `tests/test_agent_runner.py`: - `test_blocked_on_qwen_in_state_machine` — state machine has the new entry - `test_run_agent_qwen_failure_uses_blocked_on_qwen` — _run_agent uses correct status - `test_daemon_resumes_blocked_on_qwen_when_qwen_alive` — auto-resume on probe success - `test_action_dataclass_supports_reads_path` — Action fields exist - `test_permission_gate_blocks_read_path_outside_grants` — read enforcement bites - `test_permission_gate_allows_read_path_in_grants` — happy path Full suite: 944 / 20 / 73 (same baseline, +6 from this PR). ## Compat - `Action` field additions are non-breaking — defaults preserve old behavior - New status `blocked_on_qwen` only fires from new code paths; old data migration not needed - LLM prompt extension is additive; older Qwen responses (no reads_path field) parse correctly with the default `False`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Resolves the two Phase 3 Step 9 review deferrals (C2 + M4). Both are small backend cleanups; together ~150 LOC + 6 tests.
C2 — dedicated
blocked_on_qwenstatusBefore: Qwen-3.6 unavailability mapped to
blocked_on_permission. Semantic mismatch — no permission to grant; service is just down. User would see a "grant permission" prompt with nothing to grant.After:
blocked_on_qwenin state machine (running → blocked_on_qwen → running | aborted)_run_agentQwen exception handler uses the new status_daemon_one_tickadds a branch: when an agent isblocked_on_qwen, probe Qwen with a tiny call. If alive → auto-resume torunning. No user click required.When Qwen flickers (PM2 restart, OOM, network blip), agents auto-recover within one daemon tick (5 s). Before this fix, you'd have to manually
/resume.M4 — read_paths runtime enforcement
Before:
PermissionManifest.read_pathswas declared at approval time for transparency but never enforced. An agent could ask Qwen for a skill that reads/etc/passwdandpermission_gatewouldn't catch it (onlytouches_path= write was checked).After:
Actiondataclass: NEW fieldsreads_path: bool+read_path: strpermission_gatechecksaction.read_pathagainstread_paths ∪ global.read_pathsreads_path+read_pathfields. A single action can both read AND write (e.g. read template.md, write output.md)._qwen_next_actionparser populates the new fields.Side fix:
permission_gatenow expands~on BOTH sides of the path comparison. Was a latent bug forwrite_paths(action with~/Documents/xwouldn't match grant glob~/Documents/**because the action wasn't expanded). Symmetric improvement that affects both read and write enforcement.Tests
6 new tests in
tests/test_agent_runner.py:test_blocked_on_qwen_in_state_machinetest_run_agent_qwen_failure_uses_blocked_on_qwen_run_agentuses correct status on Qwen failuretest_daemon_resumes_blocked_on_qwen_when_qwen_alivetest_action_dataclass_supports_reads_pathtest_permission_gate_blocks_read_path_outside_grantstest_permission_gate_allows_read_path_in_grants~/Documents/**globFull suite
944 passed / 20 failed / 73 skipped — same 20/73 baseline, +6 from this PR.
Compat
Actionfield additions are non-breaking (defaults preserve old behavior)blocked_on_qwenonly fires from new code paths; no migration neededreads_pathparse fine (defaultFalse)Test plan
tests/test_agent_runner.py→ 42 passedpm2 restart codec-agent-runner codec-dashboard🤖 Generated with Claude Code