Help us shape the future of Go! Take the 2026 Go Developer Survey

apx

package module
v0.0.0-...-b728dfd Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 31, 2026 License: MIT Imports: 33 Imported by: 0

README

apx

A Codex Responses router that lets agents edit with compact selectors and replacement text instead of emitting full patches.

apx-router sits between Codex and the Responses API. It adds constrained functions.apx, resolves successful scripts against the declared workspace, and hands Codex a real apply_patch carrier so sandbox checks and the normal diff UI remain intact. The default prefer mode retains native apply_patch as a fallback; exclusive mode removes it for maximum output-token savings.

This project is an independent MIT-licensed derivative of yusing/hpatch, preserving its history, architecture, and attribution while adding portability and operational hardening.

TL;DR:

Goal Start here
Route Codex edits through apx Install and configure the Codex router
Make Codex prefer apx, or revert it Agent-managed prompt update and reversion
Inspect measured token savings Metrics, then run apx gain
Use the engine without Codex Standalone CLI
Read the full editing contract apx --help, apx --tool-help, doc/spec/interface.md

Why apx?

A direct Code Mode edit makes the model repeat patch framing, old context, replacement text, and the JavaScript carrier. The router moves patch reconstruction out of model output:

flowchart LR
    subgraph output["Alternative model-output payloads"]
        H["apx path<br/>functions.apx + selectors + replacement"]
        A["apply_patch baseline<br/>functions.exec + JavaScript carrier<br/>+ old context + replacement + patch framing"]
    end

    subgraph router["Router and Codex after model output"]
        B["Router reads the immutable<br/>workspace baseline"]
        C["Router generates the<br/>apply_patch envelope"]
        D["Codex applies the patch<br/>sandbox checks + normal diff"]
    end

    H --> B --> C --> D
    A --> D
What the agent has to output: left versus right

Both paths eventually apply the same patch. The saving comes from how much of that patch the model must generate before the host can act:

Model-output component apx — compact side native apply_patch — expanded side
Tool and wrapper syntax functions.apx functions.exec, JavaScript call, template literal, result plumbing
File parameter in parser.go *** Update File: parser.go inside a full patch envelope
Selection parameter rsel 50:60 @@ plus the old lines needed to identify and validate the hunk
New input 11 replacement lines, emitted once the same 11 replacement lines, each with a + prefix
Old input repeated by the model none; the router reads lines 50–60 from the immutable baseline all 11 old lines, each with a - prefix
Framing around the replacement type <<PATCH and one closing PATCH *** Begin Patch, update header, hunk header, prefixes, *** End Patch, and JavaScript closing lines
Lines in the example below 16 total: 11 content + 5 syntax/parameters 30 total: 11 new + 11 old + 8 syntax/wrapper lines

So apx does not make the replacement itself shorter. It removes repeated old input and moves generic patch/JavaScript syntax out of model output. Short fragment edits save less; large replacements, repeated edits, and long unchanged boundaries save more. The repository comparison currently measures 108 apx tokens versus 223 apply_patch tokens across six scenarios: 115 output tokens saved, or 51.6%.

The patch is not eliminated: the router generates it after inference. Savings are the difference between the two model-output payload estimates shown above. State reports, rejection diagnostics, and tool-definition costs are tracked separately as input overhead. In exclusive mode the report also accounts for the displaced native definition. These are reproducible GPT-5 token estimates, not provider billing totals.

For an 11-line function replacement, apx asks the model for this:

functions.apx
in parser.go
rsel 50:60
type <<PATCH
func parse(input []byte) (Document, error) {
	tokens, err := tokenize(input)
	if err != nil {
		return Document{}, fmt.Errorf("tokenize: %w", err)
	}
	document, err := buildDocument(tokens)
	if err != nil {
		return Document{}, fmt.Errorf("build document: %w", err)
	}
	return document, nil
}
PATCH

The same edit through direct apply_patch in Code Mode:

functions.exec
const result = await tools.apply_patch(`*** Begin Patch
*** Update File: parser.go
@@
-func parse(input []byte) (Document, error) {
-	tokens := tokenize(input)
-	if len(tokens) == 0 {
-		return Document{}, errEmptyInput
-	}
-	document := buildDocument(tokens)
-	if document.Empty() {
-		return Document{}, errEmptyDocument
-	}
-	return document, nil
-}
+func parse(input []byte) (Document, error) {
+	tokens, err := tokenize(input)
+	if err != nil {
+		return Document{}, fmt.Errorf("tokenize: %w", err)
+	}
+	document, err := buildDocument(tokens)
+	if err != nil {
+		return Document{}, fmt.Errorf("build document: %w", err)
+	}
+	return document, nil
+}
*** End Patch
`);
text(result);

The direct call repeats all 11 old lines, then writes the same 11 new lines plus patch framing and the JavaScript carrier. apx writes the new function once; the router reads the old region from the baseline. The router also supplies functions.apx to the provider with a Lark grammar. As the model writes the tool call, only tokens that can still lead to a valid script are allowed. Bad syntax never becomes a finished tool call, so there is no generate-reject-retry cycle for it. Grammar is syntax only: a valid script can still fail for missing files, ambiguous selectors, or conflicting edits, and those failures stay atomic.

Requirements

  • A release binary, or Go 1.26 or newer when installing from source
  • For the router: Codex CLI with ChatGPT file auth (codex login, credentials at ~/.codex/auth.json or $CODEX_HOME/auth.json)

Codex router

The router listens on HTTP, rewrites Responses traffic so Codex calls functions.apx, evaluates scripts against the workspace declared in x-codex-turn-metadata, and returns the translated apply_patch carrier to Codex. You see a real diff in the UI rather than a silent file rewrite.

On each request it installs a standalone functions.apx tool. --tool-mode prefer retains Code Mode's native apply_patch; --tool-mode exclusive strips that native section. The history may still label an apx apply step as apply_patch because that is what Codex ultimately runs.

Defaults:

Setting Default
Listen 127.0.0.1:8080 (--listen)
Tool mode prefer (--tool-mode prefer|exclusive)
Upstream start timeout 10m (--timeout)
Metrics / hooks $XDG_CONFIG_HOME/apx or ~/.config/apx
Endpoints POST /v1/responses, GET /healthz, GET / (dashboard), GET /api/metrics

The process must run as your login user so it can open the absolute workspace paths Codex sends. Codex-managed authentication is forwarded per request and is never printed by doctor. Non-loopback listeners are rejected unless --allow-non-loopback is explicit, including a hostname that resolves to a non-loopback bind address. Writes to a final-component symbolic link reject so standalone and router modes cannot produce different filesystem outcomes.

Install the binary
go install github.com/tuanhung303/apx/cmd/apx-router@latest

Default install path is ~/go/bin/apx-router (or $GOBIN/apx-router if GOBIN is set). Ensure that directory is on your PATH.

Tagged releases also publish self-contained macOS and Linux archives for amd64 and arm64.

macOS LaunchAgent

Install, inspect, or remove a user LaunchAgent without editing the base Codex config:

apx-router service install
apx-router service status
apx-router service uninstall

The generated service binds to loopback in prefer mode. A static reference template is available at contrib/launchd/io.github.tuanhung303.apx-router.plist.

Linux systemd user service

The unit template is published at contrib/systemd/apx-router.service (raw URL works after the repo is on GitHub):

mkdir -p ~/.config/systemd/user
curl -fsSL https://raw.githubusercontent.com/tuanhung303/apx/main/contrib/systemd/apx-router.service \
  -o ~/.config/systemd/user/apx-router.service
systemctl --user daemon-reload
systemctl --user enable --now apx-router.service
systemctl --user status apx-router.service

Optional: keep the service after logout:

loginctl enable-linger "$USER"

One-shot without a service:

apx-router --listen 127.0.0.1:8080 --tool-mode prefer

If auth lives outside ~/.codex, or the binary is not in ~/go/bin, use a drop-in:

systemctl --user edit apx-router.service
[Service]
Environment=CODEX_HOME=%h/.codex
# ExecStart=
# ExecStart=%h/.local/bin/apx-router --listen 127.0.0.1:9090

Then systemctl --user daemon-reload && systemctl --user restart apx-router.service.

Point Codex at the router

Copy contrib/codex/apx.config.toml to ~/.codex/apx.config.toml, replace its absolute instruction path, and keep the provider isolated from your base Codex configuration:

[model_providers.apx]
name = "apx"
base_url = "http://127.0.0.1:8080/v1"
wire_api = "responses"
requires_openai_auth = true

Start a CLI session with the dedicated profile:

codex --profile apx

For Codex Desktop, launch a fresh app process with equivalent -c provider overrides or place the provider in the base config. Existing tasks cannot acquire a new tool surface mid-turn.

Start sessions from a git worktree: the router requires exactly one usable absolute workspace root in turn metadata.

Useful checks:

systemctl --user status apx-router.service
journalctl --user -u apx-router.service -f
apx-router doctor
apx-router doctor --json
curl -sS http://127.0.0.1:8080/healthz
curl -sS http://127.0.0.1:8080/api/metrics
# open http://127.0.0.1:8080/ for the local dashboard
Agent-managed prompt update and reversion

The router exposes functions.apx, but Codex’s default base prompt still tells the model to use apply_patch. In prefer mode both are intentionally visible. Point Codex at a custom base-instructions file and replace its file-editing section so the model chooses apx first and native apply_patch only as fallback.

Instruction for an agent reading this README: when the user asks you to enable the apx prompt, perform the update procedure below instead of merely describing it. When the user asks to disable or revert apx, perform the reversion procedure. Resolve paths first, preserve existing files, make no change to the base config.toml, and report exactly what was created, backed up, restored, or removed. A running Codex process cannot acquire or lose this tool surface mid-turn, so finish by telling the user to start a fresh task or restart Codex.

Prompt update
  1. Resolve CODEX_HOME to ${CODEX_HOME:-$HOME/.codex} and the checked-out apx repository to an absolute path. Verify that these source files exist:

    • contrib/codex/apx.config.toml
    • contrib/codex/file-editing-instructions.md
  2. Before changing anything, create a timestamped backup directory under $CODEX_HOME/apx-backups/. Copy any existing $CODEX_HOME/apx.config.toml and $CODEX_HOME/apx-instructions.md into it with their modes preserved. Do not copy, print, or modify $CODEX_HOME/auth.json.

  3. Fetch or locate the default base prompt that matches the installed Codex version. Keep the entire prompt except its file-editing guidance. Useful upstream references:

  4. Write the result to $CODEX_HOME/apx-instructions.md. In the section usually named ## File editing or ## File editing constraints, replace only the native apply_patch preference with contrib/codex/file-editing-instructions.md. Preserve dirty-worktree rules, destructive-action rules, sandbox rules, and every unrelated section. Confirm that the resulting file contains exactly one ## File editing section, mentions functions.apx, and retains native apply_patch as the prefer-mode fallback.

  5. Create $CODEX_HOME/apx.config.toml from contrib/codex/apx.config.toml, replacing its placeholder with the absolute path to $CODEX_HOME/apx-instructions.md. This is a layered profile file; do not add these settings to the user’s base $CODEX_HOME/config.toml.

model_provider = "apx"
model_instructions_file = "/absolute/path/to/.codex/apx-instructions.md"

[model_providers.apx]
name = "apx"
base_url = "http://127.0.0.1:8080/v1"
wire_api = "responses"
requires_openai_auth = true
  1. Validate without exposing credentials:
apx-router doctor --json
codex --profile apx doctor --json

Then start a fresh codex --profile apx task and confirm that both functions.apx and, in prefer mode, native apply_patch are available.

Do not rely on project AGENTS.md alone: the stock base prompt still steers file edits toward apply_patch.

Prompt reversion
  1. Stop the apx-profile Codex process. Resolve the same CODEX_HOME; do not touch $CODEX_HOME/config.toml or $CODEX_HOME/auth.json.
  2. Find the backup directory created by the corresponding prompt update. If it contains a prior apx.config.toml or apx-instructions.md, restore those exact files and modes. If a file did not exist before the update, remove only the apx-created current file instead of inventing a replacement.
  3. Do not delete the backup automatically. Report its path so the user can remove it after verifying the restoration.
  4. Start a fresh normal Codex task without --profile apx. Verify that the router profile is no longer active. Reverting the prompt does not uninstall the binaries or service; run apx-router service uninstall separately only when the user explicitly asks to remove the service.

Standalone CLI

Install the CLI (requires Go 1.26+; binary lands in $(go env GOPATH)/bin or $GOBIN):

go install github.com/tuanhung303/apx/cmd/apx@latest

Apply a script (writes only after the full script validates and stages; success report on stderr):

apx <<'EOF'
in src/app.go
tsel 12 "oldName"
type "newName"
EOF

Translate to an OpenAI apply_patch envelope without touching files (patch on stdout, pending report on stderr):

apx translate <<'EOF'
new message.txt
type "hello world\n"
EOF

For router and translate mode, new-file content must end with \n; OpenAI Add File patches cannot faithfully represent empty or unterminated files, so apx rejects those translations instead of silently changing bytes.

Script paths are workspace-relative.

Surface Workspace root Path base inside root
Standalone CLI Process current directory, or absolute --root ., or --cwd (relative or absolute, must stay under root)
Codex router The single usable absolute path from x-codex-turn-metadata Root itself (no CLI flags)

Translated patches always use root-relative paths. Details: apx --help.

Mode Mutates files? stdout stderr
apx Yes, after full validation empty on success final-state report
apx translate No apply_patch envelope pending final-state report
apx gain No metrics report empty on success
--help / --tool-help / --version No help or version empty

Built-in references:

apx --help
apx --tool-help
apx translate --help
apx --version

Editing language (summary)

Authoritative guidance: apx --help and apx --tool-help. Contract: doc/spec/interface.md.

Selectors (minimize the complete selector-plus-replacement output):

  • tsel FROM_LINE "TEXT" [N]: a fragment, or one replacement at multiple sites.
  • bsel "START" "END": a multiline partial region identified by unique boundary literals; both are consumed.
  • rsel START:END: a region where every selected line is re-emitted anyway.
  • sel LINE START:END: a later identical match on one line, with verified coordinates.

Common commands: in / new / mv / rm, type "…" or type <<PATCH … PATCH, del, copy / cut / paste, commit.

Rules worth remembering:

  • Omit leading whitespace in tsel / bsel anchors unless it disambiguates the match.
  • First in of a file freezes an immutable baseline; selectors use that baseline until commit.
  • Disjoint edits can land together; overlapping replacements fail the whole script atomically.
  • Rejection changes nothing. Router-owned retries can replace, delete, or insert failed commands by index without resending the full script.

Multiline example:

in parser.go
rsel 50:60
type <<PATCH
func parse(input []byte) (Document, error) {
	tokens, err := tokenize(input)
	if err != nil {
		return Document{}, fmt.Errorf("tokenize: %w", err)
	}
	document, err := buildDocument(tokens)
	if err != nil {
		return Document{}, fmt.Errorf("build document: %w", err)
	}
	return document, nil
}
PATCH

Metrics

Successful CLI and router edits record paired GPT-5 output-token estimates for:

  • apx: functions.apx + the model-emitted script
  • baseline: functions.exec + a fixed program that calls tools.apply_patch with the translated envelope

Failed calls charge the full rejected apx payload against an empty-patch baseline. Final-state reports, diagnostics, and once-per-session tool definitions are tracked as input overhead separately.

apx gain

These are reproducible payload estimates, not provider billing totals. They omit reasoning tokens, commentary, and host-specific framing.

Hand-authored scenario comparison (does not update apx gain):

go run ./compare

How it works

CLI: resolve workspace (--root / --cwd or process cwd) → parse script → evaluate against an in-memory baseline → stage multi-file result → commit (normal mode) or emit one apply_patch envelope (translate).

Router: accept an authenticated POST /v1/responses → expose functions.apx alongside or instead of Code Mode apply_patch → translate against the single usable workspace from Codex metadata → return a carrier that makes Codex apply the real patch and surface the diff.

Workspace selection is host-owned. Zero or multiple usable roots fail closed. Codex still enforces sandbox permissions on apply.

Project structure

.
├── cmd/
│   ├── apx/           # CLI entry point and command-line contract
│   └── apx-router/    # Router process entry point
├── internal/
│   ├── router/           # Responses proxy, apx translation, auth, metrics, and dashboard
│   ├── apxsyntax/        # Shared quoted-string and heredoc parsing
│   └── patchtest/        # Test helper for applying translated patch envelopes
├── compare/              # Hand-authored apx vs. apply_patch token scenarios
├── contrib/              # Codex guidance plus launchd and systemd service templates
├── doc/                  # Product brief, interface specification, and architecture index
├── *.go                  # Core parser, editor, workspace, transaction, translation, hooks, and metrics
├── tool_description.md   # Embedded function-tool instructions
└── tool_grammar.lark     # Embedded constrained-decoding grammar

Tests live beside the packages they exercise. The root apx package is the reusable engine; cmd/apx and internal/router call it rather than maintaining separate editing implementations. The router dashboard is embedded from internal/router/dashboard.html.

Documentation

Doc Contents
doc/brief.md Product brief and scope
doc/spec/index.md Spec inventory
doc/spec/interface.md CLI, script, correction, metrics contracts
doc/spec/comparison.md Token comparison scenarios
doc/architecture/index.md Architecture ownership
contrib/systemd/apx-router.service Linux user unit template
contrib/launchd/io.github.tuanhung303.apx-router.plist macOS LaunchAgent reference
contrib/codex/apx.config.toml Dedicated Codex profile template
contrib/codex/file-editing-instructions.md Base-prompt replacement for file editing
AGENTS.md Codex router E2E notes for agents

Library use: module path github.com/tuanhung303/apx. Importable as a library (apx.Translate, apx.Workspace, host metrics helpers); hosts should open an *os.Root capability for the workspace before calling in.

Development

git clone https://github.com/tuanhung303/apx.git
cd apx
go test ./...
go vet ./...
go install ./cmd/apx ./cmd/apx-router

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Version = "devel"

Version is replaced at release build time with -ldflags.

Functions

func Apply

func Apply(ctx context.Context, workspace Workspace, script string) error

Apply evaluates and atomically applies script within workspace.

func CurrentVersion

func CurrentVersion() string

CurrentVersion returns release metadata from ldflags or the Go module build.

func RecordHostMetrics

func RecordHostMetrics(ctx context.Context, dataDirectory string, record HostMetricRecord) error

RecordHostMetrics persists one complete host-accounted call.

func Run

func Run(args []string, stdin io.Reader, stdout, stderr io.Writer, workingDirectory, dataDirectory string) int

Run executes the apx command-line contract with workingDirectory as the workspace root. New callers that already own a root capability should use RunWorkspace, Apply, or Translate.

func RunWorkspace

func RunWorkspace(args []string, stdin io.Reader, stdout, stderr io.Writer, workspace Workspace, dataDirectory string) int

RunWorkspace executes the command-line contract within workspace.

func ToolDescription

func ToolDescription() string

ToolDescription returns the authoritative model guidance and examples.

func ToolGrammar

func ToolGrammar() string

ToolGrammar returns the authoritative Lark grammar for model-generated calls.

func Translate

func Translate(ctx context.Context, workspace Workspace, script string) ([]byte, error)

Translate evaluates script without mutation and returns an apply_patch envelope whose paths are relative to workspace.Root.

func WithAttemptMetadata

func WithAttemptMetadata(ctx context.Context, metadata AttemptMetadata) context.Context

WithAttemptMetadata attaches host-owned attempt identity to diagnostics and hooks.

Types

type AttemptMetadata

type AttemptMetadata struct {
	SessionID     string
	CorrelationID string
	CallID        string
	Attempt       int
	Correction    bool
}

AttemptMetadata identifies one router-owned apx attempt and its correction chain.

type CommandReasonMetric

type CommandReasonMetric struct {
	Command string `json:"command"`
	Reason  string `json:"reason"`
	Errors  uint64 `json:"errors"`
}

CommandReasonMetric attributes nonzero errors to the command that raised them.

type GainMetrics

type GainMetrics struct {
	HPatchTokens            uint64 `json:"apx_tokens"`
	ApplyPatchTokens        uint64 `json:"apply_patch_tokens"`
	IneffectiveHPatchTokens uint64 `json:"ineffective_apx_tokens"`
	FailedApplyPatchTokens  uint64 `json:"failed_apply_patch_tokens"`
	SuccessfulReduction     string `json:"successful_reduction_percent"`
	OverallReduction        string `json:"overall_reduction_percent"`

	ReportInputTokens            uint64 `json:"report_input_tokens"`
	DiagnosticInputTokens        uint64 `json:"diagnostic_input_tokens"`
	DefinitionInputTokens        uint64 `json:"definition_input_tokens"`
	RemovedDefinitionInputTokens uint64 `json:"removed_definition_input_tokens"`
	// NetAddedInput is measured additions minus the removed definition credit.
	// It is a decimal string so a definition credit can make the net negative.
	NetAddedInput      string `json:"net_added_input"`
	Sessions           uint64 `json:"sessions"`
	DefinitionRequests uint64 `json:"definition_requests"`
	DefinitionSources  string `json:"definition_sources"`

	Commands       []NamedCommandMetric  `json:"commands"`
	TextSpans      []NamedCommandMetric  `json:"text_spans"`
	BlockOutcomes  []NamedCount          `json:"block_outcomes"`
	Reasons        []NamedCount          `json:"reasons"`
	CommandReasons []CommandReasonMetric `json:"command_reasons"`
}

GainMetrics is the durable aggregate reported by `apx gain`. Percentages and net input use the same formatting as the CLI report.

func EmptyGainMetrics

func EmptyGainMetrics() GainMetrics

EmptyGainMetrics returns the zero aggregate printed by `apx gain` with no metrics file.

func LoadGainMetrics

func LoadGainMetrics(dataDirectory string) (GainMetrics, error)

LoadGainMetrics reads the durable metrics aggregate reported by `apx gain`. Missing metrics yield a zero value with the same empty tables as gain.

type HostMetricRecord

type HostMetricRecord struct {
	Invocation                   InvocationMetrics
	SessionID                    string
	HPatchTokens                 uint64
	ApplyPatchTokens             uint64
	IneffectiveHPatchTokens      uint64
	FailedApplyPatchTokens       uint64
	ReportInputTokens            uint64
	DiagnosticInputTokens        uint64
	DefinitionRequests           uint64
	DefinitionInputTokens        uint64
	RemovedDefinitionInputTokens uint64
}

HostMetricRecord is the complete accounting entry calculated by the host. Invocation counters originate in apx; every token count and the session attribution originate at the host seam where the visible payload is known.

type HostTranslation

type HostTranslation struct {
	Patch      []byte
	Report     string
	Diagnostic string
	Invocation InvocationMetrics
}

HostTranslation contains the complete result needed by an in-process host. Diagnostic contains a rejection diagnostic or non-fatal hook warnings.

func TranslateForHost

func TranslateForHost(ctx context.Context, workspace Workspace, script, dataDirectory string) (HostTranslation, error)

TranslateForHost evaluates script once without mutation and returns the translated patch, final-state report, and evaluator-owned invocation metrics. On an evaluation failure it also returns the command-line diagnostic and repair context, including configured error-hook warnings.

type InvocationMetrics

type InvocationMetrics struct {
	// contains filtered or unexported fields
}

InvocationMetrics is evaluator-owned accounting returned by TranslateForHost. Its representation remains private so hosts cannot construct inconsistent command, selector, outcome, and reason totals.

type NamedCommandMetric

type NamedCommandMetric struct {
	Name        string `json:"name"`
	Invocations uint64 `json:"invocations"`
	Errors      uint64 `json:"errors"`
	ErrorRate   string `json:"error_rate_percent"`
}

NamedCommandMetric is one labeled invocations/errors pair in a gain report.

type NamedCount

type NamedCount struct {
	Name  string `json:"name"`
	Count uint64 `json:"count"`
}

NamedCount is one labeled counter in a gain report table.

type Workspace

type Workspace struct {
	Root                *os.Root
	CWD                 string
	AbsolutePathAliases []string
}

Workspace is the filesystem authority for one apx operation. Root should be opened from its canonical absolute path; absolute script paths are matched against that name or a validated alias. CWD is root-relative and defaults to ".".

Directories

Path Synopsis
cmd
apx command
apx-router command
internal
patchtest
Package patchtest applies the OpenAI apply_patch subset emitted and compared by this project.
Package patchtest applies the OpenAI apply_patch subset emitted and compared by this project.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL