@preapexis/piexis 0.2.2 → 0.2.5

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/README.md +85 -131
  2. package/apps/server/src/index.ts +97 -0
  3. package/apps/server/src/session-manager.ts +95 -0
  4. package/apps/server/src/workspace.ts +39 -0
  5. package/{tsconfig.json → apps/server/tsconfig.json} +3 -3
  6. package/apps/web/dist/assets/index-D4n3_lAw.js +10 -0
  7. package/apps/web/dist/assets/index-igiK0FPY.css +1 -0
  8. package/apps/web/dist/index.html +3 -0
  9. package/bin/piexis-web.mjs +24 -0
  10. package/extensions/agents.ts +205 -0
  11. package/extensions/ask-question.ts +54 -95
  12. package/extensions/recommended-tools.ts +62 -0
  13. package/extensions/ui.ts +204 -58
  14. package/extensions/webui.ts +45 -0
  15. package/lib/agent-config.ts +55 -0
  16. package/lib/agents.ts +208 -0
  17. package/lib/question-model.ts +98 -0
  18. package/lib/question-ui.ts +214 -0
  19. package/lib/recommended-tools.ts +119 -0
  20. package/lib/subagent-runner.ts +146 -0
  21. package/lib/ui-tips.json +41 -0
  22. package/package.json +41 -14
  23. package/packages/pi-integration/src/rpc-client.ts +99 -0
  24. package/packages/shared/src/index.ts +27 -0
  25. package/prompts/init.md +10 -2
  26. package/prompts/summary.md +7 -6
  27. package/schemas/agent.schema.json +48 -0
  28. package/skills/{claude-api/LICENSE.txt → caveman/LICENSE} +2 -2
  29. package/skills/caveman/LICENSE-MIT +21 -0
  30. package/skills/caveman/NOTICE +10 -0
  31. package/skills/caveman/SKILL.md +89 -0
  32. package/skills/find-skills/SKILL.md +141 -0
  33. package/skills/ponytail/LICENSE +21 -0
  34. package/skills/ponytail/NOTICE +9 -0
  35. package/skills/ponytail/SKILL.md +52 -0
  36. package/themes/midnight.json +81 -0
  37. package/.github/workflows/publish.yml +0 -35
  38. package/AGENTS.md +0 -58
  39. package/eval/README.md +0 -72
  40. package/eval/permission-cases.json +0 -28
  41. package/eval/permissions.mjs +0 -103
  42. package/extensions/background-tasks.ts +0 -433
  43. package/extensions/background-worker.ts +0 -50
  44. package/extensions/glob.ts +0 -32
  45. package/extensions/mode.ts +0 -397
  46. package/lib/bash-approvals.ts +0 -113
  47. package/lib/bash-policy.ts +0 -164
  48. package/lib/mode-policy.ts +0 -174
  49. package/lib/path-policy.ts +0 -182
  50. package/lib/permission-classifier.ts +0 -128
  51. package/lib/permission-config.ts +0 -91
  52. package/lib/permission-denials.ts +0 -32
  53. package/lib/permission-intent.ts +0 -128
  54. package/lib/permission-taxonomy.ts +0 -15
  55. package/lib/task-broker.ts +0 -65
  56. package/lib/task-checkout.ts +0 -39
  57. package/lib/task-git.ts +0 -44
  58. package/lib/task-patch.ts +0 -106
  59. package/lib/task-permissions.ts +0 -51
  60. package/lib/task-report.ts +0 -62
  61. package/lib/task-worktree-policy.ts +0 -56
  62. package/scripts/release.mjs +0 -32
  63. package/skills/claude-api/SKILL.md +0 -572
  64. package/skills/claude-api/csharp/claude-api/README.md +0 -362
  65. package/skills/claude-api/csharp/claude-api/batches.md +0 -14
  66. package/skills/claude-api/csharp/claude-api/files-api.md +0 -25
  67. package/skills/claude-api/csharp/claude-api/streaming.md +0 -28
  68. package/skills/claude-api/csharp/claude-api/tool-use.md +0 -164
  69. package/skills/claude-api/curl/examples.md +0 -256
  70. package/skills/claude-api/curl/managed-agents.md +0 -371
  71. package/skills/claude-api/go/claude-api/README.md +0 -188
  72. package/skills/claude-api/go/claude-api/files-api.md +0 -23
  73. package/skills/claude-api/go/claude-api/streaming.md +0 -43
  74. package/skills/claude-api/go/claude-api/tool-use.md +0 -220
  75. package/skills/claude-api/go/managed-agents/README.md +0 -564
  76. package/skills/claude-api/java/claude-api/README.md +0 -238
  77. package/skills/claude-api/java/claude-api/files-api.md +0 -27
  78. package/skills/claude-api/java/claude-api/streaming.md +0 -24
  79. package/skills/claude-api/java/claude-api/tool-use.md +0 -228
  80. package/skills/claude-api/java/managed-agents/README.md +0 -443
  81. package/skills/claude-api/php/claude-api/README.md +0 -174
  82. package/skills/claude-api/php/claude-api/batches.md +0 -15
  83. package/skills/claude-api/php/claude-api/files-api.md +0 -13
  84. package/skills/claude-api/php/claude-api/streaming.md +0 -27
  85. package/skills/claude-api/php/claude-api/tool-use.md +0 -253
  86. package/skills/claude-api/php/managed-agents/README.md +0 -443
  87. package/skills/claude-api/python/claude-api/README.md +0 -565
  88. package/skills/claude-api/python/claude-api/batches.md +0 -198
  89. package/skills/claude-api/python/claude-api/files-api.md +0 -170
  90. package/skills/claude-api/python/claude-api/sdk-upgrade.md +0 -286
  91. package/skills/claude-api/python/claude-api/streaming.md +0 -179
  92. package/skills/claude-api/python/claude-api/tool-use.md +0 -627
  93. package/skills/claude-api/python/managed-agents/README.md +0 -335
  94. package/skills/claude-api/ruby/claude-api/README.md +0 -134
  95. package/skills/claude-api/ruby/claude-api/streaming.md +0 -16
  96. package/skills/claude-api/ruby/claude-api/tool-use.md +0 -41
  97. package/skills/claude-api/ruby/managed-agents/README.md +0 -394
  98. package/skills/claude-api/shared/admin-api.md +0 -179
  99. package/skills/claude-api/shared/agent-design.md +0 -101
  100. package/skills/claude-api/shared/anthropic-cli.md +0 -269
  101. package/skills/claude-api/shared/claude-platform-on-aws.md +0 -59
  102. package/skills/claude-api/shared/cost-optimization.md +0 -233
  103. package/skills/claude-api/shared/error-codes.md +0 -262
  104. package/skills/claude-api/shared/live-sources.md +0 -166
  105. package/skills/claude-api/shared/managed-agents-api-reference.md +0 -455
  106. package/skills/claude-api/shared/managed-agents-client-patterns.md +0 -220
  107. package/skills/claude-api/shared/managed-agents-core.md +0 -359
  108. package/skills/claude-api/shared/managed-agents-environments.md +0 -226
  109. package/skills/claude-api/shared/managed-agents-events.md +0 -290
  110. package/skills/claude-api/shared/managed-agents-memory.md +0 -201
  111. package/skills/claude-api/shared/managed-agents-multiagent.md +0 -260
  112. package/skills/claude-api/shared/managed-agents-onboarding.md +0 -82
  113. package/skills/claude-api/shared/managed-agents-outcomes.md +0 -108
  114. package/skills/claude-api/shared/managed-agents-overview.md +0 -77
  115. package/skills/claude-api/shared/managed-agents-scheduled-deployments.md +0 -156
  116. package/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +0 -282
  117. package/skills/claude-api/shared/managed-agents-tools.md +0 -504
  118. package/skills/claude-api/shared/managed-agents-webhooks.md +0 -143
  119. package/skills/claude-api/shared/model-migration.md +0 -1872
  120. package/skills/claude-api/shared/models.md +0 -142
  121. package/skills/claude-api/shared/platform-availability.md +0 -53
  122. package/skills/claude-api/shared/prompt-audit.md +0 -223
  123. package/skills/claude-api/shared/prompt-caching.md +0 -292
  124. package/skills/claude-api/shared/token-counting.md +0 -56
  125. package/skills/claude-api/shared/tool-use-concepts.md +0 -526
  126. package/skills/claude-api/typescript/claude-api/README.md +0 -405
  127. package/skills/claude-api/typescript/claude-api/batches.md +0 -106
  128. package/skills/claude-api/typescript/claude-api/files-api.md +0 -98
  129. package/skills/claude-api/typescript/claude-api/streaming.md +0 -178
  130. package/skills/claude-api/typescript/claude-api/tool-use.md +0 -586
  131. package/skills/claude-api/typescript/managed-agents/README.md +0 -359
  132. package/skills-lock.json +0 -359
  133. package/tests/auto-permissions.test.mjs +0 -315
  134. package/tests/background-tasks.test.mjs +0 -27
  135. package/tests/background-worker.test.mjs +0 -60
  136. package/tests/bash-permissions.test.mjs +0 -276
  137. package/tests/glob.test.mjs +0 -31
  138. package/tests/mode.test.mjs +0 -62
  139. package/tests/permission-evaluation.test.mjs +0 -70
  140. package/tests/permission-foundation.test.mjs +0 -194
  141. package/tests/permission-policy.test.mjs +0 -158
  142. package/tests/runtime/README.md +0 -51
  143. package/tests/runtime/harness.mjs +0 -190
  144. package/tests/runtime/permissions.test.mjs +0 -192
  145. package/tests/runtime/tui.test.mjs +0 -60
  146. package/tests/skills-routing.test.mjs +0 -51
  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,201 +0,0 @@
1
- # Managed Agents - Memory Stores
2
-
3
- > **Public beta.** Memory stores ship under the `managed-agents-2026-04-01` beta header; the SDK sets it automatically on all `client.beta.memory_stores.*` calls. If `client.beta.memory_stores` is missing, upgrade to the latest SDK release.
4
-
5
- Sessions are ephemeral by default - when one ends, anything the agent learned is gone. A **memory store** is a workspace-scoped collection of small text documents that persists across sessions. When a store is attached to a session (via `resources[]`), it is mounted into the container as a filesystem directory; the agent reads and writes it with the ordinary file tools, and a system-prompt note tells it the mount is there.
6
-
7
- Every mutation to a memory produces an immutable **memory version** (`memver_...`), giving you an audit trail and point-in-time rollback/redact.
8
-
9
- > Warning: **Never store credentials, API keys, or tokens in memory stores.** Memories persist across sessions and are returned verbatim into future contexts - a key written once is replayed into every later session that mounts the store. Use vault `environment_variable` credentials instead (`shared/managed-agents-tools.md` -> Vaults). If a secret has already been written, delete the memory and redact the affected versions (see "Redact a version" below).
10
-
11
- ## Object model
12
-
13
- | Object | ID prefix | Scope | Notes |
14
- | --- | --- | --- | --- |
15
- | Memory store | `memstore_...` | Workspace | Attach to sessions via `resources[]` |
16
- | Memory | `mem_...` | Store | One text file, addressed by `path` (<= 100KB each - prefer many small files) |
17
- | Memory version | `memver_...` | Memory | Immutable snapshot per mutation; `operation` in `created` / `modified` / `deleted` |
18
-
19
- ## Create a store
20
-
21
- `description` is passed to the agent so it knows what the store contains - write it for the model, not for humans.
22
-
23
- ```python
24
- store = client.beta.memory_stores.create(
25
- name="User Preferences",
26
- description="Per-user preferences and project context.",
27
- )
28
- print(store.id) # memstore_01Hx...
29
- ```
30
-
31
- Other SDKs: TypeScript `client.beta.memoryStores.create({...})`; Go `client.Beta.MemoryStores.New(ctx, ...)`. See `shared/managed-agents-api-reference.md` -> SDK Method Reference for the full per-language table.
32
-
33
- Stores support `retrieve` / `update` / `list` (with `include_archived`, `created_at_{gte,lte}` filters) / `delete` / **`archive`**. Archive makes the store read-only - existing session attachments continue, new sessions cannot reference it; no unarchive.
34
-
35
- ### Seed with content (optional)
36
-
37
- Pre-load reference material before any session runs. `memories.create` creates a memory at the given `path`; if a memory already exists there the call returns `409` (`memory_path_conflict_error`, with the `conflicting_memory_id`). The store ID is the first positional argument.
38
-
39
- ```python
40
- client.beta.memory_stores.memories.create(
41
- store.id,
42
- path="/formatting_standards.md",
43
- content="All reports use GAAP formatting. Dates are ISO-8601...",
44
- )
45
- ```
46
-
47
- ## Attach to a session
48
-
49
- Memory stores go in the session's `resources[]` array alongside `file` and `github_repository` resources (see `shared/managed-agents-environments.md` -> Resources). Memory stores attach at **session create time only** - `sessions.resources.add()` does not accept `memory_store`. Sessions on **self-hosted** environments attach them the same way (and `memory_store` is the *only* resource type those environments accept) - see the self-hosted note below.
50
-
51
- ```python
52
- session = client.beta.sessions.create(
53
- agent=agent.id,
54
- environment_id=environment.id,
55
- resources=[
56
- {
57
- "type": "memory_store",
58
- "memory_store_id": store.id,
59
- "access": "read_write", # or "read_only"; default is "read_write"
60
- "instructions": "User preferences and project context. Check before starting any task.",
61
- }
62
- ],
63
- )
64
- ```
65
-
66
- | Field | Required | Notes |
67
- | --- | --- | --- |
68
- | `type` | Yes | `"memory_store"` |
69
- | `memory_store_id` | Yes | `memstore_...` |
70
- | `access` | - | `"read_write"` (default) or `"read_only"` - enforced at the filesystem level on the cloud mount; on self-hosted sandboxes enforced by the worker's `write`/`edit` tools and by the upload path (see below) |
71
- | `instructions` | - | Session-specific guidance for this store, in addition to the store's `name`/`description`. <= 4,096 chars. |
72
-
73
- **Max 8 memory stores per session.** Attach multiple when different slices of memory have different owners or lifecycles - e.g. one read-only shared-reference store plus one read-write per-user store, or one store per end-user/team/project sharing a single agent config.
74
-
75
- ### How the agent sees it (FUSE mount)
76
-
77
- Each attached store is mounted in the session container at `/mnt/memory/<store-name>/`. The agent interacts with it using the standard file tools (`bash`, `read`, `write`, `edit`, `glob`, `grep`) - there are no dedicated memory tools. On cloud sandboxes `access: "read_only"` makes the mount read-only at the filesystem level (on self-hosted sandboxes it is enforced by the worker's `write`/`edit` tools and the upload path - see below); `"read_write"` allows the agent to create, edit, and delete files under it. A short description of each mount (name, path, `instructions`, access) is automatically injected into the system prompt so the agent knows the store exists without you having to mention it.
78
-
79
- Writes the agent makes under the mount are persisted back to the store and produce memory versions just like host-side `memories.update` calls.
80
-
81
- **Self-hosted sandboxes: a synced local copy, not a live mount.** On a `self_hosted` environment the SDK worker (`EnvironmentWorker` - Python, TypeScript, Go; the `ant` CLI worker does not mount stores) downloads each attached store to the same `/mnt/memory/<store-name>/` path and reconciles it with the store on an interval, so writes are visible to other sessions only after sync, conflicts resolve in favor of the store, and `read_only` is enforced by the worker's tools rather than the filesystem (`bash` can still alter the local copy). Everything else - sync interval, per-session `secret`, host prep, troubleshooting - lives in `shared/managed-agents-self-hosted-sandboxes.md` § Memory stores. Not available on self-hosted environments on Claude Platform on AWS.
82
-
83
- ## Manage memories directly (host-side)
84
-
85
- Use these for review workflows, correcting bad memories, or seeding stores out-of-band.
86
-
87
- ### List
88
-
89
- Returns `Memory | MemoryPrefix` entries - a `MemoryPrefix` (`type: "memory_prefix"`, just a `path`) is a directory-like node when listing hierarchically. Use `path_prefix` to scope (include a trailing slash: `"/notes/"` matches `/notes/a.md` but not `/notes_backup/old.md`) and `depth` to bound the tree walk. Pass `view="full"` to include `content` in each item; the default `"basic"` returns metadata only.
90
-
91
- ```python
92
- for m in client.beta.memory_stores.memories.list(store.id, path_prefix="/"):
93
- if m.type == "memory":
94
- print(f"{m.path} ({m.content_size_bytes} bytes, sha={m.content_sha256[:8]})")
95
- else: # "memory_prefix"
96
- print(f"{m.path}/")
97
- ```
98
-
99
- ### Read
100
-
101
- ```python
102
- mem = client.beta.memory_stores.memories.retrieve(memory_id, memory_store_id=store.id)
103
- print(mem.content)
104
- ```
105
-
106
- `retrieve` defaults to `view="full"` (content included); `view` matters mainly on list endpoints.
107
-
108
- ### Create vs. update
109
-
110
- | Operation | Addressed by | Semantics |
111
- | --- | --- | --- |
112
- | `memories.create(store_id, path=..., content=...)` | **Path** | Create at `path`. `409` (`memory_path_conflict_error`, includes `conflicting_memory_id`) if the path is already occupied. |
113
- | `memories.update(mem_id, memory_store_id=..., path=..., content=...)` | **`mem_...` ID** | Mutate existing memory. Change `content`, `path` (rename), or both. Renaming onto an occupied path returns the same `409 memory_path_conflict_error`. |
114
-
115
- ```python
116
- mem = client.beta.memory_stores.memories.create(
117
- store.id,
118
- path="/preferences/formatting.md",
119
- content="Always use tabs, not spaces.",
120
- )
121
-
122
- client.beta.memory_stores.memories.update(
123
- mem.id,
124
- memory_store_id=store.id,
125
- path="/archive/2026_q1_formatting.md", # rename
126
- )
127
- ```
128
-
129
- ### Optimistic concurrency (precondition on `update`)
130
-
131
- `memories.update` accepts a `precondition` so you can read -> modify -> write back without clobbering a concurrent writer. The only supported type is `content_sha256`. On mismatch the API returns `409` (`memory_precondition_failed_error`) - re-read and retry against fresh state.
132
-
133
- ```python
134
- client.beta.memory_stores.memories.update(
135
- mem.id,
136
- memory_store_id=store.id,
137
- content="CORRECTED: Always use 2-space indentation.",
138
- precondition={"type": "content_sha256", "content_sha256": mem.content_sha256},
139
- )
140
- ```
141
-
142
- ### Delete
143
-
144
- ```python
145
- client.beta.memory_stores.memories.delete(mem.id, memory_store_id=store.id)
146
- ```
147
-
148
- Pass `expected_content_sha256` for a conditional delete.
149
-
150
- ## Audit and rollback - memory versions
151
-
152
- Every mutation creates an immutable `memver_...` snapshot. Versions accumulate for the lifetime of the parent memory; `memories.retrieve` always returns the current head, the version endpoints give you history.
153
-
154
- | Operation that triggers it | `operation` field on the version |
155
- | --- | --- |
156
- | `memories.create` at a new path | `"created"` |
157
- | `memories.update` changing `content`, `path`, or both (or an agent-side write to the mount) | `"modified"` |
158
- | `memories.delete` | `"deleted"` |
159
-
160
- Each version also records `created_by` - an actor object with `type` in `session_actor` / `api_actor` / `user_actor` - and, after redaction, `redacted_at` + `redacted_by`.
161
-
162
- ### List versions
163
-
164
- Newest-first, paginated. Filter by `memory_id`, `operation`, `session_id`, `api_key_id`, or `created_at_gte` / `created_at_lte`. Pass `view="full"` to include `content`; default is metadata-only.
165
-
166
- ```python
167
- for v in client.beta.memory_stores.memory_versions.list(store.id, memory_id=mem.id):
168
- print(f"{v.id}: {v.operation}")
169
- ```
170
-
171
- ### Retrieve a version
172
-
173
- ```python
174
- version = client.beta.memory_stores.memory_versions.retrieve(
175
- version_id, memory_store_id=store.id
176
- )
177
- print(version.content)
178
- ```
179
-
180
- ### Redact a version
181
-
182
- Scrubs content from a historical version while preserving the audit trail (actor + timestamps). Clears `content`, `content_sha256`, `content_size_bytes`, and `path`; everything else stays. Use for leaked secrets, PII, or user-deletion requests.
183
-
184
- ```python
185
- client.beta.memory_stores.memory_versions.redact(version_id, memory_store_id=store.id)
186
- ```
187
-
188
- ## Endpoint reference
189
-
190
- See `shared/managed-agents-api-reference.md` -> Memory Stores / Memories / Memory Versions for the full HTTP method/path tables. Raw HTTP base path:
191
-
192
- ```
193
- POST /v1/memory_stores
194
- POST /v1/memory_stores/{memory_store_id}/archive
195
- GET /v1/memory_stores/{memory_store_id}/memories
196
- PATCH /v1/memory_stores/{memory_store_id}/memories/{memory_id}
197
- GET /v1/memory_stores/{memory_store_id}/memory_versions
198
- POST /v1/memory_stores/{memory_store_id}/memory_versions/{version_id}/redact
199
- ```
200
-
201
- For cURL examples and the CLI (`ant beta:memory-stores ...`), WebFetch the Memory URL in `shared/live-sources.md` -> Managed Agents.
@@ -1,260 +0,0 @@
1
- # Managed Agents - Multiagent Sessions
2
-
3
- A coordinator agent can delegate to other agents within one session. All agents **share the container and filesystem**; each runs in its own **thread** - a context-isolated event stream with its own conversation history, model, system prompt, tools, MCP servers, and skills (from that agent's own config). Threads are persistent: the coordinator can send a follow-up to a subagent it called earlier and that subagent retains its prior turns.
4
-
5
- The SDK sets the `managed-agents-2026-04-01` beta header automatically on all `client.beta.{agents,sessions}.*` calls; no additional header is required for multiagent.
6
-
7
- ---
8
-
9
- ## When to use it - start with `self`, then add cheaper workers
10
-
11
- **If the agent's work splits into independent pieces** - several sources to research, many files or records to process, anything shaped like "look into N things, then summarize" - or one piece would fill its context with reading, **use a multiagent session instead of one long single-threaded loop.** Each delegated piece runs in its own thread with a fresh context window, threads run in parallel in the same container, and only each subagent's report comes back, so the coordinator's context stays small. There is no orchestration code to write: the coordinator is given delegation tools automatically and decides when to use them, and your client still creates one session and reads one stream.
12
-
13
- **Step 1 - the smallest useful roster is the agent itself.** Add a `multiagent` block whose only entry is `{"type": "self"}`. The coordinator can then hand self-contained sub-tasks to copies of itself - same model, system prompt, and tools, minus the ability to delegate further - and combine what they report. Nothing else changes.
14
-
15
- ```python
16
- agent = client.beta.agents.create(
17
- name="Research assistant",
18
- description="Researches a question end to end. A copy can be spawned to own one well-scoped sub-question.",
19
- model="claude-opus-5",
20
- system="You are a research assistant. When a request splits into independent sub-questions, delegate each to a copy of yourself, one self-contained task per copy, then verify and combine their reports.",
21
- tools=[{"type": "agent_toolset_20260401"}],
22
- multiagent={"type": "coordinator", "agents": [{"type": "self"}]}, # the only change vs. a single agent
23
- )
24
-
25
- session = client.beta.sessions.create(agent=agent.id, environment_id=env.id) # unchanged
26
- ```
27
-
28
- **Step 2 - move the reading-heavy work to a cheaper model.** Delegated research work is mostly searching, reading, and extracting: many input tokens, little hard reasoning. Create a second agent on a smaller current-generation model (Claude Haiku 4.5, or Claude Sonnet 5 when the worker needs more judgment) with a narrow `system` prompt and only the tools it needs, and list it next to `self`. A roster entry is only a reference: the worker runs on its own `model`, `system`, and `tools`, and its tokens are billed at its own model's rates. The large model spends its tokens on planning, checking, and synthesis; the small model does the bulk reading.
29
-
30
- ```python
31
- worker = client.beta.agents.create(
32
- name="Web researcher",
33
- description="Fast, low-cost, read-only researcher. Give it one well-scoped question; it searches, reads, and reports findings with sources.",
34
- model="claude-haiku-4-5",
35
- system="Answer exactly the question you are given. Search and read as much as you need, then report concise findings with a source URL or file path for every claim.",
36
- tools=[{
37
- "type": "agent_toolset_20260401",
38
- "default_config": {"enabled": False},
39
- "configs": [{"name": n, "enabled": True} for n in ("read", "glob", "grep", "web_fetch", "web_search")],
40
- }],
41
- )
42
-
43
- lead = client.beta.agents.create(
44
- name="Research lead",
45
- description="Plans and synthesizes research. A copy can be spawned to own one large sub-analysis.",
46
- model="claude-opus-5",
47
- system="Plan the work. Delegate each independent, reading-heavy question to Web researcher, one self-contained task per spawn, several in parallel. Keep verification and the final synthesis for yourself; spawn a copy of yourself only for a sub-analysis that needs your full capability.",
48
- tools=[{"type": "agent_toolset_20260401"}],
49
- multiagent={"type": "coordinator", "agents": [worker.id, {"type": "self"}]},
50
- )
51
- ```
52
-
53
- **Step 3 - add dedicated specialists.** When the sub-tasks call for different skills, give each its own agent - its own model, a narrow `system` prompt, and only the tools it needs - and roster them by ID next to `self`. Here the lead makes a change itself, sends the same review brief to several read-only reviewer threads for independent passes (one rostered agent can be spawned many times), and hands a test writer a self-contained brief; it then de-duplicates the findings, checks each against the code, and keeps the fix and the summary for itself.
54
-
55
- ```python
56
- reviewer = client.beta.agents.create(
57
- name="Concurrency reviewer",
58
- description="Read-only reviewer for race conditions, deadlocks, lost updates, and retry/idempotency bugs. Give it the changed file paths and the invariants that must hold; it reports findings with file:line evidence. Spawn several on the same change for independent reviews.",
59
- model="claude-sonnet-5",
60
- system="Review only the files you are pointed at. Look for concurrency bugs: unsynchronized shared state, lock ordering, non-atomic read-modify-write, retries without idempotency. Report each finding as file:line, the interleaving that triggers it, and a suggested fix; say plainly if you found none.",
61
- tools=[{"type": "agent_toolset_20260401", "default_config": {"enabled": False},
62
- "configs": [{"name": n, "enabled": True} for n in ("read", "glob", "grep")]}],
63
- )
64
- test_writer = client.beta.agents.create(
65
- name="Test writer",
66
- description="Writes and runs tests. Give it the module path, the behavior to pin down, and the test command; it adds test files, runs them, and reports results with output.",
67
- model="claude-sonnet-5",
68
- system="Write focused tests for the behavior you are given, run them with the command you are given, and report pass/fail, the relevant output, and the paths of files you added. Do not edit non-test code; if the code under test looks wrong, report that instead.",
69
- tools=[{"type": "agent_toolset_20260401", "default_config": {"enabled": True},
70
- "configs": [{"name": n, "enabled": False} for n in ("web_fetch", "web_search")]}],
71
- )
72
- lead = client.beta.agents.create(
73
- name="Engineering lead",
74
- description="Plans and makes code changes and integrates specialist reports. A copy can be spawned to own one independent change.",
75
- model="claude-opus-5",
76
- system="Make the change yourself. Then, in parallel, send the changed paths and invariants to three Concurrency reviewers and the module path and test command to Test writer. Merge and de-duplicate the reviewers' findings, check each against the code before acting on it, fix, and have Test writer re-run. Keep design decisions and the final summary for yourself.",
77
- tools=[{"type": "agent_toolset_20260401"}],
78
- multiagent={"type": "coordinator", "agents": [reviewer.id, test_writer.id, {"type": "self"}]},
79
- )
80
- ```
81
-
82
- The same shape fits a pipeline of different specialists: a fast document extractor (for example on Claude Haiku 4.5) that writes one JSON file per input document, a verifier that checks each file against its source, and a lead that applies the corrections and writes the final table to `/mnt/session/outputs/`. Put the input and output paths in every task: threads share the container's filesystem, not each other's conversation.
83
-
84
- - **Good fits:** parallel research across sources; reading large amounts of material without filling the coordinator's context; specialists with narrow prompts and tool sets rather than one agent carrying every tool. **Poor fit:** a small single-step task - every delegation costs a round-trip and a re-briefing.
85
- - **Write `name` and `description` for the coordinator to read.** The coordinator chooses whom to spawn from each roster entry's name and description (the `self` entry is listed under the coordinator's own name), so say what each agent is good at and what to hand it. Names must be unique across the roster; don't name an agent `self`.
86
- - **Say how to delegate in the coordinator's `system` prompt** - what to hand off and to whom, how many at once, what to keep for itself, and what is too small to be worth delegating (the *Delegating to subagents* sample prompt in `shared/model-migration.md` is a starting point). Subagents see none of the coordinator's conversation, so each task must carry the paths, constraints, and report format it needs. Spawning returns immediately; the subagent's report arrives in a later coordinator turn.
87
- - **Web tool domain lists layer, never widen.** A roster agent's `web_search` / `web_fetch` calls are bound by its own `allowed_domains` / `blocked_domains`, by those of every agent that called it, and by the coordinator's current lists (allow-lists intersect, block-lists union). Keep each roster agent's allow-list inside the coordinator's - disjoint lists leave the tool present but every call fails `url_not_allowed`. See `shared/managed-agents-tools.md` § Web search & web fetch settings.
88
- - **Limits:** 1-20 roster entries (at most one `self`; each rostered agent can be spawned many times), one level of delegation (a roster member must not have its own `multiagent`), and at most 25 concurrent threads per session - archive finished threads if a long session needs more (see *Interrupting and archiving threads* below).
89
-
90
- The sections below are the reference for rosters, threads, events, and client-side handling; the platform guide is `https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration.md`.
91
-
92
- ---
93
-
94
- ## Declare the roster on the coordinator
95
-
96
- `multiagent` is a **top-level field** on `agents.create()` / `agents.update()` - **not** a `tools[]` entry. `agents` lists 1-20 roster entries. Nothing changes on `sessions.create()` - the roster is resolved from the coordinator's config.
97
-
98
- ```python
99
- orchestrator = client.beta.agents.create(
100
- name="Engineering lead",
101
- model="claude-opus-5",
102
- system="You coordinate engineering work. Delegate code review to the reviewer and test writing to the test agent.",
103
- tools=[{"type": "agent_toolset_20260401"}],
104
- multiagent={
105
- "type": "coordinator",
106
- "agents": [
107
- reviewer.id, # bare string - latest version
108
- {"type": "agent", "id": test_writer.id, "version": 4}, # pinned version
109
- {"type": "self"}, # the coordinator itself
110
- ],
111
- },
112
- )
113
-
114
- session = client.beta.sessions.create(agent=orchestrator.id, environment_id=env.id)
115
- ```
116
-
117
- | Roster entry | Shape | Notes |
118
- |---|---|---|
119
- | String shorthand | `"agent_abc123"` | References the latest version of a stored agent. |
120
- | Agent reference | `{type: "agent", id, version?}` | Omit `version` to pin the latest at coordinator save time. |
121
- | Self | `{type: "self"}` | The coordinator can spawn copies of itself. |
122
- | Advisor | `{type: "advisor", model}` | A model the session's primary thread can consult mid-turn. At most one per roster. See § Advisor below. |
123
-
124
- If the session was created with `agent_with_overrides` (see `shared/managed-agents-core.md` -> Override agent configuration for a session), those overrides apply to the **coordinator and its `self` copies**. Roster agents referenced by ID always use their own as-created configuration - overrides do not propagate to them.
125
-
126
- The coordinator's thread receives delegation tools for working the roster: `list_agents` (see the roster) and `send_to_agent` (task or message a member). Up to **20 unique agents** in the roster; the coordinator may spawn **multiple copies** of each. **One level of delegation only** - and it is enforced rather than silently flattened: rostering an agent that itself carries a `multiagent.agents` roster fails the create or update with a validation error.
127
-
128
- **Inference geo pins must be roster-uniform.** When agents pin an inference geography (`model.inference_geo` - see `shared/managed-agents-core.md` § Pinning inference geography), the coordinator's pin and every roster member's must all be the same value or all be unset. A mismatched roster is a 400 validation error, both when the agent is saved and when a session-create `model` override changes any of the pins.
129
-
130
- ---
131
-
132
- ## Threads
133
-
134
- The session-level event stream is the **primary thread** - it shows the coordinator's trace plus a condensed view of subagent activity (thread status transitions and cross-thread messages, not every subagent tool call). Drill into a specific subagent via the per-thread endpoints:
135
-
136
- | Operation | HTTP | SDK (`client.beta.sessions.threads.*`) |
137
- |---|---|---|
138
- | List threads | `GET /v1/sessions/{sid}/threads` | `.list(session_id)` |
139
- | Retrieve one | `GET /v1/sessions/{sid}/threads/{tid}` | `.retrieve(thread_id, session_id=...)` |
140
- | Archive | `POST /v1/sessions/{sid}/threads/{tid}/archive` | `.archive(thread_id, session_id=...)` |
141
- | List thread events | `GET /v1/sessions/{sid}/threads/{tid}/events` | `.events.list(thread_id, session_id=...)` |
142
- | Stream thread events | `GET /v1/sessions/{sid}/threads/{tid}/stream` | `.events.stream(thread_id, session_id=...)` |
143
-
144
- Each `SessionThread` carries `id`, `status` (`running` | `idle` | `rescheduling` | `terminated`), `agent` (a resolved snapshot of the agent config - `id`, `name`, `model`, `system`, `tools`, `skills`, `mcp_servers`, `version` - except advisor threads, whose `agent` is the two-field advisor form `{"type": "advisor", "model": ...}` - see § Advisor), `parent_thread_id` (null for the primary thread, which is included in the list), `archived_at`, and optional `stats`/`usage`. Per-thread `usage.list_cost` figures do **not** sum to the session total - the session figure additionally includes session running time and each figure is rounded independently; the session-level `usage.list_cost` is authoritative. **Session status aggregates thread statuses** - if any thread is `running`, `session.status` is `running`. Max **25 concurrent threads** (advisor threads are exempt - see § Advisor). When draining a per-thread stream, break on `session.thread_status_idle` (and check its `stop_reason` as you would for the session-level idle).
145
-
146
- **A session budget is one shared cap across all threads** - no per-thread caps. Each thread's consumption is priced at its own served model, and threads pause independently (`stop_reason: budget_reached`) as the shared cap is reached; one thread can pause while another finishes its in-flight request. A thread waiting on `requires_action` outranks the cap at the session level. See `shared/managed-agents-core.md` § Session budgets.
147
-
148
- ---
149
-
150
- ## Multiagent events (on the session stream)
151
-
152
- | Event | Payload highlights | Meaning |
153
- |---|---|---|
154
- | `session.thread_created` | `session_thread_id`, `agent_name` | A new thread was created. |
155
- | `session.thread_status_running` | `session_thread_id`, `agent_name` | Thread started activity. |
156
- | `session.thread_status_idle` | `session_thread_id`, `agent_name`, **`stop_reason`** | Thread is awaiting input - or paused at the session's shared budget (`stop_reason: budget_reached`). Inspect `stop_reason` (same shape as `session.status_idle.stop_reason`). |
157
- | `session.thread_status_rescheduled` | `session_thread_id`, `agent_name` | Thread is rescheduling after a retryable error. |
158
- | `session.thread_status_terminated` | `session_thread_id`, `agent_name` | Thread ended - completed its work and self-terminated (advisor consultation threads - see § Advisor), was archived, or hit a terminal error. |
159
- | `agent.thread_message_sent` | `to_session_thread_id`, `to_agent_name`, `content` | *This* thread sent a message to another thread. On the primary stream: the coordinator sent a task or follow-up to an agent. |
160
- | `agent.thread_message_received` | `from_session_thread_id`, `from_agent_name`, `content` | A message arrived on *this* thread from another. On the primary stream: an agent sent a report or question to the coordinator. |
161
-
162
- > **Direction is relative to the thread whose stream carries the event**, not to the coordinator. The same delegated task is an `agent.thread_message_sent` on the primary stream and an `agent.thread_message_received` on the child's own stream. Reading `_received` as "a subagent finished" is wrong once you're reading a child stream.
163
-
164
- ---
165
-
166
- ## Previewing a subagent's text
167
-
168
- Each thread's stream accepts the same `event_deltas[]` parameter as the session-level stream, so you can watch a subagent's text as the model generates it:
169
-
170
- ```
171
- GET /v1/sessions/{sid}/threads/{tid}/stream?event_deltas%5B%5D=agent.message
172
- ```
173
-
174
- **Previews are thread-scoped.** A child's previews are delivered only on that child's stream and never cross-posted to the session-level stream, whose previews stay scoped to the primary thread. So watching a subagent live means opening its thread stream - the session stream will not show it, no matter what you pass.
175
-
176
- > Warning: **Only plain assistant text previews.** A subagent's *reply to its coordinator* rides `agent.thread_message_sent` and is never previewed. A worker that does nothing but report back therefore streams no deltas at all, even with a correct opt-in on the right thread. To get a live preview out of a subagent, its prompt has to make it write the answer as a plain assistant message in its own thread first, and only then report to the coordinator. Run one accumulator per connection, and exit the read loop on `session.thread_status_idle`. Opt-in, accumulate, and reconcile details: `shared/managed-agents-events.md` -> Live previews.
177
-
178
- ---
179
-
180
- ## Advisor
181
-
182
- An `{"type": "advisor", "model": "<model id>"}` roster entry gives the session's **primary thread** an advisor: a model it can consult mid-turn for strategic guidance (planning an approach, getting unstuck, reviewing work before finishing). The entry has exactly two fields - `type` and `model` - and can sit alongside any other roster forms; a roster with no other entries works too. The advisor is also available as a server tool on the Messages API (`advisor_20260301` - see `shared/tool-use-concepts.md` -> Advisor); the Managed Agents surface differs in configuration and delivery: the roster entry has **no `max_uses`, `max_tokens`, or `caching` fields**, and advice arrives through thread events rather than `advisor_tool_result` blocks.
183
-
184
- ```python
185
- agent = client.beta.agents.create(
186
- name="Backend engineer",
187
- model="claude-sonnet-5",
188
- system="You implement backend features end to end.",
189
- multiagent={
190
- "type": "coordinator",
191
- "agents": [{"type": "advisor", "model": "claude-opus-5"}],
192
- },
193
- )
194
- ```
195
-
196
- (Claude Opus 5 is the default advisor choice. It is a redacted advisor - the agent reads its advice server-side, but the client sees `[{"type": "redacted"}]`; see *Plaintext vs redacted delivery* below. For client-readable advice, a plaintext advisor such as `claude-opus-4-8` is valid only when the agent's own model is `claude-opus-4-8` or below - agents on Claude Opus 5, Claude Fable 5.1, or Claude Mythos 5.1 can only pair with redacted advisors, so client-readable advice is not available for them (pairing table: `shared/tool-use-concepts.md`).)
197
-
198
- **Rules:**
199
- - **At most one advisor entry per roster.** The entry occupies the reserved roster name `anthropic.advisor` - a roster that also lists a member literally named `anthropic.advisor` is a 400. In responses, the advisor entry is echoed **last** in the roster regardless of submitted position.
200
- - **Pairing is validated at agent save:** the advisor model must meet a minimum capability bar, and the agent's own model must not be more capable than its advisor (equals can pair). Invalid pairing -> 400. The valid pairs mirror the Messages advisor tool's executor<->advisor table (`shared/tool-use-concepts.md`).
201
- - **Only the primary thread consults it.** The advisor is not a roster agent: invisible to the coordinator's `list_agents` tool, unreachable via `send_to_agent`, and roster agents cannot consult it.
202
-
203
- **How consultations work.** Each consultation runs as a platform-spawned thread named `anthropic.advisor` that terminates itself when done; the advice is delivered to the primary thread as an `agent.thread_message_received` event. Typical event order (the reserved name rides `agent_name` on lifecycle events and `from_agent_name` on the delivery):
204
-
205
- 1. `session.thread_created`
206
- 2. `session.thread_status_running`
207
- 3. `agent.thread_message_received` - the advice
208
- 4. `session.thread_status_idle` (`stop_reason: end_turn`)
209
- 5. `session.thread_status_terminated`
210
-
211
- No `agent.tool_use` and no `agent.thread_message_sent` are emitted for a consultation, and **the advice delivery is not guaranteed to precede the advisor thread's idle/terminated events** - don't treat those as "advice already delivered."
212
-
213
- **Plaintext vs redacted delivery.** Whether your client can read the advice is the advisor model's policy, mirroring the Messages advisor tool's result variants: models that return plaintext there deliver readable text content here; models that return redacted results deliver `[{"type": "redacted"}]` as the message content on every client surface, while the agent still reads the full advice server-side. Advisor thinking is never surfaced. Clients cannot send `redacted` blocks themselves - an event containing one is a 400.
214
-
215
- **Failure and interruption.** A failed consultation - or one abandoned via a `user.interrupt` carrying the advisor thread's `session_thread_id` - never fails the agent's turn: the agent continues after a generic notice. A session-level `user.interrupt` during a consultation halts the whole session as usual (every thread, primary included), terminating the advisor thread with no advice delivered.
216
-
217
- **Threads, billing, caching.** Advisor threads are **exempt from the 25-concurrent-thread limit**. They appear in the session's thread list with `agent` set to the advisor form as configured (`{"type": "advisor", "model": ...}`) and `parent_thread_id` set to the primary thread. Consultations are billed at the advisor model's rates; their tokens appear in the advisor thread's usage and the session's totals. Advisor-side prompt caching is automatic - nothing to configure.
218
-
219
- **Removing the advisor:** update the agent with a roster that omits the entry; if the advisor is the roster's only entry, clear the roster with `"multiagent": null`.
220
-
221
- ---
222
-
223
- ## Tool permissions and custom tools from subagent threads
224
-
225
- When a subagent needs your client (a tool call that paused for approval - `always_ask`, or `auto` with no determination - or a custom tool result), the request is **cross-posted to the primary thread** with `session_thread_id` identifying the originating thread - so you only need to watch the session stream. Reply with `user.tool_confirmation` (carrying `tool_use_id`) or `user.custom_tool_result` (carrying `custom_tool_use_id`), and **echo the `session_thread_id` from the originating event** (the SDK param type and docstring expect it). The server also routes by the tool-use ID, so the echo is belt-and-suspenders rather than load-bearing - but include it.
226
-
227
- ```python
228
- for event_id in stop.event_ids:
229
- pending = events_by_id[event_id]
230
- confirmation = {
231
- "type": "user.tool_confirmation",
232
- "tool_use_id": event_id,
233
- "result": "allow",
234
- }
235
- if pending.session_thread_id is not None:
236
- confirmation["session_thread_id"] = pending.session_thread_id
237
- client.beta.sessions.events.send(session.id, events=[confirmation])
238
- ```
239
-
240
- The same pattern applies to `user.custom_tool_result`.
241
-
242
- **`auto` in multiagent sessions.** Only your `user.message` events on the primary thread can lead the server to allow a call it would otherwise deny under `auto`; nothing in a subagent's thread carries that weight (your client posts no messages there, and the coordinator's messages to the subagent carry none). A call the server denies under `auto` is **not** cross-posted - its event and the error tool result appear only on the subagent's own thread stream, and the subagent keeps running.
243
-
244
- ---
245
-
246
- ## Interrupting and archiving threads
247
-
248
- - **`user.interrupt` without `session_thread_id` interrupts every non-archived thread in the session, including the primary** - it is not a primary-only stop. Pass `session_thread_id` to target one thread.
249
- - **Against a child thread blocked on `requires_action`**, the interrupt closes each pending tool call with an *error* tool result (`"Tool execution was interrupted before completion. Please retry."`) and re-emits `session.thread_status_idle` with `stop_reason: end_turn` directly - the model is not sampled. Against a thread already `idle`, the interrupt is a no-op - with one exception: a session on a self-hosted environment whose worker failed the claimed work item (e.g. a memory-store mount error) sits `idle`, and a `user.interrupt` re-queues that work so the next worker claim retries (`shared/managed-agents-self-hosted-sandboxes.md` § Memory stores -> Troubleshooting).
250
- - **Archive requires the thread to be idle, and `requires_action` counts as idle** - a thread parked on a pending tool call can be archived directly. Only a *running* thread must be interrupted first.
251
-
252
- ---
253
-
254
- ## Pitfalls
255
-
256
- - **Don't put the roster on `sessions.create()` or in `tools[]`.** `multiagent` is a top-level agent field; update the coordinator, then start a session that references it.
257
- - **Don't assume shared context.** Threads share the filesystem but not conversation history or tools. If the coordinator needs a subagent to act on something, it must say so in the delegated message (or write it to disk).
258
- - **Depth > 1 is a validation error.** Rostering an agent that itself carries a `multiagent.agents` roster fails the create or update - only the session's coordinator delegates.
259
-
260
- For per-language bindings beyond Python, WebFetch `https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration.md` (see `shared/live-sources.md`).
@@ -1,82 +0,0 @@
1
- # Managed Agents - Onboarding Flow
2
-
3
- > **Invoked via `/claude-api managed-agents-onboard`?** You're in the right place. Run the interview below - don't summarize it back to the user, ask the questions.
4
-
5
- Claude Managed Agents is a hosted agent: Anthropic runs the agent loop and provisions a sandboxed container per session where the agent's tools execute (or your own worker, with a `self_hosted` environment - see `shared/managed-agents-self-hosted-sandboxes.md`). You supply an **agent config** (tools, skills, model, system prompt - reusable, versioned) and an **environment config** (the sandbox - reusable across agents). Each run is a **session**.
6
-
7
- The flow is four beats - **describe -> agent -> environment -> session** - the same arc as the Console quickstart, and the same philosophy: **value before credentials**. The user goes from idea to a runnable session before any auth ask; each credential is *flagged* at the moment the design makes it relevant (§2) and *collected* once, at session setup (§4), where it binds (`sessions.create()`) and gets exercised (smoke-test). Read `shared/managed-agents-core.md` alongside this - it has full detail for each knob; this doc is the interview script.
8
-
9
- ---
10
-
11
- ## 1. Describe the task
12
-
13
- **Open with a one-breath signpost and a single open prompt - don't guess, don't questionnaire.** In your own words:
14
-
15
- > Managed Agents is hosted - Anthropic runs the agent loop, the sandbox, and the infrastructure; you just define the agent. We'll do this in three moves: the agent, the environment it runs in, then a live test session. So: describe the agent you want - what should it do, and what kicks it off (a person, an event, a schedule)?
16
-
17
- Let them answer in full before configuring anything.
18
-
19
- ## 2. Configure the agent - propose, don't interrogate
20
-
21
- Their description does the interview's work. Draft the agent config from it and **present it as a proposal with your suggestions inline** - the user reacts to a concrete config instead of answering a question list. At most one batched follow-up for true gaps. Suggest where the description gives you an opening:
22
-
23
- - **Tools** - enable the full prebuilt toolset by default (`agent_toolset_20260401`: `bash`, `read`, `write`, `edit`, `glob`, `grep`, `web_fetch`, `web_search`). **Suggest MCP servers** for any third-party service the job names (GitHub, Linear, Slack, ...) - and flag the credential each one implies as you suggest it ("Linear MCP -> you'll need a Linear API token at kickoff"), so §4's auth step is a formality, not a surprise. Collection itself waits for §4. Custom tools only if the user's own app must answer calls (name, description, input schema - their handler code is theirs; don't generate it).
24
- - **Skills** - **suggest** prebuilt `xlsx`/`docx`/`pptx`/`pdf` when the job produces those artifacts; custom by `skill_id` (max 20 total per agent, prebuilt + custom combined).
25
- - **Outcome** - if the description implies checkable "done" criteria (or you can elicit them in the follow-up: not "a good report" but "a CSV with a numeric `price` column per SKU"), **suggest an Outcome kickoff** - the harness grades and iterates against a rubric (`shared/managed-agents-outcomes.md`).
26
- - **On-hand resources** - repos on disk (`github_repository`: URL, optional `mount_path`/`checkout`; token comes in §4), files to seed (Files API upload -> `{type: "file", file_id, mount_path}`; read-only), if the job references them.
27
- - **Model** - default `claude-opus-5`; `claude-fable-5-1` for the hardest long-horizon work (`shared/model-migration.md` -> Migrating to Claude Fable 5.1).
28
-
29
- > Important: **PR creation needs the GitHub MCP server too** - a `github_repository` mount is filesystem-only. Edit in the mount -> push branch via `bash` -> open the PR via the MCP `create_pull_request` tool.
30
-
31
- Full detail per knob: `shared/managed-agents-tools.md` (toolset, MCP, custom tools, skills), `shared/managed-agents-environments.md` (repos, files).
32
-
33
- ## 3. Environment
34
-
35
- Usually zero or one question:
36
-
37
- - **Reuse or create?** Environments are shared across agents - check for an existing one first.
38
- - **Networking** - default unrestricted egress. Switch to `limited` only if the user wants egress control - then set `allow_mcp_servers: true` or list every MCP server domain in `allowed_hosts`, or those tools fail silently.
39
- - **Suggest `self_hosted`** when the signals are there: tools must run on their own infra, secrets can't leave it, or they need binaries/data the cloud container won't have (`shared/managed-agents-self-hosted-sandboxes.md`; on Claude Platform on AWS the worker authenticates with IAM instead of an environment key and sessions there can't attach memory stores). Otherwise `cloud` - don't raise it unprompted for simple jobs.
40
-
41
- ## 4. Session - auth, then test run
42
-
43
- **Auth happens here - collect the credentials flagged in §2, now that the config is settled:** a vault (existing or `vaults.create()`) + `vaults.credentials.create()` for each MCP server declared in §2, `environment_variable` credentials for API keys the job uses (substituted at egress; the sandbox sees a placeholder), and the `authorization_token` for each repo mount. Credentials are write-only; MCP credentials match servers by URL and auto-refresh. See `shared/managed-agents-tools.md` -> Vaults.
44
-
45
- **Silent viability gate - run this yourself before emitting anything; surface only the gaps.** Walk the job clause by clause: every verb maps to an enabled tool or MCP server ("open a PR" -> GitHub MCP, not just the mount); every MCP server and repo mount has its credential from the auth step; every external host is reachable under the networking choice; every file/repo/dataset the job references is mounted; "done" is checkable. If something's missing, say so and resolve it - don't emit a config you already know is under-resourced.
46
-
47
- **Kickoff - pick one, never both:**
48
- - `user.message` - conversational.
49
- - `user.define_outcome` + rubric - when §2 settled on an Outcome; the harness iterates and grades until the rubric passes.
50
- - **Scheduled shape?** Skip per-session kickoff entirely - create a **deployment** (`deployments.create()` with `schedule` + `initial_events`); each firing creates the session autonomously. See `shared/managed-agents-scheduled-deployments.md`.
51
-
52
- Mechanics to bake into the runtime code: session creation resolves resources (a bad mount surfaces there, before tokens) but does not itself provision the sandbox; open the event stream *before* sending the kickoff; break on `session.status_terminated`, or `session.status_idle` with any non-`requires_action` `stop_reason` - terminal, or `budget_reached`, which is not terminal (only a budget change/removal resumes it) (`shared/managed-agents-client-patterns.md` Pattern 5); usage lands on `span.model_request_end`; artifacts land in `/mnt/session/outputs/` (`files.list({scope_id: session.id, ...})`).
53
-
54
- ## 5. Integrate - emit the code
55
-
56
- Go straight from the last answer to the code - no preamble, no lecture about setup-vs-runtime; the two-block structure shows it. Generate **two clearly-separated blocks**:
57
-
58
- **Block 1 - Setup (run once, store the IDs).** Prefer **YAML files + `ant` CLI** - agents and environments are version-controlled definitions users should check in and apply from CI:
59
-
60
- 1. `<name>.agent.yaml` (flat: `name`, `model`, `system`, `tools`, `mcp_servers`, `skills`) and `<name>.environment.yaml`
61
- 2. ```sh
62
- AGENT_ID=$(ant beta:agents create < <name>.agent.yaml --transform id -r)
63
- ENV_ID=$(ant beta:environments create < <name>.environment.yaml --transform id -r)
64
- # CI sync: ant beta:agents update --agent-id "$AGENT_ID" --version N < <name>.agent.yaml
65
- ```
66
-
67
- SDK fallback if the user asks - and **required on Claude Platform on AWS**, where auth is SigV4 and the `ant` CLI has no SigV4 mode (use the platform client from `shared/claude-platform-on-aws.md`): label it `# ONE-TIME SETUP - run once, save the IDs` and call `environments.create()` -> `agents.create()`.
68
-
69
- > Warning: **Deployments are newer than the rest of the MA surface.** Before emitting `ant beta:deployments ...` or `client.beta.deployments` / `client.beta.deployment_runs` calls, verify the user's installed CLI/SDK exposes them (`ant beta:deployments --help`; `hasattr(client.beta, "deployments")`). If not, emit raw HTTP against `POST /v1/deployments` with the `managed-agents-2026-04-01` beta header (plus `oauth-2025-04-20` when authenticating with a Bearer token from `ant auth print-credentials`), and leave an upgrade note marking what simplifies to SDK calls.
70
-
71
- **Scheduled shape? The deployment is setup, not runtime.** Create it in Block 1, after the agent/environment IDs exist (`deployments.create()` with `schedule` + `initial_events`). Block 2 is then **not** a session loop - there is no per-run kickoff to send. Emit instead: a manual-run trigger (`POST /v1/deployments/{id}/run`) so the user can test now rather than wait for the first firing - the manual run doubles as the smoke test - plus a fetch helper (latest `deployment_runs` entry -> `session_id` -> Console URL + `files.list(scope_id=session_id)` for the artifacts).
72
-
73
- **Block 2 - Runtime (every invocation; conversational and Outcome shapes).** SDK code in the detected language (Python/TS/cURL - SKILL.md -> Language Detection); don't emit shell loops here:
74
-
75
- 1. Load `agent_id` + `env_id` from config/env
76
- 2. `sessions.create(agent=AGENT_ID, environment_id=ENV_ID, resources=[...], vault_ids=[...])`, then print the Console URL so the user can watch live: `https://platform.claude.com/workspaces/default/sessions/{session.id}` (swap `default` for their workspace slug)
77
- 3. **Smoke-test when the job depends on MCP servers, credentials, or locked-down hosts** - those failures don't surface at `sessions.create()`, only on first use. One cheap probe turn ("Confirm you can reach <service> and list 1-2 items; don't start the task"), verify, then send the real kickoff. Skip when there are no external dependencies.
78
- 4. Open stream -> send the §4 kickoff -> loop with the terminal gate from §4.
79
-
80
- > Warning: **Never emit `agents.create()` and `sessions.create()` in the same unguarded block** - that teaches creating a new agent per run, the #1 anti-pattern. Single-script requests: wrap creation in `if not os.getenv("AGENT_ID"):`.
81
-
82
- Pull exact syntax from `{lang}/managed-agents/README.md` for your detected language (cURL and C#: use `curl/managed-agents.md` as the wire-level reference). Don't invent field names.