# TTS Implementation Plan

## Goal

Add agent-invokable text-to-speech so a tenant agent can decide per turn to respond with voice, while keeping the execution boundary inside the tenant container and leaving the host responsible only for message delivery.

## Decision

- Execution boundary: tenant container
- Invocation model: agent-usable MCP tool
- Initial provider: `edge-tts`
- Provider architecture: swappable adapter registry inside the container
- Delivery path: reuse existing IPC `file` messages and Discord file sending

## Architecture

### Container-side

- Add a `tts.ts` module in `container/agent-runner/src/`
- Define a provider interface:
  - `id`
  - `mode` (`remote` or `local`)
  - `synthesize(request) -> result`
- Implement an `EdgeTtsProvider`
- Add a registry that resolves provider IDs
- Add a `send_voice_message` MCP tool in `ipc-mcp-stdio.ts`
- The tool:
  - validates text length and provider choice
  - synthesizes audio into an ephemeral outbound file under the group workspace
  - writes a normal IPC `file` message
  - optionally includes a caption

### Host-side

- Keep host changes minimal
- Reuse existing `sendFile()` channel behavior
- Extend IPC file cleanup so generated outbound media is deleted after successful send
- Preserve current authorization checks for IPC file sends

### Output file lifecycle

- Generated TTS files live under `/workspace/group/.nanoclaw-outbound/tts/`
- `ipc.ts` maps `/workspace/group/...` to the host group folder path as it already does
- After successful send, host deletes files that live under the outbound TTS directory

## Acceptance Criteria

### Functional

- The agent can invoke a tool to send a voice message in Discord.
- The tool runs inside the tenant container.
- The generated audio is delivered back to the user as a Discord attachment.
- The tool accepts at least:
  - `text`
  - optional `caption`
  - optional `provider`
  - optional `voice`
- The first provider is swappable behind a registry; no host code is provider-specific.

### Behavioral

- The agent can still send normal text replies without TTS.
- The agent can choose TTS on a per-turn basis.
- Generated files do not accumulate in tenant workspaces after successful delivery.
- Unauthorized IPC file sends remain blocked by existing tenant/group checks.

### Operational

- The image contains the dependency needed to execute the initial provider.
- Failures in TTS generation return a tool error rather than crashing the container runner.
- Existing STT and attachment flows continue to work.

## Test Plan

### Unit / focused

1. Provider test
   - `EdgeTtsProvider` builds the expected command and output file path
   - provider rejects unsupported provider IDs / missing binaries / empty text

2. MCP tool test
   - `send_voice_message` writes an IPC `file` message with the expected generated path and caption
   - tool returns an error if synthesis fails

3. IPC cleanup test
   - outbound TTS files are deleted after a successful send
   - non-TTS files are not accidentally deleted by the cleanup path

### Integration-style

1. Container-side runner flow
   - simulate a container tool invocation that generates audio
   - verify the resulting IPC `file` message is processed and routed to `sendFile`

2. Discord flow
   - simulate a Discord inbound message
   - simulate agent output plus a TTS IPC file event
   - verify Discord receives a file send with the expected caption/path

### Live validation

1. Build the host checkout
2. Rebuild the agent image
3. Restart `nanoclaw.service`
4. Smoke test inside the image:
   - run the TTS provider directly against a short phrase
   - verify a non-empty audio file is produced
5. Live Discord test:
   - prompt the agent to answer with voice
   - confirm a Discord audio attachment is delivered
   - confirm the generated host-side file is removed after send

## Rollout Notes

- This initial implementation is provider-abstracted but quality-first.
- Future providers such as Kokoro or Piper should plug into the same container-side registry.
- If later needed, provider policy can be exposed through tenant config without changing the host/container contract.
