Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions docs/decisions/0004-recipe-redelivery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# ADR-0004: Codex Recipe Knowledge Re-Delivery via load_recipe

**Status:** Accepted
**Date:** 2026-06-27
**Issue:** [#4051](https://github.com/TalonT-Org/AutoSkillit/issues/4051)

## Context

Codex auto-compaction fires at 90% of the 258K context window. When triggered, it can
destroy recipe content delivered by `open_kitchen` in the initial orchestrator response.
After compaction, the agent loses the recipe steps it needs to complete the pipeline.

Two defenses exist:

1. **Primary β€” disable compaction**: `ensure_codex_mcp_registered` writes
`model_auto_compact_token_limit = 999_999_999` (an unreachable threshold) to
`~/.codex/config.toml`. `setup_session_dir` copies this config into each session
directory, ensuring every headless session inherits the setting.

2. **Guard β€” block raw recipe reads**: The `recipe_read_guard.py` PreToolUse hook
blocks `run_cmd`/`Bash` from reading recipe YAML, SKILL.md, or agent definition
files, and blocks `run_python` from calling `autoskillit.recipe.*` callables.
This prevents the agent from self-recovering from compaction loss via raw file
access β€” compaction loss is treated as a hard failure, not a recoverable state.

If the primary defense is ever relaxed (e.g., Codex fixes its compaction behavior or
the context window grows), the agent needs a sanctioned channel to re-acquire recipe
knowledge without reading raw files.

## Decision

> **`load_recipe` (the MCP tool exposed by `server/tools/tools_recipe.py`) is the
> sanctioned channel for recipe knowledge re-delivery after context compaction.**

If recipe content is lost, the agent must call `load_recipe` to re-acquire it. The
`recipe_read_guard` enforces that only this path is allowed β€” raw file access is denied.

This is a forward obligation: when the primary defense (unreachable auto-compact limit)
is relaxed, `load_recipe` re-delivery must be tested end-to-end as the recovery path.

## Consequences

- `recipe_read_guard.py` error messages direct the agent to call `load_recipe`.
- No production code changes are needed today β€” the channel already exists and works.
- Future work relaxing `CODEX_AUTO_COMPACT_LIMIT` must add integration tests verifying
`load_recipe` re-delivery restores full pipeline execution capability.
- `test_copied_config_has_auto_compact_limit` validates the primary defense path:
`setup_session_dir` preserves the override in the copied `config.toml`.
1 change: 1 addition & 0 deletions docs/decisions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
- [0001-prohibit-background-subagent-execution.md](0001-prohibit-background-subagent-execution.md) β€” prohibit `run_in_background: true` in all skills
- [0002-ban-inline-shell-scripts-from-cmd.md](0002-ban-inline-shell-scripts-from-cmd.md) β€” Prohibit inline shell scripts in recipe cmd fields; require externalization to .sh files or run_python callables
- [0003-skill-args.md](0003-skill-args.md) β€” Pass skill inputs as positional arguments, not environment variables
- [0004-recipe-redelivery.md](0004-recipe-redelivery.md) β€” Sanctioned `load_recipe` channel for recipe knowledge re-delivery after Codex context compaction
23 changes: 23 additions & 0 deletions tests/execution/backends/test_codex_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -1753,3 +1753,26 @@ def test_no_git_subdir_created(self) -> None:
self._write_all_source_files()
CodexBackend().setup_session_dir(self.session_dir)
assert not (self.session_dir / ".git").exists()

def test_copied_config_has_auto_compact_limit(self) -> None:
import tomllib

from autoskillit.execution.backends import CODEX_AUTO_COMPACT_LIMIT

(self.codex_home / "config.toml").write_text(
f"model_auto_compact_token_limit = {CODEX_AUTO_COMPACT_LIMIT}\n"
"[mcp_servers.autoskillit]\n"
)
(self.codex_home / "auth.json").write_text("{}")
CodexBackend().setup_session_dir(self.session_dir)
data = tomllib.loads((self.session_dir / "config.toml").read_text(encoding="utf-8"))
assert data["model_auto_compact_token_limit"] == CODEX_AUTO_COMPACT_LIMIT

def test_session_config_lacks_key_when_source_lacks_it(self) -> None:
import tomllib

(self.codex_home / "config.toml").write_text("[mcp_servers.autoskillit]\n")
(self.codex_home / "auth.json").write_text("{}")
CodexBackend().setup_session_dir(self.session_dir)
data = tomllib.loads((self.session_dir / "config.toml").read_text(encoding="utf-8"))
assert "model_auto_compact_token_limit" not in data
Loading