# Agent groups

A group holds the settings a set of agents share and hands them to every member that has not claimed its own. The part that surprises people — and is deliberate — is that putting an agent into a group changes nothing about its calls.

## What a group holds

A group holds **defaults**, not settings. Each one is either an opinion the group has, or no opinion at all, and the difference is the whole resource: a group that records calls and a group nobody has ever asked about recording are not the same record. An unset default leaves each member on whatever it already has; a default set to an *empty list* is an opinion, and a useful one — it is how a group says "these agents ground against nothing".

| A group may set | A group may never set |
| --- | --- |
| `model`, `models`, `temperature`, `max_tokens` | `name`, `description` |
| `speed`, `pitch`, `filler_phrase` | `languages` — and with them the voice, greeting and prompt |
| `autonomy` | `mode` |
| `knowledge_base_ids`, `tools` | the `keypad` menu |
| `transfer_number`, `carrier` | caller IDs and `direction` |
| `webhook_url`, `record_calls`, `transcribe_calls` |  |

What it may not set is what the agent *sounds like*. One voice cannot be right for an agent that speaks two languages, and a greeting is composed in a language rather than shared across a fleet. A group able to rewrite forty system prompts is a footgun with no matching use case.

A group is also allowed to hold nothing at all. "These are the clinic lines" earns its keep as a filter, and requiring an opinion would only mean inventing one.

## Joining a group changes nothing

When an agent joins, every inheritable field it already has a value for is added to its `overrides` — the list of fields whose value on the agent wins over the group's. The agent keeps everything it had, and its next call is the call it would have taken anyway.

> **The alternative is worse than it sounds.** Filing forty agents into a group to tidy up a listing would silently re-configure forty working phone lines, and the evidence would arrive days later as a complaint about a voice that changed — with nothing recording what those agents sounded like beforehand.

| Act | Effect |
| --- | --- |
| **Assign** | The agent is in the group. It filters with it and is counted in it. Nothing about its calls changes. |
| **Adopt** | The agent stops claiming a field and takes the group's value for it, from the next call onwards. |

One exception, in the opposite direction: an agent **created** with a group already set claims only the fields the create request named. A new agent's temperature came from this API's defaults rather than from you, and treating a default as a decision would leave the group with nothing to say to the agent that most needs it. Nothing is at risk, because the line does not exist yet.

## Adopting a default

Adoption is done by removing the field from the agent's `overrides`.

`PATCH /v1/agents/{id}`

**Shell**

```bash
# This agent claims speed and pitch; everything else follows the group.
curl -s -X PATCH https://voice.sphoro.com/v1/agents/$AGENT_ID \
  -H "Authorization: Bearer $SPHORO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"overrides": ["speed", "pitch"]}'
```

The list replaces whole, so this is also how an agent takes a field *back*: add its name. Every name must be a field a group can set — anything else is refused rather than stored.

## The endpoints

| Endpoint | Does |
| --- | --- |
| `POST /v1/agent_groups` | Create one. |
| `GET /v1/agent_groups` | List them. |
| `GET /v1/agent_groups/{id}` | Read one, with its defaults. |
| `PATCH /v1/agent_groups/{id}` | Change a default. Takes effect on every member that has not claimed that field. |
| `DELETE /v1/agent_groups/{id}` | Remove the group. |
| `GET /v1/agent_groups/{id}/members` | Which agents are in it, and which fields each one claims. |

**Shell**

```bash
curl -s https://voice.sphoro.com/v1/agent_groups/$GROUP_ID/members \
  -H "Authorization: Bearer $SPHORO_API_KEY"
```

## Recording never adopts on join

> `record_calls` and `transcribe_calls` are claimed by an agent **the moment it joins a group, whatever their value**, and no other field behaves this way. The consequence is real and accepted: **a group can never switch recording on for members it already has.** It has to be adopted per agent, deliberately, by somebody who knows the consent position for that line.

That is the correct trade. Inheriting the wrong model produces a worse answer, which is visible and fixable. Inheriting recording produces a recorded call that should not have been recorded, which is not.

## What "differs from the group" means

Not "agents with overrides". A field an agent has claimed but which happens to hold the same value as the group's is not a difference — adopting it would change nothing. So the count is **agents for which adoption would change at least one value**, which also means the number moves when you edit a *default*, not only when you edit an agent. A group with no opinions has no differences by definition.

Sphoro Voice shows this as a panel in the portal listing each member that is not answering the way the group says, with the change adoption would make written out — field, value now, value after — so nothing is applied to a live line sight unseen.
