tamarind-cli 0.2.0__tar.gz → 0.3.1__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.
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/.github/workflows/ci.yml +41 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/PKG-INFO +53 -2
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/README.md +47 -0
- tamarind_cli-0.3.1/docs/architecture.md +102 -0
- tamarind_cli-0.3.1/openapi/openapi-python-client.json +4 -0
- tamarind_cli-0.3.1/openapi/pipelines-openapi-python-client.json +4 -0
- tamarind_cli-0.3.1/openapi/pipelines-v1.json +555 -0
- tamarind_cli-0.3.1/openapi/pipelines-v1.lock.json +8 -0
- tamarind_cli-0.3.1/openapi/public-v1.json +1820 -0
- tamarind_cli-0.3.1/openapi/public-v1.lock.json +8 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/pyproject.toml +5 -1
- tamarind_cli-0.3.1/scripts/check_custom_tools_provenance.py +53 -0
- tamarind_cli-0.3.1/scripts/sync_custom_tools_contract.py +349 -0
- tamarind_cli-0.3.1/scripts/sync_pipelines_contract.py +184 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/__init__.py +10 -1
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/config.py +4 -5
- tamarind_cli-0.3.1/src/tamarind/custom_tools/__init__.py +31 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_contract.py +3 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/__init__.py +8 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/__init__.py +1 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/__init__.py +1 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/build_custom_tool_version.py +228 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/cancel_custom_tool_build.py +224 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/create_custom_tool.py +185 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/create_custom_tool_upload.py +214 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/delete_custom_tool.py +205 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/get_custom_tool.py +177 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/get_custom_tool_version.py +216 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/list_custom_tool_build_logs.py +249 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/list_custom_tool_versions.py +273 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/list_custom_tools.py +252 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/publish_custom_tool_version.py +224 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/api/custom_tools/update_custom_tool.py +233 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/client.py +272 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/errors.py +16 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/__init__.py +57 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_build_error.py +50 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_build_event.py +50 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_build_log_page.py +110 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_build_result.py +59 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_build_result_action.py +10 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_create_custom_tool_request.py +109 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_create_custom_tool_request_gputype.py +13 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_create_custom_tool_request_memory.py +16 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_create_version_request.py +50 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_custom_tool.py +242 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_custom_tool_gputype.py +13 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_custom_tool_memory.py +16 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_custom_tool_page.py +70 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_custom_tool_status.py +10 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_problem.py +137 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_problem_errors_type_0_item.py +47 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_update_custom_tool_request.py +304 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_update_custom_tool_request_gpu_type_type_0.py +13 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_update_custom_tool_request_memory_type_0.py +16 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_upload_session.py +91 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_upload_session_uploadheaders.py +47 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_version.py +160 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_version_page.py +70 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/models/public_version_status.py +11 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/_generated/types.py +54 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/client.py +39 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/packaging.py +584 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/resources.py +755 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/transport.py +337 -0
- tamarind_cli-0.3.1/src/tamarind/custom_tools/validation.py +146 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/errors.py +48 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/http.py +121 -20
- tamarind_cli-0.3.1/src/tamarind/pipelines/__init__.py +17 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/__init__.py +8 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/api/__init__.py +1 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/api/pipelines_public/__init__.py +1 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/api/pipelines_public/get_run.py +177 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/api/pipelines_public/list_node_run_molecules.py +268 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/client.py +272 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/errors.py +16 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/__init__.py +27 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/node_run_status.py +15 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/public_node_run.py +168 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/public_node_run_molecule.py +127 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/public_node_run_molecule_page.py +89 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/public_node_run_molecule_scores.py +47 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/public_problem.py +137 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/public_problem_errors_type_0_item.py +47 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/public_run.py +171 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/public_run_inputs.py +47 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/run_status.py +13 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/models/source.py +9 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/_generated/types.py +54 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/resources.py +204 -0
- tamarind_cli-0.3.1/src/tamarind/pipelines/transport.py +67 -0
- tamarind_cli-0.3.1/src/tamarind/py.typed +0 -0
- tamarind_cli-0.3.1/tests/test_custom_tools_generation.py +322 -0
- tamarind_cli-0.3.1/tests/test_custom_tools_packaging.py +744 -0
- tamarind_cli-0.3.1/tests/test_custom_tools_sdk.py +1009 -0
- tamarind_cli-0.3.1/tests/test_pep561.py +5 -0
- tamarind_cli-0.3.1/tests/test_pipelines_sdk.py +166 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_rest.py +120 -17
- tamarind_cli-0.3.1/uv.lock +1018 -0
- tamarind_cli-0.2.0/docs/architecture.md +0 -52
- tamarind_cli-0.2.0/uv.lock +0 -406
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/.github/workflows/publish.yml +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/.gitignore +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/LICENSE +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/install.sh +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/catalog.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/__init__.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/commands/__init__.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/commands/auth.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/commands/catalog.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/commands/files.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/commands/jobs.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/guidance.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/inputs.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/main.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/cli/output.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/jobs.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/src/tamarind/rest.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_agent_contract.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_bugbash.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_cli_commands.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_config.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_file_filters.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_inputs.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_jobs.py +0 -0
- {tamarind_cli-0.2.0 → tamarind_cli-0.3.1}/tests/test_output.py +0 -0
|
@@ -9,6 +9,47 @@ permissions:
|
|
|
9
9
|
contents: read
|
|
10
10
|
|
|
11
11
|
jobs:
|
|
12
|
+
generated:
|
|
13
|
+
name: Generated transport is current
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
timeout-minutes: 5
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: astral-sh/setup-uv@v5
|
|
19
|
+
- name: Verify backend provenance and generated client freshness
|
|
20
|
+
run: |
|
|
21
|
+
uv run --extra dev python scripts/check_custom_tools_provenance.py
|
|
22
|
+
tmp_dir=$(mktemp -d .custom-tools-generated.XXXXXX)
|
|
23
|
+
trap 'rm -rf "$tmp_dir"' EXIT
|
|
24
|
+
uv run --extra dev openapi-python-client generate --path openapi/public-v1.json \
|
|
25
|
+
--config openapi/openapi-python-client.json --meta none \
|
|
26
|
+
--output-path "$tmp_dir/generated" --overwrite
|
|
27
|
+
diff -ru --exclude=.ruff_cache src/tamarind/custom_tools/_generated "$tmp_dir/generated"
|
|
28
|
+
uv run --extra dev openapi-python-client generate --path openapi/pipelines-v1.json \
|
|
29
|
+
--config openapi/pipelines-openapi-python-client.json --meta none \
|
|
30
|
+
--output-path "$tmp_dir/pipelines-generated" --overwrite
|
|
31
|
+
diff -ru --exclude=.ruff_cache src/tamarind/pipelines/_generated "$tmp_dir/pipelines-generated"
|
|
32
|
+
|
|
33
|
+
typecheck:
|
|
34
|
+
name: SDK type check
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
timeout-minutes: 5
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v4
|
|
39
|
+
- uses: astral-sh/setup-uv@v5
|
|
40
|
+
with:
|
|
41
|
+
enable-cache: true
|
|
42
|
+
python-version: "3.12"
|
|
43
|
+
- name: Check the public SDK and transport boundary
|
|
44
|
+
run: >-
|
|
45
|
+
uv run --extra dev mypy
|
|
46
|
+
src/tamarind/custom_tools
|
|
47
|
+
src/tamarind/pipelines
|
|
48
|
+
src/tamarind/http.py
|
|
49
|
+
src/tamarind/errors.py
|
|
50
|
+
scripts/sync_custom_tools_contract.py
|
|
51
|
+
scripts/sync_pipelines_contract.py
|
|
52
|
+
|
|
12
53
|
package:
|
|
13
54
|
name: Build release artifacts
|
|
14
55
|
runs-on: ubuntu-latest
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: tamarind-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Command-line interface for the Tamarind Bio platform — submit, monitor, and download protein/molecule jobs from your terminal or an AI agent.
|
|
5
5
|
Project-URL: Homepage, https://tamarind.bio
|
|
6
6
|
Project-URL: Documentation, https://app.tamarind.bio/api-docs
|
|
@@ -10,10 +10,14 @@ License: Apache-2.0
|
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Keywords: agents,alphafold,bioinformatics,boltz,cli,protein
|
|
12
12
|
Requires-Python: >=3.10
|
|
13
|
+
Requires-Dist: attrs>=23.1
|
|
13
14
|
Requires-Dist: httpx>=0.27
|
|
14
15
|
Requires-Dist: pyyaml>=6.0
|
|
15
16
|
Requires-Dist: typer>=0.12
|
|
17
|
+
Requires-Dist: typing-extensions>=4.7
|
|
16
18
|
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
20
|
+
Requires-Dist: openapi-python-client==0.28.4; extra == 'dev'
|
|
17
21
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
18
22
|
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
19
23
|
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
@@ -99,6 +103,53 @@ tamarind submit boltz \
|
|
|
99
103
|
--name quick-fold
|
|
100
104
|
```
|
|
101
105
|
|
|
106
|
+
## Python SDK
|
|
107
|
+
|
|
108
|
+
The package exposes typed Pipelines and Custom Tools resources. Pipeline runs use
|
|
109
|
+
the same node-run terminology as the web app and public REST API:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from tamarind import Tamarind
|
|
113
|
+
|
|
114
|
+
with Tamarind() as client:
|
|
115
|
+
run = client.pipelines.get_run("run-id")
|
|
116
|
+
for node_run in run.node_runs:
|
|
117
|
+
page = node_run.molecules(limit=100)
|
|
118
|
+
print(node_run.label, page.items)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The Custom Tools resource checks archive-local
|
|
122
|
+
safety, packages a folder, uploads it directly to object storage, and submits the
|
|
123
|
+
digest-checked build request. The server
|
|
124
|
+
remains authoritative for source readiness, Versions, logs, and the evolving
|
|
125
|
+
`config.json` contract.
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from tamarind import Tamarind
|
|
129
|
+
from tamarind.errors import CustomToolNotFoundError
|
|
130
|
+
|
|
131
|
+
with Tamarind() as client:
|
|
132
|
+
try:
|
|
133
|
+
tool = client.custom_tools.get("my-esmfold")
|
|
134
|
+
except CustomToolNotFoundError:
|
|
135
|
+
tool = client.custom_tools.create("my-esmfold", display_name="My ESMFold")
|
|
136
|
+
|
|
137
|
+
result = tool.build("./my-esmfold")
|
|
138
|
+
print(result.action) # build, reuse_image, or unchanged
|
|
139
|
+
version = result.version
|
|
140
|
+
if not version.terminal:
|
|
141
|
+
version = version.monitor(timeout=1800, on_event=print)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`build()` returns a typed result describing what the request did and the durable
|
|
145
|
+
Version it produced. It is convenience orchestration, not a durable request
|
|
146
|
+
object. If the connection is lost during the build response, fetch the tool's
|
|
147
|
+
versions before retrying. Interrupting `monitor()` stops local monitoring; it
|
|
148
|
+
does not cancel the remote build.
|
|
149
|
+
|
|
150
|
+
Custom Tool CLI commands are not part of this release. Existing CLI commands
|
|
151
|
+
remain unchanged.
|
|
152
|
+
|
|
102
153
|
## Output for agents
|
|
103
154
|
|
|
104
155
|
Every command emits JSON when stdout is not a TTY, or with `--json`. Result
|
|
@@ -78,6 +78,53 @@ tamarind submit boltz \
|
|
|
78
78
|
--name quick-fold
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
## Python SDK
|
|
82
|
+
|
|
83
|
+
The package exposes typed Pipelines and Custom Tools resources. Pipeline runs use
|
|
84
|
+
the same node-run terminology as the web app and public REST API:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from tamarind import Tamarind
|
|
88
|
+
|
|
89
|
+
with Tamarind() as client:
|
|
90
|
+
run = client.pipelines.get_run("run-id")
|
|
91
|
+
for node_run in run.node_runs:
|
|
92
|
+
page = node_run.molecules(limit=100)
|
|
93
|
+
print(node_run.label, page.items)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The Custom Tools resource checks archive-local
|
|
97
|
+
safety, packages a folder, uploads it directly to object storage, and submits the
|
|
98
|
+
digest-checked build request. The server
|
|
99
|
+
remains authoritative for source readiness, Versions, logs, and the evolving
|
|
100
|
+
`config.json` contract.
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from tamarind import Tamarind
|
|
104
|
+
from tamarind.errors import CustomToolNotFoundError
|
|
105
|
+
|
|
106
|
+
with Tamarind() as client:
|
|
107
|
+
try:
|
|
108
|
+
tool = client.custom_tools.get("my-esmfold")
|
|
109
|
+
except CustomToolNotFoundError:
|
|
110
|
+
tool = client.custom_tools.create("my-esmfold", display_name="My ESMFold")
|
|
111
|
+
|
|
112
|
+
result = tool.build("./my-esmfold")
|
|
113
|
+
print(result.action) # build, reuse_image, or unchanged
|
|
114
|
+
version = result.version
|
|
115
|
+
if not version.terminal:
|
|
116
|
+
version = version.monitor(timeout=1800, on_event=print)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`build()` returns a typed result describing what the request did and the durable
|
|
120
|
+
Version it produced. It is convenience orchestration, not a durable request
|
|
121
|
+
object. If the connection is lost during the build response, fetch the tool's
|
|
122
|
+
versions before retrying. Interrupting `monitor()` stops local monitoring; it
|
|
123
|
+
does not cancel the remote build.
|
|
124
|
+
|
|
125
|
+
Custom Tool CLI commands are not part of this release. Existing CLI commands
|
|
126
|
+
remain unchanged.
|
|
127
|
+
|
|
81
128
|
## Output for agents
|
|
82
129
|
|
|
83
130
|
Every command emits JSON when stdout is not a TTY, or with `--json`. Result
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Architecture & no-drift design
|
|
2
|
+
|
|
3
|
+
The CLI, the [MCP server](https://mcp.tamarind.bio), and the web app are all
|
|
4
|
+
**thin clients** over the same platform. The CLI never re-implements business
|
|
5
|
+
logic; it only knows how to call four well-defined surfaces. This is what keeps
|
|
6
|
+
the CLI and the MCP from drifting as the platform evolves.
|
|
7
|
+
|
|
8
|
+
## Four surfaces, four single sources of truth
|
|
9
|
+
|
|
10
|
+
### 1. Job/file REST surface — source of truth: the OpenAPI spec
|
|
11
|
+
|
|
12
|
+
`submit`, `validate`, `batch`, `jobs`, `status`/`wait`, `results`, `files`,
|
|
13
|
+
`cancel`, and `delete` map onto operations in `openapi-mcp.yaml` — the same
|
|
14
|
+
server contract used to generate the MCP surface. The CLI's
|
|
15
|
+
[`rest.py`](../src/tamarind/rest.py) is intentionally a small mapping of those
|
|
16
|
+
operations.
|
|
17
|
+
|
|
18
|
+
The CLI mapping is still client code and can drift. Unit tests cover request and
|
|
19
|
+
response shapes, and authenticated no-spend smoke tests should exercise auth,
|
|
20
|
+
catalog lookup, schema lookup, and validation before a release. Changes to the
|
|
21
|
+
server contract should update the CLI tests in the same change or release train.
|
|
22
|
+
|
|
23
|
+
These calls go directly to the job API (`https://app.tamarind.bio/api/`) with
|
|
24
|
+
the `x-api-key` header.
|
|
25
|
+
|
|
26
|
+
### 2. Discovery surface — source of truth: a shared catalog module
|
|
27
|
+
|
|
28
|
+
`tools`, `modalities`, `functions`, and `schema` need per-org visibility logic
|
|
29
|
+
(which tools an account may see, per-parameter gating, example generation) that
|
|
30
|
+
must run server-side. So the CLI does **not** read the catalog database; it
|
|
31
|
+
calls the `/catalog/*` HTTP routes ([`catalog.py`](../src/tamarind/catalog.py)),
|
|
32
|
+
which return exactly the JSON the MCP's discovery tools return.
|
|
33
|
+
|
|
34
|
+
The MCP tools and the `/catalog/*` routes are served by the **same shared
|
|
35
|
+
implementation**, so a tool looks identical no matter which client you use.
|
|
36
|
+
Because the logic lives in one module, *where* discovery is hosted (the MCP host
|
|
37
|
+
today; potentially the main API or a dedicated service later) is a deployment
|
|
38
|
+
detail that can change without any client change and without drift.
|
|
39
|
+
|
|
40
|
+
### 3. Custom Tools surface — source of truth: the public OpenAPI artifact
|
|
41
|
+
|
|
42
|
+
Custom Tool creation, source upload, build, Versions, logs, and cancellation
|
|
43
|
+
are generated from the website backend's dedicated public OpenAPI artifact:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
backend-owned custom-tools-v1.generated.json
|
|
47
|
+
-> Git-object provenance verification
|
|
48
|
+
-> vendored spec + lock
|
|
49
|
+
-> openapi-python-client==0.28.4
|
|
50
|
+
-> generated models/endpoints
|
|
51
|
+
-> thin Tamarind transport and resource adapters
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The Website owns the projection and its referenced-component closure. The CLI sync
|
|
55
|
+
verifies the exact producer repository, commit, path, and bytes before atomically
|
|
56
|
+
installing the spec, provenance lock, generated server metadata, and generated package.
|
|
57
|
+
CI regenerates with the pinned mature generator and rejects drift. The handwritten
|
|
58
|
+
layer contains only HTTP/error normalization and ergonomic resource composition; it
|
|
59
|
+
does not parse OpenAPI or maintain a second schema IR/compiler.
|
|
60
|
+
|
|
61
|
+
The SDK owns archive-local concerns that only the client can decide safely:
|
|
62
|
+
deterministic ZIP construction, symlink and junction rejection, upload limits,
|
|
63
|
+
JSON parseability and top-level object shape, and warnings about the networkless
|
|
64
|
+
runtime. Archives use stored ZIP entries so source digests do not depend on a
|
|
65
|
+
platform zlib build. Modes are also platform-stable: `run.sh` and files beginning
|
|
66
|
+
with a shebang are executable, while all other files are regular. The backend owns
|
|
67
|
+
the evolving `config.json` business contract and
|
|
68
|
+
validates it before accepting a build. The SDK deliberately does not maintain a
|
|
69
|
+
second list of configuration fields, enums, or cross-field rules.
|
|
70
|
+
|
|
71
|
+
`CustomTool.build()` composes the existing requests: create upload, PUT the archive,
|
|
72
|
+
submit the digest-checked build request, and return a typed result containing the server's
|
|
73
|
+
action plus its durable Version. It adds no server-side
|
|
74
|
+
BuildRequest, queue, lease, claim, or repair state. An ambiguous build response
|
|
75
|
+
is therefore handled honestly by listing Versions before a manual retry.
|
|
76
|
+
|
|
77
|
+
### 4. Pipelines read surface — source of truth: the public OpenAPI artifact
|
|
78
|
+
|
|
79
|
+
`Tamarind().pipelines` reads a run and pages through the molecules produced by one
|
|
80
|
+
of its node runs. The sync script selects only those operations and their referenced
|
|
81
|
+
schemas from the website backend's frozen public OpenAPI artifact, verifies the exact
|
|
82
|
+
producer Git object, and records that commit in a dedicated lock. Keeping this
|
|
83
|
+
projection separate prevents Pipelines changes from broadening or perturbing the
|
|
84
|
+
Custom Tools contract.
|
|
85
|
+
|
|
86
|
+
Generated endpoint code owns URL encoding and wire-model parsing. A small resource
|
|
87
|
+
adapter adds strict runtime checks and composes `PipelineRun` → `NodeRun` → molecule
|
|
88
|
+
pages without introducing another public vocabulary.
|
|
89
|
+
|
|
90
|
+
## Why not a single binary that re-encodes the API?
|
|
91
|
+
|
|
92
|
+
A from-scratch client in another language would re-encode the request shapes and
|
|
93
|
+
the catalog logic — two copies that drift the moment the platform changes.
|
|
94
|
+
Keeping the CLI thin and anchored to the same OpenAPI contract (and,
|
|
95
|
+
server-side, the same catalog module) reduces drift and makes it testable. It
|
|
96
|
+
does not eliminate the need for compatibility tests and coordinated releases.
|
|
97
|
+
|
|
98
|
+
## Configuration indirection
|
|
99
|
+
|
|
100
|
+
Endpoints are configurable (`--api-base`, `--catalog-base`, profiles), so the
|
|
101
|
+
same binary points at prod or staging, and the discovery host can move later
|
|
102
|
+
without a new release.
|