Skip to main content
Version-gated: OpenCode >=1.18.21,<2. Versions outside that range are Unsupported. The package also declares Node >=20. A conditional real-host E2E covers opencode run, with the limits below.
OpenCode uses an owned native plugin and an owned bundled project-context Skill. chat.message prepares and captures. experimental.chat.messages.transform injects context only when the current turn matches the cached message exactly.

Maturity and installation

Setup checks opencode --version, locates the config directory through opencode debug paths, and requires the built plugin plus skills/project-context/SKILL.md. It installs them at <config>/plugins/powercontext-opencode.js and <config>/skills/project-context/, with separate ownership manifests. Setup preflights both target paths and refuses a foreign same-name plugin or Skill. Each installer has its own recovery behavior, but there is no transaction spanning both artifacts. An I/O failure after the plugin install can still leave a partial setup. Doctor checks the executable and version, the owned Skill, and plugin activation. For activation it starts a short-lived opencode serve on loopback, polls GET /session, waits for a nonce written by the plugin, and then stops the process. It creates no session, passes no model, and does not check Server readiness, prompt hooks, injection, permission UI, or Skill execution.

Minimal usage

Start OpenCode after the Server:
Then submit a normal coding prompt. Automatic recall and eligible prompt capture need no command. Use the mounted pc_* tools for exact Memory, Handoff, Experience, Skill, or Candidate-read operations. The plugin does not start or embed the PowerContext Server.

Execution flow

Prepare runs before capture, but each has separate fail-open handling. A prepare failure does not normally suppress capture. A capture failure does not erase valid prepared content. Both calls still share the turn’s HTTP budget and resolved session context. experimental.chat.system.transform adds general usage guidance only. It is not the PreparedContext injection path.

Scope and session cleanup

Scope resolution uses this order:
  1. Use POWERCONTEXT_OPENCODE_SCOPE_ID; hash it to a bounded ID if needed.
  2. Otherwise use the session directory as the derivation input. Explicit tools use context.worktree || context.directory.
  3. Resolve the Git top-level and normalize remote.origin.url to a git: Scope when possible.
  4. If no usable remote exists, hash the resolved project root to a stable local: Scope.
The directory or worktree is an input to derivation, not a Scope that takes precedence over Git identity. session.created and session.updated refresh the directory cache. If no entry exists, the plugin calls client.session.get. session.deleted removes both that entry and the session’s prepared turn. The runtime keeps at most one prepared turn per session, rather than a map containing every (sessionID, messageID) pair. Exact matching prevents an older result from entering the wrong turn. There is still an out-of-order race: an older prepare can finish last and replace the newer cache entry. The newer turn then misses injection instead of receiving stale context. Concurrent-turn behavior is Not validated.

Injection

PreparedContext must satisfy powercontext.prepared-context.v1, including matching content_bytes. The configured prepared-content bound defaults to 8,000 bytes and accepts 512–32,768 bytes. The HTTP client separately caps a response body at 1 MiB. The transform labels recalled content as host-supplied, untrusted historical evidence, marks the part synthetic, and preserves the session and message IDs. A second transform is idempotent. chat.message does not rewrite the incoming part, and the plugin performs no explicit transcript write. Real-host transcript non-persistence is Not validated; the current unit evidence covers only the synthetic transform path.

Capture and flush

Capture excludes synthetic parts. It writes a deterministic prompt Source with origin, event, cwd, session, and message metadata. Automatic capture is on by default. It skips capture when disabled, when a prompt looks like a private key or credential, or when its UTF-8 size exceeds 200,000 bytes. Prepare receives the prompt before the capture filter runs. A secret-looking prompt may therefore be sent to the configured Server as a recall query even though it is not persisted as a Source. Recall transmission and Source capture are separate privacy decisions. Automatic flush is optional and off by default. When enabled, a positive capture position starts a bounded flush_memory cursor loop. Success requires current_cursor >= position; error or call-budget exhaustion fails open and does not prove that a Memory exists. OpenCode has no pending boundary flusher, and its optional flush path has no OpenCode-specific test, so the flush E2E is Not validated. The automatic path captures neither the final assistant response nor a Task Outcome.

Owned plugin and bundled Skill

Setup may refresh an owned install but preserves foreign paths. The Skill conflict path is directly tested; the equivalent foreign-plugin conflict follows the same implementation but lacks its own direct test. The installed project-context directory is a bundled OpenCode Skill. It teaches the host when to use native plugin tools. It is not a managed Skill export. OpenCode is not a current managed export target, and real-host Skill discovery remains Not validated.

Native tools and permission

The plugin mounts 19 curated HTTP tools. They cover Memory reads and mutations, manual prepare/capture, Handoff activate/prepare/finalize/commit/continue, Experience and Skill generate/get, and read-only Candidate Review list/get. It does not mount Candidate approve/reject/revise, Work Contract creation, Handoff acknowledgement, Task Outcome recording, external-Skill registry, or Handoff-report operations. Human Candidate mutation remains outside the model surface through the Dashboard or a direct human-controlled API client. Before one of eight classified mutations, the tool calls host context.ask() with permission powercontext and the operation ID. Those mutations are: remember, capture Source, revise or retire Memory, activate or commit Handoff, generate Experience, and generate Skill. This boundary is Host-gated. pc_remember has a tested ask-and-success path. A rejected ask stops before HTTP by control flow, but real UI allow/deny and a direct denial test are Not validated. Explicit mutation payloads also reject secret-looking nested strings before transport. This check occurs after context.ask(), so the host can ask before returning secret_rejected.

Failure behavior

Automatic prepare, capture, injection, and optional flush fail open. Server unavailability or missing session context must not block normal OpenCode work. Explicit tool errors remain model-visible. A configuration error disables the plugin hooks with a warning instead of crashing the host.

Negative experiments

Run these when changing setup or the plugin. Treat them as a deployment checklist, not as a claim that every item has an automated test:
  • Report 1.18.20 or 2.0.0 from opencode --version; setup must refuse before installation.
  • Put an unowned target at either artifact path; setup must preserve it and refuse the install.
  • Stop the Server, submit a prompt, and confirm OpenCode continues without recalled context.
  • Prepare one message, then transform a different session or message ID; nothing should be injected.
  • Complete two prepares out of order; stale context must not enter the newer turn, though the newer injection may be missed.
  • Transform the matching turn twice; only one synthetic part should exist.
  • Delete the session, reuse its old IDs, and confirm both caches are empty.
  • Submit a secret-looking or over-200,000-byte prompt; recall may run, but capture must not.
  • Deny a classified mutation and confirm no HTTP operation runs.
  • Enable flush, keep the cursor behind the captured position, and confirm the bounded loop does not claim Memory exists.
  • Inspect model tools and confirm Candidate mutation, Work Contract, acknowledgement, and Task Outcome operations are absent.

Unsupported vs Not validated

The conditional real-host E2E launches a real opencode run, uses an invalid model and empty PreparedContext, and observes prepare/capture HTTP requests. It proves neither model dispatch nor transform injection. Doctor’s nonce probe is a separate activation check.

Configuration defaults

The plugin reads connection settings from the environment. These defaults control automatic recall and capture: Do not put Authorization in a URL, prompt, or committed example. Use HTTPS outside loopback. Disable capture before launch when the current work must not become Source evidence:

Finish the shared check on this host

What should happen after the OpenAPI contract changes?
  1. Store the answer in a stable Scope with pc_remember. Pass context.ask first, then confirm the Citation.
  2. Open a new session and ask the same question.
  3. The recall must include a Citation and appear only as an exact-turn synthetic part on the current user turn. It must not enter the OpenCode transcript.
  4. Stop the Server and ask again. Automatic recall may fail. Ordinary coding should continue. After you delete the old session, stale context must not be injected.
  5. Run pc_remember again. The tool should return a failure such as unavailable and must not pretend the write succeeded.

Completion checklist

Treat the integration as complete only when these results are observable:
  • OpenCode is >=1.18.21,<2, and Node satisfies the package’s >=20 declaration.
  • Setup installs both owned paths and documents the lack of cross-artifact rollback.
  • Foreign plugin and Skill targets are preserved rather than overwritten.
  • powercontext doctor opencode is treated as activation evidence, not a Server or model-turn test.
  • Scope uses an explicit override, otherwise Git identity derived from the session/worktree directory, then a local-root hash.
  • chat.message excludes synthetic text, prepares first, and captures with separate fail-open handling.
  • Transform injection requires exact session and message IDs and remains idempotent.
  • Concurrent out-of-order prepare is classified Not validated and can cause a missed, not wrong-turn, injection.
  • session.deleted clears session context and the current prepared turn.
  • Secret and byte-limit filtering applies to capture, while recall transmission is assessed separately.
  • Optional flush is off by default, bounded by cursor position and calls, and never stands in for Memory verification.
  • The 19-tool surface and eight context.ask()-gated mutations match the plugin.
  • Candidate Review stays read-only on the model surface.
  • Automatic assistant capture and Task Outcome stay Unsupported.
  • Bundled Skill and managed Skill export remain distinct.
  • Conditional real-host E2E limits remain visible.
Continue with Pi.