hpatch [--root ROOT] [--cwd CWD] reads a complete HPATCH/2 script from standard input,
evaluates its complete change set in memory, stages required filesystem content, and
then commits it. After a successful commit it writes the final active-file state report
defined by REQ-OUTPUT-001 to stderr. Stdout remains empty.
hpatch translate [--root ROOT] [--cwd CWD] performs the same parsing, filesystem
reads, target verification, and in-memory evaluation but never modifies a file. It writes
one OpenAI apply_patch envelope that represents the same net change set to stdout, then
writes the pending final active-file state report to stderr.
For normal and translate modes, omitted --root means the process current directory
and omitted --cwd means .. An explicit root must be absolute and is canonicalized
before it is opened. A relative cwd resolves beneath root. An absolute cwd is accepted
only when its canonical location is beneath root. Cwd must identify an existing
directory. The CLI opens root once and uses that pinned capability for the invocation.
hpatch gain reads no script and reports the persistent aggregate defined by
REQ-METRICS-001. hpatch --help is the complete built-in agent reference for
stdin usage, process and editing commands, orchestration, trust boundaries, and
validation. hpatch --tool-help emits a separate, shorter model-facing summary of
target choice, baseline rules, and safety. It omits CLI usage and mode descriptions,
root and cwd options, metrics, and version material, and includes workspace-relative path
and parent-directory preparation guidance. It is not a generated slice of top-level help.
hpatch translate --help summarizes translate-mode I/O and points to top-level help.
hpatch --version writes the module build version, or devel for an unversioned build.
Informational commands do not read stdin, resolve a working or configuration directory,
access metrics, or inspect project files. Any other argument list is invalid.
Acceptance:
- Given valid edits spanning multiple files, normal mode produces the specified final paths and contents, empty stdout, and one final-state report on stderr.
- For LF inputs, applying translate mode's patch-only stdout produces the same final
paths and UTF-8 contents as normal mode. For other line endings, it represents the
same logical-line edits subject to the normalization rule in
REQ-OUTPUT-001. - Translate mode leaves the source tree unchanged and reports the pending result rather than the unchanged source tree.
- Gain mode leaves the source tree and metrics unchanged.
- Each supported informational form writes its complete result to stdout with status zero and empty stderr without reading stdin or requiring a valid current directory.
- Tool help remains a concise model-facing summary of target choice, baseline rules, safety, and tool-path guidance while excluding CLI-only material; top-level help remains the complete agent reference.
- Unsupported aliases, trailing arguments, and unknown or future options fail with no stdout or final-state report.
- A nested cwd changes relative path resolution while normal mutations and translated patch paths retain the same root-relative file identity.
- A relative, absolute, or symlink path that escapes root fails without mutation, patch output, or final-state report.
In hpatch router mode, the model receives hpatch and shell as standalone custom tools.
All persistent hread, hgrep, inspect_file, shell-execution, and HPATCH workflow guidance comes
from the Codex model_instructions_file installed from
contrib/codex/file-editing-instructions.md. The router never creates, changes, or removes the
top-level Responses instructions value.
Hread, hgrep, and inspect_file remain private executable contributions; their custom-tool
specifications are not sent to the model and direct model calls to their names are not routed.
The private hread command accepts exactly one file:
hread PATH [START:END]
The shell owns quoting and argument separation. A path containing whitespace is therefore one
ordinary quoted shell argument. START:END, when present, is an inclusive logical-line range
whose positive one-based base-ten endpoints must be ordered. The start line must exist. An end
past EOF returns through the final line. One hread process never accepts a second path or a
newline-delimited batch. The model batches related reads as separate hread commands in one
shell script.
The router creates process-scoped executable frontends named shell, hread, hgrep, and inspect_file.
The Codex executor must see the frontend directory and router executable at the same absolute
paths as the router process, and that directory must precede unrelated commands on its trusted
PATH. A deployment that isolates their filesystems must provide those runtime mounts
separately from the user workspace.
Hread runs in the shell carrier's actual working directory. Relative and absolute paths keep their ordinary process meaning. Codex, not the router or hread, owns sandbox and filesystem permissions. The worker accepts only regular UTF-8 files and never mutates them. It emits only the requested logical lines:
LINE:HASH TEXT
LINE is the positive one-based logical line number. TEXT is exact logical-line content
without its terminator. HASH is lowercase hexadecimal for the first two bytes of SHA-256
over that exact content, including leading spaces and tabs. A trailing file terminator does
not create an additional empty line. Missing, inaccessible, non-regular, non-UTF-8,
reversed-range, and start-past-EOF reads return concise stderr and nonzero status.
For input metrics, hread produces its current and stock results from the same read. The stock
result preserves selected TEXT and one LF per returned logical line while omitting the
LINE:HASH prefix. The comparison does not read a file twice.
Acceptance:
- A whole-file or bounded read emits exact UTF-8 rows. Equal lines at different positions have distinct row references, and indentation changes the hash.
hread PATH,hread PATH START:END, and a shell-quoted path containing whitespace work. Extra path or range arguments fail instead of being interpreted as a batch.- Several hread commands in one shell call execute in authored shell order without an hread-owned batch format, buffer, header, or partial-success policy.
- Reading and whole-file UTF-8 validation use bounded streaming storage and observe cancellation. A formatted result rejects before exceeding 16 MiB.
- Success and failure reach Codex through the model-visible shell carrier. Replay retains the original shell call and output; it never synthesizes a model-visible hread call or includes the shell call in editable rejected-script recovery history.
- Router startup fails before serving if the private hread frontend cannot be installed. Passthrough mode installs and exposes none of these replacement surfaces.
The private hgrep command is available through the model-visible shell tool. The shell owns
quoting, redirection, pipelines, and command composition; hgrep receives the resulting ordinary
argv. It accepts familiar ripgrep pattern, matching, file, glob, type, ignore, context, and
resource-selection arguments. With no explicit path, search defaults to the shell process's
current directory.
The worker invokes installed rg with internal --json --no-config arguments. It rejects
model-supplied output, multiline, preprocessor, compressed-input, informational, and other
modes that cannot identify one complete editable source row. Ripgrep retains ownership of
regex parsing, ignore rules, traversal, matching, and search diagnostics; hgrep provides no
fallback search engine.
The worker consumes ripgrep's structured match and context events and emits each first-seen logical row once in ripgrep result order:
"PATH":LINE:HASH TEXT
PATH is JSON-quoted. LINE:HASH TEXT has exactly the identity and complete UTF-8 semantics
of REQ-READ-001. Match highlighting, match-only fragments, replacement output, trimming,
and line truncation cannot change it. Multiple matches on one path and line produce one row.
Ripgrep's no-match exit status is a successful empty result. Execution, filesystem, encoding,
cancellation, invalid-pattern, and missing-executable failures return concise nonzero
diagnostics. Output contains only complete rows and stays within 16 MiB; reaching the bound
preserves completed rows and adds a limit diagnostic.
For input metrics, hgrep produces its current and stock results from the same ripgrep event
stream. The stock result preserves each JSON-quoted PATH, TEXT, LF, result order, and
diagnostic while omitting the LINE:HASH portion. The comparison does not run ripgrep twice.
Acceptance:
- A regular-expression search with an explicit path and glob emits JSON-quoted paths, positive line numbers, four-digit hashes, and exact complete matching lines that can be copied directly into an hpatch target.
- Shell quoting determines literal arguments, and ordinary redirection or pipelines operate as shell syntax rather than becoming hgrep argv. A conflicting hgrep output or transformed input mode still rejects before ripgrep starts.
- Requested before/after context emits complete verified rows beside matches. Repeated match or context events on one row emit that row once; no matches return successful empty stdout.
- The model-visible shell call and output are replayed unchanged. No standalone hgrep call is exposed, routed, or admitted to hpatch recovery history.
- Router startup fails before serving if the private hgrep frontend cannot be installed. Passthrough mode installs and exposes none of these replacement surfaces.
The private inspect_file PATH command is available only through the model-visible shell tool.
It accepts exactly one shell-separated workspace-relative path and no options. The canonical
workspace is process.cwd(). Absolute paths, lexical escapes, @shell paths, and symlinks whose
canonical targets escape that workspace fail. In-workspace symlinks are allowed, and the final
target must be a regular file.
Extension matching is exact and case-sensitive. .go, .js, .ts, .py, .md, and .json
use pinned parsers; TypeScript selects the JavaScript parser's ts dialect, while Markdown uses
Lezer for headings and YAML for a closed initial frontmatter block. Every other extension returns
kind: "none", the reported regular-file byte size, line_count: null, parse_complete: true,
and an empty outline without reading or decoding content. Supported files must be strict UTF-8.
Their logical line count matches REQ-READ-001, including CRLF, lone CR, empty files, and final
terminators.
Success is one LF-terminated JSON document with ok, data, truncated, and truncation.
data contains the normalized requested path, kind, language, exact inspected byte size, logical
line count, parser-completeness flag, and a flat source-ordered outline. Code entries include only
imports, top-level constants and variables, types, classes, functions, and direct methods.
Markdown includes only ATX headings outside fences and top-level scalar keys parsed from a closed
initial --- YAML frontmatter block. JSON includes every recognized value as a depth-first RFC
6901 pointer and value type, including the empty root pointer. No result contains raw excerpts,
bodies, fields, comments, frontmatter values, or JSON scalar values.
The complete successful stdout, including its final LF, is at most 65,536 UTF-8 bytes. When
necessary, the worker retains the longest complete outline prefix and returns
truncation: {"reason":"output_bytes","after_entries":N}. Lezer parser recovery or YAML
frontmatter diagnostics set parse_complete: false independently of output truncation. There is
no input-size or entry-count limit. If an empty-outline success envelope cannot fit, the command
fails with output_limit.
Command failures write one closed LF-terminated JSON envelope to stdout, leave stderr empty, and
exit nonzero. Stable codes are usage, not_found, not_regular, not_utf8,
outside_workspace, read, parse, and output_limit. The centralized Codex guidance and
private call contract embed a concise success, failure, and outline-entry shape rather than the
normative specification schema. Shell replay keeps the original call and output; inspect_file is
not model-visible, directly routed, or included in hpatch recovery ancestry. Passthrough mode
installs and advertises none of these surfaces.
Acceptance:
- Each supported language projection returns only its declared navigation identifiers and exact
inclusive one-based ranges, while malformed recoverable input remains a successful partial
result with
parse_complete: false. - Markdown excludes fences, Setext headings, nested YAML frontmatter keys, and all frontmatter
values while preserving source order for repeated top-level scalar keys; JSON escapes
~and/, preserves duplicate pointers, and never returns scalar values. - Unsupported files are confined and checked as regular without content reads, UTF-8 validation, line counting, content detection, or command-level truncation.
- Router startup installs an authenticated
inspect_filefrontend and exposes or routes only hpatch, shell, and configured model-visible contributions. Request instructions remain unchanged.
In hpatch mode, hpatch-router discovers tool plugins only from the hpatch/plugins
directory beneath the platform user configuration directory. Each direct regular file whose
name ends in .js or .mjs is one compiled ECMAScript-module declaration, loaded in lexical
filename order; directories, symlinks, and other entries are not declarations. A missing or
empty directory contributes no plugins. There are no plugin-related router flags,
workspace-local discovery, remote discovery, or hot reload. TypeScript is an authoring format,
and the complete registry remains immutable for the router process lifetime. Passthrough mode
neither loads nor exposes the contributed tools.
Each plugin declares a stable plugin identity and one or more globally named tools. Each tool
provides its exact OpenAI Responses custom-tool specification, a bounded string-input parser,
a translator, and an executor-side implementation. A specification may omit format for
unconstrained text or use an OpenAI grammar format whose syntax is lark or regex. The
model-visible name, description, format, grammar definition, input limit, translator, and
implementation are part of the validated declaration. Standard JSON-schema function tools,
runtime TypeScript transpilation, and arbitrary undocumented specification fields are not
supported by this increment.
Executor-backed names must also differ from shell keywords and built-ins. This rule ensures that
the basename carrier selects an executable frontend instead of shell-owned behavior.
Before opening its listener or installing any contributed-tool wrapper, the router loads every discovered declaration and validates the complete registry. It reports all detected plugin schema, API-version, identity, duplicate-name, input, translator, implementation, and wrapper conflicts, then exits nonzero if any declaration is invalid. Failure exposes no partial registry, forwards no Responses request, starts no executor implementation, and changes no durable metrics. Locally deterministic grammar syntax and unsupported construct checks occur at startup; this does not promise to reproduce a provider's model-specific or complexity limits.
A successful translator returns a typed normal Code Mode tool-call carrier. The router
validates the carrier kind, name, and payload against the Code Mode tools available in that
request and retains ownership of response item IDs, call IDs, status, JSON and SSE framing,
history, and replay. A plugin cannot invent an unavailable carrier or return a raw Responses
envelope. The plugin API provides a canonical exec wrapper for tools that need one. The wrapper
owns the repeated outer Code Mode exec program, nested tool invocation, serialization, argument
quoting, and result forwarding. The optional exec command template contains exactly one {.}
placeholder, which the router replaces with the complete quoted frontend command. An optional
JSON parameter object cannot contain cmd. The router supplies cmd from that frontend
command. If the parameter object contains login, its value must be exactly false.
An exec translator may also return one nonempty stock command for output metrics. The router applies the same optional command template and JSON parameters, then renders the stock command through the canonical exec wrapper. This stock carrier is metric evidence only: the response, history, replay, and execution paths retain the validated frontend carrier. Without a stock command, output metrics use that frontend carrier as before.
For each executor-backed contributed tool, startup creates or verifies a stable executable
symlink beside the running hpatch-router. Its basename is exactly the contributed tool name,
and its target is the authenticated process-scoped snapshot wrapper with the same basename.
The snapshot wrapper targets the running hpatch-router executable. Without a command template,
the exec wrapper invokes only the basename and represents the parsed model input as its ordered
argv. With a command template, the router replaces {.} with that same independently quoted
basename and argv. When launched through both symlinks, the router verifies the stable frontend
location, snapshot identity, wrapper target, and registered implementation before passing the
remaining argv unchanged.
The private worker keeps the frontend standard input separate from the JavaScript host's JSON
control stream. The host exposes that input only as a dedicated inherited descriptor during
executor calls.
An executor returns its current stdout, stderr, and exit status once. It may also return one optional stock result with the same fields. The stock result represents the output that the displaced stock tool path would have returned for the same operation. The executor computes both results during the same execution; the router does not invoke a second metric-only implementation. The worker returns only the current result to Codex. It validates and records the stock result as metric evidence without allowing it to change the current output or status. When the stock result is absent or invalid, metrics use the validated current result as its stock result. Invalid optional metric evidence cannot replace or modify the current executor result.
Without exec parameters, the carrier supplies no working-directory or environment override. With exec parameters, the router forwards the JSON values without replacing the request-specific Codex contract. Codex validates those values and remains the owner of working directory, sandbox, filesystem, process, network, terminal, and permission enforcement. Missing, conflicting, incorrectly targeted, or unusable symlinks fail startup before the listener opens. The router holds one exclusive frontend lock for its process lifetime. A concurrent router fails startup. After a crash releases the lock, a later router can replace authenticated prior frontends even when the prior process snapshot remains.
Translated history retains the plugin identity, original tool name and input, and exact carrier kind, name, and payload. Replay accepts only the byte-identical retained carrier and restores the original model-visible call before upstream forwarding. Ordinary plugins do not enter hpatch recovery ancestry. Runtime model-input rejection returns a bounded diagnostic through an available Code Mode carrier; a translator protocol violation, unavailable carrier, or malformed carrier is a routing failure rather than a successful approximation.
Grammar compatibility for this requirement is pinned to OpenAI's Custom tools guide
(https://developers.openai.com/api/docs/guides/function-calling#custom-tools): regex
definitions use Rust regex syntax and do not support lookarounds or lazy quantifiers; Lark
definitions support common imports and %ignore while terminal priorities, templates,
non-common imports, and %declare are unsupported. Startup validates this stable subset
locally; provider model-specific and complexity limits remain provider-owned.
Acceptance:
- A valid discovered JavaScript declaration contributes its exact unconstrained, Lark, or regex custom-tool object to hpatch-mode Responses requests without a plugin flag.
- A missing or empty plugin directory preserves the built-in hpatch-mode behavior, while passthrough mode loads and exposes no contributed tools.
- One invalid declaration or tool symlink prevents the listener from opening; independent startup mismatches are reported together and no valid subset is exposed.
- Duplicate tool names across plugins or built-ins fail startup, and the registry does not change until process restart.
- A plugin may translate to any compatible Code Mode tool call available in the current request; an unavailable or wrong-kind carrier rejects before upstream execution.
- The exec wrapper renders the canonical outer exec shape and independently quotes every argv
value. An optional template contains exactly one
{.}, which expands to the complete frontend command. The plugin declaration does not contain or generate the outer carrier shape. - Invoking an executor-backed tool resolves its stable basename frontend through the
authenticated snapshot wrapper to
hpatch-router, verifies the pinned registry, dispatches byargv[0], and delivers the declared argv under Codex's cwd, sandbox, and permissions. - JSON and SSE responses preserve call identity while replacing a contributed call with its validated carrier, and replay restores the exact original contributed call after verifying the retained carrier.
- A model-input diagnostic is bounded and recoverable, while an invalid translator result cannot be returned or counted as a successful tool call.
- Startup validation and tool-call metrics failures cannot replace an otherwise successful translated carrier or executor result; request cancellation still propagates.
- An executor can return one validated current result with or without a validated stock result. The worker returns only the current result and does not run a second comparison execution.
When hpatch mode starts with the inherited environment variable HPATCH_DIAGNOSE exactly
equal to 1, the immutable built-in registry contributes a model-visible unconstrained custom
tool named report_issue. Any other value, including an unset variable, omits that contribution.
Passthrough mode remains unchanged because it does not construct the registry.
The tool accepts agent-authored Markdown for problems encountered while using hpatch and its
related tools. The router snapshots hooks.diagnose from the existing settings.json hook
configuration at startup and invokes those commands directly when the model calls report_issue.
It does not install an executable wrapper or frontend and does not route the report through the
executor plugin worker.
Each command is rendered against an event whose Body is the exact Markdown.
format_markdown returns that same body, and shellquote retains its existing behavior. All
configured diagnose commands share the existing 10-second error-hook timeout. A missing or empty
diagnose list is a successful no-op. Successful dispatch returns Issue reported. through the
existing Code Mode result carrier; settings or registry initialization failures prevent startup,
while rendering, execution, cancellation, or timeout failures fail the tool call.
Acceptance:
- Exactly
HPATCH_DIAGNOSE=1in hpatch mode exposes the free-formreport_issuespecification; all other values and passthrough mode expose none of it. - One report reaches each configured
hooks.diagnosecommand byte-for-byte through.Bodyandformat_markdown, without runninghooks.error. - No configured diagnose hook succeeds without side effects, while hook failures fail the translated tool call.
report_issuehas no executable wrapper, stable frontend, or plugin-worker implementation.
The first working path in doc/brief.md § Outcome supplies the built-in declaration at
plugins/shell.mjs. The generated plugin bundle contributes an unconstrained custom tool named
shell, limits its UTF-8 input to the executor argv limit, and translates successful input
through the canonical exec carrier from REQ-PLUGIN-001. The repository make install target
regenerates that bundle and installs hpatch, hpatch-router, and the centralized Codex model
instructions. It does not copy a configured shell declaration.
The tool treats the first logical line as a shebang when that line, after trimming only its
leading and trailing ASCII spaces and tabs, starts with #!. It removes #!, trims the
remaining selector, and separates the selector at ASCII spaces or tabs. A bare executable name
is valid. A direct executable path remains unchanged. A leading env or /usr/bin/env and an
optional following -S are removed so the inherited PATH selects the next executable.
An empty selector, an env selector without an executable, a NUL byte, or too many or oversized
argv values rejects before execution. Without a shebang, the selected interpreter is bash.
When a shebang is present, the script body is every input byte after the complete first-line
terminator. The tool removes only the shebang line and its terminator. It preserves all leading
and trailing body whitespace, including an absent or final line terminator. Without a shebang,
the complete input is the body. The translated argv contains each normalized interpreter field
followed by the exact body as its final value. The resulting Codex exec carrier therefore shows
a command equivalent to shell python3 'print("Hello")'; the model does not author its quoting.
After an optional interpreter shebang, a leading directive block can contain one #!cmd=
assignment and one #!params= assignment in either order. All canonical directives use
#!key=value. The tool trims ASCII spaces and tabs around each complete directive line. The
nonempty command value is a shell command template containing exactly one {.} placeholder.
The params value is a JSON object that cannot contain cmd because the script body supplies
cmd. A present login value must be exactly false. Within the leading directive block,
the tool safely normalizes # !params JSON, #!params JSON, and legacy !params JSON through
the same params validation. A duplicate directive, malformed JSON, non-object JSON, unsupported
leading directive, params object containing cmd, or unsafe login value rejects.
The tool removes recognized directive lines and their complete line terminators from the body.
The router replaces {.} with the canonical independently quoted shell frontend command and
argv. The command template then runs through the normal exec carrier shell. Without an
interpreter shebang, the nested frontend command selects bash. Without either directive,
current direct execution behavior remains unchanged. After the first body line, directive-like
lines remain ordinary body data.
The executor runs the first translated argv field as the selected interpreter and passes any
middle fields as interpreter arguments. It supplies the final exact body through an anonymous
script descriptor and invokes the interpreter with that descriptor's /dev/fd path. The
interpreter inherits the frontend standard input as program data. The executor stores no
intermediate script file. Without #!params=, the process inherits Codex's execution context.
With #!params=, Codex applies the accepted outer exec arguments before it launches the frontend.
The executor resolves bare interpreters through PATH and returns stdout, stderr, and exit
status without copying the script body into either output stream.
The shell carrier forwards the complete native exec_command result defined by the owning Code
Mode contract rather than only its output field. A result containing the native continuation
handle remains yielded rather than terminal, and the same host-owned continuation operation
resumes that session. The router and shell plugin do not poll, resume, cancel, retry, replace, or
persist the session. They do not define a second result envelope or continuation protocol. Exact
result fields, yield timing, continuation arguments, and session lifetime remain owned by Codex's
executable tool definitions in that request.
For output metrics, the shell translator supplies a stock exec command for the normalized
interpreter and exact body. Python-family executables pass the shell-quoted body as the -c
argument, and Bun and Node-family executables pass it as the -e argument. Other interpreters
receive /dev/fd/3; a quoted heredoc supplies that descriptor while leaving program stdin
available. Its interpreter-derived delimiter changes when the body contains that delimiter as a
complete line. The router applies any command template and parameters and counts the complete
canonical Code Mode exec shape. It still executes and replays only the authenticated shell
frontend carrier.
Acceptance:
- A free-form call containing
#!/usr/bin/env python3translates to an exec carrier whose visible command arguments areshell,python3, and the exact body; execution runspython3with that body as its anonymous script source. #!python3,#! python3, and#!/usr/bin/env python3selectpython3. A directly supplied path such as#!/opt/python/bin/python3remains unchanged.#!/usr/bin/env -S python3 -urunspython3with-uand the exact body as its anonymous script source.#!cmd=curl -fsSL URL | {.} | jqwithout an interpreter shebang expands{.}to the independently quotedshell bashfrontend command. The curl response becomes Bash standard input while the exact remaining body remains the script source.- When
#!python3precedes that command directive,{.}expands to the independently quotedshell python3frontend command. The command-template input becomes Python standard input. - A missing, empty, or repeated
{.}placeholder rejects before execution. A command directive in any later body line remains ordinary body text. - Input without a shebang or command directive selects
bashand uses the complete input as the script source. - Python indentation and all other body-leading or body-trailing whitespace remain byte-exact after recognized directive removal.
- The child inherits cwd, environment, and frontend standard input. Its stdout, stderr, and nonzero status are returned without script-source duplication or an intermediate script file.
- Malformed selectors and input that cannot fit the bounded exec argv return a concise diagnostic without starting an interpreter.
make installinstalls both Go binaries, no configured shell declaration, and complete Codex model instructions. If the top-level config key is absent, it renders the selected bundled model instructions, installs the default file, and adds the key. If the key exists, it remains byte-equivalent and the referenced customized file is updated only when its owned section is stock, legacy hpatch, or marked hpatch guidance; content outside that section is preserved. Everymodel_instructions_filedeclared by a personal agent TOML under the adjacentagentsdirectory is updated under the same preservation rules; relative values resolve from the declaring agent TOML and the TOML files remain unchanged. The installed router embeds shell and creates shell, hread, hgrep, and inspect_file basename frontends beside its executable at startup.#!params={"workdir":"/tmp","tty":true}before or after#!cmd=produces an exec carrier containing those fields and the router-suppliedcmd. Safe leading params near-misses produce the same carrier after normalization. An object containingcmdrejects, and a presentloginvalue must befalse.- The authoritative Code Mode owner is exactly one custom
exectool. App-server requests place it directly in anadditional_toolsinput item's tool list; CLI requests place it under that item'sfunctionsnamespace. The router removes the exact Markdownexec_commandsection and introductorytools.exec_commandexample from the owning description. It derives the request-specific argument-object shape from the app declaration or parameter-list shape from the CLI description, removescmd, and appends only that sanitized shape under#!paramsin the built-inshelldescription. Neither model-visible description containstools.exec_command. An eligible owner without a recognizable parameter shape retains the baseshelldescription and does not reject. - Direct
additional_toolsentries namedfunctions.execand top-level tools namedexecorfunctions.execare unsupported and fail before forwarding. Defining more than one eligible owner also fails before forwarding. The existingapply_patchsection extractor remains independent. Every sibling direct tool, sibling namespace, unrelated top-level tool, and other nested section remains byte-equivalent after the request rewrite. - A terminal shell carrier forwards the complete native exec result. When native execution yields, the carrier forwards that same complete result, including its continuation handle, without calling the continuation operation or starting the frontend again. No router session record or plugin-defined continuation surface is created.
- For one built-in shell input, the router emits one warning for every distinct detected interpreter-wrapper or heredoc kind rather than stopping after the first. Recovered Code Mode JavaScript emits its recovery warning first and then every detected nested shell warning. Warning insertion preserves the exact submitted command, carrier result, replay behavior, and metric classification.
Every recognized normal or translate invocation is classified after its terminal outcome.
A successful nonempty change set that parses, evaluates, translates, and completes its
requested output or mutation contributes paired estimates for two semantically equivalent
tool calls. A failed invocation contributes only its generated hpatch call estimate to
the ineffective-output counter; it contributes nothing to the effective hpatch counter.
A failed routed invocation is represented downstream by a Code Mode carrier that returns its
diagnostic and repair context. Its comparison baseline is the fixed direct-call program
carrying *** Begin Patch\n*** End Patch\n; that tokenized semantic baseline contributes
to the failed translated counter. The diagnostic carrier itself never counts as translated
hpatch output. The complete failed hpatch call remains in the ineffective-output counter and
reduces the overall output savings. gain, informational commands, and unsupported argument
forms do not contribute metrics.
Every routed contributed-tool call classified by REQ-PLUGIN-001 contributes a row keyed by
plugin identity and tool name. Its emitted estimate counts the model-visible tool name followed
by the exact input the model emitted. Its translated estimate counts the validated Code Mode
carrier name followed by the router's canonical serialized stock payload. The stock payload is
the execution carrier unless an exec translator supplies a validated stock command. In that case,
the stock carrier uses the semantic name functions.exec, and the router renders the command
through the same canonical exec wrapper, template, and parameters.
Provider-generated item IDs, call IDs, status, and JSON or SSE envelopes are excluded from both
shapes. Plugins supply content evidence but not token counts or outer carrier serialization. A
translated row's reduction is (translated - emitted) / translated * 100, may be negative, and
is n/a when translated tokens are zero. Router-side input rejection uses a separate failed row
with n/a reduction. Executor failures after Codex accepts the execution carrier do not
retroactively become router translation failures.
For hpatch, both effective and ineffective emitted estimates count the functions.hpatch tool
name followed by the editing payload the model emitted. When a recovery mutates rows of a
rejected script, the shorter recovery payload is charged while the rebuilt complete script is used
only for evaluation. The successful translated side counts the Code Mode carrier name and a
fixed free-form program that passes the complete translated patch envelope, serialized as one
string argument, to tools.apply_patch, then returns that nested tool's result. The router-only
marker and hpatch final-state report are excluded from this established semantic baseline.
All estimates use the tokenizer library's GPT-5 model mapping. Tool inputs and translated
payloads remain data and cannot alter the fixed programs used for counting.
A final-state report successfully emitted by normal or translate mode contributes its exact rendered text to a separate estimated state-report input-token counter. This is model-input overhead because the tool result becomes subsequent model context; it is not added to either model-output counter.
The host tool definitions are also model input. The router obtains the session identity, the
exact serialized collection of installed built-in and plugin tool objects, its stable per-plugin
and per-tool definition breakdown, the displaced native patch definition, and the displaced
request-specific exec_command fragments directly from the routed request. The first classified
request of a session counts these inputs once. Each removed fragment is tokenized independently,
without synthetic separator text. Subsequent requests in the same session add nothing because
the resent definition is served from the provider's prompt cache. The two removed-definition
counters remain separate. The installed-definition total is authoritative; per-tool rows and a
shared framing row reconcile it without being added again when computing net input. A host that
supplies no session or definition leaves these counters at zero, and gain states which inputs
were measured so a zero is not read as a free tool.
A failed or cancelled invocation emits no report and contributes zero report-input tokens. A partial or failed report write does not count as a complete emitted report. For each completed contributed tool execution, the current input estimate tokenizes the current stdout followed by current stderr. The stock estimate tokenizes the optional stock stdout followed by stock stderr. The current result is also the stock result when the executor omits the optional result. Exit status, provider-hidden protocol and reasoning tokens, assistant commentary, and server-generated identifiers are excluded.
A contributed tool's input reduction is (stock - current) / stock * 100, may be negative, and is
n/a when stock tokens are zero. Its signed input overhead is current - stock. The sum of these
signed tool-result overheads contributes to net added input but does not add plugin rows to the
input-overhead source table. The router's end-to-end Responses and per-session usage totals remain
authoritative for provider-consumed model input. These token counts are reproducible estimates rather
than provider billing totals.
The router's in-memory metrics snapshot also attributes successful and rejected hpatch
translations and rejected-call diagnostic input tokens to the request session. Each session
retains the latest 32 evaluator rejection identities: command index, physical source line,
operation, target kind when known, stable reason, affected path when known, the physical
multiline value row when localized, and the generated line and column reported by language
syntax validation when applicable. A command with several distinct repair locations retains one
identity per location as defined by REQ-OUTPUT-001. Each session also retains the latest
128 routed attempt identities: chain/call identity, attempt, recovery marker, and outcome,
emitted and comparison token counts, evaluated command count, and its bounded rejection
identities. These count limits are reinforced by per-session text-byte limits, so an oversized
rejection identity is not retained. Session records use the same session identity as request
lifecycle metrics and are not written to metrics.bin. Each record also carries the client's
own display title for that session when the client exposes one, resolved once per session and
treated as an optional label rather than a counter. They retain neither scripts, replacement
text, diagnostics, nor repair context. Proxy failures that occur before evaluator invocation do
not fabricate evaluator rejection identities.
The snapshot also exposes aggregate counters so a benchmark can reconcile routed calls with
client-visible file-change items without inferring failures from stderr envelopes.
Classification is persisted only after the invocation's outcome is known. Translate mode records a paired effective estimate after its complete patch reaches stdout; normal mode records one after the staged changes commit. Each records report-input tokens only after the complete final-state report is emitted. Stdin-read, parse, evaluation, translation, stdout-write, and commit failures record only the canonical hpatch estimate as ineffective. Successful no-op scripts contribute command counts and an emitted report estimate without paired effective token estimates. In normal mode, failure to render the equivalent patch after a successful commit emits a warning and records neither paired token classification, but retains command and fully emitted report metrics.
Every supported command reached by evaluation contributes one invocation. A supported operation rejected by syntax parsing contributes one invocation and one error when its operation and attempted variant are structurally recognizable. An operation whose path resolution or execution fails contributes one error after its invocation. Unknown or future operations and failures outside command processing are not attributed to a supported command. Successfully evaluated commands retain their invocation counts when a later output or filesystem-commit boundary fails. Supported command counters are:
in new mv rm type type- type+
Every structurally recognized explicit target attempt increments one target counter:
line range text-single text-multiple
Targetless type VALUE initialization has no target counter. A text target with omitted
count or count one is text-single; an explicit count intended to exceed one is
text-multiple, including an invalid multiple count. Unsupported HPATCH/1 commands and
unknown future commands are syntax failures but do not receive supported-command or
target attribution.
Terminal command errors carry stable internal reason identifiers grouped as:
script-syntax
row-missing
row-stale
occurrence-missing
invalid-count
target-order
edit-conflict
active-file
initialization
file-path
language-syntax
other
The aggregate is stored in hpatch/metrics.bin beneath the platform user configuration
directory returned by Go's os.UserConfigDir. Updates hold an exclusive interprocess lock at
hpatch/metrics.lock; gain reads hold a shared lock. The current-version metrics format uses
two alternating bounded slots holding global hpatch counters and a keyed collection of
plugin-and-tool definition, call, emitted, translated, failed-translation, current-result, and
stock-result counters, plus a persistence generation and checksum. A reader uses the valid
greatest persistence generation, so an interrupted write to the inactive slot leaves the
preceding aggregate available. The file does not grow after its current-version slots are
created. Per-counter, per-tool, collection, and aggregate overflow fails without changing the
tool result.
Only the latest metrics magic is decoded. A complete, checksummed slot whose eight-byte magic
starts with HPATCH but does not equal the current version resets the reported totals to zero.
A malformed slot, including a mismatched version with an invalid checksum, does not qualify
for reset. When a current-format slot is also valid, its totals take precedence over
mismatched-version slots. Other invalid data fails rather than producing a misleading report.
Metrics writes use normal operating-system page-cache writeback and do not request a
per-invocation filesystem sync; sudden power loss may lose increments that the operating
system had not yet flushed.
hpatch gain first writes an output-token table with one stable row per plugin and tool,
placing a failed-translation row immediately after its successful row when present, followed
by an all-tools row. Its columns are emitted tokens, translated tokens, and reduction. The
hpatch failed row retains the fixed direct-call program carrying the empty patch as its
established semantic baseline and reports n/a; its downstream diagnostic carrier remains
excluded. A separate recovery table has Recoveries and Count columns with stable
white-space error, indentation shift, and luna misuse action rows.
Gain then writes an input-token table with one stable row per executed plugin and tool, followed by
an all-tools row. Its columns are current tokens, stock tokens, and reduction. Gain next writes the
input-token overhead table for final-state reports, failure diagnostics, the exact displaced
apply_patch definition credit, the displaced exec_command section credit, and the aggregate
installed tool-definition total. Indented stable plugin-and-tool rows and any shared
serialization-framing row reconcile the installed-definition total and are descriptive children
rather than additional input. Net added input is reports plus diagnostics plus installed
definitions minus both removed definitions plus the signed sum of current tool-result tokens minus
stock tool-result tokens. Gain does not subtract definitions from output, convert input to output,
or calculate a combined input/output percentage. Unmeasured definition sources are labeled
not measured.
The router gain page places the input-token and input-token overhead tables below the output-token
table in left and right columns. It uses the headings Input token estimates and
Input token overhead estimates.
Gain then writes stable-order compact tables for aggregate command invocation and error
rates; line, range, text-single, and text-multiple target counters; error reasons; and
each error attributed to the command that raised it. The last table lists only nonzero
command-and-reason pairs and renders one none row when no errors are recorded. Every
error appears in both the aggregate reason table and the attributed table, so the two
reconcile. Percentages are rounded to one decimal place and are zero when their denominator
is zero. With no metrics file or only an obsolete record, all totals and percentages are
zero. Gain reads no stdin and does not create or rewrite a metrics file. Failure to
tokenize, lock, read, write, or close metrics emits a concise hpatch: warning: diagnostic
but does not change the success or failure of the requested effect.
Acceptance:
- Repeated successful normal and translate invocations persist cumulative paired hpatch estimates and fully emitted report-input estimates; failed invocations persist only ineffective hpatch estimates and zero report-input tokens.
- Every successfully translated contributed-tool call persists a plugin-and-tool output row whose emitted count uses the exact model-visible call shape. Its translated count uses the validated stock carrier when supplied and otherwise the validated execution carrier. A stock carrier does not change execution, history, replay, or runtime-failure classification.
- Every completed executor result persists current and stock input estimates for its plugin and
tool. An omitted stock result produces equal estimates and zero reduction without a second
execution. A zero-token stock result reports
n/a. - Gain reports stable per-plugin and per-tool output rows, optional adjacent failed rows, and one all-tools output row. It reports a separate input table with current, stock, reduction, and one all-tools row.
- The input-overhead table has no plugin child rows. Net added input includes the signed difference between current and stock tool-result estimates.
- The seven supported hpatch command counters and four target counters reconcile with aggregate command attempts and errors. No selector, clipboard, editor-generation, or script-level commit counter remains.
- Every definition-bearing request increments the definition-request counter, while the exact installed tool collection, its reconciling per-tool breakdown, and the displaced baseline definition accumulate only once per distinct session. An absent session or definition leaves definition counters zero and reports which inputs were measured.
- Failed hpatch invocations contribute their complete output to the ineffective counter; the failed translated counter receives the fixed direct-call program carrying the empty patch envelope, while the downstream diagnostic carrier is excluded.
- A recovery is charged as the shorter payload the model emitted for both effective and ineffective invocations while evaluation uses the rebuilt complete script.
- Tool inputs and translated payloads containing quotes or program-like text remain data and cannot alter the canonical programs used for counting.
- Concurrent writers lose no records, concurrent gain reads never observe a partial aggregate, and an interrupted or damaged latest state falls back to the preceding valid aggregate.
- A valid mismatched
HPATCHversion resets totals when no current state exists; malformed data does not count as a version mismatch, and current state takes precedence. - Metrics collection failure warns without changing the success or failure of the requested edit, translated carrier, executor result, or final-state report.
- Router snapshots attribute successful and rejected hpatch translations, diagnostic token
totals, at most the latest 128 recovery-aware attempt identities, and at most the latest
32 structured evaluator rejection identities to their request sessions without persisting
scripts, replacement text, diagnostics, repair context, or new per-session records in
metrics.bin; per-session text-byte limits may retain fewer identities.
HPATCH/2 replaces HPATCH/1. There are no compatibility aliases. Outside a heredoc body, blank lines are ignored and every other physical line begins exactly one command:
in PATH
new PATH
mv PATH
rm
type TARGET VALUE
type- TARGET VALUE
type+ TARGET VALUE
type VALUE
The final form is new-file initialization and is valid only under REQ-FILE-001.
Targets are:
ROW complete logical line
ROW..ROW inclusive complete-line range
ROW "TEXT" [COUNT] anchored exact literal occurrence(s)
ROW := LINE:HASH
LINE := positive one-based decimal logical line
HASH := exactly four lowercase hexadecimal digits
COUNT := positive decimal integer; default 1
No whitespace is permitted inside ROW..ROW. A line target owns the complete logical
line, including its terminator when one exists. A range owns all
complete logical lines between its endpoints, inclusively.
A text target verifies its anchor row, starts at that row's column 1, and searches exact
literal content forward through EOF. TEXT is nonempty and cannot contain a logical-line
terminator. Matching is left-to-right and resumes after each complete match. The target
contains the first COUNT non-overlapping matches and rejects if fewer exist. Matches
may occur on different lines even though each match stays within one logical line.
VALUE is either a JSON-compatible quoted string or the fixed heredoc header <<PATCH.
Inline strings decode JSON escapes and Unicode escapes and additionally accept literal
horizontal tabs. Quotes, backslashes, line terminators, NUL, and other C0 controls remain
escaped. A heredoc consists of its command header, following literal UTF-8 body, and an
unindented closing line exactly equal to PATCH:
type 12:a1b2..15:c3d4 <<PATCH
replacement
text
PATCH
No escape, interpolation, dedent, or delimiter substitution occurs. Payload bytes begin
after the header terminator and end before the closing delimiter. A nonempty final body
line therefore contributes its physical terminator. The header, body, and delimiter are
one command attributed to the header. An exact PATCH payload line must use inline escaped
text instead. Unterminated or oversized heredocs fail as one bounded header-owned syntax
error.
The grammar is unambiguous by operand shape. For example:
type 12:a1b2 "line replacement"
type- 37:8c2f "// parseCommand parses one physical script line.\n"
type 12:a1b2 "needle" "replacement"
type 12:a1b2 "needle" 3 "replacement"
type+ 12:a1b2..15:c3d4 <<PATCH
inserted after the range
PATCH
Paths are nonempty and consume the remainder of their command line. For root-scoped CLI and library evaluation through hpatch, Translate, or TranslateForHost, relative paths resolve from cwd, absolute paths must remain beneath the canonical root, lexical and symlink escapes fail, and translation emits root-relative paths. Router host evaluation through TranslateForHostAt instead uses an optional canonical metadata directory without filesystem confinement. With a directory, relative operands resolve from it; without one, relative operands reject and absolute operands remain valid. Router process cwd is never an implicit base. Emitted patch paths retain cleaned host identities for Codex to authorize.
Trailing operands, malformed rows, forbidden controls, missing values, and unknown
commands are invalid.
Acceptance:
- Every accepted nonblank command is one of the seven public commands;
tsel,rsel,copy,cut,paste,del, and script-levelcommitare syntax errors. - Line, range, and text targets parse without a separate selection command, and inline replacement values remain distinguishable from a text target's quoted literal.
- JSON-compatible values and the fixed
<<PATCHheredoc reproduce their exact decoded payloads without parsing body lines as commands. - Invalid rows, ranges, counts, strings, heredocs, operands, and commands fail before filesystem mutation, patch output, or final-state reporting.
- File and mutation commands may be interleaved while all targets retain the immutable
baseline meaning defined by
REQ-SELECT-001. - For root-scoped evaluation with root
/workspaceand cwdbin/worktree, pathmain.godenotes/workspace/bin/worktree/main.goand translates asbin/worktree/main.go.
After a routed hpatch script is rejected, its latest evaluated complete script becomes an
implicit immutable text baseline for a recovery call. A recovery payload begins with
target-bearing type, type-, or type+, omits in, and contains only target-bearing
ordinary mutations:
type LINE:HASH VALUE
type- LINE:HASH VALUE
type+ LINE:HASH VALUE
Line, range, and text targets, quoted values, the fixed <<PATCH frame, terminator
ownership, same-boundary insertion order, stale-row rejection, and edit-conflict behavior
are exactly the ordinary HPATCH/2 semantics from REQ-SYNTAX-001 and REQ-EDIT-001.
The root package applies those mutations to the rejected-script text without filesystem
access, language validation, formatting, indentation policy, whitespace cleanup, hooks,
or evaluator metrics. Every recovery mutation must succeed before the rebuilt script is
returned.
The router recognizes recovery payloads, selects the latest evaluated rejected hpatch script in the same routing session and selected canonical metadata directory or no-directory state, and calls the root text editor. It then reparses and reevaluates the complete rebuilt script normally. A malformed, stale, conflicting, cross-worktree, or otherwise invalid recovery changes neither retained ancestry nor workspace state. Proxy-rejected attempts leave the last evaluated script as the next recovery baseline. Non-hpatch plugin and shell failures never enter this ancestry. A rejected script is recoverable only while the conversation still shows its call: when a request's input no longer carries a retained call, that call and every later one leave the ancestry, so an edited or truncated conversation cannot recover a script the model can no longer see.
Every evaluator rejection exposes current targetable rejected-script LINE:HASH rows and
the instruction to use hpatch without in. Command-header rows use structured rejection
source lines. Syntax validation supplies one structured rejection entry for every distinct
actionable repair location from every changed file, ordered by originating command and then
localized value row. Complete heredocs expose the closing delimiter and bounded context around
every localized value row, so one atomic recovery payload can repair all emitted targets.
Malformed frames expose their last targetable attributable row rather than an invented trailing
row. A rebuilt script that is reevaluated and rejected becomes the next baseline, so later
recovery hashes always address the latest evaluated script.
The chain retains its correlation ID and increments the attempt for evaluated and
proxy-rejected recovery calls. Replay restores the exact short payload emitted by the model,
while retained evaluated state stores the complete rebuilt script needed by the next
recovery. Metrics charge emitted hpatch tokens to the recovery payload rather than the
rebuilt script. AttemptMetadata.Correction remains the recovery-attempt marker for hooks
and retained telemetry.
Acceptance:
- A target-bearing
type,type-, ortype+payload withoutinedits the latest rejected script with ordinary verified-row semantics. - A successful text edit is reevaluated as one complete ordinary hpatch script; success or rejection remains atomic.
- A re-rejected recovery exposes references from the current rebuilt script, not the previous baseline.
- A malformed, stale, or conflicting recovery leaves the latest evaluated script usable by a later attempt.
- Recovery cannot cross routing sessions or selected worktrees, and unrelated plugin or shell failures cannot become recovery bases.
- A rejection whose call the current input no longer shows is not a recovery base, while an older call the input still shows keeps replaying and remains recoverable.
- Correlation, attempt sequencing, replay shape, retained-root behavior, and emitted-payload token accounting remain stable across the chain.
- Indexed forms such as
N: COMMAND,N: accept,-N,+N: COMMAND,N+: COMMAND, and dotted value-row operations are ordinary script syntax errors, not compatibility paths.
An invocation has one immutable baseline for each touched existing file. The first
in PATH loads the regular UTF-8 contents visible at invocation start and makes that
logical file active. Returning to the same logical file reuses that baseline and retains
its pending edits. There are no generations and no command can materialize pending
content as a new target baseline inside the script.
new PATH creates and activates a pending empty file. It fails if the logical path
exists in the invocation workspace or pending state. Its immediately following nonblank
command may be one targetless type VALUE initializer; any intervening command closes
that initialization opportunity. The initializer is consumed even when its value is
empty. No target-bearing mutation is valid on a new file because hread could not have
produced a baseline reference for it. Further or dependent content changes require a
successful invocation followed by a fresh read.
mv PATH moves the active logical file to an unoccupied pending path. The destination
becomes active; its original baseline and pending edits move with it. Later in resolves
the new path, not the old one. Repeated moves collapse to one original-to-final move.
rm marks the active existing file deleted and clears the active file. Removing an
existing file after any content mutation in the same invocation is an edit conflict;
pending content is never silently discarded. Removing a moved, otherwise unedited file
deletes its invocation-original path. Removing a file created in the same invocation
cancels that creation, including an empty initializer.
in fails for missing or deleted paths. mv and rm fail without an active file.
new and mv fail on destination collision. Parents of new and mv destinations must
already exist. Hpatch does not create directories. All file and content changes remain
in memory until the complete invocation crosses the normal or translate boundary.
Acceptance:
- A script can edit multiple files and return to an earlier path without shifting that file's targets or losing pending disjoint edits.
- A new file accepts at most one immediately following targetless initializer and does not expose introduced content as a same-script target.
- Moves preserve baseline identity and pending edits; repeated moves collapse to one net action.
- Removal after an existing-file content mutation, path collision, use after deletion, unsupported target-bearing new-file edits, and lifecycle commands without an active file reject before external mutation or patch output.
- Failure or cancellation after any number of commands exposes no intermediate change.
Every explicit target resolves against the active existing file's immutable invocation
baseline. A row resolves by locating its one-based logical line and comparing its four
digit hash with the hash of the exact current baseline line content. An absent or
out-of-bounds line is row-missing; a present line with a different hash is row-stale.
Hpatch never scans for another line with the supplied hash and never chooses nearby or
duplicate content. Line number disambiguates equal lines; the 16-bit hash retains an
accepted approximately 1-in-65,536 random false-acceptance residual.
Both endpoints of a range must verify independently and remain ordered. A text target
then searches the verified baseline suffix exactly as defined by REQ-SCRIPT-001.
Pending edits never alter row verification, literal search, matches, or positions.
Content introduced by any command is not targetable in that script. Dependent edits
require successful application, hread inspection of the new content, and a later
invocation with fresh references.
Resolution produces one nonempty baseline span for a line or range and one or more nonempty spans for a text target. A mutation over multiple spans validates and registers all of them or none. There is no persistent selection, cursor, clipboard, shadow buffer, generation, or resume state.
Acceptance:
- A copied hread row verifies only the same line with the same complete content, including indentation; duplicate content at other line numbers is irrelevant.
- Missing and stale rows are distinct failures and neither searches for a substitute.
- Inclusive ranges verify both endpoints and reject reversed order.
- Text targets select the requested first N non-overlapping matches from the verified anchor through EOF and reject incomplete multiplicity.
- Independent targets retain their original meaning after pending edits; introduced content cannot be addressed without a later hread. A whole-file move preserves the moved file's existing baseline under its new logical path.
type TARGET VALUE replaces every target span with the decoded value. An empty target-bearing
value deletes every target span, including a terminator owned by a complete-line or range
target. type- TARGET VALUE inserts the value immediately before every span and preserves
the target. type+ TARGET VALUE inserts immediately after every span and preserves the
target. A command with multiple text matches is atomic: resolution or conflict at any match
records none of its mutations.
Replacements and deletions must have disjoint baseline interiors. An insertion strictly inside a replacement or deletion conflicts. Insertions exactly at either boundary are permitted. Multiple insertions at the same baseline boundary are permitted and render in script command order. Conflicts identify the prior command and affected baseline range; they reject the complete script before filesystem mutation or patch output.
For a complete-line or range replacement whose target owns a final LF, CRLF, or
standalone-CR terminator, nonempty type preserves that exact final terminator when the
replacement does not end in a terminator. A replacement-supplied final terminator is
authoritative and is not doubled. No terminator is synthesized for an unterminated selected
final line. An empty target-bearing type value removes owned terminators. Inserted values
are otherwise byte-exact decoded UTF-8. Existing line endings outside explicit inserted or
replaced text remain unchanged.
The engine orders registered immutable-baseline edits once and renders one final content
value per file. It never reads pending mutated content while resolving a later target.
Content movement requires emitting the destination content; mv moves whole files only.
Acceptance:
- Replacement, before insertion, after insertion, and deletion produce the specified result directly from their targets without a selection command.
- Multi-match text mutation applies the same action to every requested match or none.
- Disjoint edits are script-order independent except for deliberate insertions at the same boundary, which retain script order.
- Overlapping destructive spans and insertions strictly inside them reject atomically; boundary insertions remain valid.
- LF, CRLF, and standalone-CR complete-line replacement preserve the owned terminator for a nonempty value unless the value supplies one; an unterminated final line stays unterminated, while an empty value deletes any owned terminator.
Input is read completely and the entire script is evaluated before an external filesystem
commit or stdout. Before finalization, every changed file whose final path ends in .go
is parsed and formatted with Go's standard-library go/format; parse failures are collected
from every changed Go file before the complete transaction rejects. For at most 32
content-mutating commands in one invalid Go file, the evaluator replays command-group subsets
against the immutable baseline to select a one-minimal syntax-failing set, then attributes
each useful parser failure to the retained edit nearest its generated parser position. Larger
groups or an invalid baseline use nearest-edit attribution without subset replay. Supported
changed .py, .js, and .ts files are syntax-checked when Tree-sitter language support is
available and contribute all discovered failures to the same validation result. Parser
cascades are collapsed when blanking an earlier repair line removes a later parser failure.
Failures are deduplicated by originating command and physical heredoc value row, or by the
command's script row when no physical value row exists. Each retained location includes at
most two generated lines before and after the failing line; neighboring lines are capped at
64 runes and the failing line at 200. Supported baseline-aware indentation corrections are
applied before validation; unsupported extensions remain byte-exact or reject under
indentation policy.
An unchanged normal-mode change set performs no
filesystem operation but still reports final state.
An unchanged translate result emits no patch and fails because it cannot represent an
update; it emits no final-state report.
Translate output contains file actions in deterministic first-touch order:
*** Begin Patch
*** Update File: PATH
*** Move to: NEW_PATH
<unified diff hunks>
*** Delete File: PATH
*** Add File: PATH
+<content>
*** End Patch
Each action includes only syntax relevant to that file: additions use Add File,
deletions use Delete File, moves use Update File plus Move to, and content edits
use Update File hunks. A moved and edited file combines its content hunks and move in
one update action, with Move to immediately after Update File. Because OpenAI
apply_patch rejects an empty update action, a move with unchanged contents includes
a minimal verification hunk: one unchanged context line for a nonempty file, or an
equal remove/add of the empty line representation for an empty file. Translation is
fully rendered before stdout is written.
After every command and the requested normal filesystem commit or translated patch write succeed, the CLI writes one final-state report to stderr. Its line forms are:
in PATH
last OP PATH COUNT ranges RANGE[, RANGE[, RANGE]] [ +N more]
files add=A update=U move=M delete=D
refs COMMAND OP PATH
LINE:HASH TEXT
The first line is no active file when rm leaves none. Otherwise it names the active
final path. The last line is last none when no mutation changed final content;
otherwise it names the last effective mutation operation, that file's surviving final
path, the number of affected target spans, and at most three verified immutable-baseline
ranges. Extra ranges are summarized by +N more. RANGE is a half-open
START_LINE:START_COLUMN-END_LINE:END_COLUMN pair in one-based Unicode coordinates; a
complete-line range includes its final terminator when present. The files line counts
net original-to-final actions.
One refs block follows for every effective content-mutating command on every surviving
edited file. COMMAND is the command's positive one-based nonblank script index, OP is
its authored mutation operation, and PATH is the file's final path after pending moves.
Blocks retain authored command order. Each block contains at most four distinct current
rows, ordered by final line number: the rows containing the first and last endpoints of
the command's aggregate rendered edit extent, the immediately preceding surviving row,
and the immediately following surviving row. Missing neighbors are omitted. Coincident
endpoint or context rows are emitted once within that block. A row may appear in separate
blocks when it identifies the context of separate source commands.
The projector derives each aggregate extent from that command's effective editor splices
in rendered final content, then maps both endpoints through language-formatting offsets.
A collapsed deletion endpoint maps to its surviving containing row; its available
neighboring rows provide boundary anchors. Logical-line clamping does not invent a
trailing empty row for a final terminator. An empty surviving file reports row 1 with
the hash of empty content. When the active final file has no refs block, the report
retains the existing fallback of up to three rows from the start of that file without a
refs header, even when other surviving files have reference blocks.
Every row has REQ-READ-001 identity over the complete current final logical line.
TEXT contains at most the first 64 Unicode code points of line content, without a line
terminator or added ellipsis. Tabs are preserved and other controls are escaped so each
row stays on one report line. The hash still covers the complete untruncated content.
The projection is bounded by four rows per effective command, plus the three-row fallback;
it does not retain another original or final content copy, routed-read history, a word
diff, or translated patch text.
A successful report's LINE:HASH rows are current references for their named final paths
and may be used directly in the next invocation. The projection does not guarantee that
it contains every possible later target. When the exact target needed next is absent, the
caller obtains it with a focused hread. Saved pre-edit rows remain stale and rejection
context does not authorize guessing or reconstructing a row. The report describes only
the completed invocation; no target or editing state persists into a later invocation.
The complete report is rendered before commit or patch output, but it is emitted only after that mode-specific effect succeeds. A report-write failure after the effect is best-effort and cannot retroactively change the successful effect or claim rollback.
Normal mode stages new contents in same-directory temporary files before starting the
commit. Parse, validation, read, and evaluation failures leave the initial tree unchanged.
A staging failure attempts to remove all temporary artifacts; cleanup failure returns
nonzero and identifies every artifact it could not remove. Commit-time filesystem failures
trigger rollback attempts using staged backups. Ordinary filesystems cannot provide a
portable crash-atomic transaction over multiple paths: termination, machine failure, or
rollback failure during commit can leave a partial change set. Such a failure must return
nonzero and name the affected paths; it must never report success or claim rollback
succeeded when it did not. Existing file permission bits are preserved; files created by
new use mode 0644.
OpenAI apply_patch is a logical-line format and cannot preserve CRLF or standalone-CR
bytes when its output is applied by the tool. Translate mode therefore emits LF-only
patch text and normalizes line endings only in its displayed before/after lines. It does
not modify source files. Normal mode continues to preserve existing line endings outside
explicitly inserted strings. Applying translated output to a non-LF file may normalize
that file to LF; this is a declared format limitation, not byte equivalence.
Generic non-command failures emit concise diagnostics to stderr prefixed with hpatch:.
Command failures instead have the stable form:
OP: command N[, path "PATH"], reason REASON: MESSAGE
The visible command line omits source line, a repeated operation field, and category.
Structured host rejection data retain command index, source line, operation, path, generated
position, and localized value row when applicable; hook data also retain category. Validation
orders failures by command index and then localized value row. It emits one visible command
line per originating command and path. A command with several distinct repair locations uses
the message N distinct syntax failures, followed by bounded repair context for every
location; structured host data contain one rejection entry per location. Duplicate parser
messages that resolve to the same command and physical value row, or to the same inline script
row, remain one visible location. Independently parseable syntax failures may be reported
together before evaluation. A heredoc failure is owned by its header and may additionally
report its attributable source span. Control bytes are escaped and embedded newlines are
folded so one command failure remains one logical line.
Failures return nonzero and emit no stdout or final-state report. Malformed row syntax
receives a syntax diagnostic.
A stale row reports the actual LINE:HASH TEXT at that line and up to two neighboring
baseline rows. A missing literal occurrence reports the verified anchor context. An edit
conflict identifies the prior command and affected immutable-baseline lines. If a command
depends on content introduced by another command, the diagnostic directs the agent to
apply the prerequisite independently, reread, and submit a later invocation. A missing
row or failure without a verified baseline does not choose repair context. Repair context
is supplementary: it never changes exit status, stdout, mutation, or metrics classification.
When invalid generated source is localized to a fixed-heredoc mutation, each distinct rejection
identity includes the non-sensitive value_line. Transient root diagnostics describe every
bounded value-row context rather than mutation addresses. Routed diagnostics add current
targetable rejected-script LINE:HASH rows for the command header, every localized body
context, and the closing delimiter under REQ-CORRECT-001. Inline decoded multiline values
and failures outside a multiline replacement do not fabricate a value row.
Acceptance:
- Normal success has empty stdout and one rendered final-state report on stderr after commit; translate success has patch-only stdout and one pending-state report on stderr after the patch is completely written.
- Active paths, bounded last-mutation ranges, per-command final-reference blocks, net file counts, Unicode columns, truncation, control escaping, moved files, deletions, and empty files produce the specified report without implying cross-invocation persistence.
- One invocation editing multiple regions and files reports current final paths and rows for every effective content command in authored order. A later invocation can target an exact reported row without hread, while an unreported target requires a focused read and a saved pre-edit row still rejects as stale.
- Changed Go files are formatted with the standard library before output, and invalid Go rejects the transaction without mutation; supported changed Python, JavaScript, and TypeScript files are syntax-checked and receive supported automatic indentation correction.
- Malformed input, missing, stale, reversed, or incomplete targets, edit conflicts, unknown or future commands, invalid UTF-8, missing or non-regular files, path collisions, staging failure, translation failure, and cancellation produce no mutation, patch output, or final-state report.
- Injected external filesystem commit and rollback failures are reported without false atomicity claims and without a successful final-state report.
- Failure to write a fully rendered report after a successful external effect does not reverse that effect or record a complete report-input token estimate.
- Stale rows, incomplete literal targets, and edit conflicts emit verified repair context; a missing row fails without guessing, and a failure with no active baseline emits its diagnostic alone.
- Invalid Go localized inside a fixed
<<PATCHvalue reports its physical body row in bounded repair context and structured host rejection identity without retaining body text. - One syntax-validation rejection includes every distinct actionable repair location from all changed files, groups visible diagnostics once per originating command and path, deduplicates parser cascades by repair row, and exposes enough current rejected-script rows for one atomic recovery payload to repair all locations.
Top-level help owns the complete CLI, editing, validation, trust-boundary, report, and metrics
reference. contrib/codex/file-editing-instructions.md is the single persistent Codex workflow
source for all durable edit, shell, read, search, and inspection guidance and the source of the
HPATCH/2 section returned by tool help. Model-visible tool descriptions contain only concise
call-local contracts and request-specific schemas. The router does not use private tool
descriptions as prompt text and does not mutate Responses instructions.
make install renders the central source into Codex's configured model_instructions_file
and every instruction file selected by a personal agent TOML under the adjacent agents
directory. Relative agent values resolve from the declaring TOML. Existing settings, agent
TOMLs, and all customized content outside the owned guidance section remain byte-equivalent.
A file with current markers is refreshed idempotently; a legacy hpatch section or the pinned
stock Codex file-editing section is migrated once. Without a top-level setting, the installer
uses CODEX_MODEL or the lowest-priority bundled model, writes the default file, and adds the
setting. An unrecognized customized file fails instead of being overwritten.
The recovery template adjacent to the central source owns dynamic recovery prose. After each
actionable structured evaluator rejection, the router supplies current rejected-script
LINE:HASH rows to that template. A re-rejected recovery refreshes the rows from the latest
evaluated script.
Persistent guidance teaches this workflow:
- Submit a shell call as one free-form script without an outer wrapper. Use Bash by default or
select another interpreter with a direct compact shebang. Keep program input on standard input,
use exactly one
{.}in#!cmd=, place request-specific outer arguments in#!params=, and use native session facilities for PTY-backed or long-running executions. - Inspect, edit, or rerun a retained shell script through its
@shell/reference, and never mix retained and workspace paths in one hpatch script. - For an authorized edit, use hread as the initial source read when the named or likely owner is known. Use hgrep as the initial search when a known identifier or literal is likely to become a target. Use ordinary reads and searches for read-only work or while the owner is unknown; after discovery, hread only the smallest target-bearing range.
- Run one hread command per file and batch related commands in one shell script. Combine known
hgrep patterns and paths with repeated
-e. Copy only current emitted references. A matching hgrep row needs no hread unless surrounding or nonmatching context is required. - Choose a line, inclusive range, or anchored literal target inside the mutation command.
- Submit every known related edit in one atomic script. Split only when a later edit depends on validation or information unavailable before the current call. Keep unrelated large values in separate failure-domain calls.
- Prefer the smallest semantic mutation and let formatters own formatting. Successful
final-state
LINE:HASHrows can be used directly in the next invocation. - Use nonempty
typeto replace, empty target-bearingtypeto delete,type-to insert before, andtype+to insert after. Use inline values for short text and<<PATCHfor multiline or escape-heavy values. - After rejection, use hpatch without
inagainst the emitted rejected-script rows. Reread those rows after a stale attempt rather than reconstructing them. - Let hpatch format changed Go files and syntax-check supported changed Python, JavaScript, and TypeScript files.
Acceptance:
- A model can choose and encode every HPATCH/2 operation from tool help without learning HPATCH/1 state concepts.
- The installed prompt contains the central guidance exactly once and omits the pinned stock apply_patch, rg, and exec_command instructions.
- A configured legacy or marked customized prompt retains content before and after the owned section, and repeated installation is idempotent.
- A routed request's existing instructions remain byte-equivalent, including absence or null.
- Dynamic rejected-script references and recovery prose appear only with actionable context.
- A routed success can be followed by another hpatch call using an exact row from its report without an intervening hread; a saved pre-edit row still rejects as stale.