contree-mcp 0.1.0__py3-none-any.whl
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.
- contree_mcp/__init__.py +0 -0
- contree_mcp/__main__.py +25 -0
- contree_mcp/app.py +240 -0
- contree_mcp/arguments.py +35 -0
- contree_mcp/auth/__init__.py +2 -0
- contree_mcp/auth/registry.py +236 -0
- contree_mcp/backend_types.py +301 -0
- contree_mcp/cache.py +208 -0
- contree_mcp/client.py +711 -0
- contree_mcp/context.py +53 -0
- contree_mcp/docs.py +1203 -0
- contree_mcp/file_cache.py +381 -0
- contree_mcp/prompts.py +238 -0
- contree_mcp/py.typed +0 -0
- contree_mcp/resources/__init__.py +17 -0
- contree_mcp/resources/guide.py +715 -0
- contree_mcp/resources/image_lineage.py +46 -0
- contree_mcp/resources/image_ls.py +32 -0
- contree_mcp/resources/import_operation.py +52 -0
- contree_mcp/resources/instance_operation.py +52 -0
- contree_mcp/resources/read_file.py +33 -0
- contree_mcp/resources/static.py +12 -0
- contree_mcp/server.py +77 -0
- contree_mcp/tools/__init__.py +39 -0
- contree_mcp/tools/cancel_operation.py +36 -0
- contree_mcp/tools/download.py +128 -0
- contree_mcp/tools/get_guide.py +54 -0
- contree_mcp/tools/get_image.py +30 -0
- contree_mcp/tools/get_operation.py +26 -0
- contree_mcp/tools/import_image.py +99 -0
- contree_mcp/tools/list_files.py +80 -0
- contree_mcp/tools/list_images.py +50 -0
- contree_mcp/tools/list_operations.py +46 -0
- contree_mcp/tools/read_file.py +47 -0
- contree_mcp/tools/registry_auth.py +71 -0
- contree_mcp/tools/registry_token_obtain.py +80 -0
- contree_mcp/tools/rsync.py +46 -0
- contree_mcp/tools/run.py +97 -0
- contree_mcp/tools/set_tag.py +31 -0
- contree_mcp/tools/upload.py +50 -0
- contree_mcp/tools/wait_operations.py +79 -0
- contree_mcp-0.1.0.dist-info/METADATA +450 -0
- contree_mcp-0.1.0.dist-info/RECORD +46 -0
- contree_mcp-0.1.0.dist-info/WHEEL +4 -0
- contree_mcp-0.1.0.dist-info/entry_points.txt +2 -0
- contree_mcp-0.1.0.dist-info/licenses/LICENSE +176 -0
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from textwrap import dedent
|
|
3
|
+
from types import MappingProxyType
|
|
4
|
+
|
|
5
|
+
WORKFLOW_GUIDE = """
|
|
6
|
+
# Contree Workflow Guide
|
|
7
|
+
|
|
8
|
+
## Decision Tree: Which Image to Use?
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
Need to run code in container?
|
|
12
|
+
│
|
|
13
|
+
├─ YES: Do I need specific packages/tools?
|
|
14
|
+
│ │
|
|
15
|
+
│ ├─ YES: Check for prepared environment
|
|
16
|
+
│ │ │
|
|
17
|
+
│ │ list_images(tag_prefix="common/")
|
|
18
|
+
│ │ │
|
|
19
|
+
│ │ ├─ FOUND: Use it directly
|
|
20
|
+
│ │ │ run(image="tag:common/python-ml/python:3.11-slim")
|
|
21
|
+
│ │ │
|
|
22
|
+
│ │ └─ NOT FOUND: Build and tag it
|
|
23
|
+
│ │ 1. import_image(registry_url="docker://python:3.11-slim")
|
|
24
|
+
│ │ 2. run(command="pip install ...", disposable=false)
|
|
25
|
+
│ │ 3. set_tag(tag="common/python-ml/python:3.11-slim")
|
|
26
|
+
│ │ 4. Use the tagged image
|
|
27
|
+
│ │
|
|
28
|
+
│ └─ NO: Use base image directly
|
|
29
|
+
│ run(image="tag:python:3.11-slim")
|
|
30
|
+
│
|
|
31
|
+
└─ NO: (not using Contree)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Complete Example: Python ML Environment
|
|
37
|
+
|
|
38
|
+
### Step 1: Check for Existing Environment
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
// list_images
|
|
42
|
+
{"tag_prefix": "common/python-ml"}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**If found:** Skip to Step 4.
|
|
46
|
+
|
|
47
|
+
### Step 2: Import Base Image
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
// import_image
|
|
51
|
+
{"registry_url": "docker://docker.io/python:3.11-slim"}
|
|
52
|
+
// Returns: {"result_image": "uuid-base"}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Step 3: Install Dependencies and Tag
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
// run - install packages
|
|
59
|
+
{
|
|
60
|
+
"command": "pip install numpy pandas scikit-learn",
|
|
61
|
+
"image": "uuid-base",
|
|
62
|
+
"disposable": false
|
|
63
|
+
}
|
|
64
|
+
// Returns: {"result_image": "uuid-with-deps"}
|
|
65
|
+
|
|
66
|
+
// set_tag - save for reuse
|
|
67
|
+
{"image_uuid": "uuid-with-deps", "tag": "common/python-ml/python:3.11-slim"}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Step 4: Execute Task
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
// run - use prepared environment
|
|
74
|
+
{
|
|
75
|
+
"command": "python train_model.py",
|
|
76
|
+
"image": "tag:common/python-ml/python:3.11-slim",
|
|
77
|
+
"directory_state_id": "ds_abc123"
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Common Prepared Environments
|
|
84
|
+
|
|
85
|
+
Before building, search for these common patterns:
|
|
86
|
+
|
|
87
|
+
| Search Query | Use Case |
|
|
88
|
+
|--------------|----------|
|
|
89
|
+
| `common/python-ml` | ML libraries (numpy, pandas, sklearn) |
|
|
90
|
+
| `common/python-web` | Web frameworks (flask, fastapi) |
|
|
91
|
+
| `common/rust-toolchain` | Rust compiler and cargo |
|
|
92
|
+
| `common/node` | Node.js with common packages |
|
|
93
|
+
| `common/build-essentials` | C/C++ build tools |
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
// Example search
|
|
97
|
+
{"tag_prefix": "common/python"}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Anti-Patterns to Avoid
|
|
103
|
+
|
|
104
|
+
### ❌ Importing Without Checking
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
// WRONG: Imports every time (wastes 10-30s)
|
|
108
|
+
{"registry_url": "docker://python:3.11-slim"}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
// CORRECT: Check first
|
|
113
|
+
{"tag_prefix": "python"} // list_images
|
|
114
|
+
// Only import if not found
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### ❌ Not Tagging Prepared Images
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
// WRONG: Installs deps but doesn't tag
|
|
121
|
+
{"command": "pip install numpy pandas", "disposable": false}
|
|
122
|
+
// Next time: must reinstall everything
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
// CORRECT: Tag after installing
|
|
127
|
+
{"image_uuid": "result-uuid", "tag": "common/python-ml/python:3.11-slim"}
|
|
128
|
+
// Next time: instant reuse
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### ❌ Chaining Commands
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
// WRONG: Can't rollback individual steps
|
|
135
|
+
{"command": "apt update && apt install -y curl && pip install requests"}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
// CORRECT: One step per command
|
|
140
|
+
{"command": "apt update", "disposable": false}
|
|
141
|
+
{"command": "apt install -y curl", "disposable": false}
|
|
142
|
+
{"command": "pip install requests", "disposable": false}
|
|
143
|
+
// Can rollback to any step
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Project-Specific Environments
|
|
149
|
+
|
|
150
|
+
For project-specific setups, use the project name as scope:
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
// Tag with project scope
|
|
154
|
+
{"image_uuid": "uuid", "tag": "myproject/dev-env/python:3.11-slim"}
|
|
155
|
+
|
|
156
|
+
// Search for project environments
|
|
157
|
+
{"tag_prefix": "myproject/"}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
This keeps common environments separate from project-specific ones.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
REFERENCE_GUIDE = """
|
|
164
|
+
# Contree Tools Reference
|
|
165
|
+
|
|
166
|
+
## Quick Reference
|
|
167
|
+
|
|
168
|
+
| Tool | Action | Key Params | Returns | Cost |
|
|
169
|
+
|------|--------|------------|---------|------|
|
|
170
|
+
| `run` | Execute command | `command`, `image`, `disposable` | stdout, result_image | VM |
|
|
171
|
+
| `rsync` | Sync files | `source`, `destination` | directory_state_id | Free |
|
|
172
|
+
| `import_image` | Import from registry | `registry_url`, `tag` | result_image | VM |
|
|
173
|
+
| `list_images` | List images | `tag_prefix`, `tagged` | images[] | Free |
|
|
174
|
+
| `get_image` | Get image details | `image` | uuid, tag | Free |
|
|
175
|
+
| `set_tag` | Tag/untag image | `image_uuid`, `tag` | uuid, tag | Free |
|
|
176
|
+
| `upload` | Upload file | `content` or `path` | uuid | Free |
|
|
177
|
+
| `download` | Download file | `image`, `path` | local file | Free |
|
|
178
|
+
| `get_operation` | Poll operation | `operation_id` | state, stdout | Free |
|
|
179
|
+
| `list_operations` | List operations | `status`, `kind` | operations[] | Free |
|
|
180
|
+
| `wait_operations` | Wait for multiple | `operation_ids` | results{} | Free |
|
|
181
|
+
| `cancel_operation` | Cancel operation | `operation_id` | cancelled | Free |
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## run
|
|
186
|
+
|
|
187
|
+
Execute command in isolated container. Spawns microVM.
|
|
188
|
+
|
|
189
|
+
**Key Parameters:**
|
|
190
|
+
- `command` (required): Shell command to execute (runs as root)
|
|
191
|
+
- `image` (required): Source image UUID or `tag:name`
|
|
192
|
+
- `disposable`: `true` (default) = discard changes, `false` = save result_image
|
|
193
|
+
- `wait`: `true` (default) = block for result, `false` = return operation_id
|
|
194
|
+
- `directory_state_id`: Inject files from `rsync`
|
|
195
|
+
- `files`: Inject files from `upload` as `{"/path": "uuid"}`
|
|
196
|
+
- `env`: Environment variables (prefer over shell export)
|
|
197
|
+
- `timeout`: Max execution time in seconds (default: 30)
|
|
198
|
+
|
|
199
|
+
**Data Flow:** rsync/upload -> run -> result_image (chain) or stdout
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## rsync
|
|
204
|
+
|
|
205
|
+
Sync local files to Contree for container injection. Free (no VM).
|
|
206
|
+
|
|
207
|
+
**Key Parameters:**
|
|
208
|
+
- `source` (required): Local path or glob pattern (`/path/dir`, `/path/**/*.py`)
|
|
209
|
+
- `destination` (required): Container target path (`/app`)
|
|
210
|
+
- `exclude`: Patterns to skip (`["__pycache__", ".git", "node_modules"]`)
|
|
211
|
+
|
|
212
|
+
**Data Flow:** local files -> rsync -> directory_state_id -> run
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## import_image
|
|
217
|
+
|
|
218
|
+
Import OCI container image from registry. Spawns microVM.
|
|
219
|
+
|
|
220
|
+
**Key Parameters:**
|
|
221
|
+
- `registry_url` (required): Registry URL (`docker://docker.io/python:3.11-slim`)
|
|
222
|
+
- `tag`: Optional tag to assign (prefer UUIDs for one-off use)
|
|
223
|
+
- `wait`: `true` (default) = block for result, `false` = return operation_id
|
|
224
|
+
|
|
225
|
+
**Data Flow:** registry -> import_image -> result_image -> run
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Image References
|
|
230
|
+
|
|
231
|
+
All tools accepting `image` parameter support two formats:
|
|
232
|
+
- **UUID**: `abc-123-def-456` (direct reference)
|
|
233
|
+
- **Tag**: `tag:python:3.11` (resolved to UUID)
|
|
234
|
+
|
|
235
|
+
Prefer UUIDs for one-off operations. Tags are useful for frequently-used base images.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Resources
|
|
240
|
+
|
|
241
|
+
### Guide Resource
|
|
242
|
+
|
|
243
|
+
**URI:** `contree://guide/{section}`
|
|
244
|
+
|
|
245
|
+
Available sections: `workflow`, `reference`, `quickstart`, `state`, `async`, `tagging`, `errors`.
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
QUICKSTART_GUIDE = """
|
|
249
|
+
# Contree Quickstart Guide
|
|
250
|
+
|
|
251
|
+
## 1. Basic Command Execution
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
{"command": "python -c 'print(1+1)'", "image": "tag:python:3.11"}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## 2. File Sync + Execute
|
|
258
|
+
|
|
259
|
+
First sync local files:
|
|
260
|
+
```json
|
|
261
|
+
// rsync
|
|
262
|
+
{"source": "/path/to/project", "destination": "/app", "exclude": ["__pycache__", ".git"]}
|
|
263
|
+
// Returns: {"directory_state_id": "ds_abc123", ...}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Then run with injected files:
|
|
267
|
+
```json
|
|
268
|
+
// run
|
|
269
|
+
{"command": "python /app/main.py", "image": "tag:python:3.11", "directory_state_id": "ds_abc123"}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## 3. Build Dependency Chain
|
|
273
|
+
|
|
274
|
+
Install dependencies once, reuse:
|
|
275
|
+
```json
|
|
276
|
+
// run - create checkpoint
|
|
277
|
+
{"command": "pip install numpy pandas", "image": "tag:python:3.11", "disposable": false}
|
|
278
|
+
// Returns: {"result_image": "img-with-deps", ...}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Run multiple experiments on the prepared image:
|
|
282
|
+
```json
|
|
283
|
+
{"command": "python train_model.py", "image": "img-with-deps"}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## 4. Import and Use External Image
|
|
287
|
+
|
|
288
|
+
```json
|
|
289
|
+
// import_image
|
|
290
|
+
{"registry_url": "docker://docker.io/golang:1.21", "tag": "golang:1.21"}
|
|
291
|
+
// Returns: {"result_image": "uuid...", "result_tag": "golang:1.21"}
|
|
292
|
+
|
|
293
|
+
// run
|
|
294
|
+
{"command": "go version", "image": "tag:golang:1.21"}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Best Practices
|
|
300
|
+
|
|
301
|
+
### UUIDs vs Tags
|
|
302
|
+
|
|
303
|
+
**Prefer UUIDs** for:
|
|
304
|
+
- One-off operations
|
|
305
|
+
- Chaining operations (use returned `result_image`)
|
|
306
|
+
- Reproducibility
|
|
307
|
+
|
|
308
|
+
**Use Tags** for:
|
|
309
|
+
- Frequently-used base images (`python:3.11`, `alpine:latest`)
|
|
310
|
+
- Human-readable references
|
|
311
|
+
|
|
312
|
+
### Output Management
|
|
313
|
+
|
|
314
|
+
Default `truncate_output_at` is 8000 bytes. Adjust based on expected output:
|
|
315
|
+
|
|
316
|
+
```json
|
|
317
|
+
// Verbose output expected
|
|
318
|
+
{"command": "find / -name '*.py'", "image": "...", "truncate_output_at": 32000}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Environment Variables
|
|
322
|
+
|
|
323
|
+
Use `env` parameter, not shell export:
|
|
324
|
+
|
|
325
|
+
```json
|
|
326
|
+
// Good
|
|
327
|
+
{"command": "python app.py", "image": "...", "env": {"DEBUG": "1", "API_KEY": "secret"}}
|
|
328
|
+
|
|
329
|
+
// Avoid
|
|
330
|
+
{"command": "export DEBUG=1 && python app.py", "image": "..."}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### File Sync Patterns
|
|
334
|
+
|
|
335
|
+
Exclude build artifacts and caches:
|
|
336
|
+
|
|
337
|
+
```json
|
|
338
|
+
{
|
|
339
|
+
"source": "/project",
|
|
340
|
+
"destination": "/app",
|
|
341
|
+
"exclude": ["__pycache__", "*.pyc", ".git", "node_modules", ".venv", "dist", "build"]
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
"""
|
|
345
|
+
|
|
346
|
+
STATE_GUIDE = """
|
|
347
|
+
# Contree State Management Guide
|
|
348
|
+
|
|
349
|
+
## Immutable Snapshots
|
|
350
|
+
|
|
351
|
+
Every image UUID represents an immutable filesystem snapshot.
|
|
352
|
+
The same UUID always means the exact same state.
|
|
353
|
+
|
|
354
|
+
## Disposable Mode
|
|
355
|
+
|
|
356
|
+
`disposable` parameter controls whether filesystem changes are preserved:
|
|
357
|
+
|
|
358
|
+
- **disposable=true** (default): Changes are discarded, no new image created
|
|
359
|
+
- **disposable=false**: Changes are saved to a new image
|
|
360
|
+
|
|
361
|
+
### When to Use Each
|
|
362
|
+
|
|
363
|
+
**Use disposable=true (default)** for:
|
|
364
|
+
- Read-only operations: `cat`, `ls`, `python -c "print(x)"`
|
|
365
|
+
- Tests: Run tests without saving test artifacts
|
|
366
|
+
- Exploration: Try commands, check output
|
|
367
|
+
|
|
368
|
+
**Use disposable=false** for:
|
|
369
|
+
- Install dependencies: `pip install`, `apt install`
|
|
370
|
+
- Build artifacts: Compile code, generate files to extract
|
|
371
|
+
- Create checkpoints: Save state for rollback
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## Rollback Model
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
Base Image (tag:python:3.11)
|
|
379
|
+
|
|
|
380
|
+
+-- Run "pip install flask" (disposable=false)
|
|
381
|
+
| |
|
|
382
|
+
| v
|
|
383
|
+
| Result Image A (uuid-a)
|
|
384
|
+
| |
|
|
385
|
+
| +-- Run "pip install sqlalchemy" (disposable=false)
|
|
386
|
+
| | |
|
|
387
|
+
| | v
|
|
388
|
+
| | Result Image B (uuid-b)
|
|
389
|
+
| |
|
|
390
|
+
| +-- Run "pip install redis" (disposable=false)
|
|
391
|
+
| |
|
|
392
|
+
| v
|
|
393
|
+
| Result Image C (uuid-c)
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Undo Last Change
|
|
397
|
+
|
|
398
|
+
If `uuid-b` has issues, just use `uuid-a` instead:
|
|
399
|
+
```json
|
|
400
|
+
{"command": "python app.py", "image": "uuid-a"} // Back to Flask-only state
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Try Alternative Path
|
|
404
|
+
|
|
405
|
+
From any point, branch in a different direction:
|
|
406
|
+
```json
|
|
407
|
+
{"command": "pip install fastapi", "image": "uuid-a", "disposable": false}
|
|
408
|
+
// Creates uuid-e, parallel to uuid-b and uuid-c
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### Recover from Mistakes
|
|
412
|
+
|
|
413
|
+
Accidentally corrupted something? Previous UUIDs are untouched:
|
|
414
|
+
```json
|
|
415
|
+
// This broke things
|
|
416
|
+
{"command": "rm -rf /important", "image": "uuid-x", "disposable": false}
|
|
417
|
+
// Returns uuid-y (corrupted)
|
|
418
|
+
|
|
419
|
+
// Just use uuid-x again
|
|
420
|
+
{"command": "python app.py", "image": "uuid-x"} // Original state intact
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Response Fields
|
|
426
|
+
|
|
427
|
+
When `disposable=false`:
|
|
428
|
+
|
|
429
|
+
```json
|
|
430
|
+
{
|
|
431
|
+
"exit_code": 0,
|
|
432
|
+
"state": "SUCCESS",
|
|
433
|
+
"result_image": "uuid-new",
|
|
434
|
+
"filesystem_changed": true,
|
|
435
|
+
"stdout": "Successfully installed numpy-1.24.0"
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
- `result_image`: UUID of new image (or same as input if no changes)
|
|
440
|
+
- `filesystem_changed`: Whether any modifications occurred
|
|
441
|
+
"""
|
|
442
|
+
|
|
443
|
+
ASYNC_GUIDE = """
|
|
444
|
+
# Contree Async Execution Guide
|
|
445
|
+
|
|
446
|
+
## When to Use Each Mode
|
|
447
|
+
|
|
448
|
+
### Sequential (wait=true, default)
|
|
449
|
+
- Single commands: Just run and get result
|
|
450
|
+
- Chained operations: Each step depends on previous
|
|
451
|
+
- Most workflows: No need for parallelism
|
|
452
|
+
|
|
453
|
+
### Parallel (wait=false)
|
|
454
|
+
- Multiple independent experiments
|
|
455
|
+
- Batch processing: Launch many, wait for all
|
|
456
|
+
- Resource optimization: Maximize throughput
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
## Sequential Pattern (Default)
|
|
461
|
+
|
|
462
|
+
```json
|
|
463
|
+
// Just run commands - wait=true is the default
|
|
464
|
+
{"command": "pip install flask", "image": "tag:python:3.11", "disposable": false}
|
|
465
|
+
// Returns immediately with result: {"stdout": "...", "result_image": "uuid-1"}
|
|
466
|
+
|
|
467
|
+
{"command": "python app.py", "image": "uuid-1"}
|
|
468
|
+
// Returns: {"stdout": "...", "exit_code": 0}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## Parallel Pattern
|
|
474
|
+
|
|
475
|
+
### Launch Multiple Async
|
|
476
|
+
|
|
477
|
+
```json
|
|
478
|
+
// run with wait=false (make 3 parallel tool calls)
|
|
479
|
+
{"command": "python exp1.py", "image": "tag:python:3.11", "wait": false} // -> op-1
|
|
480
|
+
{"command": "python exp2.py", "image": "tag:python:3.11", "wait": false} // -> op-2
|
|
481
|
+
{"command": "python exp3.py", "image": "tag:python:3.11", "wait": false} // -> op-3
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Wait for All Results
|
|
485
|
+
|
|
486
|
+
```json
|
|
487
|
+
// wait_operations - single call, waits for all
|
|
488
|
+
{"operation_ids": ["op-1", "op-2", "op-3"]}
|
|
489
|
+
// Returns: {"results": {"op-1": {...}, "op-2": {...}, "op-3": {...}}}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
## Operation States
|
|
495
|
+
|
|
496
|
+
| Status | Meaning |
|
|
497
|
+
|--------|---------|
|
|
498
|
+
| `pending` | Queued, waiting for VM |
|
|
499
|
+
| `running` | Command actively executing |
|
|
500
|
+
| `success` | Completed successfully |
|
|
501
|
+
| `failed` | Completed with error |
|
|
502
|
+
| `cancelled` | Cancelled by user |
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
## wait_operations Modes
|
|
507
|
+
|
|
508
|
+
| Mode | Behavior |
|
|
509
|
+
|------|----------|
|
|
510
|
+
| `all` (default) | Wait until ALL operations complete |
|
|
511
|
+
| `any` | Return when FIRST operation completes |
|
|
512
|
+
|
|
513
|
+
```json
|
|
514
|
+
// Wait for any (returns on first completion)
|
|
515
|
+
{"operation_ids": ["op-1", "op-2", "op-3"], "mode": "any"}
|
|
516
|
+
// Returns: {"results": {"op-1": {...}}, "completed": ["op-1"], "pending": ["op-2", "op-3"]}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
## Tips
|
|
522
|
+
|
|
523
|
+
1. **Default is fine**: Use wait=true for sequential workflows
|
|
524
|
+
2. **Parallel when needed**: Only use wait=false for concurrent tasks
|
|
525
|
+
3. **Use wait_operations**: Single call to wait for multiple operations
|
|
526
|
+
4. **Cancel early**: Don't waste resources on unneeded tasks
|
|
527
|
+
"""
|
|
528
|
+
|
|
529
|
+
TAGGING_GUIDE = """
|
|
530
|
+
# Contree Tagging Convention
|
|
531
|
+
|
|
532
|
+
This convention helps AI agents organize prepared environments for reuse.
|
|
533
|
+
|
|
534
|
+
## Tag Format
|
|
535
|
+
|
|
536
|
+
```
|
|
537
|
+
{scope}/{purpose}/{base}:{tag}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
## Components
|
|
541
|
+
|
|
542
|
+
| Component | Description | Examples |
|
|
543
|
+
|-----------|-------------|----------|
|
|
544
|
+
| `{scope}` | `common` or project name | `common`, `myproject` |
|
|
545
|
+
| `{purpose}` | What was added/configured | `rust-toolchain`, `python-ml`, `web-deps` |
|
|
546
|
+
| `{base}:{tag}` | Original base image | `ubuntu:noble`, `python:3.11-slim` |
|
|
547
|
+
|
|
548
|
+
## Examples
|
|
549
|
+
|
|
550
|
+
| Scenario | Tag |
|
|
551
|
+
|----------|-----|
|
|
552
|
+
| Ubuntu + Rust toolchain | `common/rust-toolchain/ubuntu:noble` |
|
|
553
|
+
| Python + ML libraries | `common/python-ml/python:3.11-slim` |
|
|
554
|
+
| Python + web frameworks | `common/python-web/python:3.11-slim` |
|
|
555
|
+
| Project dev environment | `myproject/dev-env/python:3.11-slim` |
|
|
556
|
+
| Build tools on Alpine | `common/build-essentials/alpine:latest` |
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
## When to Tag
|
|
561
|
+
|
|
562
|
+
### Tag as COMMON (`common/...`) when:
|
|
563
|
+
- Installing standard packages (build-essential, curl, git)
|
|
564
|
+
- Adding language runtimes/compilers (rust, go, node)
|
|
565
|
+
- Installing widely-used libraries (numpy, pandas, flask)
|
|
566
|
+
|
|
567
|
+
### Tag as PROJECT-SPECIFIC (`{project}/...`) when:
|
|
568
|
+
- Installing project-specific dependencies
|
|
569
|
+
- Setting up development environment for a specific project
|
|
570
|
+
- Contains application code or config
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
## Workflow
|
|
575
|
+
|
|
576
|
+
### Before Creating New Images
|
|
577
|
+
|
|
578
|
+
Search for existing prepared environments first:
|
|
579
|
+
```json
|
|
580
|
+
// list_images
|
|
581
|
+
{"tag_prefix": "common/python"}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### After Installing Dependencies
|
|
585
|
+
|
|
586
|
+
Tag the `result_image` with `set_tag`:
|
|
587
|
+
```json
|
|
588
|
+
// set_tag
|
|
589
|
+
{"image_uuid": "result-uuid", "tag": "common/python-ml/python:3.11-slim"}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### Using Tagged Images
|
|
593
|
+
|
|
594
|
+
Reference by tag in subsequent operations:
|
|
595
|
+
```json
|
|
596
|
+
// run
|
|
597
|
+
{"command": "python train.py", "image": "tag:common/python-ml/python:3.11-slim"}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
---
|
|
601
|
+
|
|
602
|
+
## Common Tags to Search For
|
|
603
|
+
|
|
604
|
+
| Search | Use Case |
|
|
605
|
+
|--------|----------|
|
|
606
|
+
| `common/python-ml` | numpy, pandas, scikit-learn |
|
|
607
|
+
| `common/python-web` | flask, fastapi, requests |
|
|
608
|
+
| `common/rust-toolchain` | rustc, cargo |
|
|
609
|
+
| `common/node` | node, npm, common packages |
|
|
610
|
+
| `common/build-essentials` | gcc, make, cmake |
|
|
611
|
+
"""
|
|
612
|
+
|
|
613
|
+
ERRORS_GUIDE = """
|
|
614
|
+
# Contree Error Handling Guide
|
|
615
|
+
|
|
616
|
+
## Common Error Patterns
|
|
617
|
+
|
|
618
|
+
### Command Failures
|
|
619
|
+
|
|
620
|
+
**Symptom:** `exit_code` is non-zero
|
|
621
|
+
|
|
622
|
+
```json
|
|
623
|
+
{
|
|
624
|
+
"exit_code": 1,
|
|
625
|
+
"state": "SUCCESS",
|
|
626
|
+
"stdout": "",
|
|
627
|
+
"stderr": "python: can't open file 'missing.py': [Errno 2] No such file or directory"
|
|
628
|
+
}
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
**Note:** `state: SUCCESS` means the operation completed (VM ran), not that the command succeeded.
|
|
632
|
+
Check `exit_code` for command result.
|
|
633
|
+
|
|
634
|
+
**Solutions:**
|
|
635
|
+
- Check `stderr` for error message
|
|
636
|
+
- Verify file paths with `ls` command first
|
|
637
|
+
- Ensure dependencies are installed
|
|
638
|
+
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
### Timeout Exceeded
|
|
642
|
+
|
|
643
|
+
**Symptom:** `timed_out: true`
|
|
644
|
+
|
|
645
|
+
```json
|
|
646
|
+
{
|
|
647
|
+
"exit_code": -1,
|
|
648
|
+
"state": "SUCCESS",
|
|
649
|
+
"timed_out": true,
|
|
650
|
+
"stdout": "[partial output...]"
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
**Solutions:**
|
|
655
|
+
- Increase `timeout` parameter (default: 30s, max: 600s)
|
|
656
|
+
- Break long operations into smaller steps
|
|
657
|
+
- Use `wait=false` for long operations, poll with `get_operation`
|
|
658
|
+
|
|
659
|
+
---
|
|
660
|
+
|
|
661
|
+
### Image Not Found
|
|
662
|
+
|
|
663
|
+
**Symptom:** Error message about missing image
|
|
664
|
+
|
|
665
|
+
**Solutions:**
|
|
666
|
+
1. Check available images: `list_images(tag_prefix="python")`
|
|
667
|
+
2. Import missing image: `import_image(registry_url="docker://...")`
|
|
668
|
+
3. Verify tag format: Use `tag:python:3.11` not just `python:3.11`
|
|
669
|
+
|
|
670
|
+
---
|
|
671
|
+
|
|
672
|
+
### Directory State Not Found
|
|
673
|
+
|
|
674
|
+
**Symptom:** `Directory state not found: ds_xxx`
|
|
675
|
+
|
|
676
|
+
**Cause:** Directory states expire or were created in a different session.
|
|
677
|
+
|
|
678
|
+
**Solutions:**
|
|
679
|
+
- Re-run `rsync` to create new directory state
|
|
680
|
+
- Use the `directory_state_id` immediately after `rsync`
|
|
681
|
+
|
|
682
|
+
---
|
|
683
|
+
|
|
684
|
+
### Output Truncated
|
|
685
|
+
|
|
686
|
+
**Symptom:** `[TRUNCATED]` at end of stdout/stderr
|
|
687
|
+
|
|
688
|
+
**Solutions:**
|
|
689
|
+
- Increase `truncate_output_at` (default: 8000 bytes)
|
|
690
|
+
- Write output to file, then `download` it
|
|
691
|
+
- Filter output in command (e.g., `| head -100`)
|
|
692
|
+
|
|
693
|
+
---
|
|
694
|
+
|
|
695
|
+
## Debugging Workflow
|
|
696
|
+
|
|
697
|
+
1. **Check exit_code first** - 0 means success, non-zero means failure
|
|
698
|
+
2. **Read stderr** - Contains error messages and stack traces
|
|
699
|
+
3. **Verify inputs** - Images exist, files synced, paths correct
|
|
700
|
+
4. **Try interactively** - Run simpler commands to isolate issue
|
|
701
|
+
5. **Check state** - Use `list_operations` to see recent operations
|
|
702
|
+
"""
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
SECTIONS: Mapping[str, str] = MappingProxyType(
|
|
706
|
+
{
|
|
707
|
+
"workflow": dedent(WORKFLOW_GUIDE).strip(),
|
|
708
|
+
"reference": dedent(REFERENCE_GUIDE).strip(),
|
|
709
|
+
"quickstart": dedent(QUICKSTART_GUIDE).strip(),
|
|
710
|
+
"state": dedent(STATE_GUIDE).strip(),
|
|
711
|
+
"async": dedent(ASYNC_GUIDE).strip(),
|
|
712
|
+
"tagging": dedent(TAGGING_GUIDE).strip(),
|
|
713
|
+
"errors": dedent(ERRORS_GUIDE).strip(),
|
|
714
|
+
}
|
|
715
|
+
)
|