diff --git a/docs/README.md b/docs/README.md
index 3d46fe9abf..2533d74c88 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -46,6 +46,7 @@ Guides for building with the SDK's capabilities.
* [MCP Servers](./features/mcp.md): integrate Model Context Protocol servers
* [Skills](./features/skills.md): load reusable prompt modules
* [Plugin Directories](./features/plugin-directories.md): bundle skills, hooks, MCP servers, and agents as a single loadable plugin
+* [Session limits](./features/session-limits.md): set an AI Credits budget for a session
* [Image Input](./features/image-input.md): send images as attachments
* [Streaming Events](./features/streaming-events.md): real-time event reference
* [Steering & Queueing](./features/steering-and-queueing.md): message delivery modes
diff --git a/docs/features/README.md b/docs/features/README.md
index fe2a98c2ea..b695fea6d4 100644
--- a/docs/features/README.md
+++ b/docs/features/README.md
@@ -15,6 +15,7 @@ These guides cover the capabilities you can add to your Copilot SDK application.
| [MCP Servers](./mcp.md) | Integrate Model Context Protocol servers for external tool access |
| [Skills](./skills.md) | Load reusable prompt modules from directories |
| [Plugin Directories](./plugin-directories.md) | Bundle skills, hooks, MCP servers, and agents as a single loadable plugin |
+| [Session limits](./session-limits.md) | Set an AI Credits budget for a session and observe budget events |
| [Image Input](./image-input.md) | Send images to sessions as attachments |
| [Streaming Events](./streaming-events.md) | Subscribe to real-time session events (40+ event types) |
| [Steering & Queueing](./steering-and-queueing.md) | Control message delivery—immediate steering vs. sequential queueing |
diff --git a/docs/features/session-limits.md b/docs/features/session-limits.md
new file mode 100644
index 0000000000..b2e4cfe479
--- /dev/null
+++ b/docs/features/session-limits.md
@@ -0,0 +1,174 @@
+# Session limits
+
+Session limits let an application set an AI Credits budget for a Copilot session. Use `sessionLimits` when creating or resuming a session to set a soft cap for the current accounting window.
+
+## Configure a session limit
+
+Set `maxAiCredits` to the AI Credits soft cap for the session's current accounting window. Usage is checked after model calls return, so one response can exceed the configured value before the runtime blocks the next model call. The SDK forwards this value to the Copilot CLI when it creates or resumes the session.
+
+
+TypeScript
+
+
+
+```typescript
+const session = await client.createSession({
+ onPermissionRequest: approveAll,
+ sessionLimits: {
+ maxAiCredits: 30,
+ },
+});
+
+const resumed = await client.resumeSession(session.sessionId, {
+ onPermissionRequest: approveAll,
+ sessionLimits: {
+ maxAiCredits: 30,
+ },
+});
+```
+
+
+
+Python
+
+
+
+```python
+session = await client.create_session(
+ on_permission_request=PermissionHandler.approve_all,
+ session_limits={
+ "max_ai_credits": 30,
+ },
+)
+
+resumed = await client.resume_session(
+ session.session_id,
+ on_permission_request=PermissionHandler.approve_all,
+ session_limits={
+ "max_ai_credits": 30,
+ },
+)
+```
+
+
+
+Go
+
+
+
+```go
+session, err := client.CreateSession(ctx, &copilot.SessionConfig{
+ OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ SessionLimits: &rpc.SessionLimitsConfig{
+ MaxAiCredits: copilot.Float64(30),
+ },
+})
+
+resumed, err := client.ResumeSession(ctx, session.SessionID, &copilot.ResumeSessionConfig{
+ OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
+ SessionLimits: &rpc.SessionLimitsConfig{
+ MaxAiCredits: copilot.Float64(30),
+ },
+})
+```
+
+
+
+.NET
+
+
+
+```csharp
+var session = await client.CreateSessionAsync(new SessionConfig
+{
+ OnPermissionRequest = PermissionHandler.ApproveAll,
+ SessionLimits = new SessionLimitsConfig
+ {
+ MaxAiCredits = 30,
+ },
+});
+
+var resumed = await client.ResumeSessionAsync(session.SessionId, new ResumeSessionConfig
+{
+ OnPermissionRequest = PermissionHandler.ApproveAll,
+ SessionLimits = new SessionLimitsConfig
+ {
+ MaxAiCredits = 30,
+ },
+});
+```
+
+
+
+Java
+
+
+
+```java
+CopilotSession session = client
+ .createSession(new SessionConfig()
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+ .setSessionLimits(new SessionLimitsConfig(30.0)))
+ .get();
+
+CopilotSession resumed = client
+ .resumeSession(session.getSessionId(), new ResumeSessionConfig()
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+ .setSessionLimits(new SessionLimitsConfig(30.0)))
+ .get();
+```
+
+
+
+Rust
+
+
+
+```rust
+let limits = SessionLimitsConfig {
+ max_ai_credits: Some(30.0),
+};
+
+let session = client
+ .create_session(
+ SessionConfig::new()
+ .approve_all_permissions()
+ .with_session_limits(limits.clone()),
+ )
+ .await?;
+
+let resumed = client
+ .resume_session(
+ ResumeSessionConfig::new(session.id().clone())
+ .approve_all_permissions()
+ .with_session_limits(limits),
+ )
+ .await?;
+```
+
+
+
+## Observe budget events
+
+Applications can subscribe to session events to update UI when the soft cap changes or the session reaches the exhausted-budget flow.
+
+| Event type | When it is emitted | Important fields |
+|---|---|---|
+| `session.session_limits_changed` | Active session limits changed. A `null` `sessionLimits` value means no limits are active. | `sessionLimits.maxAiCredits?` |
+| `session.usage_checkpoint` | The runtime records durable aggregate usage for resume and accounting. | `totalNanoAiu`, `totalPremiumRequests?` |
+| `session_limits_exhausted.requested` | The session reached the exhausted-budget flow and needs a user decision before continuing. | `requestId`, `maxAiCredits`, `usedAiCredits` |
+| `session_limits_exhausted.completed` | The exhausted-limit prompt was resolved. | `requestId`, `response.action`, `response.additionalAiCredits?`, `response.maxAiCredits?` |
+
+Use the generated event types for the SDK language you are using. For example, TypeScript narrows by `event.type`:
+
+```typescript
+session.on((event) => {
+ if (event.type === "session_limits_exhausted.requested") {
+ showBudgetDialog({
+ requestId: event.data.requestId,
+ maxAiCredits: event.data.maxAiCredits,
+ usedAiCredits: event.data.usedAiCredits,
+ });
+ }
+});
+```
diff --git a/docs/features/streaming-events.md b/docs/features/streaming-events.md
index 423aa9e34b..3703f871d3 100644
--- a/docs/features/streaming-events.md
+++ b/docs/features/streaming-events.md
@@ -472,6 +472,24 @@ Ephemeral. Context window utilization snapshot.
| `currentTokens` | `number` | ✅ | Current tokens in the context window |
| `messagesLength` | `number` | ✅ | Current message count in the conversation |
+### `session.session_limits_changed`
+
+Session limits changed for the current accounting window. A `null` `sessionLimits` value means no limits are active.
+
+| Data Field | Type | Required | Description |
+|------------|------|----------|-------------|
+| `sessionLimits` | `SessionLimitsConfig \| null` | ✅ | Current session limits, or `null` when no limits are active |
+| `sessionLimits.maxAiCredits` | `number` | | Maximum AI Credits allowed across the session's current accounting window |
+
+### `session.usage_checkpoint`
+
+Durable aggregate usage checkpoint used to reconstruct accounting when a session is resumed.
+
+| Data Field | Type | Required | Description |
+|------------|------|----------|-------------|
+| `totalNanoAiu` | `number` | ✅ | Session-wide accumulated nano-AI units cost at checkpoint time |
+| `totalPremiumRequests` | `number` | | Total number of premium API requests used at checkpoint time |
+
### `session.task_complete`
The agent has completed its assigned task.
@@ -721,6 +739,27 @@ Ephemeral. A queued command was resolved.
|------------|------|----------|-------------|
| `requestId` | `string` | ✅ | Matches the corresponding `command.queued` |
+### `session_limits_exhausted.requested`
+
+Ephemeral. The current session budget was exhausted and the runtime needs a user decision before continuing.
+
+| Data Field | Type | Required | Description |
+|------------|------|----------|-------------|
+| `requestId` | `string` | ✅ | Use this ID when responding to the pending exhausted-limit request |
+| `maxAiCredits` | `number` | ✅ | Configured max AI Credits for the current accounting window |
+| `usedAiCredits` | `number` | ✅ | AI Credits already consumed in the current accounting window |
+
+### `session_limits_exhausted.completed`
+
+Ephemeral. A pending exhausted-limit request was resolved.
+
+| Data Field | Type | Required | Description |
+|------------|------|----------|-------------|
+| `requestId` | `string` | ✅ | Matches the corresponding `session_limits_exhausted.requested` event |
+| `response.action` | `"add" \| "set" \| "unset" \| "cancel"` | ✅ | Action selected for the exhausted-limit request |
+| `response.additionalAiCredits` | `number` | | AI Credits to add to the current max when `response.action` is `"add"` |
+| `response.maxAiCredits` | `number` | | New absolute max AI Credits when `response.action` is `"set"` |
+
## Quick reference: agentic turn flow
A typical agentic turn emits events in this order:
@@ -773,6 +812,8 @@ session.idle → Ready for next message (ephemeral)
| `session.title_changed` | ✅ | Session | `title` |
| `session.context_changed` | | Session | `cwd`, `gitRoot?`, `repository?`, `branch?` |
| `session.usage_info` | ✅ | Session | `tokenLimit`, `currentTokens`, `messagesLength` |
+| `session.session_limits_changed` | | Session | `sessionLimits` |
+| `session.usage_checkpoint` | | Session | `totalNanoAiu`, `totalPremiumRequests?` |
| `session.task_complete` | | Session | `summary?` |
| `session.shutdown` | | Session | `shutdownType`, `codeChanges`, `modelMetrics` |
| `permission.requested` | ✅ | Permission | `requestId`, `permissionRequest` |
@@ -794,5 +835,7 @@ session.idle → Ready for next message (ephemeral)
| `external_tool.completed` | ✅ | External Tool | `requestId` |
| `command.queued` | ✅ | Command | `requestId`, `command` |
| `command.completed` | ✅ | Command | `requestId` |
+| `session_limits_exhausted.requested` | ✅ | Session | `requestId`, `maxAiCredits`, `usedAiCredits` |
+| `session_limits_exhausted.completed` | ✅ | Session | `requestId`, `response.action` |
| `exit_plan_mode.requested` | ✅ | Plan Mode | `requestId`, `summary`, `planContent`, `actions` |
| `exit_plan_mode.completed` | ✅ | Plan Mode | `requestId` |
\ No newline at end of file