# Cookbook

> Short task recipes for serving a speech-to-speech agent: attach, tune turn detection, codecs, barge-in, and latency.

Copy-pasteable answers to "how do I X" for the inference modality. Everything
uses the [Voice](/modalities/voice/sdk/javascript) surface: `voice.agents.attach`
plus the turn-detection knobs. TypeScript is primary. Python is shown where it
differs.

## Attach a speech-to-speech agent to a call

This one-liner binds a registered agent to a live call sid.

```ts
import { Voice } from "clutchcall_connect/voice";

const voice = new Voice({ baseUrl: BASE_URL, apiKey: KEY, orgId: ORG });
await voice.agents.attach(callSid, "support-s2s");
```

## Originate and attach in one call

Pass `agent` to `originate`. FirstByte then bridges the model at the moment
the callee answers.

```ts
const call = await voice.calls.originate({
  to:      "+15551234567",
  from:    "+15558675309",
  trunkId: "trunk_main",
  agent:   "support-s2s",
});
```

## Attach with an inline agent spec

Skip the registry. Select the model leg and the turn-detection policy inline.

```ts
await voice.agents.attach(callSid, {
  mode:          "speech_to_speech",
  modelCodec:    "pcm16",
  inputRateHz:   16000,
  outputRateHz:  24000,
  turnDetection: { responseMinSpeechMs: 600, bargeConfirmMs: 300 },
});
```

## Pick the codec the model wants

Most speech-to-speech models ingest raw PCM16. Set the model codec. The bridge
transcodes the caller leg.

```ts
await voice.agents.attach(callSid, {
  mode:       "speech_to_speech",
  modelCodec: "pcm16",   // "opus" for a browser caller leg
});
```

> **NOTE:**
> A PSTN caller leg can be `g711_ulaw` / `g711_alaw`. A browser leg is `opus`.
>   You never set the caller codec here. You set only what the **model** expects.

## Tighten end-of-turn for snappier replies

Lower the trailing-silence threshold so the agent answers sooner after the
caller stops.

```ts
await voice.agents.attach(callSid, {
  mode: "speech_to_speech",
  turnDetection: { silenceThresholdMs: 300 },   // default 500
});
```

> **WARNING:**
> Below ~300 ms, end-of-turn fires on inter-word pauses, and the agent
>   interrupts mid-sentence. Do not go lower.

## Stop backchannels from triggering a reply

Continuers such as "mhm" / "ok" / "right" must not make the model answer.
Raise the minimum speech length that counts as a turn.

```ts
await voice.agents.attach(callSid, {
  mode: "speech_to_speech",
  turnDetection: { responseMinSpeechMs: 700 },   // default 600 — anything shorter is dropped
});
```

## Make barge-in forgiving (hold-and-confirm)

Require sustained speech before the barge-in cancels the agent. Then a
muttered continuer mid-reply does not kill the turn.

```ts
await voice.agents.attach(callSid, {
  mode: "speech_to_speech",
  turnDetection: { bargeConfirmMs: 400 },   // sustain 400ms before cancelling
});
```

## Make barge-in instant (browser / AEC legs)

When the caller leg has client-side echo cancellation, cut the agent on the
first speech frame.

```ts
await voice.agents.attach(callSid, {
  mode: "speech_to_speech",
  turnDetection: { bargeConfirmMs: 0 },   // 0 = fire immediately on onset
});
```

## Stop the agent self-triggering on a no-AEC SIP/PSTN leg

When there is no echo cancellation, the agent's own audio bleeds into the mic.
Raise the post-audio mic guard so that the agent does not read its own voice
as barge-in.

```ts
await voice.agents.attach(callSid, {
  mode: "speech_to_speech",
  turnDetection: { ttsGuardMs: 400 },   // default 200 — longer guard for noisy legs
});
```

## Inject a pre-roll prompt before the model speaks

Attach the audio bridge. Push a downlink clip before the first model turn.

```ts
const bridge = await voice.audioBridge.attach(call.sid, { codec: "pcm16" });
bridge.publishDownlink(prerollPcm);   // "connecting you now…"
await voice.agents.attach(call.sid, { mode: "speech_to_speech" });
```

## Record the caller leg while the agent runs

Tap the uplink without disturbing the agent bridge.

  
```ts
const bridge = await voice.audioBridge.attach(call.sid, {
  codec: "pcm16",
  onUplink: (frame) => recorder.feed(frame),
});
```
  
  
```python
bridge = voice.audio_bridge.attach(
    call.sid, codec="pcm16",
    on_uplink=lambda frame, ts_us: recorder.feed(frame),
)
```
  

## Watch turn boundaries and barge events

Subscribe to call status to see when the agent commits a turn or is
interrupted.

```ts
call.onStatus((s) => {
  if (s === "in_progress") console.log("agent bridged, listening");
  if (s === "completed")   console.log("call done");
});
```

## Detach the agent but keep the call up

Hand the call back to a human (or another app). Attach a bare bridge, and do
not re-attach the agent.

```ts
await voice.audioBridge.attach(call.sid, { codec: "pcm16", onUplink: humanLeg.feed });
// agent no longer drives the conversation; you own the audio now
```

## Attach a speech-to-speech agent in Python

This is the same flow, in snake_case.

```python
from clutchcall.voice import Voice

voice = Voice(base_url=BASE_URL, api_key=KEY, org_id=ORG)
call  = voice.calls.originate(
    to="+15551234567", from_="+15558675309",
    trunk_id="trunk_main", agent="support-s2s",
)
```

## Give the agent an HTTP tool

Declare an `http` tool on the agent. The model can then call it
mid-conversation. The URL substitutes `{{argument}}` values from the model's
tool arguments. FirstByte feeds the result back, and the model speaks it.

```ts
await voice.agents.attach(callSid, {
  mode: "speech_to_speech",
  tools: [
    {
      kind: "http",
      name: "lookup_order",
      description: "Look up an order by its ID and return its status.",
      spec: {
        method: "GET",
        url: "https://api.example.com/orders/{{order_id}}",
        auth: { type: "bearer", token: process.env.ORDERS_TOKEN! },
        parameters: {
          type: "object",
          properties: {
            order_id: { type: "string", description: "Order id to look up." },
          },
          required: ["order_id"],
        },
      },
    },
  ],
});
```

## Call a remote MCP server as a tool

Point an `mcp` tool at an MCP server. FirstByte sends a JSON-RPC
`tools/call` and gives the result back to the model. The auth shapes are the
same as `http`.

```ts
await voice.agents.attach(callSid, {
  mode: "speech_to_speech",
  tools: [
    {
      kind: "mcp",
      name: "search_kb",
      description: "Search the knowledge base for an answer.",
      spec: {
        method: "POST",
        url: "https://mcp.example.com/rpc",
        auth: { type: "bearer", token: process.env.MCP_TOKEN! },
        parameters: {
          type: "object",
          properties: { query: { type: "string" } },
          required: ["query"],
        },
      },
    },
  ],
});
```

> **NOTE:**
> You declare each MCP tool explicitly by name. Auto-discovery via
>   `tools/list` is not yet wired. List the tools that you want the model to
>   see.

## Make a tool silent (no narration)

Set `silent: true` so that the model does not say "let me look that up". The
model stays quiet until it has the result. Then it answers directly.

```ts
await voice.agents.attach(callSid, {
  mode: "speech_to_speech",
  tools: [
    {
      kind: "http",
      name: "lookup_order",
      description: "Look up an order by its ID.",
      silent: true,   // no "one moment…" — answer when the result is back
      spec: {
        method: "GET",
        url: "https://api.example.com/orders/{{order_id}}",
        parameters: {
          type: "object",
          properties: { order_id: { type: "string" } },
          required: ["order_id"],
        },
      },
    },
  ],
});
```

## Handle a structured tool error

A failed or denied tool call returns a structured error to the model, not raw
text. The model can then recover: fall back, ask for a different input, or
explain the limitation. Make your endpoint return a clear error body and a
non-2xx status. The model reasons over it.

```ts
// Your endpoint, on a missing order:
//   HTTP 404
//   { "error": "order_not_found", "order_id": "A-123" }
//
// The model receives that as a tool_result and can say:
//   "I couldn't find order A-123 — can you double-check the number?"
//
// Keep the tool fast: invokes are synchronous within the model turn.
await voice.agents.attach(callSid, {
  mode: "speech_to_speech",
  tools: [
    {
      kind: "http",
      name: "lookup_order",
      description: "Look up an order by its ID. Returns 404 if not found.",
      spec: {
        method: "GET",
        url: "https://api.example.com/orders/{{order_id}}",
        timeoutMs: 2000,   // tight timeout — don't stall the turn
        parameters: {
          type: "object",
          properties: { order_id: { type: "string" } },
          required: ["order_id"],
        },
      },
    },
  ],
});
```

## Related

- [Inference — Recipes](/modalities/inference/recipes) — full worked examples
- [Inference — SDK Methods](/modalities/inference/sdk-methods) — every method and the full knob table
- [Turn Detection & Barge-In](/concepts/turn-detection) — the policy these knobs drive
