Skip to content

feat: add guarded local prompt mode - #91414

Open
mrkillbob wants to merge 7 commits into
NousResearch:mainfrom
mrkillbob:codex/lean-local-prompt-pr
Open

mrkillbob wants to merge 7 commits into
NousResearch:mainfrom
mrkillbob:codex/lean-local-prompt-pr

Conversation

@mrkillbob

@mrkillbob mrkillbob commented Aug 21, 2026

Copy link
Copy Markdown

Summary

  • add an exact-route, opt-in guarded prompt profile for local coding models
  • retain worktree, tool, permission, verification, and skill-discovery contracts
  • compact skill descriptions without hiding skills

Verification

  • ./venv/bin/python -m pytest -q -n 2 --dist loadfile tests/agent/test_coding_context.py tests/agent/test_system_prompt.py tests/agent/test_prompt_builder.py
  • 134 passed, 1 skipped

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades needs-decision Awaiting maintainer decision before any implementation labels Aug 21, 2026
@Enough1122

Copy link
Copy Markdown
Contributor

AI code review — automated review for reference; please use your judgment.

Reviewed by reviewer-e (AI automated review).

Solid fail-closed design: guarded mode needs focus posture + detected coding workspace + an exact provider/model pair from user config, defaults off, and swaps verbose coaching for a compact contract while keeping every skill name visible (compact_all_categories is properly threaded into the skills-prompt cache key). Docs and hermes dump coverage included. Findings:

  1. tests/agent/test_coding_context.py:104 — copy-paste artifact: the last two assertions are byte-identical (same provider+model twice), so the headline guarantee in the docs — "matching is by pair, not independent provider/model allowlists" — is never actually tested. Add the discriminating negative: with routes [{ollama-launch, hermes-qwen3-fast}], assert guarded_prompt_enabled(provider="ollama-launch", model="unrelated") and guarded_prompt_enabled(provider="some-other-provider", model="hermes-qwen3-fast") are both False. Right now a refactor to independent allowlists would pass CI.

  2. agent/system_prompt.py:508 — guarded mode suppresses tool_use_enforcement and task-completion guidance, whose own comments say the targeted failure modes ("stopping after a stub", fabricating output) "are not model-family specific" — and this mode intentionally targets small local models, the population most prone to them. Token savings vs. behavioral scaffolding is a legitimate tradeoff, but it's currently all-or-nothing; consider letting operators re-enable individual suppressed blocks (e.g. keep_task_completion_guidance: true under guarded_prompt_mode), since the compact contract's one-line "verify it with the relevant command" may not carry the same weight for a 4B model as the full block.

  3. agent/system_prompt.py:452 — kanban guidance is also swallowed by the blanket not _guarded_prompt gate. A dispatcher-spawned worker running on an allowed local route would lose its worker-lifecycle instructions while its kanban_* tools stay registered — the model sees the tools but no protocol. Cheap fix: exempt _kanban_worker_guidance from the suppression (it's already conditional on the env-gated tool being present).

@mrkillbob
mrkillbob force-pushed the codex/lean-local-prompt-pr branch from 4fcc70b to a2a502d Compare August 24, 2026 07:05
@mrkillbob

Copy link
Copy Markdown
Author

Addressed all three review points and rebased the PR onto current main.

  • Added the two discriminating exact-pair negatives: allowed provider with unrelated model, and unrelated provider with allowed model.
  • Guarded mode now retains universal task-completion guidance and configured tool-use enforcement instead of relying only on the compact one-line contract.
  • Env-gated Kanban worker lifecycle guidance now remains active in guarded mode; ordinary chat still cannot receive it without the worker context.
  • Updated defaults commentary and operator documentation.

Fresh canonical-runner evidence: 135 passed, 1 skipped, 0 failed across test_coding_context.py, test_system_prompt.py, and test_prompt_builder.py. Exact updated head: a2a502d.

# Conflicts:
#	agent/system_prompt.py
@mrkillbob

Copy link
Copy Markdown
Author

Resolved the merge conflict with main and pushed the result.

  • Merged origin/main (a87e8831f7) into codex/lean-local-prompt-pr; merge commit b16c3f8052 is now the PR head.
  • agent/system_prompt.py was the only conflicted file. Resolution keeps main's removal of the Nous subscription prompt block (refactor(prompt): remove the ~1.2K-token Nous Subscription block from the system prompt #95005), which supersedes this branch's guarded-mode suppression of that same block; all other guarded-prompt gating (guarded_prompt_enabled, GUARDED_EXECUTION_CONTRACT, skills/steering/computer-use/execution-guidance suppressions) is unchanged.
  • Tests (scripts/run_tests.sh): tests/agent/test_coding_context.py + tests/agent/test_system_prompt.py — 67 passed, 0 failed; tests/agent/test_prompt_builder.py + tests/agent/test_phantom_tool_references.py + tests/hermes_cli/test_dump_env_visibility.py — 83 passed, 0 failed, 1 skipped.
  • The PR now reports as mergeable.

# Conflicts:
#	agent/system_prompt.py
@mrkillbob

Copy link
Copy Markdown
Author

Resolved the current merge conflict with canonical main and pushed the normal merge commit b883ef3711f.

The conflict was limited to agent/system_prompt.py; the resolution preserves the PR’s guarded computer-use guidance while incorporating upstream main through bac960e23d.

Focused verification: 150 passed, 1 skipped across coding-context, system-prompt, prompt-builder, phantom-tool-reference, and dump-environment tests.

@mrkillbobbot

Copy link
Copy Markdown

Base refresh completed at 16af18c. Resolved the five reported merge-conflict files while preserving both sides; focused verification passed: 127 passed, 1 skipped in tests/agent/test_system_prompt.py and tests/agent/test_prompt_builder.py, plus 29 passed in tests/agent/test_coding_context.py.

@mrkillbobbot

Copy link
Copy Markdown

Confirmed the existing base-refresh repair remains at commit 16af18cfe1255b0187cce635e2e580d0c12cde87 on the PR head. Focused verification recorded with the repair: 127 passed, 1 skipped in tests/agent/test_system_prompt.py and tests/agent/test_prompt_builder.py, plus 29 passed in tests/agent/test_coding_context.py.

@mrkillbobbot

Copy link
Copy Markdown

@codex review

@mrkillbobbot

Copy link
Copy Markdown

Merged pinned base 693641a and resolved the coding context conflict while preserving the PR guarded prompt helper. Focused tests: pytest -q tests/agent/test_coding_context.py tests/agent/test_system_prompt.py tests/agent/test_prompt_builder.py (156 passed, 1 skipped). Repair commit: 9c4a157.

@mrkillbobbot

Copy link
Copy Markdown

@codex review

@mrkillbob

Copy link
Copy Markdown
Author

@Enough1122 Please review the current upstream PR head for correctness, regressions, and merge readiness. This request is specifically for your AI review; do not route it to Codex.

@Enough1122

Copy link
Copy Markdown
Contributor

AI code review — automated review for reference; please use your judgment.

Reviewed current head 2fea9a08ea0 — 1 blocking issue, 1 non-blocking note.

Blocking

Guarded mode can never activate in production: config is never passed on the real call pathagent/system_prompt.py:510, agent/coding_context.py:207

_guarded_prompt_enabled(agent) calls guarded_prompt_enabled(platform=..., cwd=..., provider=..., model=...) with no config, so inside guarded_prompt_enabled the line agent_cfg = (config or {}).get("agent", {}) or {} always yields {}, raw = {}.get("guarded_prompt_mode") is None, and the function returns False before ever reaching the route check. Note _coding_mode(config) right next to it does fall back to load_config_readonly() when config is None (via _agent_config_value), so the mode gate may pass while the opt-in gate unconditionally fails — the two halves read from different config sources. The unit tests pass config explicitly, which is why they don't catch this. Fix: resolve the opt-in flag through _agent_config_value(config, "guarded_prompt_mode", ...) (which loads global config when None) instead of reading (config or {}) directly, or thread the agent/session config through _guarded_prompt_enabled.

Non-blocking

Test seam in production codeagent/system_prompt.py:326-330: import run_agent; run_agent.__dict__.get("build_skills_system_prompt") silently swaps the skills-prompt implementation based on whatever happens to be monkey-patched into another module's namespace at call time. Works for the test, but as a permanent dispatch mechanism it's fragile (any test or plugin that patches run_agent changes production prompt output with no log). Consider an explicit parameter or registry instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants