Skip to content

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 11 Sep 21:22
· 21 commits to main since this release

Fixed

  • Benchmark harness no longer charges the treatment arm for a cold plugin
    install.
    Loading a plugin makes opencode npm-install @opencode-ai/plugin
    into XDG_CONFIG_HOME/opencode, and bench/run.mjs gives every run a fresh
    HOME and config dir, so that install ran from scratch on every run: 66.7 s
    measured via opencode debug config. Both arms install, but only on treatment
    does it block startup, where it was published as plugin wall-time overhead.
    Seeding the npm cache alone still costs 9.2 s, so the harness now resolves the
    tree once per invocation and hardlinks node_modules + package.json +
    package-lock.json into each run's config dir for both arms before
    opencode starts. Verified: the plugin-load window drops to 0.213 s,
    inside the baseline range. The re-run is complete
    (power-string-sweep-v2.jsonl, 40/40, $0.2923): window 0.158 s vs 0.278 s
    per arm, wall difference +2.6 s, p = 0.83 — was +27.4 s, p = 0.018. No
    wall-time separation remains.

  • session.deleted events missing info.id, and user messages missing
    sessionID, no longer throw into the event handler's catch-all (which also
    skipped handoff arming and the task-boundary check for that event).

Changed

  • Benchmark wall-time claim corrected: harness artifact, not plugin cost.
    docs/benchmark.md reported the power study's only significant separation as
    wall time, +27.4 s per run in the treatment arm (p = 0.018). A paired per-run
    decomposition of all 40 runs (from each record's preserved run_dir — the
    session DB for message/tool timings, the opencode log for phase boundaries)
    shows the gap is entirely startup, and inside startup it is one window:
    plugin loading, baseline mean 0.25 s vs treatment 23.22 s. Cause is the
    harness's per-run HOME isolation — only the treatment arm loads a plugin, so
    only it blocks on the plugin dependency install during startup (npm, not bun;
    see Fixed above). Plugin runtime is not involved: importing dist/plugin.js
    costs 0.24 s under bun, the tool hook 0.004 ms, the audit spawn ~45 ms.
    Adjusted for that window the difference is +4.45 s, paired positive in 13 of
    20 rather than 17 of 20 — superseded by the fixed-harness re-run at
    +2.6 s, p = 0.83.

  • README first screen. The install command moved from line 69 to line 19.
    Badges cut from seven to three (version, test, license), the hand-written
    table of contents dropped in favour of GitHub's own heading outline, the
    duplicated handoff statistic removed (the same numbers are in
    ## Observed behavior), the demo caption folded into the intro paragraph, and
    the before/after diagram relocated to docs/how-it-works.md, next to the
    architecture diagram it belongs with. No content was lost, only moved or
    de-duplicated.

  • Unified policy state machine. Severity is now computed once, in
    src/budget/policy.ts, as a monotone state machine
    (HEALTHY < ATTENTION < PRESSURE < HANDOFF_RECOMMENDED < BLOCKED)
    over three axes -- call count, configured budgets, and the context window.
    The mode is applied after the max, not inside an axis. Previously eight
    independent decision sites in the tool hook each owned a threshold and a
    latch, which allowed three separate <system-reminder> blocks and two toasts
    on a single tool call. The plugin now emits at most one block per tool
    call
    , with a state header naming the driving axis and a fixed section
    order: boundary, announce, audit, budget, handoff. At most one toast, titled
    by state.

  • Reminders no longer suppress each other. The early returns after the
    task-boundary and announce reminders are gone. Deferral was a side effect of
    those returns rather than a design goal: it spread co-occurring thresholds
    across separate tool calls and skipped the budget check entirely on those
    calls. Everything due on a call now lands together in the one block.

  • token_norm_status answers from the enforcement machine. The recommend()
    ladder in src/status.ts, which re-derived severity from
    (pressured, exceeded, mode), is replaced by a direct state map. The snapshot
    gains a state field alongside recommendation. Two visible contract
    changes: a session at or past TOKEN_NORM_ANNOUNCE_AT with no budget
    pressure now reports warn where it reported continue; and in handoff mode,
    pressure without a pause now reports warn rather than handoff, matching
    what the plugin would actually do -- a handoff has always required a natural
    pause as well.

  • TOKEN_NORM_SETTLE_MS and TOKEN_NORM_SWITCH_WAIT_MS are read in config.ts
    like every other setting, so they are validated and diagnosed too.

  • The opencode client and event payloads are no longer typed as any. src/host.ts
    declares narrow interfaces covering only the host fields this plugin actually
    reads, so a renamed or removed host method now fails at build time instead of
    disappearing into a runtime catch.

Added

  • Release provenance. Every build writes dist/provenance.json recording the
    gitSha it was built from, whether the working tree was dirty, and the sha256
    of both files that leave npm's integrity story once installed --
    dist/plugin.js and scripts/usage-audit.py. The installer copies those two
    into ~/.config/opencode, where nothing else attests to them and the audit
    script is then executed. The digests ship in the tarball, are rendered as a
    table in the GitHub release notes, and provenance.json is attached as a
    release asset, so an installed copy can be checked with shasum -a 256.
    test/packaging.test.ts rehashes the packed bytes, so a stale or wrong
    digest fails the build rather than shipping.
  • Config diagnostics: a malformed or misspelled TOKEN_NORM_* setting is
    reported once at load (log line plus a toast) instead of silently falling back
    to its default. Covers non-numeric or non-positive thresholds and budgets, an
    out-of-range TOKEN_NORM_CONTEXT_WARN, a kill switch set to anything but 0
    or 1, an empty TOKEN_NORM_CHEAP_TOOLS, and unrecognized TOKEN_NORM_*
    keys. Values still fall back rather than failing the session.
  • Handoff integration-test matrix: child-session events, a late session.created,
    the settle floor, appendPrompt/submitPrompt failures, and concurrent
    handoffs.
  • Benchmark statistics: per-cell summaries now carry n, median, sd, and a
    seeded bootstrap 95% CI for the mean, plus a per-task treatment-vs-baseline
    contrast using a two-sided permutation test. Cells below five runs per arm are
    flagged underpowered so a small batch cannot be read as a result.
  • bench/run.mjs --resummarize <jsonl> recomputes a summary from existing run
    records with no paid runs and without writing to the input file.
  • Power study (bench/results/power-string-sweep.jsonl, 40 paid runs, n=20 per
    arm) refuting the earlier N=2 10-string-sweep hypothesis: tool calls differ
    by -3.05 (p = 0.65) and both arms are bimodal. Wall time is the one metric that
    separates, +27.4 s in the treatment arm (p = 0.018), cause unattributed.

Provenance

Built from 60d4ef36b0b31b7d8b443ac4afac87e109f0886c.

artifact sha256
dist/plugin.js 455e2c600b64ccd3612cdaf0c4c6c16c8f9c459360e0567e395f1a240f8a7343
scripts/usage-audit.py ad5d1c4e18eac2c73dfc7267ff6c8a1f553783d7f62cd9d32d54f25f2ffa0c73

Verify an installed copy:

shasum -a 256 ~/.config/opencode/plugins/opencode-token-norm.js
shasum -a 256 ~/.config/opencode/scripts/usage-audit.py