Skip to content

Commit 6c398d5

Browse files
Trecekclaude
andauthored
Implementation Plan: T5-P4-A3-WP3 Ensure every guard hook in a launched session can dispatch on AUTOSKILLIT_AGENT_BACKEND (#4095)
## Summary Inject `AGENT_BACKEND_ENV_VAR` (flat form: `AUTOSKILLIT_AGENT_BACKEND`) into `CmdSpec.env` for `build_interactive_cmd` and `build_resume_cmd` in both the Claude Code and Codex backends. Currently, only `build_skill_session_cmd` and `build_food_truck_cmd` set this variable. Guard hooks launched in interactive and resume sessions cannot dispatch on backend identity without reading `os.environ`, which creates an implicit cross-layer contract. This plan closes that gap, updates the `CODEX_INTERACTIVE_REQUIRED_ENV` validation gate to include the newly injected var, and adds two parametrized arch tests to enforce the invariant across all four builder methods for every registered backend. ## Requirements ## Conflict Resolution Decisions The following files had merge conflicts that were automatically resolved. Closes #4019 ## Implementation Plan Plan file: `/home/talon/projects/autoskillit-runs/impl-20260612-113600-305738/.autoskillit/temp/make-plan/t5-p4-a3-wp3-ensure-every-guard-hook-in-a-launched-session-c_plan_2026-06-12_114500.md` 🤖 Generated with [Claude Code](https://claude.com/claude-code) via AutoSkillit <!-- autoskillit:pipeline-signature steps=prepare_pr,run_arch_lenses,compose_pr,annotate_pr_diff,review_pr --> ## Token Usage Summary | Step | Model | count | uncached | output | cache_read | peak_ctx | turns | cache_write | time | |------|-------|-------|----------|--------|------------|----------|-------|-------------|------| | plan* | opus[1m] | 1 | 78 | 20.2k | 1.9M | 103.5k | 58 | 82.3k | 13m 49s | | verify* | sonnet | 1 | 1.2k | 19.1k | 485.6k | 73.4k | 27 | 52.5k | 9m 55s | | implement* | MiniMax-M3 | 1 | 2.1M | 9.3k | 0 | 0 | 63 | 0 | 11m 55s | | audit_impl* | sonnet | 1 | 44 | 10.8k | 167.0k | 42.1k | 14 | 33.1k | 4m 2s | | prepare_pr* | MiniMax-M3 | 1 | 198.3k | 3.9k | 0 | 0 | 14 | 0 | 1m 20s | | compose_pr* | MiniMax-M3 | 1 | 213.0k | 1.4k | 0 | 0 | 12 | 0 | 42s | | **Total** | | | 2.6M | 64.7k | 2.5M | 103.5k | | 167.9k | 41m 45s | \* *Step used a non-Anthropic provider; caching behavior may differ.* ## Token Efficiency | Step | LoC Changed | cache_read/LoC | cache_write/LoC | output/LoC | |------|-------------|----------------|-----------------|------------| | plan | 0 | — | — | — | | verify | 0 | — | — | — | | implement | 84 | 0.0 | 0.0 | 111.2 | | audit_impl | 0 | — | — | — | | prepare_pr | 0 | — | — | — | | compose_pr | 0 | — | — | — | | **Total** | **84** | 30192.5 | 1998.8 | 770.7 | ## Model Usage Breakdown | Model | steps | uncached | output | cache_read | cache_write | time | |-------|-------|----------|--------|------------|-------------|------| | opus[1m] | 1 | 78 | 20.2k | 1.9M | 82.3k | 13m 49s | | sonnet | 2 | 1.2k | 29.9k | 652.6k | 85.6k | 13m 57s | | MiniMax-M3 | 3 | 2.6M | 14.6k | 0 | 0 | 13m 58s | --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent ff890b6 commit 6c398d5

5 files changed

Lines changed: 83 additions & 3 deletions

File tree

src/autoskillit/core/types/_type_constants_env.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@
158158
CODEX_INTERACTIVE_REQUIRED_ENV: frozenset[str] = frozenset(
159159
{
160160
MCP_CLIENT_BACKEND_ENV_VAR,
161+
AGENT_BACKEND_ENV_VAR,
161162
}
162163
)
163164

src/autoskillit/execution/backends/claude.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,8 @@ def build_interactive_cmd(
461461
for t in tools:
462462
builder.variadic_pair(ClaudeFlags.TOOLS, t)
463463
merged: dict[str, str] = dict(SHARED_BASELINE_ENV)
464+
merged[AGENT_BACKEND_ENV_VAR] = AGENT_BACKEND_CLAUDE_CODE
465+
merged[AGENT_BACKEND_DYNACONF_ENV_VAR] = AGENT_BACKEND_CLAUDE_CODE
464466
if env_extras:
465467
merged.update(env_extras)
466468
interactive_base = {
@@ -500,6 +502,8 @@ def build_resume_cmd(
500502
case None:
501503
pass
502504
merged: dict[str, str] = dict(SHARED_BASELINE_ENV)
505+
merged[AGENT_BACKEND_ENV_VAR] = AGENT_BACKEND_CLAUDE_CODE
506+
merged[AGENT_BACKEND_DYNACONF_ENV_VAR] = AGENT_BACKEND_CLAUDE_CODE
503507
if env_extras:
504508
merged.update(env_extras)
505509
env = dict(build_agent_env(base={}, extras=merged))

src/autoskillit/execution/backends/codex.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,7 @@ def build_interactive_cmd(
888888
"AUTOSKILLIT_HEADLESS": "",
889889
"AUTOSKILLIT_HEADLESS_AUTO_GATE": "",
890890
"AUTOSKILLIT_SESSION_TYPE": "",
891+
AGENT_BACKEND_ENV_VAR: AGENT_BACKEND_CODEX,
891892
AGENT_BACKEND_DYNACONF_ENV_VAR: AGENT_BACKEND_CODEX,
892893
MCP_CLIENT_BACKEND_ENV_VAR: AGENT_BACKEND_CODEX,
893894
FOOD_TRUCK_TOOL_TAGS_ENV_VAR: "",
@@ -926,7 +927,9 @@ def build_resume_cmd(
926927
cmd.append(resume_session_id)
927928
cmd.append(prompt)
928929
filtered_base = {k: v for k, v in os.environ.items() if k not in _HEADLESS_EXCLUSIVE_VARS}
929-
resume_extras = _codex_exec_extras(session_type="", include_session_baseline=True)
930+
resume_extras = _codex_exec_extras(
931+
session_type="", include_session_baseline=True, include_agent_backend_flat=True
932+
)
930933
if env_extras:
931934
resume_extras.update(env_extras)
932935
env = self.env_policy().build_env(

tests/arch/test_env_symmetry.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,72 @@ def test_dynaconf_and_flat_backend_values_match() -> None:
158158
food_truck_spec.env["AUTOSKILLIT_AGENT_BACKEND__BACKEND"]
159159
== food_truck_spec.env["AUTOSKILLIT_AGENT_BACKEND"]
160160
), f"{name}: nested and flat AGENT_BACKEND values differ in build_food_truck_cmd"
161+
162+
163+
_ALL_GUARD_BUILDERS: list[str] = [
164+
"build_skill_session_cmd",
165+
"build_food_truck_cmd",
166+
"build_interactive_cmd",
167+
"build_resume_cmd",
168+
]
169+
170+
171+
def _call_builder(backend: object, builder_name: str) -> object:
172+
from autoskillit.core.types._type_plugin_source import DirectInstall
173+
174+
if builder_name == "build_skill_session_cmd":
175+
return backend.build_skill_session_cmd(
176+
"/autoskillit:investigate", "/repo", completion_marker="DONE"
177+
)
178+
if builder_name == "build_food_truck_cmd":
179+
return backend.build_food_truck_cmd(
180+
orchestrator_prompt="test prompt",
181+
plugin_source=DirectInstall(plugin_dir=Path("/plugins")),
182+
cwd="/repo",
183+
completion_marker="DONE",
184+
)
185+
if builder_name == "build_interactive_cmd":
186+
return backend.build_interactive_cmd()
187+
if builder_name == "build_resume_cmd":
188+
return backend.build_resume_cmd(resume_session_id="test-session", prompt="continue")
189+
msg = f"Unknown builder: {builder_name}"
190+
raise ValueError(msg)
191+
192+
193+
@pytest.mark.parametrize("builder_name", _ALL_GUARD_BUILDERS)
194+
def test_agent_backend_flat_env_var_in_all_guard_launch_builders(builder_name: str) -> None:
195+
"""AUTOSKILLIT_AGENT_BACKEND must appear in every builder's CmdSpec.env for every backend."""
196+
from autoskillit.core.types._type_backend import CmdSpec
197+
from autoskillit.execution.backends import BACKEND_REGISTRY
198+
199+
assert BACKEND_REGISTRY, "BACKEND_REGISTRY is empty — test provides no coverage"
200+
for name, cls in BACKEND_REGISTRY.items():
201+
backend = cls()
202+
if builder_name == "build_resume_cmd" and not backend.capabilities.session_resume_capable:
203+
continue
204+
spec = _call_builder(backend, builder_name)
205+
assert isinstance(spec, CmdSpec), f"{name}: {builder_name} did not return CmdSpec"
206+
assert "AUTOSKILLIT_AGENT_BACKEND" in spec.env, (
207+
f"{name}: AUTOSKILLIT_AGENT_BACKEND missing from {builder_name} env"
208+
)
209+
210+
211+
def test_agent_backend_flat_and_dynaconf_values_match_in_interactive_and_resume() -> None:
212+
"""Nested and flat AGENT_BACKEND env vars must carry the same value in interactive and resume builders.""" # noqa: E501
213+
from autoskillit.execution.backends import BACKEND_REGISTRY
214+
215+
assert BACKEND_REGISTRY, "BACKEND_REGISTRY is empty — test provides no coverage"
216+
for name, cls in BACKEND_REGISTRY.items():
217+
backend = cls()
218+
interactive_spec = backend.build_interactive_cmd()
219+
assert (
220+
interactive_spec.env["AUTOSKILLIT_AGENT_BACKEND__BACKEND"]
221+
== interactive_spec.env["AUTOSKILLIT_AGENT_BACKEND"]
222+
), f"{name}: nested and flat AGENT_BACKEND values differ in build_interactive_cmd"
223+
if not backend.capabilities.session_resume_capable:
224+
continue
225+
resume_spec = backend.build_resume_cmd(resume_session_id="test-session", prompt="continue")
226+
assert (
227+
resume_spec.env["AUTOSKILLIT_AGENT_BACKEND__BACKEND"]
228+
== resume_spec.env["AUTOSKILLIT_AGENT_BACKEND"]
229+
), f"{name}: nested and flat AGENT_BACKEND values differ in build_resume_cmd"

tests/arch/test_subpackage_isolation.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -985,7 +985,7 @@ def test_data_directories_are_not_python_packages() -> None:
985985
"dispatch gate add defense-in-depth checks",
986986
),
987987
"execution/backends/codex.py": (
988-
1062,
988+
1066,
989989
"REQ-CNST-010-E9: Codex backend — skill_sigil capability threading adds multi-line "
990990
"keyword args to _ensure_skill_prefix call sites and _has_prefix guard; "
991991
"write_guard_tool_names env injection adds 7 lines to _codex_exec_extras; "
@@ -998,7 +998,10 @@ def test_data_directories_are_not_python_packages() -> None:
998998
"; session_log_path method added to CodexSessionLocator (+5 net lines)"
999999
"; process_idle_timeout_ms field wired through build_skill_session_cmd and "
10001000
"build_food_truck_cmd CmdSpec constructors (+8 net lines)"
1001-
"; CapabilityNotSupportedError capability-gate in build_inspector_cmd (+1 net line)",
1001+
"; CapabilityNotSupportedError capability-gate in build_inspector_cmd (+1 net line); "
1002+
"AGENT_BACKEND_ENV_VAR injection in build_interactive_cmd merged_extras (+1 net line) "
1003+
"and build_resume_cmd _codex_exec_extras call expansion to multi-line (+2 net lines) "
1004+
"for T5-P4-A3-WP3 guard-hook backend dispatch",
10021005
),
10031006
}
10041007

0 commit comments

Comments
 (0)