@preapexis/piexis 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/AGENTS.md +21 -41
  2. package/README.md +73 -139
  3. package/extensions/agents.ts +205 -0
  4. package/extensions/ask-question.ts +54 -95
  5. package/extensions/recommended-tools.ts +62 -0
  6. package/extensions/ui.ts +204 -58
  7. package/lib/agent-config.ts +55 -0
  8. package/lib/agents.ts +208 -0
  9. package/lib/question-model.ts +98 -0
  10. package/lib/question-ui.ts +214 -0
  11. package/lib/recommended-tools.ts +119 -0
  12. package/lib/subagent-runner.ts +146 -0
  13. package/lib/ui-tips.json +41 -0
  14. package/package.json +5 -13
  15. package/prompts/init.md +10 -2
  16. package/prompts/summary.md +7 -6
  17. package/schemas/agent.schema.json +48 -0
  18. package/skills/{claude-api/LICENSE.txt → caveman/LICENSE} +2 -2
  19. package/skills/caveman/LICENSE-MIT +21 -0
  20. package/skills/caveman/NOTICE +10 -0
  21. package/skills/caveman/SKILL.md +89 -0
  22. package/skills/find-skills/SKILL.md +141 -0
  23. package/skills/ponytail/LICENSE +21 -0
  24. package/skills/ponytail/NOTICE +9 -0
  25. package/skills/ponytail/SKILL.md +52 -0
  26. package/skills-lock.json +6 -0
  27. package/tests/agents.test.mjs +605 -0
  28. package/tests/ask-question-pty.test.mjs +16 -0
  29. package/tests/ask-question.test.mjs +269 -0
  30. package/tests/fixtures/agents-provider.ts +55 -0
  31. package/tests/fixtures/ask-question-pty.py +91 -0
  32. package/tests/fixtures/ask-question-terminal.mjs +69 -0
  33. package/tests/fixtures/recommended-tools-terminal.mjs +103 -0
  34. package/tests/init-workflow.test.mjs +31 -0
  35. package/tests/recommended-tools-pty.test.mjs +16 -0
  36. package/tests/recommended-tools.test.mjs +423 -0
  37. package/tests/resources.test.mjs +66 -0
  38. package/tests/skills-routing.test.mjs +7 -4
  39. package/tests/ui-tip-reload.test.mjs +70 -0
  40. package/tests/ui.test.mjs +450 -0
  41. package/tsconfig.json +1 -0
  42. package/eval/README.md +0 -72
  43. package/eval/permission-cases.json +0 -28
  44. package/eval/permissions.mjs +0 -103
  45. package/extensions/background-tasks.ts +0 -433
  46. package/extensions/background-worker.ts +0 -50
  47. package/extensions/glob.ts +0 -32
  48. package/extensions/mode.ts +0 -397
  49. package/lib/bash-approvals.ts +0 -113
  50. package/lib/bash-policy.ts +0 -164
  51. package/lib/mode-policy.ts +0 -174
  52. package/lib/path-policy.ts +0 -182
  53. package/lib/permission-classifier.ts +0 -128
  54. package/lib/permission-config.ts +0 -91
  55. package/lib/permission-denials.ts +0 -32
  56. package/lib/permission-intent.ts +0 -128
  57. package/lib/permission-taxonomy.ts +0 -15
  58. package/lib/task-broker.ts +0 -65
  59. package/lib/task-checkout.ts +0 -39
  60. package/lib/task-git.ts +0 -44
  61. package/lib/task-patch.ts +0 -106
  62. package/lib/task-permissions.ts +0 -51
  63. package/lib/task-report.ts +0 -62
  64. package/lib/task-worktree-policy.ts +0 -56
  65. package/skills/claude-api/SKILL.md +0 -572
  66. package/skills/claude-api/csharp/claude-api/README.md +0 -362
  67. package/skills/claude-api/csharp/claude-api/batches.md +0 -14
  68. package/skills/claude-api/csharp/claude-api/files-api.md +0 -25
  69. package/skills/claude-api/csharp/claude-api/streaming.md +0 -28
  70. package/skills/claude-api/csharp/claude-api/tool-use.md +0 -164
  71. package/skills/claude-api/curl/examples.md +0 -256
  72. package/skills/claude-api/curl/managed-agents.md +0 -371
  73. package/skills/claude-api/go/claude-api/README.md +0 -188
  74. package/skills/claude-api/go/claude-api/files-api.md +0 -23
  75. package/skills/claude-api/go/claude-api/streaming.md +0 -43
  76. package/skills/claude-api/go/claude-api/tool-use.md +0 -220
  77. package/skills/claude-api/go/managed-agents/README.md +0 -564
  78. package/skills/claude-api/java/claude-api/README.md +0 -238
  79. package/skills/claude-api/java/claude-api/files-api.md +0 -27
  80. package/skills/claude-api/java/claude-api/streaming.md +0 -24
  81. package/skills/claude-api/java/claude-api/tool-use.md +0 -228
  82. package/skills/claude-api/java/managed-agents/README.md +0 -443
  83. package/skills/claude-api/php/claude-api/README.md +0 -174
  84. package/skills/claude-api/php/claude-api/batches.md +0 -15
  85. package/skills/claude-api/php/claude-api/files-api.md +0 -13
  86. package/skills/claude-api/php/claude-api/streaming.md +0 -27
  87. package/skills/claude-api/php/claude-api/tool-use.md +0 -253
  88. package/skills/claude-api/php/managed-agents/README.md +0 -443
  89. package/skills/claude-api/python/claude-api/README.md +0 -565
  90. package/skills/claude-api/python/claude-api/batches.md +0 -198
  91. package/skills/claude-api/python/claude-api/files-api.md +0 -170
  92. package/skills/claude-api/python/claude-api/sdk-upgrade.md +0 -286
  93. package/skills/claude-api/python/claude-api/streaming.md +0 -179
  94. package/skills/claude-api/python/claude-api/tool-use.md +0 -627
  95. package/skills/claude-api/python/managed-agents/README.md +0 -335
  96. package/skills/claude-api/ruby/claude-api/README.md +0 -134
  97. package/skills/claude-api/ruby/claude-api/streaming.md +0 -16
  98. package/skills/claude-api/ruby/claude-api/tool-use.md +0 -41
  99. package/skills/claude-api/ruby/managed-agents/README.md +0 -394
  100. package/skills/claude-api/shared/admin-api.md +0 -179
  101. package/skills/claude-api/shared/agent-design.md +0 -101
  102. package/skills/claude-api/shared/anthropic-cli.md +0 -269
  103. package/skills/claude-api/shared/claude-platform-on-aws.md +0 -59
  104. package/skills/claude-api/shared/cost-optimization.md +0 -233
  105. package/skills/claude-api/shared/error-codes.md +0 -262
  106. package/skills/claude-api/shared/live-sources.md +0 -166
  107. package/skills/claude-api/shared/managed-agents-api-reference.md +0 -455
  108. package/skills/claude-api/shared/managed-agents-client-patterns.md +0 -220
  109. package/skills/claude-api/shared/managed-agents-core.md +0 -359
  110. package/skills/claude-api/shared/managed-agents-environments.md +0 -226
  111. package/skills/claude-api/shared/managed-agents-events.md +0 -290
  112. package/skills/claude-api/shared/managed-agents-memory.md +0 -201
  113. package/skills/claude-api/shared/managed-agents-multiagent.md +0 -260
  114. package/skills/claude-api/shared/managed-agents-onboarding.md +0 -82
  115. package/skills/claude-api/shared/managed-agents-outcomes.md +0 -108
  116. package/skills/claude-api/shared/managed-agents-overview.md +0 -77
  117. package/skills/claude-api/shared/managed-agents-scheduled-deployments.md +0 -156
  118. package/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +0 -282
  119. package/skills/claude-api/shared/managed-agents-tools.md +0 -504
  120. package/skills/claude-api/shared/managed-agents-webhooks.md +0 -143
  121. package/skills/claude-api/shared/model-migration.md +0 -1872
  122. package/skills/claude-api/shared/models.md +0 -142
  123. package/skills/claude-api/shared/platform-availability.md +0 -53
  124. package/skills/claude-api/shared/prompt-audit.md +0 -223
  125. package/skills/claude-api/shared/prompt-caching.md +0 -292
  126. package/skills/claude-api/shared/token-counting.md +0 -56
  127. package/skills/claude-api/shared/tool-use-concepts.md +0 -526
  128. package/skills/claude-api/typescript/claude-api/README.md +0 -405
  129. package/skills/claude-api/typescript/claude-api/batches.md +0 -106
  130. package/skills/claude-api/typescript/claude-api/files-api.md +0 -98
  131. package/skills/claude-api/typescript/claude-api/streaming.md +0 -178
  132. package/skills/claude-api/typescript/claude-api/tool-use.md +0 -586
  133. package/skills/claude-api/typescript/managed-agents/README.md +0 -359
  134. package/tests/auto-permissions.test.mjs +0 -315
  135. package/tests/background-tasks.test.mjs +0 -27
  136. package/tests/background-worker.test.mjs +0 -60
  137. package/tests/bash-permissions.test.mjs +0 -276
  138. package/tests/glob.test.mjs +0 -31
  139. package/tests/mode.test.mjs +0 -62
  140. package/tests/permission-evaluation.test.mjs +0 -70
  141. package/tests/permission-foundation.test.mjs +0 -194
  142. package/tests/permission-policy.test.mjs +0 -158
  143. package/tests/runtime/README.md +0 -51
  144. package/tests/runtime/harness.mjs +0 -190
  145. package/tests/runtime/permissions.test.mjs +0 -192
  146. package/tests/runtime/tui.test.mjs +0 -60
  147. package/tests/task-integration.test.mjs +0 -324
  148. package/tests/task-permissions.test.mjs +0 -178
  149. package/tests/task-worktree-policy.test.mjs +0 -53
@@ -1,226 +0,0 @@
1
- # Managed Agents - Environments & Resources
2
-
3
- ## Environments
4
-
5
- Creating a session requires an `environment_id`. Environments are **reusable configuration templates** for spinning up containers in Anthropic's infrastructure - you might create different environments for different use cases (e.g. data visualization vs web development, with different package sets). Anthropic handles scaling, container lifecycle, and work orchestration.
6
-
7
- **Environment names must be unique.** Creating an environment with an existing name returns 409.
8
-
9
- ### Networking
10
-
11
- | Network Policy | Description |
12
- | ---------------- | ------------------------------------------------------------- |
13
- | `unrestricted` | Full egress (except legal blocklist) |
14
- | `limited` | Deny-by-default; opt in via `allowed_hosts` / `allow_package_managers` / `allow_mcp_servers` |
15
-
16
- ```json
17
- {
18
- "networking": {
19
- "type": "limited",
20
- "allow_package_managers": true,
21
- "allow_mcp_servers": true,
22
- "allowed_hosts": ["api.example.com"]
23
- }
24
- }
25
- ```
26
-
27
- All three `limited` fields are optional. `allow_package_managers` (default `false`) permits PyPI/npm/etc.; `allow_mcp_servers` (default `false`) permits the agent's configured MCP server endpoints without listing them in `allowed_hosts`.
28
-
29
- **MCP caveat:** Under `limited` networking, either set `allow_mcp_servers: true` or add each MCP server domain to `allowed_hosts`. Otherwise the container can't reach them and tools silently fail.
30
-
31
- **Packages caveat:** Under `limited` networking, `packages` requires `allow_package_managers: true`; otherwise the request fails with a 400. Listing the registry in `allowed_hosts` is not enough.
32
-
33
- **`networking` does not govern `web_search` / `web_fetch`.** Those tools run on Anthropic's servers (in cloud *and* self-hosted environments), so `limited` egress and `allowed_hosts` don't restrict them. To restrict the sites they can reach, set `allowed_domains` / `blocked_domains` on the tool's `configs` entry in the agent toolset - see `shared/managed-agents-tools.md` § Web search & web fetch settings.
34
-
35
- ### Creating an environment
36
-
37
- The SDK adds `managed-agents-2026-04-01` automatically. TypeScript:
38
-
39
- ```ts
40
- const env = await client.beta.environments.create({
41
- name: "my_env",
42
- config: {
43
- type: "cloud",
44
- networking: { type: "unrestricted" },
45
- },
46
- });
47
- ```
48
-
49
- ### Self-hosted sandboxes
50
-
51
- To run tool execution in **your own infrastructure** instead of Anthropic's, set `config: {type: "self_hosted"}` - the agent loop stays on Anthropic's side, but `bash` / file ops / code execute in a container you control via an outbound-polling worker. The `networking` block does not apply (you control egress). Resource mounting (`file`, `github_repository`) and memory stores behave differently - see `shared/managed-agents-self-hosted-sandboxes.md` for the worker, credentials, and cloud-vs-self-hosted comparison.
52
-
53
- ### Environment CRUD
54
-
55
- | Operation | Method | Path | Notes |
56
- | ---------------- | -------- | ------------------------------------------ | ----- |
57
- | Create | `POST` | `/v1/environments` | |
58
- | List | `GET` | `/v1/environments` | Paginated (`limit`, `after_id`, `before_id`) |
59
- | Get | `GET` | `/v1/environments/{id}` | |
60
- | Update | `POST` | `/v1/environments/{id}` | Changes apply only to **new** containers; existing sessions keep their original config |
61
- | Delete | `DELETE` | `/v1/environments/{id}` | Returns 204. |
62
- | Archive | `POST` | `/v1/environments/{id}/archive` | Makes it **read-only**; existing sessions continue, new sessions cannot reference it. No unarchive - terminal state. |
63
-
64
- ---
65
-
66
- ## Resources
67
-
68
- Attach files, GitHub repositories, and memory stores to a session. Resources are resolved during session creation, so a bad `file_id` or an unreachable repo surfaces on the create call rather than mid-run. Creating a session does **not** by itself start work or provision the sandbox - without `initial_events` the session is only registered, and the sandbox comes up when the session first needs it (see `shared/managed-agents-core.md` -> Seeding a session with `initial_events`). Max **999 file resources** per session. Multiple GitHub repositories per session are supported. For `type: "memory_store"` resources (persistent cross-session memory - max 8 per session), see `shared/managed-agents-memory.md`.
69
-
70
- ### File Uploads (input - host -> agent)
71
-
72
- Upload a file first via the Files API, then reference by `file_id` + `mount_path`:
73
-
74
- ```ts
75
- // 1. Upload
76
- const file = await client.beta.files.upload({
77
- file: fs.createReadStream("data.csv"),
78
- purpose: "agent",
79
- });
80
-
81
- // 2. Attach as a session resource
82
- const session = await client.beta.sessions.create({
83
- agent: agent.id,
84
- environment_id: envId,
85
- resources: [
86
- { type: "file", file_id: file.id, mount_path: "/workspace/data.csv" }
87
- ],
88
- });
89
- ```
90
-
91
- **`mount_path` is required** and must be absolute. Parent directories are created automatically. Agent working directory defaults to `/workspace`. Files are mounted read-only - the agent writes modified versions to new paths.
92
-
93
- ### Session outputs (output - agent -> host)
94
-
95
- The agent can write files to `/mnt/session/outputs/` during a session. These are automatically captured by the Files API and can be listed and downloaded afterwards:
96
-
97
- ```ts
98
- // After the turn completes, list output files scoped to this session:
99
- for await (const f of client.beta.files.list({
100
- scope_id: session.id,
101
- betas: ["managed-agents-2026-04-01"],
102
- })) {
103
- console.log(f.filename, f.size_bytes);
104
- const resp = await client.beta.files.download(f.id);
105
- const text = await resp.text();
106
- }
107
- ```
108
-
109
- **Requirements:**
110
- - The `write` tool (or `bash`) must be enabled for the agent to create output files.
111
- - Session-scoped `files.list` / `files.download` captures outputs written to `/mnt/session/outputs/`.
112
- - The filter parameter is **`scope_id`** (REST query param `?scope_id=<session_id>`). The SDK's files resource auto-adds only the `files-api-2025-04-14` header, so pass `betas: ["managed-agents-2026-04-01"]` explicitly (or both headers on raw HTTP) - without it the API may reject `scope_id` as an unknown field. Requires `@anthropic-ai/sdk` >= 0.88.0 / `anthropic` (Python) >= 0.92.0 - older versions don't type `scope_id`. The `ant` CLI does **not** expose this flag yet; use the SDK or curl.
113
- - Pass the session ID returned by `sessions.create()` verbatim (e.g. `sesn_011CZx...`) - the API validates the prefix.
114
- - There's a brief indexing lag (~1-3s) between `session.status_idle` and output files appearing in `files.list`. Retry once or twice if empty.
115
-
116
- > **Fallback when `scope_id` filtering is unavailable** (older SDK, or endpoint returns an error): send a follow-up `user.message` asking the agent to `read` each file under `/mnt/session/outputs/` and return the contents. The agent streams the file bodies back as `agent.message` text. This works for text files only and costs output tokens - use it to unblock, not as the primary path.
117
-
118
- This gives you a bidirectional file bridge: upload reference data in, download agent artifacts out.
119
-
120
- ### GitHub Repositories
121
-
122
- Clones a GitHub repository into the session container during initialization, before the agent begins execution. The agent can read, edit, commit, and push via `bash` (`git`). Multiple repositories per session are supported - add one `resources` entry per repo. Repositories are cached, so future sessions that use the same repository start faster.
123
-
124
- Mounting a repository also loads any skills stored in its root `.claude/skills` directory - discovered once per session, from the repository state checked out at session start (cloud sandboxes only). See `shared/managed-agents-tools.md` -> Skills from a GitHub repository.
125
-
126
- Repositories are attached for the lifetime of the session - to change which repositories are mounted, create a new session. You **can** rotate a repository's `authorization_token` on a running session via `client.beta.sessions.resources.update(resource_id, {session_id, authorization_token})`; the resource `id` is returned at session creation and by `resources.list()`.
127
-
128
- **Fields:**
129
-
130
- | Field | Required | Notes |
131
- |---|---|---|
132
- | `type` | Yes | `"github_repository"` |
133
- | `url` | Yes | The GitHub repository URL |
134
- | `authorization_token` | Yes | GitHub Personal Access Token with repository access. **Never echoed in API responses.** |
135
- | `mount_path` | No | Path where the repository will be cloned. Defaults to `/workspace/<repo-name>`. |
136
- | `checkout` | No | `{type: "branch", name: "..."}` or `{type: "commit", sha: "..."}`. Defaults to the repo's default branch. |
137
-
138
- **Token permission levels** (fine-grained PATs):
139
- - `Contents: Read` - clone only
140
- - `Contents: Read and write` - push changes and create pull requests
141
-
142
- **How auth works:** `authorization_token` is never placed inside the container. `git pull` / `git push` and GitHub REST calls against the attached repository are routed through an Anthropic-side git proxy that injects the token after the request leaves the sandbox. Code running in the container - including anything the agent writes - cannot read or exfiltrate it.
143
-
144
- > Important: **To generate pull requests** you also need GitHub **MCP server** access - the `github_repository` resource gives filesystem + git access only. See `shared/managed-agents-tools.md` -> MCP Servers. The PR workflow is: edit files in the mounted repo -> push branch via `bash` (authenticated via the git proxy using `authorization_token`) -> create PR via the MCP `create_pull_request` tool (authenticated via the vault).
145
-
146
- **TypeScript:**
147
-
148
- ```ts
149
- // 1. Create the agent - declare GitHub MCP (no auth here)
150
- const agent = await client.beta.agents.create(
151
- {
152
- name: 'GitHub Agent',
153
- model: 'claude-opus-5',
154
- mcp_servers: [
155
- { type: 'url', name: 'github', url: 'https://api.githubcopilot.com/mcp/' },
156
- ],
157
- tools: [
158
- { type: 'agent_toolset_20260401', default_config: { enabled: true } },
159
- { type: 'mcp_toolset', mcp_server_name: 'github' },
160
- ],
161
- },
162
- );
163
-
164
- // 2. Start a session - attach vault for MCP auth + mount the repo
165
- const session = await client.beta.sessions.create({
166
- agent: agent.id,
167
- environment_id: envId,
168
- vault_ids: [vaultId], // vault contains the GitHub MCP OAuth credential
169
- resources: [
170
- {
171
- type: 'github_repository',
172
- url: 'https://github.com/owner/repo',
173
- authorization_token: process.env.GITHUB_TOKEN, // repo clone token (!= MCP auth)
174
- checkout: { type: 'branch', name: 'main' },
175
- },
176
- ],
177
- });
178
- ```
179
-
180
- **Python:**
181
-
182
- ```python
183
- import os
184
-
185
- agent = client.beta.agents.create(
186
- name="GitHub Agent",
187
- model="claude-opus-5",
188
- mcp_servers=[{
189
- "type": "url",
190
- "name": "github",
191
- "url": "https://api.githubcopilot.com/mcp/",
192
- }],
193
- tools=[
194
- {"type": "agent_toolset_20260401", "default_config": {"enabled": True}},
195
- {"type": "mcp_toolset", "mcp_server_name": "github"},
196
- ],
197
- )
198
-
199
- session = client.beta.sessions.create(
200
- agent=agent.id,
201
- environment_id=env_id,
202
- vault_ids=[vault_id], # vault contains the GitHub MCP OAuth credential
203
- resources=[{
204
- "type": "github_repository",
205
- "url": "https://github.com/owner/repo",
206
- "authorization_token": os.environ["GITHUB_TOKEN"], # repo clone token (!= MCP auth)
207
- "checkout": {"type": "branch", "name": "main"},
208
- }],
209
- )
210
- ```
211
-
212
- ---
213
-
214
- ## Files API
215
-
216
- Upload and manage files for use as session resources, and download files the agent wrote to `/mnt/session/outputs/`.
217
-
218
- | Operation | Method | Path | SDK |
219
- | ---------------- | -------- | ------------------------------------- | --- |
220
- | Upload | `POST` | `/v1/files` | `client.beta.files.upload({ file })` |
221
- | List | `GET` | `/v1/files?scope_id=...` | `client.beta.files.list({ scope_id, betas: ["managed-agents-2026-04-01"] })` |
222
- | Get Metadata | `GET` | `/v1/files/{id}` | `client.beta.files.retrieveMetadata(id)` |
223
- | Download | `GET` | `/v1/files/{id}/content` | `client.beta.files.download(id)` -> `Response` |
224
- | Delete | `DELETE` | `/v1/files/{id}` | `client.beta.files.delete(id)` |
225
-
226
- The `scope_id` filter on List scopes the results to files written to `/mnt/session/outputs/` by that session. Without the filter, you get all files uploaded to your account.
@@ -1,290 +0,0 @@
1
- # Managed Agents - Events & Steering
2
-
3
- ## Events
4
-
5
- ### Sending Events
6
-
7
- Send events to a session via `POST /v1/sessions/{id}/events`.
8
-
9
- | Event Type | When to Send |
10
- | ------------------------- | --------------------------------------------------- |
11
- | `user.message` | Send a user message |
12
- | `user.interrupt` | Interrupt the agent while it's running |
13
- | `user.tool_confirmation` | Approve/deny a tool call that paused for approval (`always_ask`, or `auto` when the server reached no determination) |
14
- | `user.custom_tool_result` | Provide result for a custom tool call |
15
- | `user.define_outcome` | Start a rubric-graded iterate loop - see `shared/managed-agents-outcomes.md` |
16
- | `system.message` | Append privileged system-level context for this turn and every turn after it; see § Adding system context mid-session |
17
-
18
- #### Adding system context mid-session (`system.message`)
19
-
20
- The `system` field on the agent definition sets the top-level system prompt and is fixed for the session's lifetime. A `system.message` event **appends** to the session's system context as a `role: "system"` turn - it does not replace that prompt. The content applies to the accompanying turn and all subsequent turns. Use it for a different persona, revised constraints, or runtime-fetched context that should shape behavior going forward:
21
-
22
- ```python
23
- client.beta.sessions.events.send(
24
- session.id,
25
- events=[
26
- {
27
- "type": "system.message",
28
- "content": [
29
- {"type": "text", "text": "The user's current timezone is America/New_York."},
30
- ],
31
- },
32
- ],
33
- )
34
- ```
35
-
36
- Constraints:
37
-
38
- - **Model-gated: Claude Opus 5, Claude Opus 4.8, Claude Sonnet 5, Claude Fable 5.1, and Claude Mythos 5.1.** Only the agent's **primary** model is checked - `system.message` lands on the primary thread only, so subagent models are not considered. On an unsupported primary model the event is rejected with a `model_does_not_support_mid_conversation_system` validation error.
39
- - **While the session is idle with `stop_reason: requires_action`** (blocked on `user.custom_tool_result` / `user.tool_confirmation`), a `system.message` is accepted **only when it trails a tool result event in the same request**. Sent on its own - or alongside a `user.message` - it is rejected until the pending tool events are resolved.
40
- - `content` accepts 1-1000 text items.
41
-
42
- ### Receiving Events
43
-
44
- Three methods:
45
-
46
- 1. **Streaming (SSE)**: `GET /v1/sessions/{id}/events/stream` - real-time Server-Sent Events. **Long-lived** - the server sends periodic heartbeats to keep the connection alive.
47
- 2. **Polling**: `GET /v1/sessions/{id}/events` - paginated event list (query params: `limit` default 1000, `page`). **Returns immediately** - this is a plain paginated GET, not a long-poll.
48
- 3. **Webhooks**: Anthropic POSTs session state transitions to your HTTPS endpoint - thin payloads (IDs only), HMAC-signed, Console-registered. See `shared/managed-agents-webhooks.md`.
49
-
50
- **No-code inspection - the Console session viewer** (Console sidebar -> **Managed Agents** -> **Sessions**; Developers and Admins only). Point users here for debugging before they parse the stream themselves: a session list (ID, name, status, agent, tokens in/out, cost; filter by status/created, search by ID); a **timeline minimap** with one lane per thread in multiagent sessions; the **transcript** grouped by model request (thinking, tool calls with inputs/results, streaming text) with a **Filter events** box (matches ID, type, tool name, or text; Enter steps between matches) and copy/download-as-JSON (filtered export when a filter is active); and an **Inspector** side panel (toggle with `d`) with five tabs - **Session** (details, metadata, cumulative-cost chart vs. budget), **Events** (raw events in server order, JSON per event, plus a **Deltas** view for messages that streamed while the page was open), **Tools** (every configured tool with call counts, failures, median duration; jump to any call), **Resources** (mounted files, repos, memory stores with per-session memory changes, `/mnt/session/outputs` files, skills under `/workspace/skills`), **Threads** (status, context size, cost per thread; context-size chart for the current thread; switch threads). Deep-link with `?event={event_id}` on the session URL - handy to include in error reports alongside the Console link from `shared/managed-agents-core.md`.
51
-
52
- All **persisted** events carry `id`, `type`, and `processed_at` (ISO 8601), set when the event finishes processing. On events you send, `processed_at` is `null` while the event is still queued behind earlier ones - **except** `user.define_outcome`, `user.custom_tool_result`, and `user.tool_result`, which are processed on receipt and echoed back with `processed_at` already populated. The stream-only `event_start` / `event_delta` preview events (see § Live previews) carry only the `id` of the event they preview.
53
-
54
- > Warning: **Robust polling (raw HTTP).** If you bypass the SDK and roll your own poll loop, don't rely on `requests` or `httpx` timeouts as wall-clock caps - they're **per-chunk** read timeouts, reset every time a byte arrives. A trickling response (heartbeats, a wedged chunked-encoding body, a misbehaving proxy) can keep the call blocked indefinitely even with `timeout=(5, 60)` or `httpx.Timeout(120)`. Neither library has a "total wall-clock" timeout built in. For a hard deadline: track `time.monotonic()` at the loop level and break/cancel if a single request exceeds your budget (e.g. via a watchdog thread, or `asyncio.wait_for()` around async httpx). **Prefer the SDK** - `client.beta.sessions.events.stream()` and `client.beta.sessions.events.list()` handle timeout + retry sanely.
55
- >
56
- > If `GET /v1/sessions/{id}/events` (paginated) ever hangs after headers, you've likely hit `GET /v1/sessions/{id}/events/stream` by mistake or a server-side stall - report it; don't treat it as a client-config problem.
57
-
58
- ### Event Types (Received)
59
-
60
- Event types use dot notation, grouped by namespace:
61
-
62
- | Event Type | Description |
63
- | --- | --- |
64
- | `agent.message` | Agent text output |
65
- | `agent.thinking` | Progress signal that the agent is thinking - it does **not** carry the thinking content |
66
- | `agent.tool_use` | Agent used a built-in tool (`agent_toolset_20260401`). Carries `evaluated_permission` (`allow`/`ask`/`deny`) and usually `evaluation` - see `shared/managed-agents-tools.md` § `evaluated_permission` and `evaluation` |
67
- | `agent.tool_result` | Result from a built-in tool |
68
- | `agent.mcp_tool_use` | Agent used an MCP tool. Carries `evaluated_permission` and usually `evaluation`, same as `agent.tool_use` |
69
- | `agent.mcp_tool_result` | Result from an MCP tool |
70
- | `agent.custom_tool_use` | Agent invoked a custom tool - session goes idle, you respond with `user.custom_tool_result` |
71
- | `agent.thread_context_compacted` | Conversation context was compacted |
72
- | `session.status_idle` | Agent has finished the current task, and is awaiting input. It's either waiting for input to continue working via a `user.message`, blocked awaiting a `user.custom_tool_result` or `user.tool_confirmation`, or paused because the session budget cap was reached. The `stop_reason` attached contains more information about why the Agent has stopped working. |
73
- | `session.status_running` | Session has starting running, and the Agent is actively doing work. |
74
- | `session.status_rescheduled` | Session is (re)scheduling after a retryable error has occurred, ready to be picked up by the orchestration system. |
75
- | `session.status_terminated` | Session ended and is irreversibly unusable - **on completion or on error**, not error-only. |
76
- | `session.updated` | A session update changed at least one field - carries only the changed fields (a budget removal carries `budget: null`) |
77
- | `session.usage` | Snapshot of the session's cumulative usage and tracked list cost - see § Reaching a session budget below |
78
- | `session.error` | Error occurred during processing |
79
- | `span.model_request_start` | Model inference started |
80
- | `span.model_request_end` | Model inference completed |
81
- | `span.outcome_evaluation_start` / `_ongoing` / `_end` | Grader progress for outcome-oriented sessions - see `shared/managed-agents-outcomes.md` |
82
- | `session.thread_created` | Subagent thread spawned (multiagent), or an advisor consultation started (thread name `anthropic.advisor`) - see `shared/managed-agents-multiagent.md` |
83
- | `session.thread_status_running` / `_idle` / `_rescheduled` / `_terminated` | Thread status transitions - mostly seen in multiagent sessions, but a single-agent session's primary thread also emits `_idle` when pausing at a session budget (§ Reaching a session budget). `_idle` carries `stop_reason`. |
84
- | `agent.thread_message_sent` / `_received` | Cross-thread message, carries `to_session_thread_id` / `from_session_thread_id` (multiagent) |
85
-
86
- The stream also echoes back user-sent events (`user.message`, `user.interrupt`, `user.tool_confirmation`, `user.tool_result`, `user.custom_tool_result`, `user.define_outcome`) - except a `user.interrupt` sent while the session is paused at its budget, which is accepted and ignored and never appears (§ Reaching a session budget).
87
-
88
- Stream-only delta preview events (`event_start`, `event_delta`) are the one exception to the `{domain}.{action}` naming convention - see § Live previews below; they never appear in `GET /v1/sessions/{id}/events`.
89
-
90
- ---
91
-
92
- ## Live previews
93
-
94
- By default, assistant text reaches the stream as buffered `agent.message` events - emitted only after the model request that produced them finishes. **Live previews** let you render that text incrementally while the model is still generating. The buffered `agent.message` is always the authoritative record; a client that ignores previews still receives a complete, correct stream. The wire format is **not** Messages-API streaming: the delta type is `content_delta`, not `content_block_delta`, so Messages-API accumulator code does not carry over unchanged.
95
-
96
- **Opt in per stream connection** by adding the `event_deltas[]` query parameter, repeated once per event type to preview. Accepted values: `agent.message`, `agent.thinking` - any other value returns a 400, as does a request with more than 100 values. **Both stream endpoints accept it:** the session-level stream (`GET /v1/sessions/{id}/events/stream`) and each session thread's own stream (`GET /v1/sessions/{sid}/threads/{tid}/stream`). In a shell, quote the URL or percent-encode the brackets as `%5B%5D` - bare `[]` is a glob pattern.
97
-
98
- **Previews are thread-scoped.** A connection previews only the thread it is reading. A child thread's previews are delivered on that child's stream and are *never* cross-posted to the session-level stream, whose previews stay scoped to the primary thread. To watch a subagent's text as the model generates it, open that subagent's thread stream - see `shared/managed-agents-multiagent.md`. Run one accumulator instance per connection.
99
-
100
- ```python
101
- stream = client.beta.sessions.events.stream(
102
- session_id=session.id,
103
- event_deltas=["agent.message"],
104
- )
105
- ```
106
-
107
- When a previewed event begins, the stream emits an `event_start` carrying the upcoming event's `type` and `id`; for `agent.message` it's followed by `event_delta` events carrying incremental text:
108
-
109
- ```json
110
- {"type": "event_start", "event": {"type": "agent.message", "id": "sevt_01abc..."}}
111
- {"type": "event_delta", "event_id": "sevt_01abc...", "delta": {"type": "content_delta", "index": 0, "content": {"type": "text", "text": "Here is the summary"}}}
112
- ```
113
-
114
- `event_start` and `event_delta` have no `id` or `processed_at` of their own - the only identifier they carry is the `id` of the event they preview. For `agent.thinking`, **only** the `event_start` is emitted (a "thinking has started" signal) - no deltas follow, and the buffered `agent.thinking` that concludes the preview carries no thinking content either. It is a progress signal, not a content carrier; there is nothing to read out of it.
115
-
116
- **Accumulate-and-reconcile pattern.** Treat the preview as a scratch buffer keyed by `(event_id, index)`. On `event_start`, create an empty entry for the announced `id`. On each `event_delta`, append `delta.content.text` to `(event_id, delta.index)` and render the running text. When the buffered `agent.message` arrives, match it by `id`, **discard the accumulated preview**, and render the message's content instead. The identifiers always line up: `event_start.event.id`, every `event_delta.event_id`, and the buffered event's `id` are the same value. On a normal turn the order is fixed: `session.status_running` -> `span.model_request_start` -> `event_start` -> `event_delta`* -> buffered `agent.message` -> `span.model_request_end`. If the turn errors or is interrupted the buffered event may never arrive, but `span.model_request_end` still does - close any unreconciled preview when you see it. Python/TypeScript/Go SDKs ship an accumulator helper that implements this; in other SDKs apply the manual pattern to the generated event types.
117
-
118
- **Two guarantees the pattern relies on:** concatenating a preview's deltas in arrival order, keyed by `(event_id, index)`, yields a *prefix* of `content[index].text` in the buffered event (a prefix, not necessarily the whole text - deltas may be shed under load); and a connection emits at most one `event_start` per `event_id`, with the buffered event as the last thing that connection delivers for that `id`.
119
-
120
- **Limitations:**
121
- - **Best effort** - under load the server may shed deltas for an event; you receive a contiguous prefix and then no further deltas for that event. The buffered `agent.message` still arrives complete. Never treat an accumulated preview as final.
122
- - **No replay on reconnect** - deltas are delivered only to the connection that opted in, while it's open; this holds for the session-level stream and each thread stream alike. A connection opened after a model request started receives no deltas for that in-flight event. After a drop, follow the consolidation pattern in § Reconnecting after a dropped stream - the history fetch returns any buffered events emitted during the gap; missed deltas cannot be re-requested.
123
- - **One thread, text only** - previews cover assistant text on the thread the connection is reading. Tool use, tool results, MCP results, and activity on any *other* thread are never previewed on that connection.
124
- - **Never persisted** - `event_start` / `event_delta` exist only on the live SSE stream, never in `GET /v1/sessions/{id}/events` or any thread's event history.
125
-
126
- **Troubleshooting:**
127
-
128
- | You see | What it means |
129
- | --- | --- |
130
- | Buffered events but no `event_start` / `event_delta` | This connection didn't opt in (`event_deltas[]` is per connection, not per session), or the turn ran on a different thread. List `GET /v1/sessions/{sid}/threads` to find which one ran. |
131
- | 404 on the stream URL | Wrong path or ID, or the request carries no managed-agents beta header - the thread endpoints are beta-gated, so without it they don't exist. The thread path is `/threads/{tid}/stream`, **not** `/threads/{tid}/events/stream` (which doesn't exist) and not `/events/stream` (session level only). |
132
- | 400 naming `event_deltas` | Only `agent.message` and `agent.thinking` are accepted, max 100 values. |
133
-
134
- ---
135
-
136
- ## Steering Patterns
137
-
138
- Practical patterns for driving a session via the events surface.
139
-
140
- ### Stream-first ordering
141
-
142
- **Open the stream before sending events.** The stream only delivers events that occur *after* it's opened - it does not replay current state or historical events. If you send a message first and open the stream second, early events (including fast status transitions) arrive buffered in a single batch and you lose the ability to react to them in real time.
143
-
144
- ```ts
145
- // Correct - stream and send concurrently
146
- const [response] = await Promise.all([
147
- streamEvents(sessionId), // opens SSE connection
148
- sendMessage(sessionId, text),
149
- ]);
150
-
151
- // Wrong - events before stream opens arrive as a single buffered batch
152
- await sendMessage(sessionId, text);
153
- const response = await streamEvents(sessionId);
154
- ```
155
-
156
- **For full history,** use `GET /v1/sessions/{id}/events` (paginated list) - the stream only gives you live events from connection onward.
157
-
158
- ### Reconnecting after a dropped stream
159
-
160
- **The SSE stream has no replay.** If your connection drops (httpx read timeout, network blip) and you reconnect, you only get events emitted *after* reconnection. Any events emitted during the gap are lost from the stream.
161
-
162
- **The consolidation pattern:** on every (re)connect, overlap the stream with a history fetch and dedupe by event ID:
163
-
164
- ```python
165
- def connect_with_consolidation(client, session_id):
166
- # 1. Open the SSE stream first
167
- stream = client.beta.sessions.events.stream(session_id=session_id)
168
-
169
- # 2. Fetch history to cover any gap
170
- history = client.beta.sessions.events.list(
171
- session_id=session_id,
172
- )
173
-
174
- # 3. Yield history first, then stream - dedupe by event.id
175
- seen = set()
176
- for ev in history.data:
177
- seen.add(ev.id)
178
- yield ev
179
- for ev in stream:
180
- if ev.id not in seen:
181
- seen.add(ev.id)
182
- yield ev
183
- ```
184
-
185
- ### Message queuing
186
-
187
- **You don't have to wait for a response before sending the next message.** User events are queued server-side and processed in order. This is useful for chat bridges where the user sends rapid follow-ups:
188
-
189
- ```ts
190
- // All three go into one session; agent processes them in order
191
- await sendMessage(sessionId, "Summarize the README");
192
- await sendMessage(sessionId, "Actually also check the CONTRIBUTING guide");
193
- await sendMessage(sessionId, "And compare the two");
194
- // Stream once - agent responds to all three as a coherent turn
195
- ```
196
-
197
- Events can be sent up to the Session at any time. There is no need to wait on a specific session status to enqueue new events via `client.beta.sessions.events.send()`. One exception: a session paused at its budget (`stop_reason: budget_reached`) accepts only settle events - a `user.message` there is a 400. See § Reaching a session budget.
198
-
199
- ### Interrupt
200
-
201
- A `user.interrupt` event **jumps the queue** (ahead of any pending user messages) and forces the session into `idle`. Exception: while the session is paused at its budget, an interrupt is accepted and ignored - it is never persisted and changes nothing (§ Reaching a session budget). Use this for "stop" / "nevermind" / "cancel" commands:
202
-
203
- ```ts
204
- await client.beta.sessions.events.send(sessionId, {
205
- events: [{ type: 'user.interrupt' }],
206
- });
207
- ```
208
-
209
- The agent stops mid-task. It does not see the interrupt as a message - it just halts. Send a follow-up `user` event to explain what to do instead. If an outcome is active, the interrupt also marks `span.outcome_evaluation_end.result: "interrupted"` (see `shared/managed-agents-outcomes.md`) - though not at a budget pause, where the interrupt is accepted and ignored (see § Reaching a session budget).
210
-
211
- **The interrupted turn ends with `stop_reason: end_turn`** - the same value a turn that finishes on its own carries. There is no interruption-specific stop reason, so a drain loop can't distinguish the two from `stop_reason` alone; track that you sent the interrupt.
212
-
213
- **Against an already-`idle` session an interrupt is normally a no-op.** The exception is a session on a self-hosted environment whose worker failed the claimed work item (a memory-store mount error, for instance): it sits `idle` with `stop_reason: requires_action` and no error event, and `user.interrupt` re-queues the work for the next worker claim (`shared/managed-agents-self-hosted-sandboxes.md` § Memory stores -> Troubleshooting).
214
-
215
- **In a multiagent session, omitting `session_thread_id` interrupts every non-archived thread, including the primary** - it is not primary-only. Pass `session_thread_id` to stop one thread. See `shared/managed-agents-multiagent.md`.
216
-
217
- > **Note**: Interrupt events may have empty IDs in the current implementation. When troubleshooting, use the `processed_at` timestamp along with surrounding event IDs. (Not applicable to an interrupt sent at the budget cap - that event is never persisted, so there is nothing to locate.)
218
-
219
- ### Reaching a session budget
220
-
221
- A session created with a budget (see `shared/managed-agents-core.md` § Session budgets) pauses instead of overspending. Before every model request the platform checks whether consumed list cost has reached the cap and pauses the thread if it has, and the session goes idle with `stop_reason: budget_reached` rather than terminating. On the stream, the pause arrives as three events, in order:
222
-
223
- 1. `session.thread_status_idle` with `stop_reason: budget_reached`, for each thread as it pauses. When a thread's final request both crosses the cap and finishes its turn, that thread reports `stop_reason: end_turn` while the session still reports `budget_reached` - key on the **session-level** `stop_reason`, not thread-level ones, to detect the pause.
224
- 2. `session.usage` - a snapshot of the session's cumulative usage and tracked list cost.
225
- 3. `session.status_idle` with `stop_reason: budget_reached`. The `session.usage` event always immediately precedes this idle.
226
-
227
- While at the cap the session accepts **only settle events** (`user.tool_confirmation`, `user.tool_result`, `user.custom_tool_result`, `user.interrupt`); anything that starts new work, including `user.message`, is a 400 naming that list. A `user.interrupt` sent while the session is paused at its budget (all threads paused at the cap) is accepted and ignored: it does not appear in the event list and changes nothing. Raise or remove the budget to continue. When one thread waits on a tool ask and another is paused at the cap, the session-level `stop_reason` is `requires_action`, not `budget_reached` - settling the ask doesn't trigger a model request, so respond as usual.
228
-
229
- **No event resumes a session paused at its cap.** Update the session's budget instead: change it to a value above the consumed list cost (higher or lower than the old cap), or remove it with `"budget": null`. An accepted update resumes the paused work automatically.
230
-
231
- **`session.usage`** carries the session's cumulative token totals, `list_cost` (`{amount, currency}`, rounded to the nearest cent), `active_seconds` (concurrent-thread overlap counted once - the figure runtime cost is priced on), `server_tool_use` counts (`web_search_requests`, and `web_fetch_requests` - informational, currently always 0 since web fetch is not metered), and an echo of the session's `budget` when one is set. It appears in the events list and the session stream - a stream reader sees the final cost of the work that hit the cap without an extra fetch; child threads' own streams do not carry it. The same totals live on the session object's `usage` field, and each thread's own `usage` carries per-thread `list_cost` and `active_seconds` - but per-thread costs do **not** sum to the session total: the session figure additionally includes session running time and each figure is rounded independently, so the session figure is the authoritative one. To enforce a spend limit, set a budget rather than polling usage and interrupting the session yourself - the platform's gate runs before each model request.
232
-
233
- ### Event payloads
234
-
235
- some events carry useful metadata beyond the status change itself:
236
-
237
- `session.status_idle` - includes a `stop_reason` field which elaborates on why the session stopped and what type of further action is required by the user.
238
- ```json
239
- {
240
- "id": "sevt_456",
241
- "processed_at": "2026-04-07T04:27:43.197Z",
242
- "stop_reason": {
243
- "event_ids": [
244
- "sevt_123"
245
- ],
246
- "type": "requires_action"
247
- },
248
- "type": "status_idle"
249
- }
250
- ```
251
-
252
- `span.model_request_end` contains a `model_usage` field for cost tracking and efficiency analysis:
253
-
254
- ```json
255
- {
256
- "type": "span.model_request_end",
257
- "id": "sevt_456",
258
- "is_error": false,
259
- "model_request_start_id": "sevt_123",
260
- "model_usage": {
261
- "cache_creation_input_tokens": 0,
262
- "cache_read_input_tokens": 6656,
263
- "input_tokens": 3571,
264
- "output_tokens": 727
265
- },
266
- "processed_at": "2026-04-07T04:11:32.189Z"
267
- }
268
- ```
269
-
270
- **`agent.thread_context_compacted`** - emitted when the conversation history was summarized to fit context. Includes `pre_compaction_tokens` so you know how much was squeezed:
271
-
272
- ```json
273
- {
274
- "id": "sevt_abc123",
275
- "processed_at": "2026-03-24T14:05:15.787Z",
276
- "type": "agent.thread_context_compacted"
277
- }
278
- ```
279
-
280
- ### Archive
281
-
282
- When done with a session, archive it to free resources:
283
-
284
- ```ts
285
- await client.beta.sessions.archive(sessionId);
286
- ```
287
-
288
- > Archiving a **session** is routine cleanup - sessions are per-run and disposable. **Do not generalize this to agents or environments**: those are persistent, reusable resources, and archiving them is permanent (no unarchive; new sessions cannot reference them). See `shared/managed-agents-overview.md` -> Common Pitfalls.
289
-
290
-