Skip to content

About

VCC compaction and observability tools

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

opencode-safe-compaction

Install

One command installs whatever the host supports — it probes for OpenCode, Pi and Codex and installs the matching targets:

curl -fsSL https://raw.githubusercontent.com/shyba/better-compact/default/install.sh | sh

Per-runtime behavior:

runtime detected what gets installed
OpenCode >=1.18.4 <1.19.0 full plugin (compaction hooks + TUI selector), as before
OpenCode absent or unsupported version skipped gracefully — no failure
pi on PATH pi install <install-dir> — registers both entry points (src/pi.ts, src/cat.ts) from the package manifest; maintained with pi update --extensions
~/.pi/agent present but no pi CLI fallback shims written into ~/.pi/agent/extensions/
codex (~/.codex present) sessions covered by the sync daemon (codex-jsonl source); codex has no plugin API
always better-compact CLI wrapper; sync daemon if sync env is set

Sync daemon (optional at install time, runtime-independent):

# before install, or later from the install dir:
OPENCODE_SAFE_COMPACTION_SYNC_URL=<session-center-url> OPENCODE_SAFE_COMPACTION_SYNC_SETUP_ARGS="--token <bearer>" \
  curl -fsSL https://raw.githubusercontent.com/shyba/better-compact/default/install.sh | sh

Sync-only hosts (no compaction at all, no runtime probing):

curl -fsSL https://raw.githubusercontent.com/shyba/better-compact/default/install-sync-only.sh | sh -s -- --url <session-center-url> [--token <bearer>]

Compatibility and prerequisites

  • OpenCode supplies the Bun runtime used by the plugin. A separate Bun installation is optional for the one-command installer and required only for development or packaging.
  • OpenCode >=1.18.4 <1.19.0. The plugin uses experimental V1 hooks, so the upper bound is intentional.
  • Either model: "selected" to follow each compaction's selected model, or a fixed provider/model. The initial configuration below uses opencode-go/glm-5.2, but the package itself is provider-neutral.
  • The selected model must have a positive output limit and leave positive usable input under OpenCode's V1 overflow calculation.

VCC modes and exact recall

VCC is opt-in. The default vcc_mode: "off" keeps the existing provider-backed compaction contract. vcc_mode: "hybrid" is available for both OpenCode V1 and Pi: it compiles a deterministic candidate, makes at most one bounded semantic request, validates the response, and falls back to the authoritative candidate on missing auth, refusal, abort, or malformed output. vcc_mode: "offline" uses that deterministic candidate directly and never resolves auth or calls a provider.

OpenCode V1 still uses the host-selected preparation boundary; its offline mode therefore does not change which messages the host keeps. Pi exposes the full session_before_compact contract, so its offline/hybrid path follows Pi VCC: the extension rebuilds the active branch, preserves custom/branch-summary messages, chooses a stable user-turn cut, and returns the complete compaction result. If that branch cannot form a safe cut, the extension returns undefined and Pi's native fallback remains in control.

Both hosts expose a bounded vcc_recall tool for the current canonical scope (OpenCode session or Pi active branch). Discovery returns verified handles; expansion re-resolves canonical records and emits redacted, transformed output with exact source/payload digests. Handles are scoped and restart-resolvable; malformed, stale, incomplete, cross-scope, and oversized requests return explicit statuses. scope: "all" is unavailable in these V1 adapters. Recall does not require RAG, Postgres, network access, persistence, or workspace reads.

To roll back to the legacy behavior, remove vcc_mode (or set it to "off") before loading an older plugin. The option default and durable Pi option round-trip remain legacy-safe; no transcript, archive, RAG, or filesystem data is deleted by changing modes.

Before enabling the plugin, remove only the stale deepseek-v4-flash-free model-limit override from the global OpenCode JSONC configuration. Do not remove the provider, other model entries, credentials, or unrelated settings. The usual global file is ${XDG_CONFIG_HOME:-~/.config}/opencode/opencode.jsonc; use the path reported by OpenCode if the configuration directory was overridden. The plugin deliberately does not rewrite provider catalogs.

One-command installation

Review the installer, then run it on each server:

curl -fsSL https://raw.githubusercontent.com/shyba/opencode-better-compact-plugin/default/install.sh | sh

The installer follows OpenCode's selected model by default on a fresh installation. It never asks for a model, and an upgrade preserves the policy already configured by the plugin.

After installation, run /compaction-model inside the OpenCode TUI (or choose Compaction model in the command palette). The native selector lists connected models plus Follow selected model. A selection is persisted through the same locked, atomic JSONC update used by the installer, then OpenCode reloads its instances. No LLM request is spent on the settings change.

In Follow selected model mode, automatic compaction uses the model on the latest user turn and manual /compact uses the model selected when the command runs. Changing the ordinary OpenCode model therefore applies to the next compaction without another plugin setting change.

The installer clones the default branch over HTTPS to $HOME/.local/share/better-compact (a pre-rename checkout at ~/.local/share/opencode/plugins/safe-compaction is adopted in place), adds the absolute source tuple to both the global server and TUI configurations, preserves JSONC comments and unrelated settings, and verifies the exact runtime module and configuration hook before writing anything. The server tuple carries all plugin options; the TUI tuple carries only the synchronized model policy. If exactly one server entry points to a different local safe-compaction checkout, the installer imports that module to verify the expected plugin identity, preserves its validated tuple options, and replaces only its source string with the managed path. Unverifiable lookalikes and duplicate entries are refused. It first loads a generated minimal configuration containing only the exact installed tuple, then checks compatibility with the real target configuration. Fresh installs must retain all three exact numeric thresholds. For a pre-existing partial tuple, values intentionally inherited from earlier configuration or plugin hooks must still satisfy the plugin's type and safety bounds; explicit tuple values remain exact. Both checks use a temporary HOME and XDG directories; inherited OPENCODE_CONFIG, OPENCODE_CONFIG_CONTENT, and pure mode are neutralized. OpenCode 1.18.x reports the pre-plugin object from opencode debug config, so the installer verifies hook activation with its direct runtime preflight and treats a debug result that omits hook fields as valid; hosts that expose post-hook fields still receive the threshold/override check. An unrelated plugin therefore cannot impersonate successful activation. It is idempotent: rerunning it fast-forwards a clean checkout and does not duplicate either tuple. Configuration edits are serialized with a directory lock and committed by atomic rename as one rollback-safe transaction. When an existing file changes successfully, a timestamped *.safe-compaction-backup-* copy remains beside it.

The shortest command follows the mutable default branch. For a security-sensitive server, pin the reviewed installer and checkout to the same lowercase 40-character commit:

commit="COPY_A_REVIEWED_40_CHARACTER_COMMIT_HERE"
curl -fsSL "https://raw.githubusercontent.com/shyba/opencode-better-compact-plugin/$commit/install.sh" |
  env OPENCODE_SAFE_COMPACTION_REF="$commit" sh

Before writing configuration, the installer imports the exact absolute server and TUI runtime entrypoints, checks both plugin identities, initializes the server with the exact tuple options, and runs its configuration hook. Existing tuples go through the same runtime option validation, including unknown-key and cross-limit checks. Duplicate root plugin keys are rejected rather than collapsed by JSONC parsing.

Checkout update, configuration activation, direct runtime preflight, and compatible opencode debug config verification form one installer transaction. A checkout-parent lock serializes transactions sharing an install path even when their configuration directories differ; a second lock serializes transactions sharing a configuration directory. Both are held from before clone/update through verification and commit. A later failure restores the prior configuration, permissions, and Git commit, or removes a newly created clone. Successfully written configuration and installer-created backups use mode 0600 because OpenCode configuration may contain credentials. Rollback compares the activated configuration digest before restoring it, so it refuses to overwrite a file changed independently during activation; in that case it also preserves the checkout so the independently edited tuple cannot be left pointing at removed or rolled-back source. An uncatchable termination such as SIGKILL or a host power loss cannot run the shell rollback trap; the next installer removes a lock whose recorded process no longer exists, but inspect the timestamped backup and checkout before rerunning.

The command requires git and OpenCode >=1.18.4 <1.19.0. It first checks PATH, then the official installer locations ($OPENCODE_INSTALL_DIR, $XDG_BIN_DIR, $HOME/bin, and $HOME/.opencode/bin) and common npm, Bun, pnpm, mise, Nix, and Homebrew user/system locations. It uses Bun 1.3.14 or newer when one is already available. Otherwise it downloads the pinned Bun 1.3.14 archive from the official oven-sh/bun GitHub release into the installer transaction directory, verifies the archive against a platform-specific SHA-256 digest, and deletes it when the installer exits. The automatic bootstrap supports Linux, macOS, and FreeBSD on x86-64 or ARM64; it needs curl, a SHA-256 implementation (sha256sum, shasum, or openssl), and unzip or busybox. If OpenCode or Bun is installed elsewhere, automatic download is undesirable, or a different compaction model is required, pass overrides to sh:

curl -fsSL https://raw.githubusercontent.com/shyba/opencode-better-compact-plugin/default/install.sh |
  OPENCODE_SAFE_COMPACTION_OPENCODE="$HOME/.local/bin/opencode" \
  OPENCODE_SAFE_COMPACTION_BUN="$HOME/.bun/bin/bun" \
  OPENCODE_SAFE_COMPACTION_MODEL="provider/model" \
  sh

Other supported overrides are OPENCODE_SAFE_COMPACTION_DIR, OPENCODE_SAFE_COMPACTION_CONFIG_DIR, OPENCODE_SAFE_COMPACTION_REPO, and OPENCODE_SAFE_COMPACTION_REF (an alternate branch or exact lowercase 40-character commit). Exact commits are fetched and checked out detached. All directory overrides must be absolute. The installer respects an existing OPENCODE_CONFIG_DIR. It refuses insecure http:// and git:// repository URLs, including an insecure existing origin that would otherwise normalize to the requested HTTPS GitHub repository. It also refuses a dirty checkout, a mismatched remote or branch, duplicate or unverifiable plugin entries, and configurations containing the stale deepseek-v4-flash-free limit override. It never rewrites provider catalogs.

The checked-in runtime/server.js and runtime/tui.js entrypoints are Bun bundles with their runtime dependencies included, so the installer does not populate node_modules and an isolated OpenCode process does not depend on the installer host's global modules. OpenCode may manage its standard plugin SDK in the configuration directory when it first loads a TUI plugin. A bootstrapped Bun is temporary and is not installed into the user account or retained by the plugin; the in-app selector uses OpenCode's own Bun runtime. Restart a running OpenCode server after installation.

The installer also creates better-compact in the user bin directory when a persistent Bun executable is available.

Command guide

Use better-compact --help, better-compact sync --help, or append --help to a specific command. Help exits before configuration, local state or service actions. Unknown options, missing option values and conflicting flags are rejected. --config FILE and --state FILE select explicit local configuration and state paths.

For the current session-center S3 transport:

Command Purpose
sync setup --url-stdin Configure the receiver and credentials
sync run Run continuous sync in the foreground
sync run --once Scan all configured files, bypass retry skips, and upload until idle
sync run --pass Perform one normal pass, preserving retry skips
sync status Inspect local receipts and failures; does not prove remote coverage
sync verify [--json] Read-only remote completeness report; distinguish archives, loading and indexing
sync install / sync uninstall Start/install or stop/remove the background service
sync retry-s3 --kind KIND --path PATH [--root ROOT] Clear one failed version for retry without resetting checkpoints; stop background sync first
sync compact --yes Compact the local acknowledged cache

Prefix commands above with better-compact. Zero local failures or a successful upload is not proof of searchable coverage. sync verify reports unsupported or unavailable stages as unknown, not complete; the receiver must support verification to establish remote coverage. Verification does not repair or resend data.

Plugin maintenance uses install, install pi, update, and doctor. installation reset --yes explicitly replaces local sync identity and state; it is not a completeness check.

Legacy Postgres commands remain implemented for older installations:

Command Purpose and S3 boundary
sync migrate Apply mirror migrations; no-op for S3
sync reconcile Rebuild the OpenCode snapshot; no-op for S3, not archive verification
sync prune (--missing-directories|--blank-directories) --yes Delete selected local Codex files; refuses S3
sync backfill-hierarchy [--dry-run] Stage Codex hierarchy; refuses S3
installation adopt --yes Adopt the remote Postgres mirror identity
rag setup, run, status, migrate, install, uninstall, tunnel install|uninstall Separate legacy Postgres embedding worker, not the session-center VCC index

Use better-compact rag setup --help for runtime and model options. These commands are compatibility tools, not prerequisites for S3 sync. The RAG projection remains fixed at 384 dimensions; a model override must fit that existing projection.

The generated wrapper points at the persistent checkout and also falls back to the standard checkout path if an older wrapper still references a deleted temporary installer directory. Re-running the installer repairs that wrapper in place.

better-compact install pi delegates to Pi's package manager and registers both the recovery-ledger and /cat extensions. For a Git-installed Better Compact checkout, it uses that managed checkout as a local Pi package, so better-compact update updates both hosts together. Set OPENCODE_SAFE_COMPACTION_PI when the pi executable is not on PATH; set OPENCODE_SAFE_COMPACTION_PI_SOURCE to a git:..., npm:..., or absolute package path to override the source. The command refuses an implicit npx source because its temporary directory disappears after the command; provide an explicit source instead.

installation reset --yes intentionally discards the local sync identity, cursors, and outbox so the next run starts a new installation. installation adopt --yes is the explicit clone/recovery operation: it imports the remote source incarnation and high-water mark, then acknowledges matching local rows. Both commands are destructive and require the flag.

doctor checks the managed checkout, both OpenCode configuration surfaces, the OpenCode and Bun executables, and performs a read-only SQLite probe. update runs the same rollback-safe checkout/configuration transaction as the installer. If Bun was bootstrapped temporarily during installation, install Bun separately or invoke the CLI with OPENCODE_SAFE_COMPACTION_BUN=/path/to/bun.

The default sync transport is session-center S3 ingest. The runner reads configured Codex JSONL and Pi JSONL sources read-only, uploads only changed bytes to PUT /file/<file_id>, and marks rewritten files with x-vcc-full: true; a growing file is appended only after verifying that its acknowledged prefix is unchanged. The local SQLite state stores the last acknowledged path version so an interrupted PUT can be replayed safely; S3 archives remain the durable source of truth and vcc_load.py projects them into vcc.*. OpenCode V1 SQLite remains an explicit warning until session-center's normalized-record feeder is enabled; it is never sent to the retired opencode.* writer. better-compact sync status reports acknowledged S3 file versions and retry failures, and better-compact sync compact --yes vacuums only the local cache.

New uploads include source identity in both the file ID and frame headers. The first changed version of an older, unscoped receipt is sent in full so its new source does not start with an orphan suffix. Unchanged legacy files are not automatically resent; sync verify reports their missing provenance as unknown. After an uncertain upload acknowledgement or a file change during upload, the next retry sends a full replacement instead of assuming the old checkpoint is still the remote end.

Remote verification requires a receiver with POST /verify and its read-only projection checker configured. It checks actual archived bytes, recompiles expected VCC blocks, and checks dense/BM25 point presence and dense source associations. It does not measure retrieval quality. Limits or unavailable backends return unknown and a nonzero exit, not partial success. The receiver currently limits a reconstructed file to 64 MiB, archive reads per request to 256 MiB, and projected blocks to 50,000 / 64 MiB. Files are checked serially and progress goes to stderr, including with --json.

If an older hand-written config enables sync without an S3 URL/token, sync run warns and performs only local compatibility staging; it does not write Postgres or pretend that a remote upload succeeded. Run sync setup before relying on remote durability.

Completed source snapshots are guarded against destructive partial reads in the legacy Postgres transport. An OpenCode database whose session/message/part/todo counts shrink, or a JSONL inventory whose files/bytes shrink, is uploaded as ordinary updates but is not allowed to emit tombstones until a complete stable snapshot is observed. The S3 transport emits no tombstones: S3 lifecycle policy owns retention and the VCC loader rebuilds from archives.

For S3, every poll performs a metadata-only JSONL walk (regular files are stated but unchanged bytes are not opened); only new/changed files are hashed and uploaded. The safe defaults are a 30-second sync.poll_interval_ms and a 5-minute sync.rescan_interval_ms full-content validation, so same-size rewrites and coarse timestamp files are caught without hashing the corpus continuously. The full-content validation is a rotating, budgeted deep verify: it re-hashes unchanged files from a persisted resume cursor for at most sync.deep_verify_bytes_per_pass bytes (default 32 MiB) per pass, so a multi-gigabyte corpus is re-verified as a steady trickle instead of one hashing burst, and completed cycles are timestamped so a scan can never be forgotten. A file is only hashed once it has been untouched for sync.settle_ms (default 30 seconds), because hashing a session file that is still being appended to races the writer and discards the result; sync run --once and sync retry-s3 hash immediately, and a due retry is never held back by the settle window. Files whose automatic retries were exhausted while an endpoint was down can be picked up again with sync retry-s3 --kind KIND --all. Duplicate configured roots are scanned once, preferring the first source entry. A file or source failure is persisted and retried sync.failure_retry_attempts times (default 3) at sync.failure_retry_interval_ms (default 5 minutes); after exhaustion that exact version is left alone until its metadata changes or an explicit sync run --once clears the retry state. sync run --once bypasses scan and retry skips entirely. Codex thread hierarchy is extracted by the session-center VCC loader for S3 archives. The local sync backfill-hierarchy command remains a legacy-Postgres maintenance operation and refuses configured S3 transports.

Use better-compact sync setup --url-stdin to configure a server without putting the session-center URL or bearer token in shell history:

IFS= read -r SESSION_CENTER_URL
IFS= read -r -s S3_SYNC_TOKEN
printf '%s\n' "$SESSION_CENTER_URL" | S3_SYNC_TOKEN="$S3_SYNC_TOKEN" better-compact sync setup --url-stdin
unset SESSION_CENTER_URL S3_SYNC_TOKEN
better-compact sync install

The command enables sync, stores the URL and token only in the mode-0600 ~/.local/state/better-compact/sync.env, and preserves an existing source list. For a new S3 configuration it discovers Codex and Pi JSONL roots only; OpenCode SQLite still needs session-center's separate normalized-record feeder and is not silently routed to the retired opencode.* writer. Use --url URL and --token TOKEN for a convenient non-interactive invocation, or omit them when the configured environment variables are already exported. --allow-insecure-remote is required for a trusted non-local HTTP session-center endpoint; HTTPS is preferred. Session-center owns the VCC schema and archive loader, so sync migrate is a no-op in the default S3 mode. Manual sync run commands also read the stored sync.env values, so no separate export is needed.

S3 sync does not delete archive objects when a local file disappears; retention is controlled by session-center/S3 lifecycle policy. Accordingly, the legacy sync prune and hierarchy-backfill commands refuse to run in S3 mode instead of querying the removed opencode.* schema.

The S3 bearer token stays in the environment or the separate mode-0600 service environment file; it is never written to JSON configuration. better-compact sync install installs the opt-in per-user service and preserves the local path-version ledger across restarts. The explicit transport: "postgres" setting remains only as a migration compatibility path for hosts that have not moved to session-center yet.

The optional RAG projection is separate from the S3 VCC path and is not yet a consumer of vcc.* archives. Its current worker reads legacy normalized opencode.* rows, so keep it disabled for an S3-only deployment until the session-center embedding hand-off is implemented. When using the explicit legacy transport, the existing 384-dimensional BGE projection remains available: run better-compact rag migrate once with the admin environment variables to create rag.embedding_current_384 and install the version-aware chunk uniqueness required for multiple reviewed chunkers, then use better-compact rag status to inspect model, dimension, row, and table-size metadata. The evaluated first-delivery default is BAAI/bge-small-en-v1.5 (384 dimensions, 512-token chunks with 64-token overlap, fixed 0.8 vector / 0.2 lexical retrieval). Embedding generation remains off the compaction request path and is performed by a bounded streaming worker:

better-compact rag setup --model-path /absolute/path/to/bge-small-en-v1.5 --python /absolute/path/to/rag-venv/bin/python3
better-compact rag migrate       # once, with admin credentials
better-compact rag install       # opt-in systemd user service
better-compact rag status

The worker reads only synced opencode-v1-sqlite rows, stages up to 2,048 messages at a time, and embeds them in measured 16-example CPU inference batches. Live polling uses the recent-part index installed by rag migrate; a complete reconciliation sweep is throttled to every 15 minutes instead of rescanning the full history on every idle poll. Backfill discovery runs as one server-side cursor and prefetches the next page while the current page is embedded. Documents and chunks are staged with bulk inserts and one batched missing-chunk select; a single lookahead page is staged on a separate connection while the current page is embedded. Vector writes use a dedicated writer connection, so the next CPU encode starts while the previous vector batch is being inserted and committed. It persists only a small cursor file, keeps no transcript staging file, and can be run in the foreground with better-compact rag run --once. The service requires a Python environment containing sentence-transformers, torch, and psycopg[binary]; use a durable virtualenv rather than /tmp for a long-lived installation. Remote PostgreSQL must use certificate-verifying TLS, or the explicit trusted-LAN insecure opt-in already required by sync. See docs/rag_embedding_pilot.md for the benchmark protocol and storage gate.

Execution backend is controlled independently from the model and storage format. The default backend: "auto" selects the Torch model when the configured directory contains Transformer weights, otherwise it uses the ONNX bundle. compute_dtype: "bfloat16" is the default when the CPU exposes oneDNN AVX-512 BF16; unsupported hosts fall back to float32 automatically. The measured CPU path uses length bucketing, direct Torch forward calls, and a 16-example inference batch. To force the measured path explicitly:

better-compact rag setup \
  --backend torch --compute-dtype bfloat16 --length-bucketing \
  --batch-size 16 --message-batch-size 2048 --threads 16 \
  --model-path /absolute/path/to/bge-small-en-v1.5 \
  --python /absolute/path/to/rag-venv/bin/python3

The Torch path needs the full Transformer model directory (for example, one containing model.safetensors), not an ONNX-only export. Length bucketing groups similarly sized chunks before encoding to reduce padding; it is enabled by default. The backend and dtype are recorded in audit.pipeline_run, while the embedding model identity and vector dimension remain unchanged, so switching execution paths does not trigger a destructive re-embedding or mix a new schema into the compaction path. Existing running services keep their current process settings until restarted.

Example source configuration:

{
  "version": 1,
  "sync": { "enabled": true, "transport": "s3", "s3_url_env": "SESSION_CENTER_URL", "s3_token_env": "S3_SYNC_TOKEN", "allow_insecure_remote": false, "poll_interval_ms": 30000, "rescan_interval_ms": 300000, "settle_ms": 30000, "deep_verify_bytes_per_pass": 33554432, "failure_retry_attempts": 3, "failure_retry_interval_ms": 300000 },
  "sources": [
    { "kind": "codex-jsonl", "database": "~/.codex/sessions" },
    { "kind": "pi-jsonl", "database": "~/.pi/agent/sessions" }
  ],
  "installation": { "name": "workstation" }
}

During compaction, the model receives the bounded ledger and a single-response JSON contract. The JSON projection contains goal, constraints, decisions, current state, files, evidence, blockers, and next actions, with every claim tied to stable references in the canonical ledger. The plugin strictly parses it (including duplicate-key rejection), validates its digest and provenance, and renders it into the durable Markdown summary together with the canonical ledger block. The ledger remains the verified recovery anchor; projected files and actions are useful model-authored organization, not independent evidence. Legacy Markdown summaries remain accepted for compatibility. Missing sections, invented digests, stale references, oversized output, refusals, and split/empty responses are replaced with a bounded ledger-grounded summary so the host remains usable; the hook also marks that provisional result with an optimistic retry signal, which a retry-capable future host can use to discard it and retry the model before cutover. Current V1 ignores unknown output fields, so no OpenCode fork is required.

Manual Git installation

Clone this repository to a stable absolute path:

install_dir="$HOME/.local/share/better-compact"
git clone --branch default https://github.com/shyba/better-compact.git "$install_dir"

Add the tuple below to the global server configuration's existing plugin array, replacing USER with the account's actual home directory. The plugin path must be absolute; environment variables are not expanded inside JSON. JSONC comments are allowed. Keep other plugin entries intact.

{
  "plugin": [
    [
      "/home/USER/.local/share/better-compact/runtime",
      {
        "model": "opencode-go/glm-5.2",
        "tail_turns": 4,
        "preserve_recent_tokens": 16000,
        "reserved_tokens": 32000,
        "max_output_tokens": 16384,
        "max_user_text_bytes": 524288,
        "max_inline_data_bytes": 10485760,
        "max_historical_part_bytes": 131072,
        "max_ledger_bytes": 12288,
        "max_summary_bytes": 49152
      }
    ]
  ]
}

OpenCode loads TUI plugins from its separate global tui.jsonc. Add the same runtime directory there with the synchronized model policy:

{
  "plugin": [
    [
      "/home/USER/.local/share/better-compact/runtime",
      {
        "model": "opencode-go/glm-5.2"
      }
    ]
  ]
}

The runtime-directory path must be absolute. Its package metadata exposes separate bundled server and TUI entrypoints; the two configuration entries activate the compaction hooks and the in-app selector respectively. No workspace entry, submodule, parent checkout, or prebuilt dist directory is required.

Options

Tuple options use snake case. Unknown keys and invalid values fail during plugin setup instead of being ignored.

Option Default Meaning
model selected selected follows the model chosen for each compaction; provider/model pins a dedicated compaction model.
response_mode json json asks the model for the single strict JSON projection and validates it; markdown keeps the legacy Markdown contract for a compatibility window. The legacy Markdown validator stays available in both modes for already-stored plugin summaries.
semantic_checkpoints false Pi only: opt in to validated semantic objects derived from bounded /cat source during compaction. OpenCode behavior is unchanged.
max_semantic_source_bytes 262144 Pi only: maximum aggregate exact /cat source supplied to one experimental semantic checkpoint.
tail_turns 4 Number of recent ordinary user requests retained in the recovery ledger. May be zero.
preserve_recent_tokens 16000 Recent-history budget written into OpenCode's V1 compaction settings.
reserved_tokens 32000 Context reserved from compaction input; must leave usable model context.
max_output_tokens 16384 Plugin ceiling for compaction output, further capped by the model limit.
max_user_text_bytes 524288 Maximum combined UTF-8 bytes in all newly admitted text parts. Client-supplied synthetic flags do not bypass it.
max_inline_data_bytes 10485760 Maximum combined decoded bytes in newly admitted inline data: attachments. Encoded representation and metadata have derived fixed bounds.
max_historical_part_bytes 131072 Maximum UTF-8 bytes exposed from one historical text part during a compaction/recovery attempt.
max_ledger_bytes 12288 Maximum canonical recovery-ledger block size.
max_summary_bytes 49152 Maximum accepted summary size. In json mode it must leave at least 2048 bytes for the projection after the ledger and rendering overhead; markdown mode retains the legacy 1024-byte margin.

For values OpenCode already exposes (model, tail_turns, preserve_recent_tokens, and reserved_tokens), precedence is explicit tuple option, existing OpenCode value, then plugin default. Other plugin limits use the explicit tuple option or plugin default. The configuration hook pins a dedicated model or removes that override in selected mode, sets temperature zero, and applies the selected compaction thresholds. The request hook validates and caps the actual per-compaction model in either mode while preserving an omitted temperature when the model declares that parameter unsupported. Existing explicit compaction.auto and compaction.prune values are preserved; absent values default to true and false respectively.

Safety-critical tuple values also have hard ceilings: tail_turns=64, max_user_text_bytes=8388608, max_inline_data_bytes=67108864, max_historical_part_bytes=1048576, max_ledger_bytes=262144, max_summary_bytes=1048576, and max_semantic_source_bytes=4194304. These ceilings keep a tuple from disabling the plugin's resource bounds.

Runtime behavior

The plugin has five defensive stages:

  1. Admission rejects oversized new user text, decoded inline data, or an oversized encoded data-URL representation and reports the measured and allowed byte counts. Data-URL metadata has a fixed 16 KiB ceiling. It recommends file references or smaller chunks; it never silently truncates a new request.
  2. Compaction asks the OpenCode API for the newest 10,000 session messages plus todos, then applies smaller deterministic newest-first message, part, and collection budgets while building the ledger. If that recent window has no prior plugin-valid ledger, it follows at most 255 older 256-message cursor pages until it finds the newest older one or reaches the scan budget; the surrounding provider prose is never trusted. Overflow-replay matching uses the same paging contract and stops as soon as it has the two distinct durable requests needed for proof. Page order or durable creation time establishes chronology rather than caller-controlled Message IDs. Requests and constraints come only from eligible user text; assistant, synthetic, and ignored prose cannot become recovered intent. The fallback Goal chooses the newest substantive request instead of a terse acknowledgement such as check or continue; if every recovered request is terse, it uses an honest placeholder. Synthetic carry-over actions are not accumulated across ledgers. Touched paths come from explicit patch/file artifacts and absolute paths in agent-authored tool-call arguments (the same trust level as the assistant text that already feeds requests); tool outputs are never scanned, so untrusted third-party bytes cannot become resume context. Paths prefer the freshest turns under the cap, and short URL/API fragments are rejected. Credential-shaped values are redacted, and tool evidence hashes only a bounded source prefix rather than scanning or copying an unbounded output.
  3. The compaction prompt asks for one strict JSON object with the required semantic fields and stable ledger references. Per-section item limits bound the response: constraints 16, decisions 16, current state 24, blockers 16, evidence 32, files 64, and next actions 16, each item at most 1,024 bytes with at most 8 references. The accepted response is rendered as Markdown with Goal, Constraints, Decisions, Current state, Files, Evidence, Blockers/questions, and Next actions, followed by a versioned projection block and the canonical SHA-256 recovery-ledger block. Stale projected next actions whose references no longer resolve on a later compaction are surfaced as bounded [stale projected action] blockers/questions anchored to the surviving goal instead of being silently dropped.
  4. During an initial compaction in selected mode, the cloned provider history is left byte-for-byte unchanged so a provider may reuse its existing prompt-cache prefix; the bounded ledger and replacement instruction are appended as the new compaction request. Recovery and overflow replay histories are redacted and byte-bounded; dedicated-model compactions also use the safer transformed history because they cannot reuse the ordinary model's cache. Reasoning is converted to unsigned text; credential-keyed tool input values, outputs, errors, state metadata, and attachments are redacted or bounded; inline and external attachments are replaced by metadata-only omission markers. Bounded opaque provider continuation metadata is preserved because same-model Gemini and similar providers require its signatures to replay tool calls. Malformed historical data URLs cannot abort compaction. Normal turns are left byte-for-byte unchanged.
  5. Every targeted nonempty compaction text is replaced with the exact deterministic summary derived from the canonical ledger. Auto-continuation re-loads durable history, binds to the precise core compaction parent, and permits continuation only when that exact summary and ledger still match. An earlier plugin's disabled setting is preserved.

The plugin stores only bounded attempt metadata in process memory, keyed by the full Session ID, with a 30-minute TTL and a 128-attempt cap. Idle can retain a small recovery-complete tombstone until expiry so later turns in the same process do not repeat recovery. After a restart there is no durable proof that clone-only recovery was seen, so the plugin conservatively reinjects bounded recovery context. Events remain cleanup/audit signals, not correctness gates.

Internal failures in the experimental compaction, history-transform, completion, and event hooks are contained at the plugin boundary. Before a summary is accepted, the plugin leaves the hook output unchanged so OpenCode can use its native behavior. Auto-continuation fails closed when durable summary validation cannot complete. A metadata-only warning identifies the hook, Session ID when available, and error class; it never includes the error message, stack, conversation content, ledger, todo, or tool output. Configuration errors, unsafe model-limit combinations, and oversized newly admitted requests remain deliberate failures because silently ignoring them would weaken the configured safety policy.

Security and privacy

  • The runtime plugin has no telemetry, external network client, or runtime dependency. Its OpenCode client calls read the active session and todo records from the host server.
  • It creates no shadow transcript, sidecar recovery file, or external cache. Durable conversation history remains owned by OpenCode.
  • Recovery content is bounded, credential-shaped values are redacted, and provider-authored legacy summary prose is omitted. A prior plugin-valid canonical ledger may be chained to retain bounded facts across repeated compactions; its provider-authored prose is never carried forward.
  • Runtime logging contains metadata only. Contained internal failures warn with the hook, Session ID when available, and error class; conversation text, error messages and stacks, tool output, ledger bodies, and credentials are not logged.
  • New oversized requests fail closed. Historical truncation affects only the cloned model-visible history used during recovery, not durable records.

The optional live eval adapters described below either spawn an installed OpenCode executable or make an explicit HTTP request to the configured evaluation endpoint. They are development tooling, are never imported by the runtime entry point, and use only the checked-in synthetic corpus.

Limits

This plugin mitigates compaction failures; it cannot make OpenCode's core cutover atomic.

  • Support is limited to the legacy V1 session compactor. V2 support is deferred until public V2 compaction hooks exist.
  • Core still owns history head/tail selection, tail_start_id, boundary acceptance, durable replay, and cutover.
  • A zero-text provider response emits no text-complete hook, so the plugin cannot insert a replacement summary. It suppresses ordinary auto-continuation and can rebuild recovery context on the next user turn.
  • A split response can expose more than one text part. The first targeted part is replaced by one deterministic summary and later text parts are blanked.
  • Overflow replay bypasses the normal auto-continue hook and remains in durable storage. The plugin can sanitize only the cloned provider-visible replay.
  • Nonempty summaries without a valid plugin ledger are represented only by metadata-only legacy_context omission records. A prior plugin-valid canonical ledger can be chained as bounded input, but its surrounding provider-authored prose is neither copied nor trusted.
  • Direct recovery facts are extracted from the newest 10,000 messages. Older plugin-valid ledgers remain discoverable across a bounded scan of up to 65,280 additional messages and can carry their already-bounded facts forward. Ordinary pre-plugin history outside those windows is not reprocessed, because V1 exposes neither metadata-only projection nor a response-byte limit.
  • V1 pagination bounds message count, not serialized response bytes. The plugin validates page size, distinct and non-overlapping message identities, cursor reuse, the total page budget, and Session identity, but a single historical message with enormous parts can still make one host API response large; a strict response-byte cap requires a core/API change.
  • Prompt-cache reuse is provider-dependent rather than guaranteed. selected mode preserves the initial history prefix, but OpenCode's model selection, provider cache TTL, cache-key rules, and provider-side normalization still determine whether tokens are actually cache-read.
  • OpenCode core owns the automatic-compaction trigger threshold; the plugin applies the configured auto/prune policy and safe history budgets but cannot force a lower trigger cadence through the V1 plugin hooks.
  • Pending todos remain in the canonical ledger across task switches because V1 exposes no authoritative task-abandonment signal. The summary bounds and orders them, but the plugin does not guess that an unfinished todo is safe to discard.
  • During recovery, the model is explicitly asked to compare pending todos with the recent request thread and flag possible zombies by ID. That review is advisory only; the plugin never marks, deletes, or rewrites a todo from model judgment alone.
  • Attempt state is process-local. This plugin does not implement clustered compaction ownership or crash-safe provider retries.

Pi support

The package ships two pi extension entry points alongside the OpenCode V1 plugin. They are separate from the OpenCode runtime and are loaded by pi, not by OpenCode.

Install the repository as a Pi package to load both extensions automatically:

pi install git:github.com/shyba/better-compact

The unpinned Git package follows the repository's default branch and can be refreshed with pi update --extensions. The package manifest points Pi at the TypeScript sources, so this works from a Git checkout without a checked-in dist/ directory. For a one-off or source checkout, load either entry point explicitly with pi -e /abs/path/to/src/pi.ts or pi -e /abs/path/to/src/cat.ts. Built dist/pi.js and dist/cat.js entry points are also available after bun run build.

opencode-safe-compaction/pi — recovery-ledger compaction

A pi extension that re-uses the same host-agnostic core (ledger.ts, projection.ts, validation.ts, options.ts) to do ledger-grounded compaction through pi's own events, with no OpenCode involvement.

Install the built module and load it as a pi extension:

pi -e /abs/path/to/dist/pi.js

or add it to ~/.pi/agent/settings.json packages/extensions. The extension registers:

  • session_before_compact — in offline/hybrid, rebuilds the active Pi branch from branchEntries, recovers after a missing compact-all boundary, preserves custom and branch-summary messages, and cuts at a stable user-turn boundary before compiling the bounded recovery ledger. Offline makes zero provider calls; hybrid makes at most one request and falls back deterministically. Any internal failure returns undefined so Pi's native compaction remains the fallback.
  • session_before_tree — the same ledger/projection pipeline for /tree branch summarization when the user opts into a summary.
  • /compaction-model — pick a dedicated compaction model or "follow selected model". The choice is persisted to <cwd>/.pi/safe-compaction.json (mode 0600, atomic rename).

Plugin-side options are layered: <cwd>/.pi/safe-compaction.json wins per key, then $HOME/.pi/safe-compaction.json (fleet-wide default -- set {"vcc_mode": "offline"} there once per host instead of per directory), then built-in defaults (vcc_mode: "off", i.e. the plugin steps aside and pi compacts natively with a model call). OPENCODE_SAFE_COMPACTION_GLOBAL_DIR overrides the global location for containers and tests. A present but unreadable file warns on stderr and falls through to the next layer instead of silently reverting to defaults. Layering can never crash session start: flags that require model calls (semantic_checkpoints, response_mode: markdown) are downgraded with a warning when combined with a no-model vcc_mode, so a fleet-wide offline default always holds.

{
  "model": "selected",
  "response_mode": "json",
  "semantic_checkpoints": false,
  "max_semantic_source_bytes": 262144,
  "tail_turns": 4,
  "max_output_tokens": 16384,
  "max_user_text_bytes": 524288,
  "max_inline_data_bytes": 10485760,
  "max_historical_part_bytes": 131072,
  "max_ledger_bytes": 12288,
  "max_summary_bytes": 49152
}

model: "selected" follows the model pi selects for the session; provider/model pins a dedicated compaction model. preserve_recent_tokens and reserved_tokens are not persisted here — pi manages its own recent-history budgets via compaction.keepRecentTokens / compaction.reserveTokens.

semantic_checkpoints: true enables the experimental /cat semantic checkpoint path. Exact /cat or /cat --fixed source is bounded by max_semantic_source_bytes and supplied to the checkpoint model as reference data. Validated semantic objects and source digests are stored in the Better Compact SQLite state database; the durable compaction summary carries only a compact nucleus and content-addressed snapshot reference. Invalid semantic output or local-state failures are ignored without invalidating ordinary compaction. The feature is disabled by default and currently applies only to Pi, where /cat is available.

Todo recovery depends on a user-installed todo-tracking extension (e.g. the todo example). The adapter scans the branch for the most recent todo-tool result; with no such extension installed the ledger's todos section is empty.

Pi-specific limits:

  • No admission hook for oversized input — pi rejects oversized messages at its own layer.
  • No history-transform hook — sanitization runs inside the ledger build (input) and the rendered summary (output) only. The model-visible history pi sends to its own summarizer is untouched.
  • Runs under both Bun-based and Node-based pi installs; under Node a minimal node:crypto-backed shim provides the Bun.CryptoHasher the shared core uses.

opencode-safe-compaction/cat — attach files to context

A /cat slash command that attaches full file contents to the conversation, bypassing pi's bash-output truncation (pi truncates !cmd output at ~50 KiB and !!cmd excludes it from context entirely).

/cat <ext> [dir] [tokens]          shorthand: all <ext> files under dir (default .); dot is optional
/cat <glob>... [tokens]            explicit globs: src/**/*.ts, 'src/**/*.{ts,tsx}'; simple basenames recurse
/cat <patterns> --exclude-git-ignored  omit files ignored by Git (opt-in)
/cat <patterns> --fixed            pin the patterns: each compaction re-attaches fresh copies
/cat --reset                       clear the pinned set

Examples:

/cat rs src                        all .rs files under src (".rs" also works)
/cat .rs src                       all .rs files under src
/cat '*rs'                         all files whose basename ends in "rs" (find -name style)
/cat rs --exclude-git-ignored       Rust files except paths ignored by Git
/cat .md                           all .md files under cwd
/cat src/main.ts src/util.ts       two literal files
/cat 'src/**/*.ts' 80000           glob capped at 80k tokens
/cat .rs src --fixed               pin: compaction keeps these loaded, re-read fresh each time
/cat --reset                       stop pinning; next compaction stops embedding them

Behavior:

  • Files are read in full (no truncation), wrapped in <file:path> markers, and injected as a single user message via pi.sendUserMessage. When the agent is streaming the message is queued as a follow-up.
  • The injected message is a context handoff: the model treats the files as loaded reference context, replies ok. and waits when no task is in progress, and otherwise begins with yes and continues only when the files are relevant to the existing task.
  • Before injecting, the projected token count (current usage from ctx.getContextUsage() plus chars / 4 per character, the same heuristic pi uses) is compared against the model context window. If it would exceed warnThreshold (default 0.95) of the window, the command refuses and lists the three largest files; run /compact first or re-run with a narrower pattern or a [tokens] budget.
  • A trailing integer argument is a token budget: the read stops once cumulative estimates exceed it.
  • A default skip list excludes node_modules, .git, dist, build, out, target, vendor, __pycache__, .next, .nuxt, .turbo, .cache, .venv, venv, .idea, .vscode, .gradle. Binary files, files over maxFileBytes (4 MiB), and results over maxTotalBytes (32 MiB) or maxFileCount (1000) are skipped with a marker.
  • Settings live in <cwd>/.pi/cat-files.json (mode 0600): warnThreshold, unknownUsageFraction, charsPerToken, maxFileBytes, maxTotalBytes, maxFileCount, skipDirs.

Keeping files loaded across compaction (--fixed)

For review/exploration sessions that read a codebase without editing it, --fixed pins the resolved patterns (scoped to the pi session) so files stay loaded no matter how often the conversation is compacted:

  • The pin is recorded in <cwd>/.pi/cat-files.json as a fixed entry (sessionId, resolved patterns, optional tokenBudget); when several sessions share a cwd, it is a bounded array of entries. Other sessions do not inherit them.
  • On every compaction, the recovery-ledger extension re-reads the pinned files fresh from disk and embeds them, verbatim and deterministic (no model involvement), at the front of the compaction summary. Edited files come back updated; deleted files drop out of the block.
  • The summary therefore stays [pinned files][conversation summary][ledger]. The pinned block is byte-identical across turns, so providers cache it as a fixed prefix: after the first turn, the files cost cache reads, not full input tokens. The conversation after them still compacts normally.
  • While a fixed pin is active, its old attachment messages are recognized by a marker and excluded from the recovery ledger and summarization prompt, so compaction never pays input tokens to re-summarize stale file contents. Ordinary /cat attachments keep the normal compaction behavior.
  • If the pinned block plus the ordinary summary would exceed 80% of the context window, compaction embeds a bounded paths-only block plus a note instead of overflowing; if even that cannot fit, it keeps the ordinary summary. /cat itself still refuses attachments over warnThreshold.
  • /cat --reset clears the current session's pin; the next compaction falls back to the normal bounded summary for that session.

Token counts are approximate (chars / 4, matching pi's own estimator). Lower charsPerToken (e.g. 3.5) or warnThreshold for pessimistic estimates. There is no confirmation dialog — the command either injects or refuses.

Development and packaging

Run commands from this repository, not from an enclosing checkout:

bun install --frozen-lockfile
bun run typecheck
bun test
bun run build
bun -e 'await Promise.all([import("./dist/index.js"), import("./dist/tui.js"), import("./dist/pi.js"), import("./dist/cat.js")])'
npm pack --dry-run

dist/ is deliberately untracked. build:runtime refreshes the checked-in isolated OpenCode entrypoints, which are the one deliberate exception: they exist so a git-clone Pi/OpenCode install works without a build step, and test/tui.test.ts loads them directly. Nothing else generated or exploratory belongs in the tree -- no review notes, no parked investigation documents, no design scratch files, no personal notes. Research protocols live in docs/, operator-facing behaviour lives in this README, and a document that nothing links to is a document that should not be committed. Tests and fixtures use reserved example hosts (example.test, 127.0.0.1) rather than a real one, and never carry internal addresses, hostnames, credentials, or unsanitized transcripts. while prepack builds the npm server and TUI modules plus TypeScript declarations. The package includes the transactional configurator used by the selector and exposes opencode-safe-compaction, opencode-safe-compaction/server, opencode-safe-compaction/tui, opencode-safe-compaction/pi, and opencode-safe-compaction/cat; the root and server exports resolve to the server runtime, the TUI export contains only the selector, and the pi and cat exports are the pi extension entry points. Keep private: true until publication is explicitly approved.

For a portability smoke test, copy or clone the repository outside any OpenCode checkout, repeat install/build/test, and load either the absolute runtime directory or the packed artifact from an isolated OpenCode configuration. Nothing in source, tests, or eval tooling depends on the parent checkout.

Evaluation

The eval/ directory contains 30 sanitized, synthetic transcripts. The runner evaluates three repetitions per case under four provider-backed conditions, plus a separate deterministic provider-free offline evaluator lane:

  • baseline uses a checked-in snapshot of the OpenCode 1.18.4 V1 compaction prompt. It is informational and does not import OpenCode core.
  • plugin reproduces the deterministic fallback contract: whatever the model produced, the accepted text is the ledger-grounded authoritative summary, and continuation is gated on its structure and digest.
  • markdown and json exercise the two projection contracts end to end: the model is prompted with the Markdown or JSON contract, its output is validated (including digest and stable references), and only a valid projection is accepted before any fallback.

Provider transport is a separate adapter shared by all conditions. The default fixture adapter is deterministic and deliberately cycles through valid, malformed, and refusal responses so the harness and fallback path can be tested offline. For the json condition the valid response is a ledger-referenced JSON projection. It is not evidence of model quality or live-provider performance.

# Offline harness/tests: 30 cases x 3 repetitions x 4 provider-backed conditions
bun test eval

# Print the deterministic report
bun eval/run.ts --provider fixture --repetitions 3

# Bound a live qualification to the first case while probing a provider
bun eval/run.ts --provider opencode-cli --max-cases 1 --repetitions 3

The report includes per-condition provider-call counts, structural validity, plugin digest validity, invalid/empty auto-continuations, exact normalized key-fact recall, and matches against each case's explicit unsupported-material-claim list. The structural/digest denominator contains nonempty accepted responses; zero-text responses are reported separately because the runtime text hook cannot replace them. The plugin gate requires 100% structure/digest validity after applicable fallback, zero invalid or empty auto-continuations, at least 95% key-fact recall, zero listed unsupported claims, and zero provider errors. The same requirements apply to the Markdown and JSON projection conditions in projection_gates.

The report also exposes offline metrics and offline_gates for the provider-free deterministic evaluator lane, plus vcc_eval for the provider-free 31-scenario VCC safety matrix. Its gates include an explicit zero-provider-call check, and the CLI exits nonzero if any VCC, offline, plugin, or projection gate fails. These lanes prove candidate compilation and the bounded safety contracts without a provider call; Pi uses the same provider-free candidate when vcc_mode=offline.

The unsupported-claim metric is a closed, reproducible corpus check, not a general semantic hallucination judge. Review accepted summaries separately before treating a new provider/model as qualified.

To use the installed OpenCode executable and its existing provider authentication, run the CLI adapter. SAFE_COMPACTION_EVAL_OPENCODE may be an absolute executable path or a command available on PATH; the model must use OpenCode's provider/model form.

SAFE_COMPACTION_EVAL_OPENCODE="/absolute/path/to/opencode" \
SAFE_COMPACTION_EVAL_MODEL="opencode-go/glm-5.2" \
bun eval/run.ts --provider opencode-cli --repetitions 3

The adapter starts one non-interactive opencode run --agent compaction process per condition, case, and repetition, injects an evaluation-only {"*":"deny"} permission override for that agent, passes the tagged synthetic message sequence through stdin, and uses the selected model from the installed OpenCode configuration. A full 30-case, three-repetition comparison starts 360 isolated CLI runs and may consume provider quota. It requests OpenCode's JSON event format, extracts completed text parts, and records bounded latency plus step-finish input/output/reasoning/cache/cost telemetry when the host supplies it. CLI stderr is consumed but never copied into the eval report or error log, avoiding accidental conversation-content logging.

Use --max-cases N to impose an explicit corpus budget; it selects the first N deterministic cases and reports that reduced denominator. Each CLI process also has a 120-second default wall-clock limit. Override it for a controlled qualification with SAFE_COMPACTION_EVAL_TIMEOUT_MS (positive integer, at most 15 minutes). Use --concurrency N to run at most N isolated provider processes at once; it defaults to 1, is capped at 16, and is deliberately independent from the compaction semantics. Repetitions are capped at 100 to keep the evaluator's work allocation bounded. A timeout is recorded as a provider error and never treated as a successful continuation or fallback.

The report also includes a provider-free offline evaluator lane compiled from the deterministic candidate. This lane is evaluation evidence only: it exercises candidate compilation outside a host. Pi vcc_mode=offline is available and makes no provider call: the Pi hook returns the deterministic candidate directly, usage is optional per compaction.md, and test/pi-adapter.test.ts asserts providerCalls === 0 against a complete stub that throws if reached. OpenCode V1 does not expose provider-free compaction.

To measure behavior after the boundary, add --continuation to run one bounded follow-up provider turn after each accepted summary. The continuation report records compaction/continuation call counts, zero-text responses, provider errors, source-grounded next-action correctness, exact wording recall as a diagnostic, forbidden claims, and both phases' provider telemetry. The correctness gate uses a typed fixture oracle: the pending todo, target, required source atoms, at least one explicit action marker, and forbidden claims must all be satisfied. Action markers are fixture-defined alternatives for a paraphrasable action; source atoms remain exact. It does not use token similarity or an LLM judge. It is an evaluation probe, not a runtime retry or a second compaction call:

SAFE_COMPACTION_EVAL_OPENCODE="/absolute/path/to/opencode" \
SAFE_COMPACTION_EVAL_MODEL="opencode-go/glm-5.2" \
bun eval/run.ts --provider opencode-cli --continuation --max-cases 3 --repetitions 1

The continuation probe has separate hard gates and does not alter the normal compaction report or its fallback decisions. Continuation report schema v3 also counts oracle_violations and requires zero high-risk violations for the qualified (non-baseline) conditions. exact_next_action_recall remains available as a wording-fidelity diagnostic; paraphrases can pass correctness only when the source-grounded oracle is fully satisfied.

Alternatively, run the same corpus directly against an OpenAI-compatible chat-completions endpoint:

SAFE_COMPACTION_EVAL_URL="https://provider.example/v1/chat/completions" \
SAFE_COMPACTION_EVAL_MODEL="provider-model-id" \
SAFE_COMPACTION_EVAL_API_KEY="..." \
bun eval/run.ts --provider openai-compatible --repetitions 3

No live-provider result is checked in or claimed by this repository. Record the provider, model, endpoint implementation, date, and raw aggregate report when running a qualification eval; never commit credentials or unsanitized transcripts.

About

VCC compaction and observability tools

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages