> ## Documentation Index
> Fetch the complete documentation index at: https://docs.karta.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Sessions & participants

> Multi-turn conversation handles, multi-party messaging, and per-sender attribution - without making the session record a message store.

A **session** is a lightweight orchestration handle. It carries metadata, the
list of participants, the current sub-agent, and any pending approval. The
session record is not the message store. Conversation history lives with the
harness and the transcript Karta records for the session. That single fact
explains most of how sessions behave.

## A session is a handle, not a store

```python theme={null}
from karta import Karta

app = Karta()
session = app.session(metadata={"customer_id": "abc123"})

session.send_sync("I need help with my order")
session.send_sync("It's order #12345")     # same thread; harness has the history
```

What the handle tracks:

| Field             | Meaning                                                           |
| ----------------- | ----------------------------------------------------------------- |
| `id`              | The session identifier (maps to a harness thread under the hood). |
| `metadata`        | Arbitrary key/values you set - tag and route on these.            |
| `participants`    | The humans and AIs in the conversation.                           |
| `current_agent`   | Which sub-agent is handling turns right now.                      |
| `status`          | `active`, etc.                                                    |
| `pending_input`   | A `request_id` awaiting an approval decision, if any.             |
| `permission_mode` | How approval prompts are handled for this session.                |

Because the handle is cheap and the harness is the source of truth,
**resuming** a session means addressing an existing one by its `session_id` -
not by its metadata. `app.session(metadata=...)` always mints a *new* session,
so hold onto the `Session` you created (or stash its `id`) and resume by that.

```python theme={null}
# First turn - keep the id:
session = app.session(metadata={"customer_id": "abc123"})
session.send_sync("I need help with my order")
saved_id = session.id

# Later - resume by id (same Session, or pass the id back in):
session.send_sync("Any updates?")              # same handle, still live
app.send_sync("Any updates?", session_id=saved_id)   # or address it by id
```

Over HTTP the same thing is create-with-`session_id` (or fetch-then-send); see
the [Sessions API](/api-reference/sessions#resume).

## Sessions run inside a karta

[Kartas & memory](/concepts/instances-and-memory) covers this model in full;
here is how it maps onto sessions.

For a user-facing product, each stable user id can get its own durable karta -
a life coach or tutor that remembers one person across visits. Use
[verified identity](/sdks/widget/identity) when the agent needs to act on
per-user data or credentials. Soft or anonymous ids can provide continuity, but
they are advisory only; any browser page can set them, so your backend must not
treat them as proof of who the user is.

To have a team work with a roster of virtual employees, name stable
`agent_instance_id` values from your backend. One team might use
`auditor-west`, `auditor-east`, `reviewer-risk`, `reviewer-finance`, and
`reviewer-compliance` as five separate kartas. Each karta has its own files,
sessions, memory, and working style; each acting person is still attributed per
turn for usage and per-seat caps.

How you name a shared karta depends on the surface:

* **From your backend** (server-to-server with an API key, or a backend-minted
  session token), pass it in session metadata:

  ```python theme={null}
  session = app.session(metadata={"agent_instance_id": "support-desk"})
  ```

* **In an embedded widget**, configure the stable karta id on the embed key
  itself (Embed keys -> the key -> "Karta ID"). Every widget session from that
  key then joins that virtual employee, backend worker, or other deliberately
  shared karta.

`agent_instance_id` is the API field behind the public `karta` noun. It is
trusted only when it comes from your backend or the embed key's server-side
config. Public browser metadata cannot redirect a widget into another shared
karta.

## Participants and attribution

A session is not limited to one human and one bot. Multiple **participants** -
humans and AIs - can share a session, and every message is attributed to its
sender.

```python theme={null}
from karta import Karta, HumanAgent

app = Karta()
session = app.session()

alice = HumanAgent(name="alice", display_name="Alice Chen")
bob   = HumanAgent(name="bob",   display_name="Bob Park")

session.send_sync("I need help with deployment", participant=alice)
session.send_sync("I can help - what's the error?", participant=bob)
```

This supports shared inboxes, human-in-the-loop handoffs, and agent-to-agent
collaboration. Karta delivers
each event to every participant in the session - sending or streaming through a
session does this fan-out for you. When you build agent-to-agent topologies
explicitly, the [gateway API](/api-reference/gateway) exposes the same
submit-and-deliver surface directly.

## Sub-agent handoff

A session has a *current sub-agent*, and you can hand off mid-conversation to
another sub-agent. The handoff fires an `agent.handoff`
[lifecycle hook](/sdks/python/hooks-and-policies).

```python theme={null}
session = app.session(metadata={"customer_id": "abc123"})
session.send_sync("I need help with my order")     # -> default agent
session.current_agent = app.agents["billing"]       # handoff
session.send_sync("Check invoice #789")             # -> billing agent
```

See [Sub-agents](/concepts/agents) for how sub-agents are discovered and routed.

## Pending input & approvals

When the harness needs permission - to run a tool, write a file - it emits an
[`input_required`](/concepts/streaming-events#input_required) event and the
session records `pending_input`. You resolve it with a decision:

```python theme={null}
async for event in session.stream_pending_input("approve_once", request_id=req_id):
    ...
```

Over HTTP this is the [inputs endpoint](/api-reference/inputs); decisions are
`approve_once`, `approve_session`, or `deny`. How aggressively Karta prompts is
governed by `permission_mode` and the
[`input_required_policy`](/build/configuration) config.

## Over HTTP

The same model is exposed on the wire: create a session, send (or stream)
messages, resolve inputs. See the [Sessions](/api-reference/sessions) and
[Messages](/api-reference/messages) API reference.

<CardGroup cols={2}>
  <Card title="Sub-agents" icon="user-astronaut" href="/concepts/agents">
    Discovery, routing, and handoff.
  </Card>

  <Card title="Streaming events" icon="wave-pulse" href="/concepts/streaming-events">
    The typed events every message and approval flows through.
  </Card>
</CardGroup>
