agentlift 0.1.0__tar.gz

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 (32) hide show
  1. agentlift-0.1.0/LICENSE +21 -0
  2. agentlift-0.1.0/PKG-INFO +311 -0
  3. agentlift-0.1.0/README.md +289 -0
  4. agentlift-0.1.0/pyproject.toml +45 -0
  5. agentlift-0.1.0/setup.cfg +4 -0
  6. agentlift-0.1.0/src/agentlift/__init__.py +26 -0
  7. agentlift-0.1.0/src/agentlift/anthropic_target.py +167 -0
  8. agentlift-0.1.0/src/agentlift/cli.py +339 -0
  9. agentlift-0.1.0/src/agentlift/cost.py +84 -0
  10. agentlift-0.1.0/src/agentlift/diagnostics.py +52 -0
  11. agentlift-0.1.0/src/agentlift/diff.py +131 -0
  12. agentlift-0.1.0/src/agentlift/graders.py +61 -0
  13. agentlift-0.1.0/src/agentlift/lockfile.py +66 -0
  14. agentlift-0.1.0/src/agentlift/model.py +94 -0
  15. agentlift-0.1.0/src/agentlift/parser.py +371 -0
  16. agentlift-0.1.0/src/agentlift/planner.py +319 -0
  17. agentlift-0.1.0/src/agentlift/py.typed +0 -0
  18. agentlift-0.1.0/src/agentlift/runtime.py +184 -0
  19. agentlift-0.1.0/src/agentlift.egg-info/PKG-INFO +311 -0
  20. agentlift-0.1.0/src/agentlift.egg-info/SOURCES.txt +30 -0
  21. agentlift-0.1.0/src/agentlift.egg-info/dependency_links.txt +1 -0
  22. agentlift-0.1.0/src/agentlift.egg-info/entry_points.txt +2 -0
  23. agentlift-0.1.0/src/agentlift.egg-info/requires.txt +5 -0
  24. agentlift-0.1.0/src/agentlift.egg-info/top_level.txt +1 -0
  25. agentlift-0.1.0/tests/test_cost.py +20 -0
  26. agentlift-0.1.0/tests/test_diff.py +95 -0
  27. agentlift-0.1.0/tests/test_examples.py +43 -0
  28. agentlift-0.1.0/tests/test_idempotency.py +89 -0
  29. agentlift-0.1.0/tests/test_isolation.py +43 -0
  30. agentlift-0.1.0/tests/test_parser.py +62 -0
  31. agentlift-0.1.0/tests/test_permissions.py +41 -0
  32. agentlift-0.1.0/tests/test_planner.py +85 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pawel Huryn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,311 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentlift
3
+ Version: 0.1.0
4
+ Summary: Deploy the Claude agents you already run locally to Anthropic's Managed Agents cloud. One folder, one command.
5
+ Author: Pawel Huryn
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/phuryn/agentlift
8
+ Project-URL: Repository, https://github.com/phuryn/agentlift
9
+ Keywords: anthropic,claude,agents,managed-agents,llm,deploy,agent-sdk,mcp,skills
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Software Development :: Build Tools
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: anthropic>=0.105.0
18
+ Requires-Dist: PyYAML>=6.0
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=8.0; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ # agentlift
24
+
25
+ **Deploy the Claude agents you already run locally to Anthropic's Managed Agents cloud. One folder, one command.**
26
+
27
+ Anthropic's Managed Agents runs your whole agent loop in the cloud — you call it by ID over REST. But there is no console: to attach a skill, wire an MCP server, or restrict a tool, you write API calls by hand. So most people never move their local agents up.
28
+
29
+ agentlift closes that gap. Point it at the agent folder you already use with Claude Code / the Agent SDK (`CLAUDE.md` + skills + `.mcp.json`). It uploads your skills, maps your tool allowlist, wires your remote MCP servers, and creates the hosted agent — deterministically, idempotently, no new format to learn.
30
+
31
+ ```bash
32
+ pip install -e . # PyPI release pending; install from a clone for now
33
+ agentlift deploy ./my-agent
34
+ agentlift run my-agent --task "what changed in the API this week?"
35
+ ```
36
+
37
+ > The agent definition is the portable asset. The runtime is a deploy choice.
38
+ > **Own the definition. Rent the runtime.**
39
+
40
+ ---
41
+
42
+ ## Why this exists
43
+
44
+ `POST /v1/agents` is powerful and completely UI-less. A real agent has a system prompt, a few skills (each a directory of files uploaded via a separate multipart endpoint), an MCP server or two, a tool allowlist, maybe a subagent roster. Wiring all of that by hand — and keeping it in sync as the agent changes — is the reason "just deploy it to the cloud" rarely happens.
45
+
46
+ agentlift makes the deploy unit the same folder you develop against locally. Nothing new to learn; the thing you already have *is* the input.
47
+
48
+ ## Install
49
+
50
+ ```bash
51
+ git clone https://github.com/phuryn/agentlift && cd agentlift
52
+ pip install -e . # PyPI release pending
53
+ export ANTHROPIC_API_KEY=sk-ant-... # needs Managed Agents beta access
54
+ ```
55
+
56
+ ## The folder is the agent
57
+
58
+ agentlift reads a convention you may already use. Minimal single-agent project:
59
+
60
+ ```
61
+ my-agent/
62
+ └── .managed-agents/ # the deploy folder — everything here is a deploy target
63
+ └── knowledge-agent/
64
+ ├── agent.md # YAML frontmatter + system prompt
65
+ ├── skills/
66
+ │ └── receipt-stamp/
67
+ │ └── SKILL.md # uploaded as a managed skill
68
+ └── knowledge/
69
+ └── pm-basics.md # folded into the system prompt
70
+ ```
71
+
72
+ `agent.md`:
73
+
74
+ ```markdown
75
+ ---
76
+ name: knowledge-agent
77
+ model: claude-haiku-4-5
78
+ tools: [read, glob, grep] # built-in tool allowlist (omit = all)
79
+ ---
80
+ You are the Knowledge Agent. Answer product questions concisely.
81
+ Always sign off as "Best, Knowledge Agent".
82
+ ```
83
+
84
+ Why a dedicated `.managed-agents/` folder instead of reusing `.claude/agents/`? Because that's where Claude's **local** agents and native subagents live — and those aren't deploy targets. A separate folder keeps "ship to the cloud" cleanly apart from "runs on my machine." Already have an embedded agent folder (`.claude/agents/<name>/` with `CLAUDE.md` + `.mcp.json` + `.claude/skills/...`)? Point agentlift straight at it to deploy just that one — `CLAUDE.md`, `.mcp.json`, and `.claude/skills/` are all read for back-compat. See [docs/convention.md](docs/convention.md).
85
+
86
+ ## See exactly what will happen (no network)
87
+
88
+ ```console
89
+ $ agentlift plan ./examples/quickstart
90
+
91
+ Skills to upload: 1
92
+ - receipt-stamp (035823c8, 1 file(s)) used by: knowledge-agent
93
+
94
+ Agents to create: 1
95
+ - knowledge-agent [claude-haiku-4-5]
96
+ tools: builtins:read/glob/grep
97
+ skills: @skill:035823c8
98
+
99
+ Diagnostics:
100
+ info [knowledge-agent]: inlined 1 knowledge file(s) into the system prompt
101
+
102
+ Deployable: yes
103
+ ```
104
+
105
+ The plan is a pure function of the folder — same input, same plan. It is the dry-run, the diff, and the thing the tests assert against.
106
+
107
+ ## Deploy and run
108
+
109
+ ```console
110
+ $ agentlift deploy ./examples/quickstart -y
111
+ Uploading skills...
112
+ skill 'receipt-stamp': uploaded skill_01Ph... (used by knowledge-agent)
113
+ Creating agents...
114
+ agent 'knowledge-agent': created agent_019L... v1
115
+ Lockfile written: ./examples/quickstart/.agentlift-lock.json
116
+
117
+ $ agentlift run knowledge-agent --project ./examples/quickstart \
118
+ --task "What is a North Star metric? One sentence."
119
+
120
+ [managed] knowledge-agent
121
+ ------------------------------------------------------------
122
+ A North Star metric is the single measure that best captures the value
123
+ users get from your product.
124
+
125
+ RECEIPT: metric captured
126
+
127
+ Best, Knowledge Agent
128
+ ------------------------------------------------------------
129
+ latency 5.9s | in 4121 out 220 | ~$0.0044 | tool_used=False
130
+ ```
131
+
132
+ The `RECEIPT:` line is the uploaded `SKILL.md` firing **inside the hosted runtime** — proof the skill rode along, not just the prompt.
133
+
134
+ ## Proven, not asserted
135
+
136
+ `benchmarks/run_benchmark.py` deploys the quickstart agent and runs it on both runtimes. Real numbers ([benchmarks/results.md](benchmarks/results.md), `claude-haiku-4-5`, N=5):
137
+
138
+ | Arm | N | Pass% | Median latency | Avg cost |
139
+ |---|---|---|---|---|
140
+ | managed (cloud) | 5 | 100% | 5.9s | $0.0052 |
141
+ | local (your machine) | 5 | 100% | 2.3s | $0.0034 |
142
+
143
+ Pass = the uploaded skill fired **and** the answer was on-topic. Same folder, two runtimes, identical behavior. (The live deploy → cloud-run → skill-applied path is also pinned by `tests/live/`.)
144
+
145
+ ## What agentlift maps
146
+
147
+ | Local definition | → Managed Agents | Notes |
148
+ |---|---|---|
149
+ | `CLAUDE.md` / `agent.md` body | `system` prompt | frontmatter sets model, tools, etc. |
150
+ | `tools: [read, glob, ...]` | `agent_toolset_20260401` configs | mapped to `read/glob/grep/bash/edit/write/web_fetch/web_search`; unmappable tools dropped with a warning |
151
+ | `tools: [bash:ask]` / `allowedTools: [x:ask]` | tool `permission_policy` | `:ask` gates a tool behind caller approval; `:allow` (default) auto-approves — the deployable form of a hook |
152
+ | `skills/<name>/SKILL.md` (+ files) | uploaded skill → `{type:"custom", skill_id}` | content-addressed; identical skills upload **once** and are shared across agents |
153
+ | `.mcp.json` **remote** server | `mcp_servers:[{type:"url"}]` + `mcp_toolset` | per-server `allowedTools` becomes the **specific-tool** allowlist (and supports `:ask`) |
154
+ | `.mcp.json` **stdio** server (`npx ...`) | ✗ rejected | managed agents need a remote URL; clear error (or `--skip-unsupported`) |
155
+ | `knowledge/*.md` | folded into `system` | managed agents have no persistent local FS; see [limitations](docs/limitations.md) |
156
+ | `subagents: [a, b]` | `multiagent` coordinator | roster deployed first; depth-limit-1 enforced |
157
+
158
+ Full table and the exact wire format: [docs/anthropic-mapping.md](docs/anthropic-mapping.md).
159
+
160
+ ## Isolation: each agent gets only its folder
161
+
162
+ A deployed agent's context is exactly its own system prompt + its own (and `shared/`) skills + its own (and `shared/`) MCP servers + its inlined knowledge. The repo-root `CLAUDE.md`, a sibling agent's skills, and your machine's MCP servers **cannot leak in.**
163
+
164
+ This is the same isolation the local Agent SDK has to fight for — there the CLI walks up the directory tree and pulls in the repo-root `CLAUDE.md`, repo-root skills, and user-level MCP servers unless you set an explicit skills allowlist and `strictMcpConfig: true`. In the cloud there's no tree to walk: the agent only ever gets what agentlift uploads, and agentlift scopes uploads to the agent folder. You get isolation **by construction** — pinned by [`tests/test_isolation.py`](tests/test_isolation.py).
165
+
166
+ ## Permissions and hooks
167
+
168
+ Claude Code hooks are local scripts, so they can't run in a cloud sandbox. Their main job — gating a tool behind approval — deploys as a per-tool **permission policy**. Append `:ask` to any built-in or specific MCP tool:
169
+
170
+ ```yaml
171
+ tools: [read, glob, grep, bash:ask] # bash pauses for approval
172
+ ```
173
+ ```jsonc
174
+ { "mcpServers": { "github": { "type": "url", "url": "https://…/mcp",
175
+ "allowedTools": ["search_issues", "create_issue:ask"] } } } // writes gated
176
+ ```
177
+
178
+ At runtime an `:ask` call pauses the session (`requires_action`) for your app to approve or reject. Arbitrary hook *code* (custom block logic, PostToolUse capture) doesn't deploy — do path-guarding by not enabling the tool, and metadata capture from the session event stream. Details: [docs/deploying.md](docs/deploying.md#permissions-the-deployable-hook).
179
+
180
+ ## Multi-agent, shared resources, subagents
181
+
182
+ ```
183
+ .managed-agents/
184
+ ├── shared/
185
+ │ ├── skills/cite-sources/SKILL.md # one skill, many agents (uploaded once)
186
+ │ └── mcp.json # one MCP server, many agents
187
+ ├── bug-finder/agent.md # skills: [bug-report, shared/cite-sources]
188
+ ├── researcher/agent.md # mcp: [shared/docs]
189
+ └── lead/agent.md # subagents: [bug-finder, researcher] → coordinator
190
+ ```
191
+
192
+ Subagents are unambiguous here: `lead`'s roster references other agents **in the
193
+ same `.managed-agents/` folder**, so they're deploy targets too. Your local
194
+ Claude subagents in `.claude/agents/` are never swept in.
195
+
196
+ ```console
197
+ $ agentlift plan ./examples/team
198
+ Skills to upload: 2
199
+ - cite-sources (417213e5, 1 file(s)) used by: bug-finder, researcher
200
+ - bug-report (6d58998e, 1 file(s)) used by: bug-finder
201
+ Agents to create: 3
202
+ - bug-finder [claude-haiku-4-5] tools: builtins:read/glob/grep/bash(ask)
203
+ - researcher [claude-haiku-4-5] tools: builtins:read/web_search, mcp:docs:all
204
+ - lead [claude-haiku-4-5] (coordinator -> @agent:bug-finder, @agent:researcher)
205
+ Deployable: yes
206
+ ```
207
+
208
+ `bash(ask)` is a per-tool permission: `bug-finder` declares `tools: [..., bash:ask]`,
209
+ so the hosted agent pauses for caller approval before each `bash` call.
210
+
211
+ ## How it works
212
+
213
+ `parse → plan → apply → run`.
214
+
215
+ - **parse** — read the folder into an in-memory project. Pure file IO.
216
+ - **plan** — produce a deterministic list of API operations with symbolic refs (`@skill:hash`, `@agent:name`), skill dedup, validation, and diagnostics. No network. This is what `agentlift plan` prints and what the offline tests assert.
217
+ - **apply** — execute the plan: upload skills (deduped), create agents in dependency order, write a `.agentlift-lock.json` mapping local definitions → remote IDs.
218
+ - **run** — invoke a deployed agent by ID (or run the same folder locally with `--local`).
219
+
220
+ The lockfile makes re-deploys idempotent: an unchanged skill is not re-uploaded, an unchanged agent is not re-created (verified in `tests/test_idempotency.py`, no network). Details: [docs/how-it-works.md](docs/how-it-works.md).
221
+
222
+ ## Deploying — three ways, all things you already know
223
+
224
+ Deploy is declarative: the folder is the desired state, and `deploy` makes the cloud match it. Trigger it however you already work.
225
+
226
+ 1. **A command** (solo): `agentlift plan .` then `agentlift deploy . --yes`.
227
+ 2. **Git push** (teams, recommended): commit `.managed-agents/`, copy [`examples/deploy-workflow/ci-deploy.yml`](examples/deploy-workflow/ci-deploy.yml) into `.github/workflows/`, add an `ANTHROPIC_API_KEY` secret. Every push that touches the folder validates, deploys (idempotent), and commits the updated lockfile. Review in PRs; roll back with `git revert`.
228
+ 3. **From Claude Code**: drop [`examples/claude-code-skill/deploy-managed-agents/`](examples/claude-code-skill/) into `.claude/skills/` and just say *"deploy my managed agents."*
229
+
230
+ Full guide + trade-offs: [docs/deploying.md](docs/deploying.md).
231
+
232
+ ```
233
+ agentlift validate <path> parse + plan, report problems (exit 1 on errors)
234
+ agentlift plan <path> [--json] show the deploy plan (dry run, no network)
235
+ agentlift diff <path> [--remote] what a deploy would change vs the lockfile
236
+ agentlift deploy <path> [--prune] upload skills + create agents; write lockfile
237
+ agentlift run <agent> --task "..." invoke a deployed agent (--local for the same folder locally)
238
+ agentlift list <path> what's currently deployed (from the lockfile)
239
+ agentlift destroy <path> archive every agent in the lockfile
240
+ agentlift bench <agent> --task "..." managed vs local: latency / cost / pass
241
+ ```
242
+
243
+ `agentlift diff` shows new / changed / unchanged / stale before you deploy:
244
+
245
+ ```console
246
+ $ agentlift diff .
247
+ Skills:
248
+ + house-style (new)
249
+ = cite-sources (unchanged)
250
+ Agents:
251
+ ~ researcher (changed)
252
+ = fact-checker (unchanged)
253
+ Stale (in lockfile, not in folder — archived with --prune):
254
+ - old-agent
255
+
256
+ 2 change(s) pending. Run: agentlift deploy <path>
257
+ ```
258
+
259
+ ## Where the deployed IDs live
260
+
261
+ `deploy` writes **`.agentlift-lock.json`** next to the path you deployed — a map from each local definition to the remote object it became (`skill_…`, `agent_…`, version, spec hash). **Commit it.** It's what makes re-deploys idempotent (unchanged skills/agents are skipped), makes `agentlift run lead …` resolve by name, and lets a teammate or CI reuse the same cloud objects instead of duplicating them. It holds only IDs/hashes/titles — no secrets. It's per Anthropic account; commit it when your team shares one. More: [docs/deploying.md](docs/deploying.md#where-the-ids-live-the-lockfile).
262
+
263
+ ## Tests
264
+
265
+ ```bash
266
+ pytest -m "not live" # deterministic translation + idempotency — no API key, runs in CI
267
+ pytest -m live # deploy to the real API, run, LLM-grade the output (needs ANTHROPIC_API_KEY)
268
+ ```
269
+
270
+ Offline tests pin the translation (tool mapping, per-tool permissions, skill dedup, stdio rejection, coordinator ordering, context isolation, diff, idempotency). Live tests deploy to Anthropic and confirm the uploaded skill actually fires in the cloud, graded by an LLM. CI runs the offline suite on every push and the live suite when an `ANTHROPIC_API_KEY` secret is present ([.github/workflows/ci.yml](.github/workflows/ci.yml)). A separate on-demand [live-demo workflow](.github/workflows/live-demo.yml) deploys the team example to a real account, runs the benchmark, and tears everything down — so the deploy path is demonstrably live, not just asserted.
271
+
272
+ ## Limitations (read these)
273
+
274
+ - **Remote MCP only.** Managed agents connect to URL MCP servers; local `stdio` servers (`npx ...`) can't be deployed. Host them behind HTTPS first.
275
+ - **No inline MCP auth.** A managed URL MCP server carries no credentials in this API shape. The server must be public or authenticate itself.
276
+ - **Knowledge files are inlined** into the system prompt (no persistent local FS in the managed sandbox). Large reference sets should become a skill bundle.
277
+ - **Anthropic only, for now.** The planner is provider-agnostic; OpenAI / Google targets are on the roadmap.
278
+
279
+ Each of these is surfaced as a `agentlift plan` diagnostic, not a silent surprise. More: [docs/limitations.md](docs/limitations.md).
280
+
281
+ ## Documentation
282
+
283
+ Everything is here or one click away:
284
+
285
+ | Doc | What's in it |
286
+ |---|---|
287
+ | [docs/convention.md](docs/convention.md) | The `.managed-agents/` folder spec, frontmatter, skills, MCP, `:ask` permissions, native subagents |
288
+ | [docs/deploying.md](docs/deploying.md) | The three deploy paths, the lockfile / where IDs live, isolation, hooks |
289
+ | [docs/how-it-works.md](docs/how-it-works.md) | `parse → plan → apply → run`, determinism, idempotency, the confirmed wire format |
290
+ | [docs/anthropic-mapping.md](docs/anthropic-mapping.md) | Exact local → Managed Agents field mapping + API constraints |
291
+ | [docs/limitations.md](docs/limitations.md) | Honest constraints (stdio MCP, MCP auth, knowledge inlining, skill descriptions) |
292
+ | [CONTRIBUTING.md](CONTRIBUTING.md) | Architecture and dev setup |
293
+
294
+ ### Examples ([examples/](examples/))
295
+
296
+ - [`quickstart/`](examples/quickstart/) — one agent, one skill, knowledge, a tool allowlist
297
+ - [`team/`](examples/team/) — multi-agent: coordinator + roster, a shared skill, a remote MCP server, a `bash:ask` permission
298
+ - [`in-a-project/`](examples/in-a-project/) — `.managed-agents/` embedded in a real project; proves isolation (repo `CLAUDE.md`, app code, and a local `.claude/agents/` subagent are never deployed) + a coordinator with two shared-skill subagents
299
+ - [`deploy-workflow/`](examples/deploy-workflow/) — the git-push-to-deploy GitHub Action
300
+ - [`claude-code-skill/`](examples/claude-code-skill/) — deploy from inside Claude Code
301
+
302
+ ## Roadmap
303
+
304
+ - Authenticated remote MCP via the Vaults API
305
+ - `agentlift diff --remote` deeper drift detection (full account reconciliation)
306
+ - Additional deploy targets (OpenAI Agent Builder, Google Managed Agents) behind the same convention
307
+ - A skill-bundle mode for large `knowledge/` sets
308
+
309
+ ## License
310
+
311
+ MIT — see [LICENSE](LICENSE). Built on the [Anthropic Python SDK](https://github.com/anthropics/anthropic-sdk-python).
@@ -0,0 +1,289 @@
1
+ # agentlift
2
+
3
+ **Deploy the Claude agents you already run locally to Anthropic's Managed Agents cloud. One folder, one command.**
4
+
5
+ Anthropic's Managed Agents runs your whole agent loop in the cloud — you call it by ID over REST. But there is no console: to attach a skill, wire an MCP server, or restrict a tool, you write API calls by hand. So most people never move their local agents up.
6
+
7
+ agentlift closes that gap. Point it at the agent folder you already use with Claude Code / the Agent SDK (`CLAUDE.md` + skills + `.mcp.json`). It uploads your skills, maps your tool allowlist, wires your remote MCP servers, and creates the hosted agent — deterministically, idempotently, no new format to learn.
8
+
9
+ ```bash
10
+ pip install -e . # PyPI release pending; install from a clone for now
11
+ agentlift deploy ./my-agent
12
+ agentlift run my-agent --task "what changed in the API this week?"
13
+ ```
14
+
15
+ > The agent definition is the portable asset. The runtime is a deploy choice.
16
+ > **Own the definition. Rent the runtime.**
17
+
18
+ ---
19
+
20
+ ## Why this exists
21
+
22
+ `POST /v1/agents` is powerful and completely UI-less. A real agent has a system prompt, a few skills (each a directory of files uploaded via a separate multipart endpoint), an MCP server or two, a tool allowlist, maybe a subagent roster. Wiring all of that by hand — and keeping it in sync as the agent changes — is the reason "just deploy it to the cloud" rarely happens.
23
+
24
+ agentlift makes the deploy unit the same folder you develop against locally. Nothing new to learn; the thing you already have *is* the input.
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ git clone https://github.com/phuryn/agentlift && cd agentlift
30
+ pip install -e . # PyPI release pending
31
+ export ANTHROPIC_API_KEY=sk-ant-... # needs Managed Agents beta access
32
+ ```
33
+
34
+ ## The folder is the agent
35
+
36
+ agentlift reads a convention you may already use. Minimal single-agent project:
37
+
38
+ ```
39
+ my-agent/
40
+ └── .managed-agents/ # the deploy folder — everything here is a deploy target
41
+ └── knowledge-agent/
42
+ ├── agent.md # YAML frontmatter + system prompt
43
+ ├── skills/
44
+ │ └── receipt-stamp/
45
+ │ └── SKILL.md # uploaded as a managed skill
46
+ └── knowledge/
47
+ └── pm-basics.md # folded into the system prompt
48
+ ```
49
+
50
+ `agent.md`:
51
+
52
+ ```markdown
53
+ ---
54
+ name: knowledge-agent
55
+ model: claude-haiku-4-5
56
+ tools: [read, glob, grep] # built-in tool allowlist (omit = all)
57
+ ---
58
+ You are the Knowledge Agent. Answer product questions concisely.
59
+ Always sign off as "Best, Knowledge Agent".
60
+ ```
61
+
62
+ Why a dedicated `.managed-agents/` folder instead of reusing `.claude/agents/`? Because that's where Claude's **local** agents and native subagents live — and those aren't deploy targets. A separate folder keeps "ship to the cloud" cleanly apart from "runs on my machine." Already have an embedded agent folder (`.claude/agents/<name>/` with `CLAUDE.md` + `.mcp.json` + `.claude/skills/...`)? Point agentlift straight at it to deploy just that one — `CLAUDE.md`, `.mcp.json`, and `.claude/skills/` are all read for back-compat. See [docs/convention.md](docs/convention.md).
63
+
64
+ ## See exactly what will happen (no network)
65
+
66
+ ```console
67
+ $ agentlift plan ./examples/quickstart
68
+
69
+ Skills to upload: 1
70
+ - receipt-stamp (035823c8, 1 file(s)) used by: knowledge-agent
71
+
72
+ Agents to create: 1
73
+ - knowledge-agent [claude-haiku-4-5]
74
+ tools: builtins:read/glob/grep
75
+ skills: @skill:035823c8
76
+
77
+ Diagnostics:
78
+ info [knowledge-agent]: inlined 1 knowledge file(s) into the system prompt
79
+
80
+ Deployable: yes
81
+ ```
82
+
83
+ The plan is a pure function of the folder — same input, same plan. It is the dry-run, the diff, and the thing the tests assert against.
84
+
85
+ ## Deploy and run
86
+
87
+ ```console
88
+ $ agentlift deploy ./examples/quickstart -y
89
+ Uploading skills...
90
+ skill 'receipt-stamp': uploaded skill_01Ph... (used by knowledge-agent)
91
+ Creating agents...
92
+ agent 'knowledge-agent': created agent_019L... v1
93
+ Lockfile written: ./examples/quickstart/.agentlift-lock.json
94
+
95
+ $ agentlift run knowledge-agent --project ./examples/quickstart \
96
+ --task "What is a North Star metric? One sentence."
97
+
98
+ [managed] knowledge-agent
99
+ ------------------------------------------------------------
100
+ A North Star metric is the single measure that best captures the value
101
+ users get from your product.
102
+
103
+ RECEIPT: metric captured
104
+
105
+ Best, Knowledge Agent
106
+ ------------------------------------------------------------
107
+ latency 5.9s | in 4121 out 220 | ~$0.0044 | tool_used=False
108
+ ```
109
+
110
+ The `RECEIPT:` line is the uploaded `SKILL.md` firing **inside the hosted runtime** — proof the skill rode along, not just the prompt.
111
+
112
+ ## Proven, not asserted
113
+
114
+ `benchmarks/run_benchmark.py` deploys the quickstart agent and runs it on both runtimes. Real numbers ([benchmarks/results.md](benchmarks/results.md), `claude-haiku-4-5`, N=5):
115
+
116
+ | Arm | N | Pass% | Median latency | Avg cost |
117
+ |---|---|---|---|---|
118
+ | managed (cloud) | 5 | 100% | 5.9s | $0.0052 |
119
+ | local (your machine) | 5 | 100% | 2.3s | $0.0034 |
120
+
121
+ Pass = the uploaded skill fired **and** the answer was on-topic. Same folder, two runtimes, identical behavior. (The live deploy → cloud-run → skill-applied path is also pinned by `tests/live/`.)
122
+
123
+ ## What agentlift maps
124
+
125
+ | Local definition | → Managed Agents | Notes |
126
+ |---|---|---|
127
+ | `CLAUDE.md` / `agent.md` body | `system` prompt | frontmatter sets model, tools, etc. |
128
+ | `tools: [read, glob, ...]` | `agent_toolset_20260401` configs | mapped to `read/glob/grep/bash/edit/write/web_fetch/web_search`; unmappable tools dropped with a warning |
129
+ | `tools: [bash:ask]` / `allowedTools: [x:ask]` | tool `permission_policy` | `:ask` gates a tool behind caller approval; `:allow` (default) auto-approves — the deployable form of a hook |
130
+ | `skills/<name>/SKILL.md` (+ files) | uploaded skill → `{type:"custom", skill_id}` | content-addressed; identical skills upload **once** and are shared across agents |
131
+ | `.mcp.json` **remote** server | `mcp_servers:[{type:"url"}]` + `mcp_toolset` | per-server `allowedTools` becomes the **specific-tool** allowlist (and supports `:ask`) |
132
+ | `.mcp.json` **stdio** server (`npx ...`) | ✗ rejected | managed agents need a remote URL; clear error (or `--skip-unsupported`) |
133
+ | `knowledge/*.md` | folded into `system` | managed agents have no persistent local FS; see [limitations](docs/limitations.md) |
134
+ | `subagents: [a, b]` | `multiagent` coordinator | roster deployed first; depth-limit-1 enforced |
135
+
136
+ Full table and the exact wire format: [docs/anthropic-mapping.md](docs/anthropic-mapping.md).
137
+
138
+ ## Isolation: each agent gets only its folder
139
+
140
+ A deployed agent's context is exactly its own system prompt + its own (and `shared/`) skills + its own (and `shared/`) MCP servers + its inlined knowledge. The repo-root `CLAUDE.md`, a sibling agent's skills, and your machine's MCP servers **cannot leak in.**
141
+
142
+ This is the same isolation the local Agent SDK has to fight for — there the CLI walks up the directory tree and pulls in the repo-root `CLAUDE.md`, repo-root skills, and user-level MCP servers unless you set an explicit skills allowlist and `strictMcpConfig: true`. In the cloud there's no tree to walk: the agent only ever gets what agentlift uploads, and agentlift scopes uploads to the agent folder. You get isolation **by construction** — pinned by [`tests/test_isolation.py`](tests/test_isolation.py).
143
+
144
+ ## Permissions and hooks
145
+
146
+ Claude Code hooks are local scripts, so they can't run in a cloud sandbox. Their main job — gating a tool behind approval — deploys as a per-tool **permission policy**. Append `:ask` to any built-in or specific MCP tool:
147
+
148
+ ```yaml
149
+ tools: [read, glob, grep, bash:ask] # bash pauses for approval
150
+ ```
151
+ ```jsonc
152
+ { "mcpServers": { "github": { "type": "url", "url": "https://…/mcp",
153
+ "allowedTools": ["search_issues", "create_issue:ask"] } } } // writes gated
154
+ ```
155
+
156
+ At runtime an `:ask` call pauses the session (`requires_action`) for your app to approve or reject. Arbitrary hook *code* (custom block logic, PostToolUse capture) doesn't deploy — do path-guarding by not enabling the tool, and metadata capture from the session event stream. Details: [docs/deploying.md](docs/deploying.md#permissions-the-deployable-hook).
157
+
158
+ ## Multi-agent, shared resources, subagents
159
+
160
+ ```
161
+ .managed-agents/
162
+ ├── shared/
163
+ │ ├── skills/cite-sources/SKILL.md # one skill, many agents (uploaded once)
164
+ │ └── mcp.json # one MCP server, many agents
165
+ ├── bug-finder/agent.md # skills: [bug-report, shared/cite-sources]
166
+ ├── researcher/agent.md # mcp: [shared/docs]
167
+ └── lead/agent.md # subagents: [bug-finder, researcher] → coordinator
168
+ ```
169
+
170
+ Subagents are unambiguous here: `lead`'s roster references other agents **in the
171
+ same `.managed-agents/` folder**, so they're deploy targets too. Your local
172
+ Claude subagents in `.claude/agents/` are never swept in.
173
+
174
+ ```console
175
+ $ agentlift plan ./examples/team
176
+ Skills to upload: 2
177
+ - cite-sources (417213e5, 1 file(s)) used by: bug-finder, researcher
178
+ - bug-report (6d58998e, 1 file(s)) used by: bug-finder
179
+ Agents to create: 3
180
+ - bug-finder [claude-haiku-4-5] tools: builtins:read/glob/grep/bash(ask)
181
+ - researcher [claude-haiku-4-5] tools: builtins:read/web_search, mcp:docs:all
182
+ - lead [claude-haiku-4-5] (coordinator -> @agent:bug-finder, @agent:researcher)
183
+ Deployable: yes
184
+ ```
185
+
186
+ `bash(ask)` is a per-tool permission: `bug-finder` declares `tools: [..., bash:ask]`,
187
+ so the hosted agent pauses for caller approval before each `bash` call.
188
+
189
+ ## How it works
190
+
191
+ `parse → plan → apply → run`.
192
+
193
+ - **parse** — read the folder into an in-memory project. Pure file IO.
194
+ - **plan** — produce a deterministic list of API operations with symbolic refs (`@skill:hash`, `@agent:name`), skill dedup, validation, and diagnostics. No network. This is what `agentlift plan` prints and what the offline tests assert.
195
+ - **apply** — execute the plan: upload skills (deduped), create agents in dependency order, write a `.agentlift-lock.json` mapping local definitions → remote IDs.
196
+ - **run** — invoke a deployed agent by ID (or run the same folder locally with `--local`).
197
+
198
+ The lockfile makes re-deploys idempotent: an unchanged skill is not re-uploaded, an unchanged agent is not re-created (verified in `tests/test_idempotency.py`, no network). Details: [docs/how-it-works.md](docs/how-it-works.md).
199
+
200
+ ## Deploying — three ways, all things you already know
201
+
202
+ Deploy is declarative: the folder is the desired state, and `deploy` makes the cloud match it. Trigger it however you already work.
203
+
204
+ 1. **A command** (solo): `agentlift plan .` then `agentlift deploy . --yes`.
205
+ 2. **Git push** (teams, recommended): commit `.managed-agents/`, copy [`examples/deploy-workflow/ci-deploy.yml`](examples/deploy-workflow/ci-deploy.yml) into `.github/workflows/`, add an `ANTHROPIC_API_KEY` secret. Every push that touches the folder validates, deploys (idempotent), and commits the updated lockfile. Review in PRs; roll back with `git revert`.
206
+ 3. **From Claude Code**: drop [`examples/claude-code-skill/deploy-managed-agents/`](examples/claude-code-skill/) into `.claude/skills/` and just say *"deploy my managed agents."*
207
+
208
+ Full guide + trade-offs: [docs/deploying.md](docs/deploying.md).
209
+
210
+ ```
211
+ agentlift validate <path> parse + plan, report problems (exit 1 on errors)
212
+ agentlift plan <path> [--json] show the deploy plan (dry run, no network)
213
+ agentlift diff <path> [--remote] what a deploy would change vs the lockfile
214
+ agentlift deploy <path> [--prune] upload skills + create agents; write lockfile
215
+ agentlift run <agent> --task "..." invoke a deployed agent (--local for the same folder locally)
216
+ agentlift list <path> what's currently deployed (from the lockfile)
217
+ agentlift destroy <path> archive every agent in the lockfile
218
+ agentlift bench <agent> --task "..." managed vs local: latency / cost / pass
219
+ ```
220
+
221
+ `agentlift diff` shows new / changed / unchanged / stale before you deploy:
222
+
223
+ ```console
224
+ $ agentlift diff .
225
+ Skills:
226
+ + house-style (new)
227
+ = cite-sources (unchanged)
228
+ Agents:
229
+ ~ researcher (changed)
230
+ = fact-checker (unchanged)
231
+ Stale (in lockfile, not in folder — archived with --prune):
232
+ - old-agent
233
+
234
+ 2 change(s) pending. Run: agentlift deploy <path>
235
+ ```
236
+
237
+ ## Where the deployed IDs live
238
+
239
+ `deploy` writes **`.agentlift-lock.json`** next to the path you deployed — a map from each local definition to the remote object it became (`skill_…`, `agent_…`, version, spec hash). **Commit it.** It's what makes re-deploys idempotent (unchanged skills/agents are skipped), makes `agentlift run lead …` resolve by name, and lets a teammate or CI reuse the same cloud objects instead of duplicating them. It holds only IDs/hashes/titles — no secrets. It's per Anthropic account; commit it when your team shares one. More: [docs/deploying.md](docs/deploying.md#where-the-ids-live-the-lockfile).
240
+
241
+ ## Tests
242
+
243
+ ```bash
244
+ pytest -m "not live" # deterministic translation + idempotency — no API key, runs in CI
245
+ pytest -m live # deploy to the real API, run, LLM-grade the output (needs ANTHROPIC_API_KEY)
246
+ ```
247
+
248
+ Offline tests pin the translation (tool mapping, per-tool permissions, skill dedup, stdio rejection, coordinator ordering, context isolation, diff, idempotency). Live tests deploy to Anthropic and confirm the uploaded skill actually fires in the cloud, graded by an LLM. CI runs the offline suite on every push and the live suite when an `ANTHROPIC_API_KEY` secret is present ([.github/workflows/ci.yml](.github/workflows/ci.yml)). A separate on-demand [live-demo workflow](.github/workflows/live-demo.yml) deploys the team example to a real account, runs the benchmark, and tears everything down — so the deploy path is demonstrably live, not just asserted.
249
+
250
+ ## Limitations (read these)
251
+
252
+ - **Remote MCP only.** Managed agents connect to URL MCP servers; local `stdio` servers (`npx ...`) can't be deployed. Host them behind HTTPS first.
253
+ - **No inline MCP auth.** A managed URL MCP server carries no credentials in this API shape. The server must be public or authenticate itself.
254
+ - **Knowledge files are inlined** into the system prompt (no persistent local FS in the managed sandbox). Large reference sets should become a skill bundle.
255
+ - **Anthropic only, for now.** The planner is provider-agnostic; OpenAI / Google targets are on the roadmap.
256
+
257
+ Each of these is surfaced as a `agentlift plan` diagnostic, not a silent surprise. More: [docs/limitations.md](docs/limitations.md).
258
+
259
+ ## Documentation
260
+
261
+ Everything is here or one click away:
262
+
263
+ | Doc | What's in it |
264
+ |---|---|
265
+ | [docs/convention.md](docs/convention.md) | The `.managed-agents/` folder spec, frontmatter, skills, MCP, `:ask` permissions, native subagents |
266
+ | [docs/deploying.md](docs/deploying.md) | The three deploy paths, the lockfile / where IDs live, isolation, hooks |
267
+ | [docs/how-it-works.md](docs/how-it-works.md) | `parse → plan → apply → run`, determinism, idempotency, the confirmed wire format |
268
+ | [docs/anthropic-mapping.md](docs/anthropic-mapping.md) | Exact local → Managed Agents field mapping + API constraints |
269
+ | [docs/limitations.md](docs/limitations.md) | Honest constraints (stdio MCP, MCP auth, knowledge inlining, skill descriptions) |
270
+ | [CONTRIBUTING.md](CONTRIBUTING.md) | Architecture and dev setup |
271
+
272
+ ### Examples ([examples/](examples/))
273
+
274
+ - [`quickstart/`](examples/quickstart/) — one agent, one skill, knowledge, a tool allowlist
275
+ - [`team/`](examples/team/) — multi-agent: coordinator + roster, a shared skill, a remote MCP server, a `bash:ask` permission
276
+ - [`in-a-project/`](examples/in-a-project/) — `.managed-agents/` embedded in a real project; proves isolation (repo `CLAUDE.md`, app code, and a local `.claude/agents/` subagent are never deployed) + a coordinator with two shared-skill subagents
277
+ - [`deploy-workflow/`](examples/deploy-workflow/) — the git-push-to-deploy GitHub Action
278
+ - [`claude-code-skill/`](examples/claude-code-skill/) — deploy from inside Claude Code
279
+
280
+ ## Roadmap
281
+
282
+ - Authenticated remote MCP via the Vaults API
283
+ - `agentlift diff --remote` deeper drift detection (full account reconciliation)
284
+ - Additional deploy targets (OpenAI Agent Builder, Google Managed Agents) behind the same convention
285
+ - A skill-bundle mode for large `knowledge/` sets
286
+
287
+ ## License
288
+
289
+ MIT — see [LICENSE](LICENSE). Built on the [Anthropic Python SDK](https://github.com/anthropics/anthropic-sdk-python).
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "agentlift"
7
+ version = "0.1.0"
8
+ description = "Deploy the Claude agents you already run locally to Anthropic's Managed Agents cloud. One folder, one command."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Pawel Huryn" }]
13
+ keywords = ["anthropic", "claude", "agents", "managed-agents", "llm", "deploy", "agent-sdk", "mcp", "skills"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ "Topic :: Software Development :: Build Tools",
19
+ ]
20
+ dependencies = [
21
+ "anthropic>=0.105.0",
22
+ "PyYAML>=6.0",
23
+ ]
24
+
25
+ [project.optional-dependencies]
26
+ dev = ["pytest>=8.0"]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/phuryn/agentlift"
30
+ Repository = "https://github.com/phuryn/agentlift"
31
+
32
+ [project.scripts]
33
+ agentlift = "agentlift.cli:main"
34
+
35
+ [tool.setuptools.packages.find]
36
+ where = ["src"]
37
+
38
+ [tool.setuptools.package-data]
39
+ agentlift = ["py.typed"]
40
+
41
+ [tool.pytest.ini_options]
42
+ markers = [
43
+ "live: tests that hit the real Anthropic API (require ANTHROPIC_API_KEY; cost a few cents)",
44
+ ]
45
+ testpaths = ["tests"]