fix(ai_mail): inbox-poller spawn_agent now passes --model DEFAULT_MODEL (sonnet). The daemon asymmetry Vera flagged lived in ai_mail's own handlers/dispatch/daemon.py (name collision, not @daemon branch — their scheduled wakes import wake_branch directly and were covered by efb530e0). DEFAULT_MODEL imported from wake.py, single source; 200k window pin already covered via shared dispatch_monitor wrapper. Test asserts --model in captured spawn cmd. 778 tests green, seedgo 100%, both re-run by devpulse. Investigated by @daemon, built by @ai_mail

This commit is contained in:
AIOSAI
2026-07-19 09:32:42 -07:00
parent efb530e0b2
commit 0529a4e7c4
3 changed files with 51 additions and 2 deletions
+6 -2
View File
@@ -20,8 +20,12 @@ PyPI version — not the changelog header.
spawned agent — Sonnet 5 is 1M-context native, and without the pin every
dispatched agent would silently inherit a 1M window. E2E-proven: a live
dispatched probe reported `claude-sonnet-5` + `WINDOW=200000` from inside.
- Known gap (daemon-side, next): daemon.py `spawn_agent()` builds its claude
command with no `--model` flag, bypassing wake.py resolution entirely.
- Follow-up landed same morning: the "daemon gap" was a name collision —
the unpatched `spawn_agent()` was ai_mail's own inbox-poller
(`handlers/dispatch/daemon.py`), not the @daemon branch. It now passes
`--model DEFAULT_MODEL` (imported from wake.py, single source); the 200k pin
was already covered via the shared dispatch_monitor wrapper. @daemon's
scheduled wakes import `wake_branch` directly and were covered from the start.
**fix(spawn, commons, prax, hooks)** — S304 audit fix campaign, Track A
(DPLAN-0250, four owner dispatches verified + committed by devpulse):
@@ -34,6 +34,7 @@ from aipass.ai_mail.apps.handlers.json import json_handler
from aipass.ai_mail.apps.handlers.dispatch.status import log_dispatch
from aipass.ai_mail.apps.handlers.paths import find_repo_root
from aipass.ai_mail.apps.handlers.dispatch.test_token import scan_and_ack_test_emails
from aipass.ai_mail.apps.handlers.dispatch.wake import DEFAULT_MODEL
# Infrastructure paths
@@ -408,6 +409,8 @@ def spawn_agent(
"-c",
"-p",
prompt,
"--model",
DEFAULT_MODEL,
"--max-turns",
str(max_turns),
"--permission-mode",
+42
View File
@@ -1317,6 +1317,48 @@ def test_spawn_agent_strips_claude_env_vars(tmp_path, monkeypatch):
assert captured_env.get("AIPASS_SESSION_TYPE") == "daemon"
def test_spawn_agent_claude_cmd_includes_model_flag(tmp_path):
"""Poller-triggered spawn passes --model DEFAULT_MODEL to the claude invocation."""
branch_path = tmp_path / "branch"
branch_path.mkdir()
(branch_path / "logs").mkdir()
message = {"from": "@devpulse", "id": "msg1", "subject": "Test task"}
config = {"max_turns_per_wake": 50}
state = {"daily_counts": {}, "session_cycles": {}}
captured_args = []
def capture_popen(*args, **kwargs):
captured_args.append(args[0] if args else kwargs.get("args"))
mock_proc = MagicMock()
mock_proc.pid = 22222
return mock_proc
with (
patch(
"aipass.ai_mail.apps.handlers.dispatch.daemon.subprocess.Popen",
side_effect=capture_popen,
),
patch(
"aipass.ai_mail.apps.handlers.dispatch.daemon._acquire_lock",
return_value=(True, "Lock acquired"),
),
patch("aipass.ai_mail.apps.handlers.dispatch.daemon.log_dispatch"),
patch(
"aipass.ai_mail.apps.handlers.dispatch.daemon.send_notification",
create=True,
),
):
result = spawn_agent(branch_path, "@testbranch", message, config, state)
assert result is True
monitor_cmd = captured_args[0]
assert "--model" in monitor_cmd
model_idx = monitor_cmd.index("--model")
assert monitor_cmd[model_idx + 1] == daemon_mod.DEFAULT_MODEL
def test_spawn_agent_prompt_includes_reply_id(tmp_path):
"""Prompt includes explicit reply command with the dispatch email ID."""
branch_path = tmp_path / "branch"