## OpenCues is a standard, not just a product

OpenCues is an open standard for two text surfaces 
 - **Cues**  highlighted text that shows alternatives when you select it
 - **Blanks**  `_`requests for input from an LLM

Anyone can implement the OpenCues standard, in any editor, terminal, browser or app. The standard is defined by a small set of `.md` files, not by any single piece of software.

That means OpenCues belongs to its contributors, not its authors. Adopt it. Extend it. Build on top of it. Use it everywhere.

---

## Why an open standard?

Closed assistants live inside one app. Every other app you use has to do without them, or be wrapped in clumsy shortcuts that lose context the moment you switch tabs.

A standard fixes this once. When the way an editor asks for a cue or fills a blank is shared, every editor inherits the same vocabulary. New cues and blanks you write are immediately usable by everyone. New integrations you write are immediately useful for every existing cue and blank. The work compounds, instead of fragmenting across walled gardens.

---

## The two surfaces

OpenCues defines two text surfaces. Each has its own spec, file format and conformance contract - a runtime can implement one surface and be conformant for that surface (you don't have to implement both). Throughout this page, "**a conformant runtime**" means any implementation that satisfies the runtime contract for at least one surface and passes the matching fixtures in [`spec/conformance/`](https://github.com/opencues/opencues/tree/master/spec/conformance).

| Surface | Direction | Operates on | Trigger |
|---|---|---|---|
| **Cues** | LLM → user | one word (or sentence) | regex `match:` / `keywords:` list |
| **Blanks** | user → system | one `_` slot | `blankShapes` / `blankKeywords` leading the sentence that ends in `_` |

OpenCues can accept definitions from multiple sources (e.g. work and home).  Individual sources declare their definitions in folders: 
- `cues/<name>/CUE.md`
- `blanks/<name>/BLANK.md`

Two uppercase master files at the project root (`CUES.md`, `BLANKS.md`) configure each surface as a whole.

Since `0.5` the standard also specifies one smaller format alongside the two text surfaces: `katas/<name>/KATA.md`, a guided in-editor scenario (covered under "What you can contribute" below).

---

## What you can contribute

Most contributions won't touch the standard at all. You're probably adding a cue, a blank, or an integration for a new host. All cue and blank folders live under one of two roots: 

  - **user-level** `~/.cues/`, available in every project you open

  - **project-level** `<cwd>/.cues/`, scoped to that project and wins on name conflicts with the user-level copy. 

If the environment variable `$OPENCUES_HOME` is set, it takes precedence over both (it is a CI / power-user escape hatch). 

The paths below are relative to whichever root you drop them into, and **the folder name (`<name>`) is the source id throughout**.
At the root folders, optional **master files** (`CUES.md`, `BLANKS.md`) configure each surface as a whole: surface enable flags, project-wide `ignore:` lists, and `disable:` to subtract a named source without modifying the user-level library. User-level and project-level libraries **ADD** to each other - moving into a project *extends* what the user already has; it never silently removes anything. Name collisions fall to the project copy; `disable:` is the only way to subtract. Missing or zero-byte master files are treated as absent. Each source folder MAY also carry colocated `scripts/`, `references/`, or `assets/` subfolders for helper code, on-demand documentation, and non-code resources.

- **Cues.** A cue lives at `<root>/cues/<name>/CUE.md`. `scope:` controls what the cue claims: `scope: words` (default) means the cue highlights individual words and needs a `match:` (regex) or `keywords:` (list); `scope: sentence` means the cue rewrites whole sentences and needs neither trigger (sentence-cues are disabled by default and gated by the runtime's `sentence-cues-mode` setting in `OPENCUES.md`). The body is either a JSON array of tip-groups (each group maps words to their tips and alternatives), a prompt the LLM uses to generate alternatives, or both - words listed in the static JSON skip the LLM call, others fall through to the prompt. You can ship a new "legal", "more-formal", "medical" or "Tagalog" cue without changing a line of code, with edits picked up live (hot-reload covered in "What the standard fixes" below). A word-scope catch-all uses `match: .*` plus a low `priority:` so the more specific domain cues claim their words first; the shipped `spelling` cue is exactly this:

  ```yaml
  ---
  name: spelling
  scope: words
  priority: 10
  match: .*
  ---

  You are a spell-checker. Identify MISSPELLED words in the input
  and output their corrections.
  ```

  A sentence-scope cue like the shipped `more-formal` skips word triggers entirely:

  ```yaml
  ---
  name: more-formal
  scope: sentence
  priority: 85
  description: Sentence-scope cue - rewrites each sentence to be more formal
  ---

  Rewrite each sentence in the buffer to be MORE FORMAL...
  ```

- **Blanks.** A blank is what happens when you type `_`. A new blank lives at `<root>/blanks/<name>/BLANK.md`, plus optionally a sibling script (`<name>-blank.sh`, `.js`, etc.), a runtime class, or a static value list. Volume control, brightness, weather lookups, stock prices, OpenCues' own settings selector - all are blanks. Anyone can add one (subject to the trust / sandbox declarations the blank carries). The shipped `example` blank (a "type `time _` and get the local clock back" hello-world) is the minimum that fires:

  ```yaml
  ---
  name: example
  blankKeywords: time, clock
  blankScript: ./time-blank.sh
  ---
  ```

  Three parts: `name` (matches the folder, becomes the id), a trigger (`blankKeywords`, or the `blankShapes` anchored-phrase grammar keywords desugar into — a blank claims a `_` when its trigger leads the sentence that `_` ends), and at most one binding (`blankScript` for a sibling script, `impl` for a runtime class, or `stepValues` for a static cycle list — a blank that declares no binding resolves `impl` implicitly from its name).

- **Katas.** A kata is a guided, in-editor scenario: an ordered lesson the user works through in their real editor while the runtime coaches the next micro-action. One lives at `<root>/katas/<name>/KATA.md`: optional frontmatter (`name`, `id`, `title`, and a `next:` curriculum link), then one `## ` heading per step; a file with zero steps is treated as absent. The **file format is part of the standard** ([`spec/kata-spec.md`](https://github.com/opencues/opencues/blob/master/spec/kata-spec.md), with a JSON Schema and conformance fixtures); the coaching runtime deliberately is not - a second runtime may coach differently, or not at all, and still be conformant for the format. Three security floors are normative for any runtime that does consume katas: a kata starts only on explicit user consent (never self-starts), exiting is deterministic and model-independent, and coaching is display-only (no buffer writes, no execution, no side-effects).

- **Integrations.** An integration is what lets an editor, browser or other host speak the OpenCues runtime contract. Each integration has two layers: the host-side bootstrap (in `integrations/<host>/`) and the runtime adapter (in `packages/opencues-runtime/adapters/<host>/`). Reference implementations exist for Claude Code, OpenCode, Gemini CLI, Chrome and a standalone shell app (`oc-shell`). In the lightest shipped adapter the host-contract implementation is about 160 lines of TypeScript plus under 300 of boot wiring; the host-side bootstrap is typically a few hundred more.

Documentation, test cases and typo fixes are equally welcome. Issues labelled `good first issue` on GitHub are a good place to start.

---

## Contributing to the standard itself

The standard lives at [`spec/`](https://github.com/opencues/opencues/tree/master/spec) in the OpenCues repo. Contributing to the standard means changing one of:

- `spec/cue-spec.md`, `spec/blank-spec.md`, `spec/identity-context-spec.md`, `spec/kata-spec.md` - the per-surface formats and runtime contracts.
- `spec/core.md` - shared rules across the surfaces: search path, master files, host compatibility, hot-reload, routing.
- `spec/schemas/*.schema.json` - the JSON Schemas any runtime can validate against.
- `spec/conformance/` - executable fixtures any conformant runtime can exercise.
- The master `CUES.md` and `BLANKS.md` shape - adding new project-level settings, `ignore:` semantics, `disable:` rules.

The standard is currently `0.10` (draft). Field names, conformance rules and behaviour may change between minor versions - **production interop guarantees only begin at `1.0`**, and the standard isn't intended to be depended on for security-sensitive workloads until then. Files declare their spec version in frontmatter (`spec: opencues/0.10`); runtimes MUST refuse files declaring a `spec:` newer than they support, and treat absent `spec:` as `0.1-alpha` (that omit-default is permanent, so unannotated files keep working). Proposals follow a lightweight process modelled on MCP SEPs - draft in [`spec/proposals/`](https://github.com/opencues/opencues/tree/master/spec/proposals), discuss, then PR once there's consensus.

A runtime-specific feature can be **promoted** to the standard once a second independent runtime adopts it. That keeps the spec lean: only features that have proven themselves twice make it in.

[`spec/conformance/`](https://github.com/opencues/opencues/tree/master/spec/conformance) is the executable contract a second runtime would target - valid + invalid examples for every surface, wire-format cases for the LLM `INDEX:alt1,alt2` parser, and routing scenarios. As of `0.10` no second implementation exists; the suite is what would prove one conformant.

---

## What the standard fixes

Contributions should extend OpenCues. They shouldn't redefine it. A few things are deliberately fixed:

- **The `.md` configuration format.** Master files (`CUES.md`, `BLANKS.md`) and per-source files (`CUE.md`, `BLANK.md`) have a stable shape - YAML for config, plain markdown for content. Unknown frontmatter fields are preserved (warn at validate time, never error at load time) so forward compatibility doesn't break extension.
- **Per-word routing for cues.** Each highlighted word is dispatched to exactly one cue - never combined into a giant prompt. Every cue needs a `match:` (regex) or `keywords:` (list); sources without either are rejected. Among those that match, the highest `priority:` wins; ties break by declaration order. Catch-all behaviour is expressed as `match: .*` at a low priority (the shipped `spelling` cue). Words no cue claims simply produce no cue and aren't navigable. One bad cue can never poison every word.
- **Per-item dispatch for blanks.** Each `_` routes to one blank by its declared trigger — a `blankShapes` match against the sentence the `_` ends (`blankKeywords` is shorthand that desugars to shapes).
- **Deterministic triggers, not LLM-routed ones.** A `CUE.md` source fires when its `match:` regex literally hits a word the user typed - not when an LLM reads its `description:` and decides. This is the key distinction from [Anthropic's `SKILL.md`](https://github.com/anthropics/skills) (where the LLM uses the file's `description:` to choose whether to invoke). The same deterministic principle applies to blank keyword binding.
- **Three search-path locations.** `$OPENCUES_HOME` is consulted first when set, then `<cwd>/.cues/` (project), then `~/.cues/` (user); earlier entries shadow later ones on name collisions. Missing directories are silently skipped.
- **Hot-reload is sacred.** Config file changes (`.md`) MUST NOT require a restart. The reference runtime re-checks the search path on user input (a ~2s debounce) plus a background poll every few seconds; a conformant runtime can use any mechanism (fixed-interval polling, file watchers) as long as edits land within a couple of seconds.
- **Host compatibility is declarative.** Sources declare which hosts can load them via `on-host:` / `not-on-host:`. A shell-script blank is auto-tagged native-only and skipped by browser hosts; no runtime needs special-case logic.
- **A small standard CLI.** A conformant runtime SHOULD ship `opencues validate` (run the linting rules against a search-path directory), `opencues list` (enumerate discovered sources with their triggers and priorities), and `opencues seed-configs` (copy shipped defaults into the user search path on first install). Names and semantics are normative; flags are runtime-specific.

These are the foundations. If you find yourself wanting to change one, what you're really proposing is a change to the standard - the right place for that is a proposal in `spec/proposals/`.

Everything else - cues, blanks, integrations, tests, docs, performance, model choice, prompt wording - is up for grabs.

---

## What the standard is *not*

- **Not a UI spec.** How alternatives render (ANSI dim, CSS highlight, popup), what keys cycle them (Up/Down, Tab, voice), how the cursor moves - all out of scope. Each integration decides.
- **Not a prompt-design spec.** The LLM-mode wire format - one line per matched word in the shape `INDEX:alt1,alt2,alt3`, where `INDEX` is the 0-based position of the word in the runtime's batch - is normative so any LLM provider can be plugged in; the prompts themselves are each runtime's own.
- **Not the runtime's own settings.** `OPENCUES.md` - voice mode, debug mode, fluid-blank toggles - is OpenCues-runtime-specific. Another implementation that parks its runtime config in a different file is still conformant.

---

## Security model

OpenCues' threat model rests on one structural property: **no out-of-band channels**. A conformant runtime MUST NOT route LLM-generated text - from any of OpenCues' LLM-driven surfaces - into tool execution, network fetches, clipboard writes, file writes, or any other side-effect layer. Worst-case prompt injection in any of those surfaces lands as user-visible text the user reviews before submitting - by design it can never trigger an action on its own. Any runtime that wires an LLM-output → side-effect channel invalidates the threat model and MUST re-review its security posture before shipping.

Two trust and validation rules ride on top of that:

- **`blankScript:` blanks are user-trusted only.** Local-or-shipped only; auto-install requires per-pack user confirmation with the script visible. `stepValues` and `impl` blanks (no executable code in the file itself) are not subject to this restriction and are safe to grow registry distribution as the standard evolves.
- **JS-impl blanks (`impl: ./blank.js`) run in a capability-restricted sandbox.** No `fs` / `process` / `require` / `__dirname` / `Buffer` exposure; `import` statements are stripped before evaluation; `network:` is enforced as a hostname-exact allow-list; `storage:` is namespaced so blank A can't read blank B's data. The full six MUSTs are in [`spec/blank-spec.md`](https://github.com/opencues/opencues/blob/master/spec/blank-spec.md) § Profile 3a.
What the standard does **not** require: OS-level sandboxing (offered as opt-in via `sandbox: strict` / `sandbox-net` / `sandbox-fs` in `BLANK.md`), network-egress restrictions on the runtime's own LLM calls, rules for handling personally identifiable information (those live in optional runtime features), or cryptographic provenance for shipped packs - all deferred until a registry mechanism warrants them.

---

## How to contribute

The full process, the project layout, the conventions and the test commands all live in [`CONTRIBUTING.md`](https://github.com/opencues/opencues/blob/master/CONTRIBUTING.md) at the OpenCues repo root. Read it before opening a pull request.

Two things to know before you start:

- **Keep PRs focused.** One feature or fix per PR. Anything else you notice along the way goes into its own PR.
- **Test what you ship.** Run your cue, blank or integration end-to-end before opening the PR, and describe what you tested. For `opencues-core` changes, run `pnpm --filter @opencues/core test`. Spec changes should land an entry in `spec/conformance/` too.

Hot-reload is sacred. Config file changes (`.md`) must not require a restart. Patch file changes (`.ts`) must not require re-running `setup.sh` more than once. If your change breaks either rule, it isn't ready.
