{
  "version": 1,
  "project": {
    "name": "speech",
    "title": "Voice",
    "description": "Queue text, choose voices, and control playback."
  },
  "generatedAt": "2026-10-28T16:13:29.025Z",
  "artifacts": {
    "llms": "/voice/docs/llms.txt",
    "agents": "/voice/docs/AGENTS.md",
    "fullManifest": "/voice/docs/agent-docs.json",
    "allMarkdown": "/voice/docs/llms-full.txt",
    "markdownBase": "/voice/docs/markdown/"
  },
  "recommendedReadOrder": [
    "speech:overview",
    "speech:quickstart",
    "speech:agents",
    "speech:cli",
    "speech:api"
  ],
  "docs": [
    {
      "id": "speech:overview",
      "slug": "overview",
      "product": "speech",
      "kind": "doc",
      "catalogKind": "guide",
      "current": true,
      "title": "Overview",
      "description": "Voice queues and plays spoken updates from agents and scripts. Its macOS app, Voice.app, is a separate process from Lattices.",
      "order": null,
      "sourcePath": "products/voice/docs/overview.md",
      "url": "/voice/docs/overview",
      "rawUrl": "/voice/docs/markdown/overview.md",
      "markdownUrl": "/voice/docs/overview.md",
      "headings": [
        {
          "depth": 2,
          "text": "What Voice owns",
          "anchor": "what-voice-owns"
        },
        {
          "depth": 2,
          "text": "Independent lifecycle",
          "anchor": "independent-lifecycle"
        },
        {
          "depth": 2,
          "text": "Voice output and Lattices voice input are separate",
          "anchor": "voice-output-and-lattices-voice-input-are-separate"
        },
        {
          "depth": 3,
          "text": "Private connection",
          "anchor": "private-connection"
        },
        {
          "depth": 2,
          "text": "Providers",
          "anchor": "providers"
        },
        {
          "depth": 2,
          "text": "Stable identifiers",
          "anchor": "stable-identifiers"
        },
        {
          "depth": 2,
          "text": "Next steps",
          "anchor": "next-steps"
        }
      ],
      "tokensEstimate": 840,
      "frontmatter": {
        "title": "Overview",
        "description": "Voice queues and plays spoken updates from agents and scripts. Its macOS app, Voice.app, is a separate process from Lattices."
      },
      "markdown": "---\ntitle: Overview\ndescription: Voice queues and plays spoken updates from agents and scripts. Its macOS app, Voice.app, is a separate process from Lattices.\n---\n\nVoice is the Lattices family product for spoken output. Agents and scripts\nsend text. Voice queues the text, renders audio, and plays it with an optional\nreadalong HUD. Playback continues after the requesting process disconnects.\n\nVoice is both the product name and the macOS app name. It keeps some\nidentifiers from its earlier Speech name. See [Stable identifiers](#stable-identifiers).\n\n## What Voice owns\n\nVoice.app is a menu bar app (`LSUIElement`) with its own process, settings, and\nlocal RPC server. Voice.app owns:\n\n- the speech queue and job state;\n- synthesis through the configured provider;\n- audio playback and the playback HUD;\n- voice preferences and provider credentials;\n- the authenticated RPC server on `~/.lattices/run/speech.sock`.\n\nLattices is a client of Voice.app. Lattices does not own the queue or the\nVoice.app process.\n\n## Independent lifecycle\n\nVoice.app starts, runs, and quits independently of Lattices.\n\n- Quitting Lattices does not quit Voice.app or clear its queue.\n- Quitting Voice.app does not quit Lattices.\n- If Voice.app is not running, Lattices speech output requests fail with an\n  error. Lattices does not use a fallback voice.\n\n## Voice output and Lattices voice input are separate\n\nThe family `voice` domain covers two directions. Different processes serve\neach direction.\n\n| Direction | Commands | Process | Address |\n| --- | --- | --- | --- |\n| Output (speaking) | `voice.say`, `voice.stop`, `voice.pause`, `voice.list`, and related verbs | Voice.app | `~/.lattices/run/speech.sock` |\n| Input (listening) | `voice.listen`, `voice.stopListening`, `voice.simulate` | Lattices menu bar app | `~/.lattices/run/voice.sock` |\n| Agent API gateway | All `voice.*` verbs | Lattices core daemon | `~/.lattices/run/lattices.sock` |\n\n`lats voice say` and the other output commands call the Lattices core daemon\nover `lattices.sock`. The daemon renames each output verb to its `speech.*` method and\nforwards the request to Voice.app over `speech.sock`. Dictation and spoken commands\nstay in Lattices. Voice.app has no microphone runtime.\n\nFor the listening side, see [Lattices voice](/lattices/docs/voice).\n\n### Private connection\n\nVoice 0.3.0 and Lattices 0.15.1 use private Unix sockets in\n`~/.lattices/run`.\n\n| Direction | Endpoint |\n| --- | --- |\n| Output | `speech.sock`, owned by Voice.app |\n| Input | `voice.sock`, the Vox runtime hosted in the Lattices menu bar app |\n| `voice.*` routing | `lattices.sock`, the Lattices core daemon |\n\nThe routing and the independent lifecycles do not change. Voice.app opens\n`127.0.0.1:9397` only when it starts with `LATTICES_SPEECH_TCP=1`. Remote\nmachines reach Voice only through the opt-in Lattices tailnet gateway, with an\napproved grant such as `speech:speech.enqueue`. See [API](/voice/docs/api#private-connection).\n\n## Providers\n\n| Provider id | Where it runs | Requirement |\n| --- | --- | --- |\n| `system` | macOS system voices | None. This is the default provider. |\n| `phonon` | On this Mac, on the Apple Neural Engine | Apple silicon. Voice.app downloads the model (137 MB) on the first Phonon request. |\n| `openai` | OpenAI cloud | Credentials in Voice settings |\n| `elevenlabs` | ElevenLabs cloud | Credentials in Voice settings |\n\nVoice.app renders Phonon in-process through Gradium's Phonon 31m, an Apache 2.0\nmodel with 38 English voices. Phonon does not use Vox or a Python runtime. Its\nspeaking rate is fixed, so `rate` does not apply. Voice.app keeps provider credentials in\nthe macOS Keychain.\n\n## Stable identifiers\n\nVoice.app keeps identifiers from its earlier Speech name. Existing installs and\nclients depend on these values, so they do not change:\n\n| Identifier | Value |\n| --- | --- |\n| Bundle ID | `dev.lattices.Speech` |\n| RPC methods | `speech.*` |\n| RPC socket | `~/.lattices/run/speech.sock` |\n| Capability token file | `~/Library/Application Support/Speech/RPC/capability` |\n| Keychain service | `dev.lattices.app.voice` |\n\n## Next steps\n\n- [Quickstart](/voice/docs/quickstart): install Voice.app and speak a first line.\n- [CLI](/voice/docs/cli): the commands that reach Voice and their playback semantics.\n- [API](/voice/docs/api): the direct `speech.*` RPC reference.\n",
      "content": "Voice is the Lattices family product for spoken output. Agents and scripts\nsend text. Voice queues the text, renders audio, and plays it with an optional\nreadalong HUD. Playback continues after the requesting process disconnects.\n\nVoice is both the product name and the macOS app name. It keeps some\nidentifiers from its earlier Speech name. See [Stable identifiers](#stable-identifiers).\n\n## What Voice owns\n\nVoice.app is a menu bar app (`LSUIElement`) with its own process, settings, and\nlocal RPC server. Voice.app owns:\n\n- the speech queue and job state;\n- synthesis through the configured provider;\n- audio playback and the playback HUD;\n- voice preferences and provider credentials;\n- the authenticated RPC server on `~/.lattices/run/speech.sock`.\n\nLattices is a client of Voice.app. Lattices does not own the queue or the\nVoice.app process.\n\n## Independent lifecycle\n\nVoice.app starts, runs, and quits independently of Lattices.\n\n- Quitting Lattices does not quit Voice.app or clear its queue.\n- Quitting Voice.app does not quit Lattices.\n- If Voice.app is not running, Lattices speech output requests fail with an\n  error. Lattices does not use a fallback voice.\n\n## Voice output and Lattices voice input are separate\n\nThe family `voice` domain covers two directions. Different processes serve\neach direction.\n\n| Direction | Commands | Process | Address |\n| --- | --- | --- | --- |\n| Output (speaking) | `voice.say`, `voice.stop`, `voice.pause`, `voice.list`, and related verbs | Voice.app | `~/.lattices/run/speech.sock` |\n| Input (listening) | `voice.listen`, `voice.stopListening`, `voice.simulate` | Lattices menu bar app | `~/.lattices/run/voice.sock` |\n| Agent API gateway | All `voice.*` verbs | Lattices core daemon | `~/.lattices/run/lattices.sock` |\n\n`lats voice say` and the other output commands call the Lattices core daemon\nover `lattices.sock`. The daemon renames each output verb to its `speech.*` method and\nforwards the request to Voice.app over `speech.sock`. Dictation and spoken commands\nstay in Lattices. Voice.app has no microphone runtime.\n\nFor the listening side, see [Lattices voice](/lattices/docs/voice).\n\n### Private connection\n\nVoice 0.3.0 and Lattices 0.15.1 use private Unix sockets in\n`~/.lattices/run`.\n\n| Direction | Endpoint |\n| --- | --- |\n| Output | `speech.sock`, owned by Voice.app |\n| Input | `voice.sock`, the Vox runtime hosted in the Lattices menu bar app |\n| `voice.*` routing | `lattices.sock`, the Lattices core daemon |\n\nThe routing and the independent lifecycles do not change. Voice.app opens\n`127.0.0.1:9397` only when it starts with `LATTICES_SPEECH_TCP=1`. Remote\nmachines reach Voice only through the opt-in Lattices tailnet gateway, with an\napproved grant such as `speech:speech.enqueue`. See [API](/voice/docs/api#private-connection).\n\n## Providers\n\n| Provider id | Where it runs | Requirement |\n| --- | --- | --- |\n| `system` | macOS system voices | None. This is the default provider. |\n| `phonon` | On this Mac, on the Apple Neural Engine | Apple silicon. Voice.app downloads the model (137 MB) on the first Phonon request. |\n| `openai` | OpenAI cloud | Credentials in Voice settings |\n| `elevenlabs` | ElevenLabs cloud | Credentials in Voice settings |\n\nVoice.app renders Phonon in-process through Gradium's Phonon 31m, an Apache 2.0\nmodel with 38 English voices. Phonon does not use Vox or a Python runtime. Its\nspeaking rate is fixed, so `rate` does not apply. Voice.app keeps provider credentials in\nthe macOS Keychain.\n\n## Stable identifiers\n\nVoice.app keeps identifiers from its earlier Speech name. Existing installs and\nclients depend on these values, so they do not change:\n\n| Identifier | Value |\n| --- | --- |\n| Bundle ID | `dev.lattices.Speech` |\n| RPC methods | `speech.*` |\n| RPC socket | `~/.lattices/run/speech.sock` |\n| Capability token file | `~/Library/Application Support/Speech/RPC/capability` |\n| Keychain service | `dev.lattices.app.voice` |\n\n## Next steps\n\n- [Quickstart](/voice/docs/quickstart): install Voice.app and speak a first line.\n- [CLI](/voice/docs/cli): the commands that reach Voice and their playback semantics.\n- [API](/voice/docs/api): the direct `speech.*` RPC reference."
    },
    {
      "id": "speech:quickstart",
      "slug": "quickstart",
      "product": "speech",
      "kind": "doc",
      "catalogKind": "guide",
      "current": true,
      "title": "Quickstart",
      "description": "Install Voice.app, confirm that Lattices can reach it, and speak a first line.",
      "order": null,
      "sourcePath": "products/voice/docs/quickstart.md",
      "url": "/voice/docs/quickstart",
      "rawUrl": "/voice/docs/markdown/quickstart.md",
      "markdownUrl": "/voice/docs/quickstart.md",
      "headings": [
        {
          "depth": 2,
          "text": "Prerequisites",
          "anchor": "prerequisites"
        },
        {
          "depth": 2,
          "text": "Install Voice.app",
          "anchor": "install-voice-app"
        },
        {
          "depth": 2,
          "text": "Check the connection",
          "anchor": "check-the-connection"
        },
        {
          "depth": 2,
          "text": "Speak",
          "anchor": "speak"
        },
        {
          "depth": 2,
          "text": "Choose a default voice",
          "anchor": "choose-a-default-voice"
        },
        {
          "depth": 2,
          "text": "Build from source",
          "anchor": "build-from-source"
        }
      ],
      "tokensEstimate": 495,
      "frontmatter": {
        "title": "Quickstart",
        "description": "Install Voice.app, confirm that Lattices can reach it, and speak a first line."
      },
      "markdown": "---\ntitle: Quickstart\ndescription: Install Voice.app, confirm that Lattices can reach it, and speak a first line.\n---\n\n## Prerequisites\n\n- macOS 26 or later.\n- Apple silicon, if you use the `phonon` provider.\n- The Lattices CLI and menu bar app. See the\n  [Lattices quickstart](/lattices/docs/quickstart).\n\n## Install Voice.app\n\n1. Start the Lattices menu bar app:\n\n   ```sh\n   lats app\n   ```\n\n2. In the Lattices menu, choose **Apps › Voice › Install**.\n\n   Lattices downloads `Speech.dmg` from the latest `speech-v*` GitHub\n   release. The installer checks the bundle ID, the Developer ID signature,\n   and Gatekeeper assessment before it installs the app. The installer runs\n   on Bun. If Bun is missing, the menu shows **Needs Bun** and an\n   **Install Bun…** item.\n\n3. If Voice.app is installed but not running, choose **Apps › Voice › Open**.\n\nResult: the Voice icon appears in the menu bar.\n\n## Check the connection\n\n```sh\nlats voice status\n```\n\nThe `Voice helper` line shows `running` when Lattices can reach Voice.app.\nOtherwise it shows one of these error codes:\n\n| Error code | Meaning | Recovery |\n| --- | --- | --- |\n| `helper_not_installed` | Voice.app is not on this Mac. | Choose **Apps › Voice › Install** in the Lattices menu. |\n| `helper_unreachable` | Voice.app is installed but not running. | Choose **Apps › Voice › Open**. |\n| `helper_unauthorized` | Voice.app is running, but the request did not carry the capability token. | Retry through `lats`, which sends the token. A custom client must send the token. See [API](/voice/docs/api). |\n\n## Speak\n\n```sh\nlats voice say \"Build finished\"\n```\n\nThe command prints the job state and job id, for example `queued <id>`. The\ncommand returns when Voice.app accepts the job, not when playback ends.\n\nTo use the on-device Phonon provider:\n\n```sh\nlats voice say \"The tests passed\" --provider phonon\n```\n\nThe first Phonon request downloads the model (137 MB) and prepares it for this\nMac, which takes about 20 seconds. Later requests render locally.\n\n## Choose a default voice\n\n```sh\nlats voice list\nlats voice select Maeve --provider phonon\n```\n\n`select` sets the voice that a provider uses when a request names no voice.\nVoice settings show the same choice.\n\n## Build from source\n\nTo build Voice.app from the monorepo, follow **Build from source** in\n[`products/voice/README.md`](https://github.com/lattices-dev/lattices/tree/main/products/voice#readme).\nPackaging writes `products/voice/.artifacts/Voice.app` and does not install or\nlaunch it.\n",
      "content": "## Prerequisites\n\n- macOS 26 or later.\n- Apple silicon, if you use the `phonon` provider.\n- The Lattices CLI and menu bar app. See the\n  [Lattices quickstart](/lattices/docs/quickstart).\n\n## Install Voice.app\n\n1. Start the Lattices menu bar app:\n\n   ```sh\n   lats app\n   ```\n\n2. In the Lattices menu, choose **Apps › Voice › Install**.\n\n   Lattices downloads `Speech.dmg` from the latest `speech-v*` GitHub\n   release. The installer checks the bundle ID, the Developer ID signature,\n   and Gatekeeper assessment before it installs the app. The installer runs\n   on Bun. If Bun is missing, the menu shows **Needs Bun** and an\n   **Install Bun…** item.\n\n3. If Voice.app is installed but not running, choose **Apps › Voice › Open**.\n\nResult: the Voice icon appears in the menu bar.\n\n## Check the connection\n\n```sh\nlats voice status\n```\n\nThe `Voice helper` line shows `running` when Lattices can reach Voice.app.\nOtherwise it shows one of these error codes:\n\n| Error code | Meaning | Recovery |\n| --- | --- | --- |\n| `helper_not_installed` | Voice.app is not on this Mac. | Choose **Apps › Voice › Install** in the Lattices menu. |\n| `helper_unreachable` | Voice.app is installed but not running. | Choose **Apps › Voice › Open**. |\n| `helper_unauthorized` | Voice.app is running, but the request did not carry the capability token. | Retry through `lats`, which sends the token. A custom client must send the token. See [API](/voice/docs/api). |\n\n## Speak\n\n```sh\nlats voice say \"Build finished\"\n```\n\nThe command prints the job state and job id, for example `queued <id>`. The\ncommand returns when Voice.app accepts the job, not when playback ends.\n\nTo use the on-device Phonon provider:\n\n```sh\nlats voice say \"The tests passed\" --provider phonon\n```\n\nThe first Phonon request downloads the model (137 MB) and prepares it for this\nMac, which takes about 20 seconds. Later requests render locally.\n\n## Choose a default voice\n\n```sh\nlats voice list\nlats voice select Maeve --provider phonon\n```\n\n`select` sets the voice that a provider uses when a request names no voice.\nVoice settings show the same choice.\n\n## Build from source\n\nTo build Voice.app from the monorepo, follow **Build from source** in\n[`products/voice/README.md`](https://github.com/lattices-dev/lattices/tree/main/products/voice#readme).\nPackaging writes `products/voice/.artifacts/Voice.app` and does not install or\nlaunch it."
    },
    {
      "id": "speech:agents",
      "slug": "agents",
      "product": "speech",
      "kind": "doc",
      "catalogKind": "guide",
      "current": true,
      "title": "For agents",
      "description": "How an agent should give spoken updates through Voice, check that Voice.app is reachable, and stay inside the output boundary.",
      "order": null,
      "sourcePath": "products/voice/docs/agents.md",
      "url": "/voice/docs/agents",
      "rawUrl": "/voice/docs/markdown/agents.md",
      "markdownUrl": "/voice/docs/agents/index.md",
      "headings": [
        {
          "depth": 2,
          "text": "Scope",
          "anchor": "scope"
        },
        {
          "depth": 2,
          "text": "First check",
          "anchor": "first-check"
        },
        {
          "depth": 2,
          "text": "First success",
          "anchor": "first-success"
        },
        {
          "depth": 2,
          "text": "Rules for spoken updates",
          "anchor": "rules-for-spoken-updates"
        },
        {
          "depth": 2,
          "text": "Choose a surface",
          "anchor": "choose-a-surface"
        },
        {
          "depth": 2,
          "text": "Read next",
          "anchor": "read-next"
        }
      ],
      "tokensEstimate": 749,
      "frontmatter": {
        "title": "For agents",
        "description": "How an agent should give spoken updates through Voice, check that Voice.app is reachable, and stay inside the output boundary."
      },
      "markdown": "---\ntitle: For agents\ndescription: How an agent should give spoken updates through Voice, check that Voice.app is reachable, and stay inside the output boundary.\n---\n\nVoice plays text out loud for agents and scripts. Use it for short spoken\nupdates, such as a finished build or a question that needs the user's\nattention. Voice does not listen. Dictation and spoken commands belong to\n[Lattices voice](/lattices/docs/voice).\n\n## Scope\n\n| Owned by Voice | Not owned by Voice |\n| --- | --- |\n| The speech queue, job state, and playback | The microphone, dictation, and spoken commands |\n| Voice and provider choice | Window, session, and workspace control |\n| The playback HUD | Visible notes. Use [Canvas](/canvas/docs/agents) |\n\nVoice has no CLI of its own. Agents use `lats voice` output commands. The\nLattices daemon forwards them to Voice.app.\n\n## First check\n\nRun this before you speak. It does not make a sound.\n\n```sh\nlats voice status --json\n```\n\nRead the `helper` section. If Voice.app is running, the section shows the\nqueue. If not, it shows one of these codes:\n\n| Code | What to do |\n| --- | --- |\n| `helper_not_installed` | Tell the user to choose **Apps › Voice › Install** in the Lattices menu. Do not install it yourself. |\n| `helper_unreachable` | Tell the user to choose **Apps › Voice › Open**. |\n| `helper_unauthorized` | Use `lats`, which sends the capability token. Do not read or copy the token into a custom client unless the user asks for one. |\n\nLattices does not use a fallback voice. A failed `say` is silent.\n\n## First success\n\nSpeak one short line:\n\n```sh\nlats voice say \"Tests passed\" --json\n```\n\nExpected result: a job with an id and a state such as `queued`. The command\nreturns when Voice.app accepts the job, not when playback ends. The state is\nnever `completed` in this result.\n\nTo stop speech that you started:\n\n```sh\nlats voice stop\n```\n\n`stop` clears the whole queue, including jobs from other callers. `skip` ends\nonly the active job. `lats voice stop` does not stop listening. Use\n`lats voice stopListening` for that.\n\n## Rules for spoken updates\n\n1. Speak only when the user asked for spoken updates, or when a long task ends\n   and the user is away from the screen.\n2. Keep each line short. Put details in the terminal or in a note.\n3. Do not read secrets, tokens, file contents, or long logs out loud.\n4. Do not change the default voice with `lats voice select` unless the user\n   asks. The choice is a saved user preference.\n5. Respect the limits: 8,000 characters per job and 32 jobs in the queue.\n\n## Choose a surface\n\n| Need | Use |\n| --- | --- |\n| Speak from a shell or agent terminal | `lats voice say` |\n| Speak from a client that already uses the Lattices agent API | `voice.say` on the Lattices daemon. See [API](/voice/docs/api#choose-a-path). |\n| Speak without the Lattices daemon | `speech.enqueue` directly on Voice.app, with the capability token. See [API](/voice/docs/api#authentication). |\n| Speak from another machine | Use the opt-in tailnet gateway with a grant such as `speech:speech.enqueue`. See [API](/voice/docs/api#private-connection). |\n\nVoice 0.3.0 and Lattices 0.15.1 use private sockets in `~/.lattices/run`.\nInstall matching app and CLI versions. `lats` handles authentication for you.\n\n## Read next\n\n- [Quickstart](/voice/docs/quickstart): install Voice.app and speak a first line.\n- [CLI](/voice/docs/cli): every command that reaches Voice and its playback semantics.\n- [API](/voice/docs/api): the `speech.*` RPC, authentication, and events.\n",
      "content": "Voice plays text out loud for agents and scripts. Use it for short spoken\nupdates, such as a finished build or a question that needs the user's\nattention. Voice does not listen. Dictation and spoken commands belong to\n[Lattices voice](/lattices/docs/voice).\n\n## Scope\n\n| Owned by Voice | Not owned by Voice |\n| --- | --- |\n| The speech queue, job state, and playback | The microphone, dictation, and spoken commands |\n| Voice and provider choice | Window, session, and workspace control |\n| The playback HUD | Visible notes. Use [Canvas](/canvas/docs/agents) |\n\nVoice has no CLI of its own. Agents use `lats voice` output commands. The\nLattices daemon forwards them to Voice.app.\n\n## First check\n\nRun this before you speak. It does not make a sound.\n\n```sh\nlats voice status --json\n```\n\nRead the `helper` section. If Voice.app is running, the section shows the\nqueue. If not, it shows one of these codes:\n\n| Code | What to do |\n| --- | --- |\n| `helper_not_installed` | Tell the user to choose **Apps › Voice › Install** in the Lattices menu. Do not install it yourself. |\n| `helper_unreachable` | Tell the user to choose **Apps › Voice › Open**. |\n| `helper_unauthorized` | Use `lats`, which sends the capability token. Do not read or copy the token into a custom client unless the user asks for one. |\n\nLattices does not use a fallback voice. A failed `say` is silent.\n\n## First success\n\nSpeak one short line:\n\n```sh\nlats voice say \"Tests passed\" --json\n```\n\nExpected result: a job with an id and a state such as `queued`. The command\nreturns when Voice.app accepts the job, not when playback ends. The state is\nnever `completed` in this result.\n\nTo stop speech that you started:\n\n```sh\nlats voice stop\n```\n\n`stop` clears the whole queue, including jobs from other callers. `skip` ends\nonly the active job. `lats voice stop` does not stop listening. Use\n`lats voice stopListening` for that.\n\n## Rules for spoken updates\n\n1. Speak only when the user asked for spoken updates, or when a long task ends\n   and the user is away from the screen.\n2. Keep each line short. Put details in the terminal or in a note.\n3. Do not read secrets, tokens, file contents, or long logs out loud.\n4. Do not change the default voice with `lats voice select` unless the user\n   asks. The choice is a saved user preference.\n5. Respect the limits: 8,000 characters per job and 32 jobs in the queue.\n\n## Choose a surface\n\n| Need | Use |\n| --- | --- |\n| Speak from a shell or agent terminal | `lats voice say` |\n| Speak from a client that already uses the Lattices agent API | `voice.say` on the Lattices daemon. See [API](/voice/docs/api#choose-a-path). |\n| Speak without the Lattices daemon | `speech.enqueue` directly on Voice.app, with the capability token. See [API](/voice/docs/api#authentication). |\n| Speak from another machine | Use the opt-in tailnet gateway with a grant such as `speech:speech.enqueue`. See [API](/voice/docs/api#private-connection). |\n\nVoice 0.3.0 and Lattices 0.15.1 use private sockets in `~/.lattices/run`.\nInstall matching app and CLI versions. `lats` handles authentication for you.\n\n## Read next\n\n- [Quickstart](/voice/docs/quickstart): install Voice.app and speak a first line.\n- [CLI](/voice/docs/cli): every command that reaches Voice and its playback semantics.\n- [API](/voice/docs/api): the `speech.*` RPC, authentication, and events."
    },
    {
      "id": "speech:cli",
      "slug": "cli",
      "product": "speech",
      "kind": "doc",
      "catalogKind": "guide",
      "current": true,
      "title": "CLI",
      "description": "The lats voice commands that reach Voice, how the Lattices daemon forwards them to Voice.app, and what each playback command does.",
      "order": null,
      "sourcePath": "products/voice/docs/cli.md",
      "url": "/voice/docs/cli",
      "rawUrl": "/voice/docs/markdown/cli.md",
      "markdownUrl": "/voice/docs/cli.md",
      "headings": [
        {
          "depth": 2,
          "text": "Request path",
          "anchor": "request-path"
        },
        {
          "depth": 2,
          "text": "Commands that reach Voice",
          "anchor": "commands-that-reach-voice"
        },
        {
          "depth": 2,
          "text": "Playback semantics",
          "anchor": "playback-semantics"
        },
        {
          "depth": 2,
          "text": "Limits",
          "anchor": "limits"
        },
        {
          "depth": 2,
          "text": "Errors",
          "anchor": "errors"
        }
      ],
      "tokensEstimate": 881,
      "frontmatter": {
        "title": "CLI",
        "description": "The lats voice commands that reach Voice, how the Lattices daemon forwards them to Voice.app, and what each playback command does."
      },
      "markdown": "---\ntitle: CLI\ndescription: The lats voice commands that reach Voice, how the Lattices daemon forwards them to Voice.app, and what each playback command does.\n---\n\nVoice has no CLI of its own. The Lattices CLI sends speech output commands\nthrough the Lattices core daemon. This page describes the commands that reach\nVoice and their playback behavior. For the full `lats voice` reference,\nincluding listening and spoken commands, see\n[Lattices voice](/lattices/docs/voice).\n\n## Request path\n\n```text\nlats voice say \"…\"\n  → Lattices core daemon   lattices.sock   voice.say\n  → Voice.app              speech.sock   speech.enqueue\n```\n\nThe CLI reads the capability token from\n`~/Library/Application Support/Speech/RPC/capability` and sends it in the\nWebSocket handshake to the daemon. The daemon forwards output verbs only for\nclients that present the token.\n\nIn Voice 0.3.0 and Lattices 0.15.1, both hops use private Unix sockets,\n`~/.lattices/run/lattices.sock` and `~/.lattices/run/speech.sock`. The CLI finds\nthem and their tokens for you. The command set does not change.\n\nThe CLI binary is also installed as\n`lattices`. Both names run the same commands.\n\n## Commands that reach Voice\n\n| Command | Daemon verb | Voice.app method | Result |\n| --- | --- | --- | --- |\n| `lats voice say <text> [--voice <id>] [--provider <id>] [--rate <n>]` | `voice.say` | `speech.enqueue` | The new job, with its id and state |\n| `lats voice stop` | `voice.stop` | `speech.stop` | Queue status |\n| `lats voice pause` | `voice.pause` | `speech.pause` | Queue status |\n| `lats voice resume` | `voice.resume` | `speech.resume` | Queue status |\n| `lats voice seek <seconds>` | `voice.seek` | `speech.seek` | Queue status |\n| `lats voice skip` | `voice.skip` | `speech.next` | Queue status |\n| `lats voice list` | `voice.list` | `speech.voices` | Voices, with availability |\n| `lats voice select <voice> [--provider <id>]` | `voice.select` | `speech.preferredVoice.set` | The saved preference |\n| `lats voice status` | `voice.status` | `speech.status` | Lattices listening state, plus a `helper` section with the Voice.app queue |\n\nAdd `--json` to any command for machine-readable output.\n\n`lats voice listen`, `stopListening`, `simulate`, and `intents` do not reach\nVoice. Lattices serves those commands itself.\n\n## Playback semantics\n\n- **`say` queues.** `say` returns when Voice.app accepts the job. The returned\n  state reflects the job at that moment and is never `completed`. Jobs play\n  in order.\n- **The queue outlives the caller.** A job keeps playing after the CLI process\n  exits.\n- **`stop` clears the queue.** `stop` stops the active job and cancels every\n  queued job.\n- **`skip` keeps the queue.** `skip` ends the active job and starts the next\n  queued job.\n- **`pause`, `resume`, and `seek` act on the active job only.** `pause` fails\n  with `Nothing is playing` when no job plays. `resume` fails with\n  `Nothing is paused` when no job is paused. `seek` takes a position in\n  seconds from the start of the job.\n- **`select` sets a per-provider default.** The voice applies to later `say`\n  requests for that provider that name no voice.\n- **`--rate`** must be between 0.25 and 4.0. The default is 1.0.\n\n`lats voice stop` stops speech output. `lats voice stop` does not stop voice\ncapture. To stop capture, use `lats voice stopListening`.\n\n## Limits\n\n| Limit | Value |\n| --- | --- |\n| Text per job | 8,000 characters |\n| Jobs in the queue | 32 |\n| Recent finished jobs kept in status | 8 |\n\n## Errors\n\nIf Lattices cannot reach Voice.app, output commands fail with one of these\ncodes. Lattices does not fall back to another voice.\n\n| Error code | Meaning | Recovery |\n| --- | --- | --- |\n| `helper_not_installed` | Voice.app is not on this Mac. | In the Lattices menu, choose **Apps › Voice › Install**. |\n| `helper_unreachable` | Voice.app is installed but not running. | Choose **Apps › Voice › Open**. |\n| `helper_unauthorized` | Voice.app is running, but the client did not send the capability token. | Use `lats`, or send the token from your client. See [API](/voice/docs/api). |\n| `voice_stop_changed` | `voice.stop` was called while Lattices was listening and nothing was speaking. | Use `lats voice stopListening` to stop capture. |\n",
      "content": "Voice has no CLI of its own. The Lattices CLI sends speech output commands\nthrough the Lattices core daemon. This page describes the commands that reach\nVoice and their playback behavior. For the full `lats voice` reference,\nincluding listening and spoken commands, see\n[Lattices voice](/lattices/docs/voice).\n\n## Request path\n\n```text\nlats voice say \"…\"\n  → Lattices core daemon   lattices.sock   voice.say\n  → Voice.app              speech.sock   speech.enqueue\n```\n\nThe CLI reads the capability token from\n`~/Library/Application Support/Speech/RPC/capability` and sends it in the\nWebSocket handshake to the daemon. The daemon forwards output verbs only for\nclients that present the token.\n\nIn Voice 0.3.0 and Lattices 0.15.1, both hops use private Unix sockets,\n`~/.lattices/run/lattices.sock` and `~/.lattices/run/speech.sock`. The CLI finds\nthem and their tokens for you. The command set does not change.\n\nThe CLI binary is also installed as\n`lattices`. Both names run the same commands.\n\n## Commands that reach Voice\n\n| Command | Daemon verb | Voice.app method | Result |\n| --- | --- | --- | --- |\n| `lats voice say <text> [--voice <id>] [--provider <id>] [--rate <n>]` | `voice.say` | `speech.enqueue` | The new job, with its id and state |\n| `lats voice stop` | `voice.stop` | `speech.stop` | Queue status |\n| `lats voice pause` | `voice.pause` | `speech.pause` | Queue status |\n| `lats voice resume` | `voice.resume` | `speech.resume` | Queue status |\n| `lats voice seek <seconds>` | `voice.seek` | `speech.seek` | Queue status |\n| `lats voice skip` | `voice.skip` | `speech.next` | Queue status |\n| `lats voice list` | `voice.list` | `speech.voices` | Voices, with availability |\n| `lats voice select <voice> [--provider <id>]` | `voice.select` | `speech.preferredVoice.set` | The saved preference |\n| `lats voice status` | `voice.status` | `speech.status` | Lattices listening state, plus a `helper` section with the Voice.app queue |\n\nAdd `--json` to any command for machine-readable output.\n\n`lats voice listen`, `stopListening`, `simulate`, and `intents` do not reach\nVoice. Lattices serves those commands itself.\n\n## Playback semantics\n\n- **`say` queues.** `say` returns when Voice.app accepts the job. The returned\n  state reflects the job at that moment and is never `completed`. Jobs play\n  in order.\n- **The queue outlives the caller.** A job keeps playing after the CLI process\n  exits.\n- **`stop` clears the queue.** `stop` stops the active job and cancels every\n  queued job.\n- **`skip` keeps the queue.** `skip` ends the active job and starts the next\n  queued job.\n- **`pause`, `resume`, and `seek` act on the active job only.** `pause` fails\n  with `Nothing is playing` when no job plays. `resume` fails with\n  `Nothing is paused` when no job is paused. `seek` takes a position in\n  seconds from the start of the job.\n- **`select` sets a per-provider default.** The voice applies to later `say`\n  requests for that provider that name no voice.\n- **`--rate`** must be between 0.25 and 4.0. The default is 1.0.\n\n`lats voice stop` stops speech output. `lats voice stop` does not stop voice\ncapture. To stop capture, use `lats voice stopListening`.\n\n## Limits\n\n| Limit | Value |\n| --- | --- |\n| Text per job | 8,000 characters |\n| Jobs in the queue | 32 |\n| Recent finished jobs kept in status | 8 |\n\n## Errors\n\nIf Lattices cannot reach Voice.app, output commands fail with one of these\ncodes. Lattices does not fall back to another voice.\n\n| Error code | Meaning | Recovery |\n| --- | --- | --- |\n| `helper_not_installed` | Voice.app is not on this Mac. | In the Lattices menu, choose **Apps › Voice › Install**. |\n| `helper_unreachable` | Voice.app is installed but not running. | Choose **Apps › Voice › Open**. |\n| `helper_unauthorized` | Voice.app is running, but the client did not send the capability token. | Use `lats`, or send the token from your client. See [API](/voice/docs/api). |\n| `voice_stop_changed` | `voice.stop` was called while Lattices was listening and nothing was speaking. | Use `lats voice stopListening` to stop capture. |"
    },
    {
      "id": "speech:api",
      "slug": "api",
      "product": "speech",
      "kind": "doc",
      "catalogKind": "guide",
      "current": true,
      "title": "API",
      "description": "The speech.* WebSocket RPC that Voice.app serves over private speech.sock, its capability token, and the Lattices voice.* gateway.",
      "order": null,
      "sourcePath": "products/voice/docs/api.md",
      "url": "/voice/docs/api",
      "rawUrl": "/voice/docs/markdown/api.md",
      "markdownUrl": "/voice/docs/api.md",
      "headings": [
        {
          "depth": 2,
          "text": "Choose a path",
          "anchor": "choose-a-path"
        },
        {
          "depth": 2,
          "text": "Authentication",
          "anchor": "authentication"
        },
        {
          "depth": 2,
          "text": "Private connection",
          "anchor": "private-connection"
        },
        {
          "depth": 2,
          "text": "Wire format",
          "anchor": "wire-format"
        },
        {
          "depth": 2,
          "text": "Methods",
          "anchor": "methods"
        },
        {
          "depth": 3,
          "text": "speech.enqueue parameters",
          "anchor": "speech-enqueue-parameters"
        },
        {
          "depth": 3,
          "text": "Playback lease",
          "anchor": "playback-lease"
        },
        {
          "depth": 2,
          "text": "Models",
          "anchor": "models"
        },
        {
          "depth": 2,
          "text": "Events",
          "anchor": "events"
        },
        {
          "depth": 2,
          "text": "Gateway errors",
          "anchor": "gateway-errors"
        },
        {
          "depth": 2,
          "text": "MCP",
          "anchor": "mcp"
        }
      ],
      "tokensEstimate": 1361,
      "frontmatter": {
        "title": "API",
        "description": "The speech.* WebSocket RPC that Voice.app serves over private speech.sock, its capability token, and the Lattices voice.* gateway."
      },
      "markdown": "---\ntitle: API\ndescription: The speech.* WebSocket RPC that Voice.app serves over private speech.sock, its capability token, and the Lattices voice.* gateway.\n---\n\nVoice.app serves an authenticated JSON-over-WebSocket RPC on\n`~/.lattices/run/speech.sock`. Clients can call Voice.app directly, or call the\nLattices core daemon on `~/.lattices/run/lattices.sock`, which forwards `voice.*` output\nverbs to Voice.app.\n\n## Choose a path\n\n| Path | Address | Method names | Use when |\n| --- | --- | --- | --- |\n| Lattices gateway | `~/.lattices/run/lattices.sock` | `voice.*` (or `speech.*`, forwarded unchanged) | You already use the Lattices agent API. |\n| Direct | `~/.lattices/run/speech.sock` | `speech.*` | You need Voice without the Lattices daemon. |\n\nBoth paths require the capability token.\n\n\"Lattices gateway\" here means the core daemon's `voice.*` forwarding on the same\nmachine. It is not the opt-in tailnet gateway for remote\naccess.\n\n## Authentication\n\nVoice.app creates a random 32-byte token each time it starts. Voice.app writes\nthe base64 token to:\n\n```text\n~/Library/Application Support/Speech/RPC/capability\n```\n\nThe directory has mode `0700`. The file has mode `0600`. Voice.app removes the\nfile when it quits. A missing file means that Voice.app is not running.\n\nA client must send the token in the WebSocket upgrade request:\n\n```text\nx-lattices-speech-token: <token>\n```\n\nVoice.app refuses `speech.*` access when any of these conditions is true:\n\n- the header is missing, or appears more than once;\n- the token does not match;\n- the handshake contains an `Origin` header. Browser pages cannot connect.\n\nAn unauthorized `speech.*` call returns the error\n`Speech access requires the local capability`. Voice.app also withholds\n`speech.*` events from unauthorized connections.\n\nThe Lattices daemon checks the same token over `lattices.sock` before it forwards a\n`voice.*` output verb. The Lattices CLI reads the file and sends the header\nautomatically.\n\n## Private connection\n\nVoice 0.3.0 and Lattices 0.15.1 use the private sockets below.\nUpgrade the app and its clients together.\n\n- Voice.app listens on `~/.lattices/run/speech.sock` and accepts only peers\n  with the same user ID. It writes a connection token to\n  `~/.lattices/run/speech.token`.\n- A direct client sends `Host: localhost` and either `x-lattices-token` with the\n  connection token or `x-lattices-speech-token` with the capability token\n  described above. `Origin` is still refused.\n- Port 9397 opens only when Voice.app starts with `LATTICES_SPEECH_TCP=1`.\n- The Lattices daemon path moves to `~/.lattices/run/lattices.sock`. `lats`\n  finds both sockets and tokens for you.\n- From another machine, Voice is reachable only through the Lattices tailnet\n  gateway (`lats gateway start --tailnet`). The client pairs with exact grants,\n  for example `lats --host HOST pair --grant speech:speech.enqueue`, and a\n  person on the host approves the request. The gateway exposes `speech.status`,\n  `speech.voices`, `speech.enqueue`, `speech.pause`, `speech.resume`,\n  `speech.seek`, `speech.stop`, `speech.next` and `speech.preferredVoice.set`.\n  Voice's native playback reservation and events stay local. The gateway\n  does not forward core `voice.*` aliases. Remote calls use\n  `gateway.call` with `{ \"product\": \"speech\", \"method\": \"speech.enqueue\", \"params\": { \"text\": \"…\" } }`.\n\nSee [Private IPC and the gateway](/lattices/docs/concepts#private-ipc-and-the-gateway).\n\n## Wire format\n\nThe wire format matches the Lattices daemon.\n\n```json\n{ \"id\": \"1\", \"method\": \"speech.enqueue\", \"params\": { \"text\": \"Build finished\" } }\n```\n\n```json\n{ \"id\": \"1\", \"result\": { \"id\": \"…\", \"state\": \"queued\", \"…\": \"…\" }, \"error\": null }\n```\n\nEvents have this shape:\n\n```json\n{ \"event\": \"speech.changed\", \"data\": { \"current\": null, \"queued\": [], \"recent\": [] } }\n```\n\n## Methods\n\n| Method | Gateway verb | Params | Returns |\n| --- | --- | --- | --- |\n| `speech.enqueue` | `voice.say` | `text` (required), `provider`, `model`, `voice`, `rate`, `instructions`, `voiceSettings`, `cachePolicy`, `source` | `SpeechJob` |\n| `speech.status` | part of `voice.status` | none | `SpeechStatus` |\n| `speech.pause` | `voice.pause` | `id` | `SpeechStatus` |\n| `speech.resume` | `voice.resume` | `id` | `SpeechStatus` |\n| `speech.seek` | `voice.seek` | `seconds` (required), `id` | `SpeechStatus` |\n| `speech.stop` | `voice.stop` | `id` | `SpeechStatus` |\n| `speech.next` | `voice.skip` | `id` | `SpeechStatus` |\n| `speech.voices` | `voice.list` | none | `[SpeechVoice]` |\n| `speech.preferredVoice.set` | `voice.select` | `voice` (required; empty clears), `provider` | `SpeechPreferredVoice` |\n| `speech.playback.reserve` | `voice.lease` | none | `true` |\n\nFor the control methods, `id` is optional. If you send `id`, it must match the\nactive job. Otherwise the call fails with `Speech job <id> is not the active job`.\n\n### `speech.enqueue` parameters\n\n| Param | Type | Notes |\n| --- | --- | --- |\n| `text` | string | Required. At most 8,000 characters. |\n| `provider` | string | `system` (default), `phonon`, `openai`, or `elevenlabs`. |\n| `model` | string | Provider model override. |\n| `voice` | string | Voice id from `speech.voices`. If omitted, Voice.app uses the provider's preferred voice. |\n| `rate` | double | 0.25 to 4.0. Default 1.0. |\n| `instructions` | string | Delivery instructions for providers that accept them. At most 1,000 characters. |\n| `voiceSettings` | object | `stability`, `similarity`, `style`, `useSpeakerBoost`. |\n| `cachePolicy` | string | `reuse` (default) or `fresh`. |\n| `source` | object | `kind`, `label`, `taskId`, `sessionId`. URLs are refused. |\n\n`speech.enqueue` returns the job with its actual state. The state is never\n`completed` at enqueue time. Voice.app does not fetch remote audio and does\nnot open source links.\n\n### Playback lease\n\n`speech.playback.reserve` reserves the speaker for the calling connection.\nWhile a lease is held, Voice.app pauses the playing job and defers new\nplayback. Only one connection can hold the lease. Another connection gets the\nerror `Another voice session is speaking`.\n\nVoice.app has no release method. The lease ends when the connection closes.\nThrough the gateway, `voice.release` closes the client's helper connection,\nwhich ends the lease.\n\n## Models\n\n**`SpeechJob`**: `id`, `state`, `text`, `provider`, `model`, `voice`,\n`source`, `error`, `progress` (0 to 1), `currentTime` (seconds), `duration`\n(seconds).\n\nJob states: `queued`, `generating`, `playing`, `paused`, `completed`,\n`cancelled`, `failed`.\n\n**`SpeechStatus`**: `current` (active job or null), `queued`, `failure` (kept\nuntil dismissed), `recent` (up to 8 finished jobs), `preferredVoices`.\n\n**`SpeechVoice`**: `id`, `label`, `provider`, `available`, `default`.\n\n**`SpeechPreferredVoice`**: `provider`, `voice`, plus `label` and `available`\nin the `speech.preferredVoice.set` result.\n\n## Events\n\nVoice.app broadcasts `speech.changed` with a `SpeechStatus` payload when the\nqueue changes. The Lattices daemon relays the event under both names:\n`speech.changed` and `voice.changed`.\n\n## Gateway errors\n\nWhen the daemon cannot forward a request, it returns an error string that\nstarts with a machine code: `helper_not_installed`, `helper_unreachable`, or\n`helper_unauthorized`. See [CLI errors](/voice/docs/cli#errors).\n\n## MCP\n\nThe Lattices MCP server does not register speech tools. Use the CLI or this\nRPC.\n",
      "content": "Voice.app serves an authenticated JSON-over-WebSocket RPC on\n`~/.lattices/run/speech.sock`. Clients can call Voice.app directly, or call the\nLattices core daemon on `~/.lattices/run/lattices.sock`, which forwards `voice.*` output\nverbs to Voice.app.\n\n## Choose a path\n\n| Path | Address | Method names | Use when |\n| --- | --- | --- | --- |\n| Lattices gateway | `~/.lattices/run/lattices.sock` | `voice.*` (or `speech.*`, forwarded unchanged) | You already use the Lattices agent API. |\n| Direct | `~/.lattices/run/speech.sock` | `speech.*` | You need Voice without the Lattices daemon. |\n\nBoth paths require the capability token.\n\n\"Lattices gateway\" here means the core daemon's `voice.*` forwarding on the same\nmachine. It is not the opt-in tailnet gateway for remote\naccess.\n\n## Authentication\n\nVoice.app creates a random 32-byte token each time it starts. Voice.app writes\nthe base64 token to:\n\n```text\n~/Library/Application Support/Speech/RPC/capability\n```\n\nThe directory has mode `0700`. The file has mode `0600`. Voice.app removes the\nfile when it quits. A missing file means that Voice.app is not running.\n\nA client must send the token in the WebSocket upgrade request:\n\n```text\nx-lattices-speech-token: <token>\n```\n\nVoice.app refuses `speech.*` access when any of these conditions is true:\n\n- the header is missing, or appears more than once;\n- the token does not match;\n- the handshake contains an `Origin` header. Browser pages cannot connect.\n\nAn unauthorized `speech.*` call returns the error\n`Speech access requires the local capability`. Voice.app also withholds\n`speech.*` events from unauthorized connections.\n\nThe Lattices daemon checks the same token over `lattices.sock` before it forwards a\n`voice.*` output verb. The Lattices CLI reads the file and sends the header\nautomatically.\n\n## Private connection\n\nVoice 0.3.0 and Lattices 0.15.1 use the private sockets below.\nUpgrade the app and its clients together.\n\n- Voice.app listens on `~/.lattices/run/speech.sock` and accepts only peers\n  with the same user ID. It writes a connection token to\n  `~/.lattices/run/speech.token`.\n- A direct client sends `Host: localhost` and either `x-lattices-token` with the\n  connection token or `x-lattices-speech-token` with the capability token\n  described above. `Origin` is still refused.\n- Port 9397 opens only when Voice.app starts with `LATTICES_SPEECH_TCP=1`.\n- The Lattices daemon path moves to `~/.lattices/run/lattices.sock`. `lats`\n  finds both sockets and tokens for you.\n- From another machine, Voice is reachable only through the Lattices tailnet\n  gateway (`lats gateway start --tailnet`). The client pairs with exact grants,\n  for example `lats --host HOST pair --grant speech:speech.enqueue`, and a\n  person on the host approves the request. The gateway exposes `speech.status`,\n  `speech.voices`, `speech.enqueue`, `speech.pause`, `speech.resume`,\n  `speech.seek`, `speech.stop`, `speech.next` and `speech.preferredVoice.set`.\n  Voice's native playback reservation and events stay local. The gateway\n  does not forward core `voice.*` aliases. Remote calls use\n  `gateway.call` with `{ \"product\": \"speech\", \"method\": \"speech.enqueue\", \"params\": { \"text\": \"…\" } }`.\n\nSee [Private IPC and the gateway](/lattices/docs/concepts#private-ipc-and-the-gateway).\n\n## Wire format\n\nThe wire format matches the Lattices daemon.\n\n```json\n{ \"id\": \"1\", \"method\": \"speech.enqueue\", \"params\": { \"text\": \"Build finished\" } }\n```\n\n```json\n{ \"id\": \"1\", \"result\": { \"id\": \"…\", \"state\": \"queued\", \"…\": \"…\" }, \"error\": null }\n```\n\nEvents have this shape:\n\n```json\n{ \"event\": \"speech.changed\", \"data\": { \"current\": null, \"queued\": [], \"recent\": [] } }\n```\n\n## Methods\n\n| Method | Gateway verb | Params | Returns |\n| --- | --- | --- | --- |\n| `speech.enqueue` | `voice.say` | `text` (required), `provider`, `model`, `voice`, `rate`, `instructions`, `voiceSettings`, `cachePolicy`, `source` | `SpeechJob` |\n| `speech.status` | part of `voice.status` | none | `SpeechStatus` |\n| `speech.pause` | `voice.pause` | `id` | `SpeechStatus` |\n| `speech.resume` | `voice.resume` | `id` | `SpeechStatus` |\n| `speech.seek` | `voice.seek` | `seconds` (required), `id` | `SpeechStatus` |\n| `speech.stop` | `voice.stop` | `id` | `SpeechStatus` |\n| `speech.next` | `voice.skip` | `id` | `SpeechStatus` |\n| `speech.voices` | `voice.list` | none | `[SpeechVoice]` |\n| `speech.preferredVoice.set` | `voice.select` | `voice` (required; empty clears), `provider` | `SpeechPreferredVoice` |\n| `speech.playback.reserve` | `voice.lease` | none | `true` |\n\nFor the control methods, `id` is optional. If you send `id`, it must match the\nactive job. Otherwise the call fails with `Speech job <id> is not the active job`.\n\n### `speech.enqueue` parameters\n\n| Param | Type | Notes |\n| --- | --- | --- |\n| `text` | string | Required. At most 8,000 characters. |\n| `provider` | string | `system` (default), `phonon`, `openai`, or `elevenlabs`. |\n| `model` | string | Provider model override. |\n| `voice` | string | Voice id from `speech.voices`. If omitted, Voice.app uses the provider's preferred voice. |\n| `rate` | double | 0.25 to 4.0. Default 1.0. |\n| `instructions` | string | Delivery instructions for providers that accept them. At most 1,000 characters. |\n| `voiceSettings` | object | `stability`, `similarity`, `style`, `useSpeakerBoost`. |\n| `cachePolicy` | string | `reuse` (default) or `fresh`. |\n| `source` | object | `kind`, `label`, `taskId`, `sessionId`. URLs are refused. |\n\n`speech.enqueue` returns the job with its actual state. The state is never\n`completed` at enqueue time. Voice.app does not fetch remote audio and does\nnot open source links.\n\n### Playback lease\n\n`speech.playback.reserve` reserves the speaker for the calling connection.\nWhile a lease is held, Voice.app pauses the playing job and defers new\nplayback. Only one connection can hold the lease. Another connection gets the\nerror `Another voice session is speaking`.\n\nVoice.app has no release method. The lease ends when the connection closes.\nThrough the gateway, `voice.release` closes the client's helper connection,\nwhich ends the lease.\n\n## Models\n\n**`SpeechJob`**: `id`, `state`, `text`, `provider`, `model`, `voice`,\n`source`, `error`, `progress` (0 to 1), `currentTime` (seconds), `duration`\n(seconds).\n\nJob states: `queued`, `generating`, `playing`, `paused`, `completed`,\n`cancelled`, `failed`.\n\n**`SpeechStatus`**: `current` (active job or null), `queued`, `failure` (kept\nuntil dismissed), `recent` (up to 8 finished jobs), `preferredVoices`.\n\n**`SpeechVoice`**: `id`, `label`, `provider`, `available`, `default`.\n\n**`SpeechPreferredVoice`**: `provider`, `voice`, plus `label` and `available`\nin the `speech.preferredVoice.set` result.\n\n## Events\n\nVoice.app broadcasts `speech.changed` with a `SpeechStatus` payload when the\nqueue changes. The Lattices daemon relays the event under both names:\n`speech.changed` and `voice.changed`.\n\n## Gateway errors\n\nWhen the daemon cannot forward a request, it returns an error string that\nstarts with a machine code: `helper_not_installed`, `helper_unreachable`, or\n`helper_unauthorized`. See [CLI errors](/voice/docs/cli#errors).\n\n## MCP\n\nThe Lattices MCP server does not register speech tools. Use the CLI or this\nRPC."
    }
  ],
  "prompts": []
}
