Skip to content

Latest commit

 

History

History
229 lines (198 loc) · 15.9 KB

gate-addons.md

File metadata and controls

229 lines (198 loc) · 15.9 KB

Gate add-ons — advisory path bindings for the reasoning kernel

---
title: "Gate add-ons for the reasoning kernel"
owner: "Local_Development"
status: "production"
surface: "prompt_kernel_addons"
last_verified: "2026-09-12"
tags: ["prompt_kernel", "kernel", "addons", "paths"]
related_code: ["prompt_kernel/addons.py", "prompt_kernel/render.py"]
summary: "Advisory host-path bindings per kernel gate, rendered as section 6 without touching the frozen kernel graph."
reproduce:
  files: ["prompt_kernel/addons.py", "prompt_kernel/tests/test_addons.py"]
  commands: ["python -m pytest prompt_kernel/tests/ -q", "python -m prompt_kernel --install"]
  expected_outputs: ["pytest: 78 passed", "installed=<sha256> matches prompt_kernel/baseline.json"]
---

Principle

The kernel (prompt_kernel/source.py) is a frozen graph: gates, edges, rules, identities, and budgets. Host-project conventions — where plans live, where experiments go, what to update at closure — are not kernel content. They are advisory bindings, owned by a data-only registry and rendered inline inside each gate's <Gx_RULES> block (after the gate's own rules):

prompt_kernel/addons.py   →  data registry: GateAddon(gate_id, addon_id, lines)
prompt_kernel/render.py   →  <Gx_RULES> blocks: kernel rules (inline prose) + addon lines
prompt_kernel/artifacts.py→  manifest: addons.count + addons.sha256
python -m prompt_kernel --install
                          →  packages/opencode/src/session/prompt/reasoning_prompt.txt
prompt_kernel/baseline.json → production sha256 pin

Kernel structure stays stable: gates, edges, shared_rules: reference lists, and state contracts are untouched. Rule naming follows the single-use principle: a rule gets a #### @ID header only when something references it (another rule's @REF, a shared_rules: list, or the compatibility contract pin CONTRACT_PINNED_RULES); single-use rules render as plain bullets. Addon lines must not use @-references (outside kernel namespace validation).

Which new rule goes into which gate

Gate Owns Bind Do not bind
G1 GROUND what to read before work read-surfaces: plans, docs mutable write paths
G2 DECOMPOSE candidate geography scratch/draft lanes plan artifacts
G3 MASTER_PLAN plan artifacts plan location + mandatory sections implementation paths
G4 AUTHORIZE (free) approval-artifact paths if they appear execution paths
G6 GROUND_PLAN (free) binding-surface hints progress logs
G7 IMPLEMENT work logging progress log path plan completion moves
G8 ORACLE (free) evidence/artifact locations closure moves
G9 CLEAN_STATE closure geography completed-plans move, docs/index updates, deprecation new rules of other gates

Rule of thumb: if deleting the line would change what the kernel decides → it belongs in source.py (requires a full kernel change cycle). If it only says where a thing lives on disk in this host project → it is an addon.

Current inventory

Gate Addon Binding
G1 PATH_GROUNDING plans/*.md, docs/ first; never .opencode/plans/
G1 TOOL_GROUNDING codegraph, read, messagesearch, webfetch/universalsearch
G1 PATH_SURFACE_DOCS .opencode/skills/<surface>/references/** before building on a framework API; cite file:line (2026-09-20)
G2 PATH_EXPERIMENTS experiments/ scratch; futures/ drafts; [ISO8601]_name one-offs. This row was the ONLY record of the binding for a month — the registry lacked it until 2026-09-20, so the working prompt carried no trace of the project's own law while this doc described it (2026-09-20)
G2 TOOL_DECOMPOSE todowrite
G3 PATH_PLANS plans/[ISO8601]_<description>.md; Smoke Tests before G4
G4 TOOL_AUTHORIZE checkstate (identity/permission/window), question (ASK)
G6 TOOL_BINDING codegraph explore/impact, read-only task grounding
G6 SURFACE_CONSUMERS enumerate a multi-consumer surface's importers before binding, and name the one you touch (2026-09-20)
G7 PATH_PROGRESS _progress_log.md [TIMESTAMP] entry per bounded task
G7 TOOL_IMPLEMENT edit, write; crash-prone shell via cmd_runner
G8 TOOL_ORACLE tests via cmd_runner, jobwait, logsearch, dbread
G8 ORACLE_INSTRUMENT_CHECK a capture is evidence only after it is validated — whole object, unoccluded, not a viewport crop (2026-09-20)
G9 PATH_CLOSURE undone task → [~] + reason, plan moves by outcome + stale-ref scan; docs/index update; obsolete/
G9 TOOL_CLOSURE messagesearch verify; git status

Identity add-ons (2026-09-28)

Section 5 (IDENTITY_CONTRACTS) stopped advertising gates:/may_mutate: — gates are a workflow property, not an identity property — and gained tool rows instead: one tools: line per identity, rendered from IDENTITY_ADDONS (IdentityAddon(identity_id, addon_id, lines), validated by validate_identity_addons, injected into the ### IDENTITY block by _render_identity). They exist because the provider tool catalogue is deliberately IDENTICAL for every identity (session/llm.ts — «Wire filtering is FORBIDDEN here… breaks the KV prefix»), so the rows are the only place an identity can read its real ACL.

The rows are kept honest by the extractor, not by discipline:

Half Where What it does
Extractor packages/opencode/script/kernel-tools-manifest.ts prints identity → { allowed, denied } from the LIVE rulesets (agent.ts) via agentDeniesPolicy — the agent-scoped half of SessionTools.denied (Gate A), evaluated on the tool POLICY (session/tools.ts:259 calls denied(item.policy), not the id)
Parity test — removed 2026-09-29 the runtime is decoupled from the kernel build, so no TS test reads reasoning_prompt.txt any more (the old kernel-identity-tools.test.ts and kernel-may-mutate.test.ts are gone). Rows are regenerated from this extractor; ACL behaviour is proven by test/agent/agent.test.ts

Format: narrow identities list what they may use (tools: checkstate, memory, reasoningexit.); wide identities list exclusions (tools: all except planenter, planexit.). Prose after ; carries path-scoped edit boundaries (write/edit: plans/ only.) that ctx.ask enforces at execution — the extractor prints only the id sets, so a prose note never appears in its output, and only the ACL keeps a note true. MCP tools are host-configured and not enumerated: the rows describe the builtin catalogue.

Product-only by decision (owner, 2026-09-28: «У claude и codex свои тулы не наши»): the Claude and Codex registries render section 5 WITHOUT tool rows; when those harnesses expose a machine-readable ACL source, add per-registry rows.

Render measured 2026-09-28: product 48 274 B / 6 290 tokens against caps 49 000 B (KERNEL.utf8_budget) and 7 000 tokens (test_dedup.py) — the step that admits the rows (~1.3 KB of rows against ~0.6 KB of removed gates:/may_mutate: lines) is recorded in source.py beside the cap.

Codex harness variant

addons_codex.py is a third registry, alongside the product default and addons_claude.py. It keeps the graph unchanged while binding this Codex harness to Read/Glob/Grep, Edit/Write, LSP, AST Edit, Hub, Eval Browser access, Todo, Task, and mounted codegraph_explore.

python -m prompt_kernel --codex writes reviewed/runtime artifacts to prompt_kernel/dist_codex/. The kernel reaches Codex ONLY as $CODEX_HOME/AGENTS.md (t64, owner 2026-10-16: «developer_instructions писать не надо просто AGENTS.md в ~.codex и все» — reverses t54; the global file is read whole, with no project_doc_max_bytes cap). --codex --install writes no receiver; the command prints the pointer to --codex-global install, the single writer.

Divergences from the ADID methodology (deliberate): makeups/ is not bound — opencode bans mocks/stubs in tests; RAG indexing and the adm XML pipeline are skill-owned in the ADID package and stay out of the opencode kernel.

Absences are bindings too (2026-09-17). Three shared mechanisms landed in source.py on 2026-09-16 — the DELEGATION protocol, @COMPACTION_CADENCE, and the persisted-criterion store INTENTION_RESET requires — and the product and Claude registries each got a host binding for them while this one did not, which left the variant declaring mechanisms it named no instrument for. It now binds them, including where the honest binding is that the instrument does not exist:

Gate Addon Binding
G1 PATH_GROUNDING no memory tool here, so criteria persist in plans/*.md and _progress_log.md, read at grounding
G2 PATH_EXPERIMENTS the same law as the product — experiments/ scratch, experiments_history/ after a content check; both folders are the REPOSITORY's, so this line is host-independent
G1 PATH_SURFACE_DOCS no skill tool — .opencode/skills/<surface>/references/** are plain files to read
G6 SURFACE_CONSUMERS codegraph_explore, plus LSP references/implementation for language-aware reach
G8 ORACLE_INSTRUMENT_CHECK Browser through Eval is the instrument; a viewport crop is not
G7 TOOL_DELEGATE Task — brief it once with binding, falsifier and the parent intention; re-brief a follow-up slice
G8 TOOL_ORACLE no isolated model call is bound, and a Task shares this frame → a self-verdict closes Inferred or Unknown
G9 TOOL_CLOSURE the fold is lossy — handles go to plans/, docs/, _progress_log.md before it runs

Naming a missing instrument is what keeps the rung from being skipped silently: an agent told only "stamp it with an outside falsifier" on a host that has none will reach for the nearest thing that answers, which is itself.

How to add an addon

  1. Append GateAddon(gate_id, addon_id, lines) to GATE_ADDONS in prompt_kernel/addons.py (or IdentityAddon(identity_id, addon_id, lines) to IDENTITY_ADDONS for a section-5 tool row — see Identity add-ons).

  2. Constraints (enforced by validate_addons() / validate_identity_addons()): gate_id ∈ G1–G9 / identity_id ∈ KERNEL.identities, unique addon_id, non-empty lines. No @-references in lines.

  3. Budgets are shared — CURRENT (2026-10-01): byte cap KERNEL.utf8_budget 58 000 and token cap 7 700, the same for all three variants (test_dedup.py, test_addons_claude.py, test_addons_codex.py, raised together); reasons per step in source.py and kernel-release-2026-09-29.md. The rest of this item is HISTORY: byte cap (36 000, raised from 35 000 on 2026-09-20) and token cap in tests/test_dedup.py::test_compacted_runtime_budget (4 750, raised from 4 500 there). The Claude and Codex variants carry their own ceilings in tests/test_addons_claude.py and tests/test_addons_codex.py (36 000 / 4 750) because --claude --install writes a whole file rather than filling a sized slot. Current renders, measured 2026-09-20, second step (the four addon bindings): product 36 904 bytes / 4 819 tokens, Claude 36 691 / 4 782, Codex 36 983 / 4 836. The caps are now 37 000 bytes (KERNEL.utf8_budget) and 5 000 tokens (test_compacted_runtime_budget), with both variants at 38 000 / 5 150 — their +1 000 B / +150 tok margin, restored, because --claude --install writes a whole file rather than filling a sized slot. Before this step: product 35 419 / 4 603. Both figures were MEASURED, never estimated, and measured with an instrument that reports bytes and tokens TOGETHER — the byte assert runs first, so a failing byte cap hides the token number entirely: the first run of this change reported four byte failures and left the token cap unexercised. History: 25 000 → 26 000 → 27 000 → 28 000 → 30 000 → 31 000 → 32 000 → 35 000 → 36 000 → 37 000 bytes, 2 950 → 3 100 → 3 300 → 3 450 → 3 700 → 3 850 → 3 950 → 4 500 → 4 750 → 5 000 tokens, each step named in the test comments with what it admits. The 2026-09-20 steps admit, first, @INSTRUMENT_RUNG, @MEDOID_SIMPLEX and @CLAIM_CITATION — the three rules that describe the entry: an instrument may not certify below its rung, a surface needs at least three medoids with independent sources, and a claim above Guess carries its mechanism and a pin — and then the four addon bindings those rules pointed at: PATH_EXPERIMENTS, PATH_SURFACE_DOCS, SURFACE_CONSUMERS, ORACLE_INSTRUMENT_CHECK. Formal notation is admissible where it is tighter than prose — restating @LOOP_PROGRESS as a strict lexicographic decrease gave back 88 bytes with no loss of meaning. Growth policy (Alexander, 2026-09-11 → 2026-09-12): raises are deliberate, not drift — "28к мелкая плата за будущие ошибки… нужны дополнения по мере использования". As of 2026-09-12 raising is no longer an owner gate ("можешь поставить столько сколько тебе надо"): the cap existed partly to stop models that append without reading, and that failure is actually caught by dedup.py (find_unapproved_semantic_overlaps ≥ 0.58, repeated_ngrams width 5 / 4×), whose allowlists are both empty. The cap now works as a scarcity detector — it forces the question "what here is dead?" before anything new lands. Convention: raise at the point of need, in the same commit, naming what it admits.

    The cap is a brevity prompt, never a gate (Alexander, 2026-09-20): "потолка кернела не существует, потолок сделан чтобы писать лаконично и все. Reasoning на первом месте всегда. Это окупается не 1000 токенов, а забегами на 100000000 токенов." On the same class of limit: "Да, мы экономим и токены и кэш, но не на решениях." The discipline is therefore cut prose, never a decision — redundancy, preamble and formatting go first; a rule, a criterion or a falsifier is never trimmed to fit a number. The exchange rate is not symmetric and the owner states it from experience, not from arithmetic: a missing rule is paid for in whole runs (his own comparison, 2026-09-20 — a task this governed loop closed in five requests, which a stronger ungoverned model failed outright and reviewers called commodity work), while a line is paid for in bytes.

    What may be cut (owner-aligned, 2026-09-23, recorded from a Codex review that adopted the kernel itself). The cap must never push a reduction that shortens the kernel mechanically by bytes; a cut is legitimate only where ONE of three is demonstrated: (a) the rule is duplicated by another executing rule; (b) the rationale can be removed without changing behaviour; (c) the rule has no observable consumer and prevents no known defect class. The reformulation is theirs and it is better than the older phrasing: not «the kernel must shrink», but «the kernel must not swell with UNPROVEN prose». A byte-stable prefix amortizes its size across the whole cache; dynamic noise never amortizes — it is paid again at every step. Critical rules stay even when long.

  4. python -m pytest prompt_kernel/tests/ -q → all green.

  5. python -m prompt_kernel --install → note installed=<sha256>.

  6. Update sha256 in prompt_kernel/baseline.json.

  7. Rebuild the opencode binary; open a new session (old checkpoints keep the previous system prefix until compact).

Rollback

Revert addons.py (delete) + render.py/artifacts.py edits (.bak), restore baseline.json sha, re-run --install. assert_current_kernel_unchanged() must be green afterwards.