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

# Context

> Every turn hands your agent a context object with the conversation, the user, the workspace and the switches that user set.

The single argument to an agent body is `context`. It is the whole per-request
input, and `llm.run(context=context)` reads everything it needs from it.

```python theme={null}
@cycls.agent(web=web, volumes={"/workspace": chats})
async def my_agent(context):
    print(context.last_message)
    print(context.user.id, context.user.plan)

    async for ev in llm.run(context=context):
        yield ev
```

## Fields

| Field                    | Type             | Meaning                                                              |
| ------------------------ | ---------------- | -------------------------------------------------------------------- |
| `context.messages`       | `Messages`       | the conversation as `{"role", "content"}` dicts                      |
| `context.messages.raw`   | `list`           | the same turns with every part, including components and attachments |
| `context.last_message`   | `str`            | the text of the most recent message                                  |
| `context.user`           | `User` or `None` | the authenticated user when auth is configured                       |
| `context.chat_id`        | `str`            | the current chat                                                     |
| `context.workspace`      | `Workspace`      | this user's storage scope                                            |
| `context.workspace_id`   | `str` or `None`  | the active workspace in multi-workspace mode                         |
| `context.prod`           | `bool`           | `True` under `deploy`, `False` under `run`                           |
| `context.disabled_tools` | `list`           | tools this user switched off in Settings                             |
| `context.connectors`     | `list`           | connectors the user mentioned with `@`                               |
| `context.approvals`      | `list`           | approvals granted from a confirm card, this turn only                |
| `context.auto`           | `bool`           | the composer's automatic approval switch                             |

## Messages

`context.messages` reads as plain text, which is what most code wants.

```python theme={null}
[{"role": "user", "content": "Summarize this"},
 {"role": "assistant", "content": "Here is the summary"}]
```

`context.messages.raw` keeps everything: component parts, tool steps and
attachment references. Reach for it when you need to inspect what actually
happened rather than what was said.

## The user

```python theme={null}
user = context.user

user.id                 # stable user id
user.org_id             # organization id, when the user is acting in one
user.org_slug
user.org_role
user.org_permissions
user.plan               # subscription tier from the JWT claim
user.features           # feature flags
```

`context.user` is `None` when no auth provider is configured. See
[Authentication](/web/auth) for how the claims are populated.

## The workspace

`context.workspace` is this user's scope on the `/workspace` volume, and it is
what `cycls.DB` writes to.

```python theme={null}
from datetime import datetime, timezone

db = cycls.DB(context.workspace)
month = datetime.now(timezone.utc).strftime("%Y-%m")

entry = await db.get(f"usage/{month}", {"count": 0})
entry["count"] += 1
await db.put(f"usage/{month}", entry)
```

Files the user sees in the files panel live under `context.workspace.root`.

## Gating on environment

`context.prod` separates a local run from a deployment, which is the clean way to
keep billing and quota checks out of your development loop.

```python theme={null}
exempt = not context.prod

if context.user.plan == "o:free_org" and not exempt:
    yield {"type": "callout", "callout": "This workspace needs a paid plan.", "style": "error"}
    yield {"type": "ui", "action": "open_plan_modal"}
    return
```

## Respecting user switches

`context.disabled_tools` holds the tools this person turned off. `llm.run()`
already honors it. You only need to read it if you are running your own loop or
want to explain the difference to the user.

```python theme={null}
if "WebSearch" in context.disabled_tools:
    yield "_Web search is off in your settings, so this answer is from memory._\n\n"
```

A switch can never enable a tool that `allowed_tools` did not grant.

## Logging with attribution

`cycls.log` writes one structured line that the log backend captures, with user
and chat attribution attached.

```python theme={null}
cycls.log("cap_hit", user=context.user, chat_id=context.chat_id,
          kind="user_free_monthly", count=entry["count"])
```

Query it later with `cycls logs -q` or `cycls sql`. See
[Observability](/ship/observability).

## Next

<Card title="Knowledge" icon="book" href="/agents/knowledge">
  Workspace instructions and skills the model loads on demand.
</Card>
