# Build with Ampersand

Ampersand runs your agent and keeps its conversation history. You send messages,
provide the tools it can use, and decide where its replies appear.

## How it works {#how-it-works}

An **agent** defines the assistant. A **session** holds its conversation and a copy
of the agent settings. You can create several sessions with the same agent settings;
each session keeps its own history.

| Concept | What it does |
| --- | --- |
| **Agent** | The model, instructions, and tools that define how an assistant behaves. |
| **Session** | One assistant's saved conversation. Reuse its ID to continue without resending history. |
| **Turn** | The work triggered by input: generate a reply, request tools, or finish silently. A tool result lets the same turn continue. |
| **Event** | Input you send to a session, or a progress update you receive from it. |
| **Team** | Optional. With `multi_agent.enabled`, one session works as a main agent plus subagents of itself; you use it like any session. See [multi-agent mode](#multi-agent). |

Start with `sessions.create(agent=..., input=...)`. Send later messages and tool
results through `sessions.events.create(session_id, events=[...])`. When the
session is idle, new input starts a turn.

Read events through SSE, or retrieve saved session and turn state. A session can
continue across many turns.

## Send your first message {#quickstart}

### Set up your client {#setup}

The Python examples use the official OpenAI Python client. They are tested with
`openai==3.24.0`.

Get an API key from the [developer console](https://ampersand.humansand.ai/).
See [Pricing and limits](PRICING.html.md) for token prices and account tiers.
In Bash or Zsh, replace `YOUR_API_KEY` and set:

```bash
export AMPERSAND_BASE_URL="https://ampersand.humansand.ai/v1"
export AMPERSAND_API_KEY="YOUR_API_KEY"
export AMPERSAND_MODEL="ampersand-v0.1"
```

Keep your key on your backend. Do not put it in browser code or commit it to
source control.

### Use the tutor model {#tutor-model}

Set `AMPERSAND_MODEL="ampersand-tutor-v0.1"`, or use that value for `model`
when creating a saved agent and `agent.model` when creating a session. Keep the
same base URL and Ampersand API key; the [same token rates](PRICING.html.md) apply.
Check `/models` for availability.

Send the student's messages as text. Use `agent.instructions` for lesson context
and to identify the student when several people participate. The model sees
`metadata.sender` as a `name:` line before the text, without renaming it; messages
without a sender appear as plain text. Other metadata, including `sender_name`,
`channel`, and `timestamp`, is saved but not shown to the tutor.

Older context is summarized automatically; [input limits](GUIDES.html.md#input-limits)
still apply. High reasoning is the default with a 16,384-token output budget;
`agent.reasoning.effort="low"` allows 8,192 tokens, and `"max"` allows 32,768.
The standard model also supports `medium` with 16,384 tokens; the tutor does not.
All budgets include thinking and the reply; see
[reasoning settings](GUIDES.html.md#reasoning-effort). Silent completion
is off by default. Tools, images, multi-agent mode, or `allow_silent_completion=true`
return `400`.
Continue through the same session API below. The tool examples require
`ampersand-v0.1`.

### Create a session and read the answer {#create-a-session}

Create a session with a model and a first message. The Python example sets up
the client, prints each event, and collects the answer. The JavaScript snippet
shows the request; its download below includes client setup and the stream loop.

```python
import os

from openai import OpenAI

with OpenAI(
    base_url=os.environ["AMPERSAND_BASE_URL"],
    api_key=os.environ["AMPERSAND_API_KEY"],
    max_retries=0,
) as client:
    with client.beta.agents.sessions.create(
        agent={
            "model": os.environ["AMPERSAND_MODEL"],
            "instructions": "Help the team plan a reliable launch.",
        },
        environment={"type": "none"},
        input="Suggest three checks before a product launch.",
        stream=True,
    ).with_result_collection() as stream:
        for event in stream:
            print(event.to_json(indent=None), flush=True)
            if event.type in {
                "agent.session.idle",
                "agent.session.failed",
                "agent.session.requires_action",
            }:
                break
        result = stream.get_final_result()
    print(result.output_text)
    session_id = result.session_id
```
```javascript
const stream = await client.beta.agents.sessions.create({
  agent: {
    model: process.env.AMPERSAND_MODEL,
    instructions: "Answer the user in text.",
  },
  environment: {type: "none"},
  input: "Suggest three checks before a product launch.",
  stream: true,
}, {signal: AbortSignal.timeout(120_000)});
```
```curl
curl --no-buffer --fail-with-body "${AMPERSAND_BASE_URL%/}/agents/sessions" \
  -H "Authorization: Bearer $AMPERSAND_API_KEY" \
  -H 'OpenAI-Beta: agents=v1' \
  -H 'Content-Type: application/json' \
  -d "$(python3 -c 'import json, os; print(json.dumps({"agent": {"model": os.environ["AMPERSAND_MODEL"], "instructions": "Answer the user in text."}, "environment": {"type": "none"}, "input": "Suggest three checks before a product launch.", "stream": True}))')"
```

<details>
<summary>Run this example</summary>

- **Python:** Install Python 3.12 or later and `uv`. Download [first_response.py](first_response.py), then run `uv run --script first_response.py`. The script installs the client in an isolated environment.
- **JavaScript:** Use Node.js 22 or later. Run `npm install openai@7.28.0`, download [first_response.mjs](first_response.mjs) to the same folder, then run `node first_response.mjs`.
- **cURL:** You need Python 3 to build the request JSON. Paste the command above into the terminal where you set the variables. It prints raw SSE events; press Ctrl-C after `agent.session.idle`.

</details>

The Python example prints JSON events, followed by the answer. Each event includes
the session ID. It also saves that ID in `session_id` for the next request.
The JavaScript download prints the session ID, then the answer:

```text
Session: sess_example
Check billing, tools, and recovery.
```

Your session ID and answer will differ.

Keep the session ID to continue the conversation.
If the connection fails, use that ID to [check the saved state](GUIDES.html.md#recovery).
The Python and JavaScript downloads close the stream after the turn and stop
with an error if it fails. The JavaScript download allows up to 120 seconds for
the response.

### Send a follow-up {#follow-up}

Send the next message to the same session. Add this code after
`session_id = result.session_id`, inside the `with OpenAI(...) as client:` block:

```python
client.beta.agents.sessions.events.create(
    session_id,
    events=[{
        "type": "agent.session.input.message",
        "input": [{
            "role": "user",
            "content": [{"type": "input_text", "text": "Which check should I do first?"}],
        }],
    }],
    idempotency_key="launch-followup-1",
)
```

This request returns `202` with no body. For live output, use the complete example
below instead. It opens the stream before sending the message.

<details>
<summary>Send the message and read the answer</summary>

```python
with client.beta.agents.sessions.events.stream(session_id) as stream:
    client.beta.agents.sessions.events.create(
        session_id,
        events=[{
            "type": "agent.session.input.message",
            "input": [{
                "role": "user",
                "content": [{"type": "input_text", "text": "Which check should I do first?"}],
            }],
        }],
        idempotency_key="launch-followup-1",
    )
    for event in stream:
        if event.type == "agent.session.turn.output_text.done":
            print(event.text)
        elif event.type == "agent.session.turn.completed":
            break
        elif event.type in {"agent.session.turn.failed", "agent.session.turn.cancelled"}:
            raise RuntimeError(f"Turn {event.turn_id}: {event.turn.status}; {event.turn.error}")
    else:
        raise RuntimeError("Stream closed before the turn finished. Check the saved session.")
```

</details>

These examples assume the previous turn has finished and one process writes to
the session. Use a new idempotency key for each new message; reuse the same key
and request body when retrying that message.

### How tools work {#complete-example}

A function tool has a name, a description, and a JSON Schema for its arguments.
When the agent returns `agent.session.requires_action`, **your application runs
the requested function and returns its result** so the same turn can continue.
Declare every action the agent can take as a function tool, including chat
actions such as posting or staying silent. Do not ask for them as markup in its
text; see [chat actions](GUIDES.html.md#chat-actions).
See the [returned event and handler](GUIDES.html.md#send-message) for `send_message`,
or run the [small function example](GUIDES.html.md#complete-example).
The [full application](GUIDES.html.md#personal-sharing-implementation) adds message
routing and permission checks for a group chat with personal assistants.

### How teams work {#multi-agent}

For long, multi-step tasks such as fixing bugs in a repository, a session can
work as a team: a main agent that brings in up to `max_concurrent_subagents`
subagents of itself. Add `multi_agent` next to the effort you already set;
nothing else in the request changes:

```python
agent={
    "model": "ampersand-v0.1",
    "tools": [run_command],
    "reasoning": {"effort": "high"},
    "multi_agent": {"enabled": True, "max_concurrent_subagents": 3},
}
```

Guidelines:

- Keep multi-agent mode off (the default) for questions, chat, and short tasks.
- Use the session as you would a single agent. Every agent's function calls
  arrive in its `required_actions`; answer them on the same session.
- Make your tools safe to run concurrently; calls from several agents can be
  pending at once.
- Wait for `agent.session.idle`: it means every agent is done, and the main
  agent's final reply is the answer.
- Every subagent runs at the session's effort and adds to your usage.

See the [API reference](API_REFERENCE.html.md#multi-agent) for the fields and
[multi-agent mode](GUIDES.html.md#multi-agent) for client code.

<span id="group-chat"></span>
<span id="messaging-primitives"></span>
<span id="messages"></span>
<span id="send-message"></span>
<span id="silent-completion"></span>
<span id="examples"></span>
<span id="personal-sharing-implementation"></span>
<span id="personal-sharing-demo"></span>
<span id="group-assistant-sessions"></span>
<span id="personal-sharing-tools"></span>

## Next steps {#integration}

- [Chat integration example](GUIDES.html.md#group-chat): messaging, group/personal setup, and the recorded rollout.

- [OpenAI compatibility](GUIDES.html.md#openai-compatibility): supported features and differences.
- [Streaming, recovery, and webhooks](GUIDES.html.md#streaming): receive updates and handle interrupted connections.
- [Multi-agent mode](GUIDES.html.md#multi-agent): let one session work as a team of agents.
- [API reference](API_REFERENCE.html.md): endpoints, parameters, events, and errors.

<span id="follow-work"></span>
