feat(ai_mail): dispatch default Sonnet 5 — alias pass-through + 200k context pin. MODEL_MAP replaced by KNOWN_MODEL_ALIASES pass-through (CLI resolves latest-in-class, map can never go stale), DEFAULT_MODEL opus->sonnet, dispatch_monitor pins CLAUDE_CODE_AUTO_COMPACT_WINDOW=200000 per spawn (Sonnet 5 is 1M-native — without pin agents inherit 1M window). E2E: live @cli dispatch probe reported claude-sonnet-5 + WINDOW=200000 from inside the spawned session. 777 tests green, seedgo 100%, both re-run by devpulse. Daemon spawn_agent --model gap confirmed, handled separately. Patrick ruling S326, built by @ai_mail
This commit is contained in:
@@ -11,6 +11,18 @@ PyPI version — not the changelog header.
|
||||
|
||||
## [2026-07-19]
|
||||
|
||||
**feat(ai_mail)** — dispatched agents default to Sonnet 5 (Patrick ruling S326):
|
||||
|
||||
- wake.py model resolution passes bare aliases (`sonnet`/`opus`/`haiku`)
|
||||
straight to the Claude CLI, which resolves latest-in-class — the pinned-ID
|
||||
MODEL_MAP is gone and can never go stale again. Default flips opus → sonnet.
|
||||
- dispatch_monitor pins `CLAUDE_CODE_AUTO_COMPACT_WINDOW=200000` on every
|
||||
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.
|
||||
|
||||
**fix(spawn, commons, prax, hooks)** — S304 audit fix campaign, Track A
|
||||
(DPLAN-0250, four owner dispatches verified + committed by devpulse):
|
||||
|
||||
|
||||
@@ -449,6 +449,9 @@ def main():
|
||||
for key in list(spawn_env.keys()):
|
||||
if key.startswith("CLAUDE") or key == "AIPASS_BOT_ID":
|
||||
spawn_env.pop(key)
|
||||
# Pin agent context window to 200k (Sonnet 5 is 1M native; without this,
|
||||
# agents inherit 1M which causes cost + runaway risk).
|
||||
spawn_env["CLAUDE_CODE_AUTO_COMPACT_WINDOW"] = "200000"
|
||||
# Strip caller identity vars to prevent dispatch context leakage.
|
||||
spawn_env.pop("AIPASS_CALLER_BRANCH", None)
|
||||
spawn_env.pop("AIPASS_CALLER_CWD", None)
|
||||
|
||||
@@ -63,13 +63,9 @@ MONITOR_SCRIPT = Path(__file__).parent / "dispatch_monitor.py"
|
||||
# Default prompt when no custom message provided
|
||||
DEFAULT_PROMPT = "Hi. Check inbox, process new emails, update memories when done."
|
||||
|
||||
# Model shorthand mapping
|
||||
MODEL_MAP = {
|
||||
"sonnet": "claude-sonnet-4-6",
|
||||
"opus": "claude-opus-4-6",
|
||||
"haiku": "claude-haiku-4-5-20251001",
|
||||
}
|
||||
DEFAULT_MODEL = "opus"
|
||||
# Model aliases — passed directly to claude CLI which resolves latest-in-class.
|
||||
KNOWN_MODEL_ALIASES: frozenset = frozenset({"sonnet", "opus", "haiku"})
|
||||
DEFAULT_MODEL = "sonnet"
|
||||
|
||||
# Branches that cannot be woken manually by cross-branch drone commands.
|
||||
# Dispatch-send path (dispatch.py._orchestrate_dispatch_send) bypasses this check.
|
||||
@@ -591,8 +587,8 @@ def wake_branch(
|
||||
config = _load_config()
|
||||
max_turns = config.get("max_turns_per_wake", 100)
|
||||
|
||||
# Resolve model: shorthand -> full ID, or pass through if already a full ID
|
||||
resolved_model = MODEL_MAP.get(model or DEFAULT_MODEL, model or MODEL_MAP[DEFAULT_MODEL])
|
||||
# Pass model directly to CLI — aliases resolve latest-in-class automatically
|
||||
resolved_model = model or DEFAULT_MODEL
|
||||
|
||||
lock_file_path = str(branch_path / ".ai_mail.local" / ".dispatch.lock")
|
||||
if custom_message:
|
||||
@@ -781,7 +777,7 @@ if __name__ == "__main__":
|
||||
print(" --fresh Start fresh session (claude -p) instead of resuming (claude -c -p)")
|
||||
print(" --auto Respect autonomous_pause (used by daemon). Manual wake ignores it.")
|
||||
print(" --sender @branch Set return-to-sender for bounce emails (default: @devpulse)")
|
||||
print(" --model NAME Model to use: opus (default), sonnet, haiku, or full model ID")
|
||||
print(" --model NAME Model to use: sonnet (default), opus, haiku, or full model ID")
|
||||
print()
|
||||
print("Output: Step-by-step status of the dispatch pipeline:")
|
||||
print(" ✅ resolve → @branch found at /path/to/branch")
|
||||
|
||||
@@ -29,7 +29,7 @@ from aipass.ai_mail.apps.handlers.dispatch.wake import (
|
||||
_find_claude_bin,
|
||||
resolve_branch,
|
||||
DispatchStatus,
|
||||
MODEL_MAP,
|
||||
KNOWN_MODEL_ALIASES,
|
||||
DEFAULT_MODEL,
|
||||
_acquire_lock,
|
||||
_load_config,
|
||||
@@ -572,24 +572,22 @@ def _fake_open_factory(real_status_path, mapping):
|
||||
# --- Model flag tests ---------------------------------------------------
|
||||
|
||||
|
||||
def test_model_map_has_expected_entries():
|
||||
"""MODEL_MAP should contain sonnet, opus, haiku shorthand mappings."""
|
||||
assert "sonnet" in MODEL_MAP
|
||||
assert "opus" in MODEL_MAP
|
||||
assert "haiku" in MODEL_MAP
|
||||
assert "claude-sonnet-4-6" in MODEL_MAP["sonnet"]
|
||||
assert "claude-opus-4-6" in MODEL_MAP["opus"]
|
||||
def test_known_model_aliases_has_expected_entries():
|
||||
"""KNOWN_MODEL_ALIASES should contain sonnet, opus, haiku."""
|
||||
assert "sonnet" in KNOWN_MODEL_ALIASES
|
||||
assert "opus" in KNOWN_MODEL_ALIASES
|
||||
assert "haiku" in KNOWN_MODEL_ALIASES
|
||||
|
||||
|
||||
def test_default_model_is_opus():
|
||||
"""Default model should be opus."""
|
||||
assert DEFAULT_MODEL == "opus"
|
||||
def test_default_model_is_sonnet():
|
||||
"""Default model should be sonnet."""
|
||||
assert DEFAULT_MODEL == "sonnet"
|
||||
|
||||
|
||||
def test_model_map_values_are_full_ids():
|
||||
"""All MODEL_MAP values should be full claude model IDs."""
|
||||
for key, value in MODEL_MAP.items():
|
||||
assert value.startswith("claude-"), f"{key} -> {value} doesn't start with 'claude-'"
|
||||
def test_known_model_aliases_are_bare_names():
|
||||
"""All KNOWN_MODEL_ALIASES should be bare alias names (no 'claude-' prefix)."""
|
||||
for alias in KNOWN_MODEL_ALIASES:
|
||||
assert not alias.startswith("claude-"), f"{alias} should be a bare alias"
|
||||
|
||||
|
||||
# --- _find_claude_bin tests ------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user