Skip to content

[FEATURE] Session-scoped ingredient locking with structural enforcement#3381

Merged
Trecek merged 6 commits into
developfrom
session-scoped-ingredient-locking-configure-overlay-hook-enf/3357
May 31, 2026
Merged

[FEATURE] Session-scoped ingredient locking with structural enforcement#3381
Trecek merged 6 commits into
developfrom
session-scoped-ingredient-locking-configure-overlay-hook-enf/3357

Conversation

@Trecek
Copy link
Copy Markdown
Collaborator

@Trecek Trecek commented May 31, 2026

Summary

Add a lock_ingredients MCP tool and ingredient_lock_guard.py PreToolUse hook to structurally enforce recipe ingredient values within a kitchen session. The orchestrator calls lock_ingredients once at session start to declare ingredient values (e.g., investigate=false). The server resolves these into a locked_steps dict (step name -> allowed bool) written to the hook config overlay. A server-side check in run_skill is the primary enforcer; the hook provides supplemental UX feedback and audit trail. close_kitchen already deletes the overlay file, clearing all locks automatically.

This addresses the recurring step-skipping failure class (#1083, #3337, #3065, #3238) where orchestrators re-interpret user instructions at every step and drift.

Requirements

Add a unified session-scoped ingredient locking mechanism: a single MCP tool (lock_ingredients) that lets the orchestrator declare ingredient values at session start, persists them to the hook config overlay, and a new PreToolUse hook (ingredient_lock_guard.py) that structurally enforces those values — blocking run_skill calls that violate the declared configuration.

This closes the gap where the orchestrator can misinterpret or forget user instructions mid-session. Once the orchestrator locks investigate=false, the system enforces it mechanically — the orchestrator cannot override its own declaration without explicitly unlocking first.

Closes #3357

Implementation Plan

Plan file: /home/talon/projects/autoskillit-runs/impl-20260530-183542-576015/.autoskillit/temp/make-plan/session_scoped_ingredient_locking_plan_2026-05-30_190000.md

🤖 Generated with Claude Code via AutoSkillit

Token Usage Summary

Step Model count uncached output cache_read peak_ctx turns cache_write time
plan* opus[1m] 1 125 33.0k 4.8M 134.7k 238 117.9k 25m 18s
verify* opus[1m] 1 49 12.9k 696.3k 70.4k 156 54.2k 6m 36s
implement* opus[1m] 1 264.6k 37.1k 8.0M 19.3k 522 0 1h 12m
audit_impl* opus[1m] 1 295 26.7k 380.5k 66.3k 88 74.4k 10m 6s
prepare_pr* opus[1m] 1 61.4k 4.5k 273.9k 0 21 0 58s
compose_pr* opus[1m] 1 53.3k 1.3k 165.7k 27.6k 16 42.2k 48s
review_pr* opus[1m] 1 930 34.5k 2.2M 154.9k 89 275.4k 9m 57s
resolve_review* opus[1m] 1 72 15.4k 3.5M 102.0k 82 102.9k 12m 53s
diagnose_ci* opus[1m] 1 38.0k 2.8k 555.8k 0 33 0 1m 39s
resolve_ci* opus[1m] 1 34 3.6k 253.3k 50.9k 23 36.8k 5m 39s
Total 418.8k 171.8k 20.8M 154.9k 703.7k 2h 26m

* 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 1195 6675.0 0.0 31.0
audit_impl 0
prepare_pr 0
compose_pr 0
review_pr 0
resolve_review 120 29072.0 857.5 128.2
diagnose_ci 0
resolve_ci 0
Total 1315 15823.9 535.1 130.6

Model Usage Breakdown

Model steps uncached output cache_read cache_write time
opus[1m] 10 418.8k 171.8k 20.8M 703.7k 2h 26m

Trecek and others added 6 commits May 30, 2026 21:07
Adds lock_ingredients MCP tool and ingredient_lock_guard PreToolUse hook
for structurally enforcing recipe ingredient values within a kitchen session.

- lock_ingredients tool: locks ingredient values to overlay, resolves locked_steps
  from active_recipe_steps using skip_when_false, supports unlock by rebuilding
  locked_steps from remaining ingredients
- _write_ingredient_locks helper: atomic R-M-W with fcntl.flock serialization
- _check_ingredient_locks: server-side enforcement in run_skill (primary gate),
  with resume_session_id exemption
- ingredient_lock_guard.py: supplemental PreToolUse hook (fail-open)
- Registered in HOOK_REGISTRY (run_skill + dispatch_food_truck) and NEW_SUBDIR_BASENAMES
- Updated FREE_RANGE_TOOLS, _DISPLAY_CATEGORIES, _UNFORMATTED_TOOLS, _TOOL_PARAMS
- Updated hardcoded test fixtures (test_type_constants, test_gate,
  test_server_tool_registration, test_doc_counts)
- Added INGREDIENT LOCKING section to sous-chef SKILL.md
- Updated doc tool counts (55->56) across all documentation files
- Added test files: test_lock_ingredients.py, test_run_skill_locks.py,
  test_ingredient_lock_guard.py
- Updated sub-CLAUDE.md file tables for new files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- fcntl allowlist: add tools_kitchen.py to fcntl allowed paths
- doc counts: update test_docs_state_55_mcp_tools to test_docs_state_56_mcp_tools
- never_raises contract: refactor lock check into single-expression guard
- json write sites: update allowlist line numbers for atomic_write
- _TOOL_PARAMS sync: add tools_kitchen to _SERVER_TOOL_MODULES
- unlock bug: fix locked_ingredients pop on dict copy; pass None signal
  for locked_steps when pipeline becomes empty
- headless denial test: assert result["result"] instead of result["error"]
- business logic extraction: move step-computation to module-level
  helpers (_compute_locked_steps, _compute_unlocked_steps, _apply_unlock_keys)
- subpackage isolation: bump hooks/guards exemption to 24 files
- hook registry hash: sync via task sync-hooks-hash
- arch violations: replace print() with sys.stdout.write() in guard
- hook test env: add cwd parameter to _run helper

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fail-open tests (empty stdin, invalid JSON, missing overlay) previously
only asserted exit code 0 without checking that no deny JSON was emitted.
A regression that outputs a deny JSON on malformed input would pass silently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The second unlock loop via .get() was redundant — the first loop already
pops all unlock_keys from li[pipeline_id] when pipeline_id exists. The
second loop using .get() either operates on the same dict (no-op since
keys were already popped) or on a transient dict (no effect).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move overlay read + merge + step computation into _write_ingredient_locks
so all state operations happen under the flock. Previously lock_ingredients
read the overlay outside the flock to compute current_pipeline_li, then
passed the pre-computed values to _write_ingredient_locks which read
again under flock — a concurrent call between reads could lose updates.

Also removes dead _compute_locked_steps function (extracted but never
called — _compute_unlocked_steps was used instead).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The _write_ingredient_locks refactor moved the atomic_write call from
line 762 to line 751. Update the _LEGACY_JSON_WRITES allowlist to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Trecek Trecek force-pushed the session-scoped-ingredient-locking-configure-overlay-hook-enf/3357 branch from a47353e to 15a21be Compare May 31, 2026 04:07
@Trecek Trecek added this pull request to the merge queue May 31, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 31, 2026
@Trecek Trecek added this pull request to the merge queue May 31, 2026
Merged via the queue into develop with commit 795f93d May 31, 2026
3 checks passed
@Trecek Trecek deleted the session-scoped-ingredient-locking-configure-overlay-hook-enf/3357 branch May 31, 2026 04:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant