> ## 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.

# Hooks & policies

> React to lifecycle events, and gate messages against configurable policies.

Two mechanisms let you observe and constrain a Karta app without touching the
harness: **hooks** (react to lifecycle events) and **policies** (validate
messages before they run).

## Hooks

Register a handler for a lifecycle event with `@app.on(...)`. Handlers receive a
`HookEvent` carrying the session, message, and any event-specific payload.

```python theme={null}
app = Karta()

@app.on("message.completed")
async def log_response(event):
    print(f"Session {event.session.id}: {event.message.text}")

@app.on("agent.handoff")
async def track_handoff(event):
    print(f"Handoff: {event.payload['from']} -> {event.payload['to']}")
```

### Available events

| Event               | Fires when                                            |
| ------------------- | ----------------------------------------------------- |
| `session.created`   | A new session is opened.                              |
| `message.received`  | An inbound message is accepted, before the turn runs. |
| `message.completed` | A turn finishes and the agent response is ready.      |
| `agent.handoff`     | A session's `current_agent` changes.                  |

Use hooks for logging, metrics, side effects (notify a channel on handoff), or
enrichment - they observe the flow without owning it.

Hook handlers are best-effort. Handlers fired by synchronous state changes, such
as `session.created` and `agent.handoff`, may run after a later event. Exceptions
are logged and do not fail the turn.

| HookEvent field | Type              | Meaning                                                                            |
| --------------- | ----------------- | ---------------------------------------------------------------------------------- |
| `name`          | `str`             | Event name that fired.                                                             |
| `session`       | `Session \| None` | Session associated with the event, when present.                                   |
| `message`       | `Message \| None` | Message associated with the event, when present.                                   |
| `payload`       | `dict`            | Event-specific data, such as `{ "from": "...", "to": "..." }` for `agent.handoff`. |
| `timestamp`     | `datetime`        | UTC timestamp for the hook event object.                                           |

## Policies

Policies validate messages against configurable rules before a turn runs -
message counts and keyword gates.

```python theme={null}
app.policy("max_messages_per_session", 50)
app.policy("require_human_approval_for", ["refund", "delete account"])
```

A message that violates a policy is rejected before reaching the harness, so
policy checks are cheap and deterministic - exactly the kind of guardrail you
want in code rather than left to the model.

| Policy                       | Value           | Rejects when                                                    |
| ---------------------------- | --------------- | --------------------------------------------------------------- |
| `max_messages_per_session`   | integer         | The session message count exceeds the limit.                    |
| `require_human_approval_for` | list of strings | The inbound text contains one of the listed restricted intents. |

`max_tokens_per_session` is accepted as a compatibility alias for
`max_messages_per_session`; new code should use `max_messages_per_session`.

<Note>
  Hooks and policies run as part of Karta's orchestration, around the harness.
  They do not see or alter conversation history (the harness owns that); they
  gate and observe the turns that flow through Karta. See
  [How Karta works](/concepts/how-karta-works).
</Note>
