tether-sdk 0.2.0__tar.gz → 0.2.2__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.
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/AGENTS.md +3 -1
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/PKG-INFO +23 -1
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/README.md +22 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/reference/agent-interface.md +1 -1
- tether_sdk-0.2.2/docs/reference/providers.md +119 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/release-notes.md +22 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/troubleshooting.md +1 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/agent-interface.md +22 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/cli-and-headless.md +12 -0
- tether_sdk-0.2.2/tests/fixtures/dialects.jsonl +6 -0
- tether_sdk-0.2.2/tests/providers/test_declarative.py +400 -0
- tether_sdk-0.2.2/tests/providers/test_dialects.py +79 -0
- tether_sdk-0.2.2/tests/test_context_config.py +101 -0
- tether_sdk-0.2.2/tests/test_misses.py +108 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_tether.py +1 -1
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/__init__.py +3 -1
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/cli.py +55 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/harness.py +14 -4
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/mcp.py +4 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/providers/__init__.py +6 -0
- tether_sdk-0.2.2/tether/providers/base.py +183 -0
- tether_sdk-0.2.2/tether/providers/declarative.py +291 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/store.py +26 -0
- tether_sdk-0.2.0/docs/reference/providers.md +0 -42
- tether_sdk-0.2.0/tether/providers/base.py +0 -140
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/.github/workflows/ci.yml +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/.github/workflows/delivery.yml +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/.github/workflows/docs.yml +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/.gitignore +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/LICENSE +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/architecture.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/benchmarks.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/extending.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/index.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/javascripts/mathjax.js +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/reference/context-and-store.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/reference/harness.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/reference/index.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/reference/multiplexer-and-decorator.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/reference/sandbox-and-skills.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/reference/tools.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/releasing.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/built-in-tools.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/index.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/multi-agent.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/sdk-and-custom-tools.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/security-and-sandbox.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/skills.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/workflows-and-telemetry.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/README.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/checks.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/course.yaml +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/ruff.toml +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/skills/planner/SKILL.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/skills/searcher/SKILL.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/skills/searcher/tools.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/skills/writer/SKILL.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/skills/writer/tools.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/mkdocs.yml +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/pyproject.toml +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/skills/researcher/SKILL.md +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/__init__.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/conftest.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/providers/__init__.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/providers/test_anthropic.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/providers/test_base.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/providers/test_ollama.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/providers/test_openai.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_agent_interface.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_cli.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_context.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_decorator.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_harness.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_integration.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_multiplexer.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_pipeline.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_release_hardening.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_retro.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_sandbox.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_security_hardening.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_skills.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_store.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_structured_history.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/tools/__init__.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/tools/test_agent.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/tools/test_bash.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/tools/test_done.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/tools/test_edit.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/tools/test_mem.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/tools/test_web.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/api.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/context.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/decorator.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/multiplexer.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/pipeline.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/providers/anthropic.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/providers/ollama.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/providers/openai.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/retro.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/sandbox.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/schema.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/skills.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/tools/__init__.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/tools/agent.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/tools/bash.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/tools/done.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/tools/edit.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/tools/mem.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/tools/web.py +0 -0
- {tether_sdk-0.2.0 → tether_sdk-0.2.2}/uv.lock +0 -0
|
@@ -70,7 +70,7 @@ Tether implements the architectural principles from *"LLM-Native Applications as
|
|
|
70
70
|
```text
|
|
71
71
|
tether/
|
|
72
72
|
├── __init__.py # Public SDK exports: Harness, Pipeline, Session, Skill, SandboxPolicy, tool
|
|
73
|
-
├── cli.py # Headless-capable CLI: `tether run|pipeline|status|read|query|send|list|wait|stats|retro|events|mcp` (`--json` + documented exit codes)
|
|
73
|
+
├── cli.py # Headless-capable CLI: `tether run|pipeline|status|read|query|send|list|wait|stats|retro|events|misses|mcp` (`--json` + documented exit codes)
|
|
74
74
|
├── harness.py # Control Plane: bounded agent loop, pipelines, SQLite inbox, worker spawn
|
|
75
75
|
├── pipeline.py # Workflow Plane: declarative multi-stage YAML pipelines & model routing
|
|
76
76
|
├── api.py # Agent-facing JSON session view, exit codes, events (`--json`)
|
|
@@ -148,12 +148,14 @@ tether/
|
|
|
148
148
|
- Model identifier format: `<provider>/<model_name>` (e.g., `ollama/qwen3.5:9b`, `openai/gpt-4o-mini`, `anthropic/claude-3-7-sonnet-latest`).
|
|
149
149
|
- If no provider prefix is given, defaults to `ollama/<model>`.
|
|
150
150
|
- Custom SDK transports (e.g., internal corporate gateways, custom local runners, or test harnesses) can be injected via `Harness(provider=...)` or registered under a custom `<prefix>/<model>` via `register_provider(prefix, factory)`.
|
|
151
|
+
- Declarative providers: `~/.tether/providers.yaml` (override with `TETHER_PROVIDERS`, never read from the workspace) maps a model-id prefix to a `kind` (`openai`, `ollama`, `anthropic` or `command`); `claude-cli/sonnet` uses the entry `claude-cli`. The `command` kind runs `argv` directly (never through a shell) with `{model}`, `{system}` and `{conversation}` placeholders, and reads the reply and usage from JSON paths. Built-in prefixes (`ollama`, `openai`, `anthropic`, `claude`) cannot be shadowed, keys are env var names (never values), and an invalid file raises `ValueError: providers.yaml: ...`. Implementation in `tether/providers/declarative.py`; docs in `docs/reference/providers.md`.
|
|
151
152
|
- Environment variables:
|
|
152
153
|
- `OLLAMA_HOST` (default `http://localhost:11434`); `TETHER_OLLAMA_FALLBACK=1` opts in to substituting an installed model when the requested one 404s (default: fail loudly with the installed list)
|
|
153
154
|
- `OPENAI_API_KEY`, `OPENAI_BASE_URL` (default `https://api.openai.com/v1` — works with OpenRouter, vLLM, Groq, etc.)
|
|
154
155
|
- `ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL` (default `https://api.anthropic.com`)
|
|
155
156
|
|
|
156
157
|
---
|
|
158
|
+
- **Context config (#54):** `max_context_tokens` / `fold_observations_after` resolve as explicit argument (`--context-tokens` / `--fold-after`, MCP `context_tokens` / `fold_after`) > `providers.yaml` entry keys `context_tokens` / `fold_after` > defaults 8192 / 2.
|
|
157
159
|
|
|
158
160
|
## 4. Development Workflow & Commands
|
|
159
161
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tether-sdk
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: General-purpose, headless-first LLM-Native micro-harness for small language models
|
|
5
5
|
Project-URL: Homepage, https://github.com/igormcsouza/tether
|
|
6
6
|
Project-URL: Repository, https://github.com/igormcsouza/tether
|
|
@@ -282,6 +282,28 @@ codex mcp add tether -- tether mcp --workspace "$PWD" # Codex CLI
|
|
|
282
282
|
|
|
283
283
|
Cursor, Gemini CLI, VS Code and Claude Desktop configs, a "let your harness install it" prompt and a delegation rule for `CLAUDE.md`/`AGENTS.md` are in [Use Tether Inside Your Harness](docs/tutorial/agent-interface.md).
|
|
284
284
|
|
|
285
|
+
### Any model, no code: providers.yaml
|
|
286
|
+
|
|
287
|
+
Declare extra providers in `~/.tether/providers.yaml` (override the path with `TETHER_PROVIDERS`). The entry key is the model-id prefix, so `claude-cli/sonnet` uses the entry `claude-cli`. It works in `tether run`, `tether pipeline`, the SDK and `tether mcp`, and the file is never read from the workspace.
|
|
288
|
+
|
|
289
|
+
Kinds: `openai`, `ollama`, `anthropic` and `command`. The `command` kind wraps any program that takes a prompt and prints JSON (a `claude -p`, `codex exec` or `gemini -p` style command), and runs `argv` directly, never through a shell. Keys are given as env var names, never values.
|
|
290
|
+
|
|
291
|
+
```yaml
|
|
292
|
+
claude-cli:
|
|
293
|
+
kind: command
|
|
294
|
+
argv: [claude, -p, --model, "{model}", --tools, "", --system-prompt, "{system}", --output-format, json, --no-session-persistence, --setting-sources, "", --disable-slash-commands, --strict-mcp-config]
|
|
295
|
+
stdin: "{conversation}"
|
|
296
|
+
reply: result
|
|
297
|
+
usage: {prompt: [usage.input_tokens, usage.cache_creation_input_tokens, usage.cache_read_input_tokens], completion: usage.output_tokens}
|
|
298
|
+
timeout: 180
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
tether run --model claude-cli/sonnet "fix the failing test"
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
On one small bug-fix task (v0.2.0 test), this backend used about 15x fewer tokens than plain `claude -p` (21k vs 330k). That is a single small task, not a benchmark. See [Declarative providers](docs/reference/providers.md#declarative-providers).
|
|
306
|
+
|
|
285
307
|
### Python SDK Usage
|
|
286
308
|
|
|
287
309
|
```python
|
|
@@ -261,6 +261,28 @@ codex mcp add tether -- tether mcp --workspace "$PWD" # Codex CLI
|
|
|
261
261
|
|
|
262
262
|
Cursor, Gemini CLI, VS Code and Claude Desktop configs, a "let your harness install it" prompt and a delegation rule for `CLAUDE.md`/`AGENTS.md` are in [Use Tether Inside Your Harness](docs/tutorial/agent-interface.md).
|
|
263
263
|
|
|
264
|
+
### Any model, no code: providers.yaml
|
|
265
|
+
|
|
266
|
+
Declare extra providers in `~/.tether/providers.yaml` (override the path with `TETHER_PROVIDERS`). The entry key is the model-id prefix, so `claude-cli/sonnet` uses the entry `claude-cli`. It works in `tether run`, `tether pipeline`, the SDK and `tether mcp`, and the file is never read from the workspace.
|
|
267
|
+
|
|
268
|
+
Kinds: `openai`, `ollama`, `anthropic` and `command`. The `command` kind wraps any program that takes a prompt and prints JSON (a `claude -p`, `codex exec` or `gemini -p` style command), and runs `argv` directly, never through a shell. Keys are given as env var names, never values.
|
|
269
|
+
|
|
270
|
+
```yaml
|
|
271
|
+
claude-cli:
|
|
272
|
+
kind: command
|
|
273
|
+
argv: [claude, -p, --model, "{model}", --tools, "", --system-prompt, "{system}", --output-format, json, --no-session-persistence, --setting-sources, "", --disable-slash-commands, --strict-mcp-config]
|
|
274
|
+
stdin: "{conversation}"
|
|
275
|
+
reply: result
|
|
276
|
+
usage: {prompt: [usage.input_tokens, usage.cache_creation_input_tokens, usage.cache_read_input_tokens], completion: usage.output_tokens}
|
|
277
|
+
timeout: 180
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
tether run --model claude-cli/sonnet "fix the failing test"
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
On one small bug-fix task (v0.2.0 test), this backend used about 15x fewer tokens than plain `claude -p` (21k vs 330k). That is a single small task, not a benchmark. See [Declarative providers](docs/reference/providers.md#declarative-providers).
|
|
285
|
+
|
|
264
286
|
### Python SDK Usage
|
|
265
287
|
|
|
266
288
|
```python
|
|
@@ -42,7 +42,7 @@ Optional: `pip install "tether-sdk[mcp]"` (installs the official `mcp` SDK; core
|
|
|
42
42
|
|
|
43
43
|
| Tool | Arguments |
|
|
44
44
|
|------|-----------|
|
|
45
|
-
| `tether_run` | `goal`, `model`, `skill`, `tools`, `verify[]`, `max_steps`, `sandbox`, `no_network`, `output_schema`, `wait` (default true) |
|
|
45
|
+
| `tether_run` | `goal`, `model`, `skill`, `tools`, `verify[]`, `max_steps`, `context_tokens`, `fold_after`, `sandbox`, `no_network`, `output_schema`, `wait` (default true) |
|
|
46
46
|
| `tether_status` | `id` |
|
|
47
47
|
| `tether_wait` | `ids[]`, `timeout` |
|
|
48
48
|
| `tether_read` | `id`, `mode` = `full` \| `digest` \| `steps` |
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Model Providers
|
|
2
|
+
|
|
3
|
+
## Structured history
|
|
4
|
+
|
|
5
|
+
The harness passes neutral messages (assistant `tool_calls` with `id` and dict `arguments`, then `{"role": "tool", "tool_call_id": ...}`) to providers whose `structured_history` attribute is true, and each adapter translates: Ollama sends them as-is (plus `tool_name`), OpenAI-compatible JSON-encodes `arguments`, Anthropic maps them to `tool_use` and `tool_result` blocks (merging consecutive same-role messages). A custom provider without the attribute gets the textual `act('...')` / `Observation:` form. Adapter payloads are unit-tested with mock transports; they have not been verified live against OpenAI or Anthropic.
|
|
6
|
+
|
|
7
|
+
## `tether.providers`
|
|
8
|
+
|
|
9
|
+
::: tether.providers
|
|
10
|
+
options:
|
|
11
|
+
show_root_heading: false
|
|
12
|
+
members_order: source
|
|
13
|
+
|
|
14
|
+
## `tether.providers.base`
|
|
15
|
+
|
|
16
|
+
::: tether.providers.base
|
|
17
|
+
options:
|
|
18
|
+
show_root_heading: false
|
|
19
|
+
members_order: source
|
|
20
|
+
|
|
21
|
+
Ollama: an unknown model fails with `Ollama model … not found; installed: …`. Set `TETHER_OLLAMA_FALLBACK=1` (or `OllamaProvider(fallback=True)`) to substitute an installed model instead; a warning is printed to stderr and `tether stats` records the substitute ([#21](https://github.com/igormcsouza/tether/issues/21)).
|
|
22
|
+
|
|
23
|
+
## Declarative providers
|
|
24
|
+
|
|
25
|
+
Add providers without writing code by declaring them in `~/.tether/providers.yaml` (override the path with the `TETHER_PROVIDERS` environment variable). The file is read only from the home directory (or `TETHER_PROVIDERS`), never from the workspace, so a cloned repo cannot plant a provider.
|
|
26
|
+
|
|
27
|
+
The entry key is the model-id prefix: `claude-cli/sonnet` uses the entry named `claude-cli`, and `sonnet` is passed on as the model name. Declared providers work in `tether run`, `tether pipeline`, the Python SDK and `tether mcp`.
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
claude-cli:
|
|
31
|
+
kind: command
|
|
32
|
+
argv: [claude, -p, --model, "{model}", --tools, "", --system-prompt, "{system}", --output-format, json, --no-session-persistence, --setting-sources, "", --disable-slash-commands, --strict-mcp-config]
|
|
33
|
+
stdin: "{conversation}"
|
|
34
|
+
reply: result
|
|
35
|
+
usage: {prompt: [usage.input_tokens, usage.cache_creation_input_tokens, usage.cache_read_input_tokens], completion: usage.output_tokens}
|
|
36
|
+
timeout: 180
|
|
37
|
+
context_tokens: 32000
|
|
38
|
+
fold_after: 8
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
tether run --model claude-cli/sonnet "fix the failing test"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Context budget
|
|
46
|
+
|
|
47
|
+
Every entry, of any kind, may set two optional positive integers: `context_tokens` (the context budget, default 8192) and `fold_after` (how many recent turns keep full observations, default 2). The `--context-tokens` / `--fold-after` flags and the Harness arguments override them.
|
|
48
|
+
|
|
49
|
+
### Kinds
|
|
50
|
+
|
|
51
|
+
| `kind` | Fields |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `openai` | `base_url`, `api_key_env` |
|
|
54
|
+
| `ollama` | `host` |
|
|
55
|
+
| `anthropic` | `base_url`, `api_key_env` |
|
|
56
|
+
| `command` | `argv` (list), `reply` (JSON path), optional `stdin` template, optional `usage`, optional `timeout` (seconds, default 300) |
|
|
57
|
+
|
|
58
|
+
`api_key_env` is the **name** of an environment variable, never the key itself.
|
|
59
|
+
|
|
60
|
+
### The `command` kind
|
|
61
|
+
|
|
62
|
+
A `command` entry wraps any program that takes a prompt and prints JSON.
|
|
63
|
+
|
|
64
|
+
- **Placeholders**: `{model}`, `{system}`, `{conversation}` and `{schema}` (the act JSON schema, compact) are substituted in `argv` items and in the `stdin` template. If `stdin` is omitted it defaults to `{conversation}`.
|
|
65
|
+
- **No shell**: `argv` is executed directly, never through a shell.
|
|
66
|
+
- **Reply**: `reply` is a dotted path into the JSON the command prints (`result` in the example).
|
|
67
|
+
- **Usage**: `usage.prompt` is a path or a list of paths (their values are summed); `usage.completion` is a path. Both are optional.
|
|
68
|
+
- **Constrained output**: use `{schema}` to make the CLI constrain its reply to the act schema, for example `argv: [claude, -p, --json-schema, "{schema}", --output-format, json]`. The reply may then be an object with `cmd` and optional `stdin` instead of a string; if the `reply` path gives no string or object, a top-level `structured_output` object is used.
|
|
69
|
+
|
|
70
|
+
Measured cost (v0.2.2, one small task): with `claude -p`, `--json-schema` adds about 2k prompt tokens to every call (the CLI wraps the schema in a hidden tool), so the same 5-step task used ~26k prompt tokens instead of ~7k. Use it when a model's plain replies are not being understood, not as a default.
|
|
71
|
+
- **Protocol**: the model should answer with a single JSON `{"cmd", "stdin"}` object. The `{system}` placeholder already contains that instruction.
|
|
72
|
+
- **Dialects**: when the reply is not that JSON, Tether tries table-driven extractors in order, first match wins: `<tool_call>` JSON, raw JSON containing `"cmd"`, `<parameter name="cmd">X</parameter>` with optional `<parameter name="stdin">Y</parameter>` (inside any wrapper such as `<invoke>`), and text `act(...)`.
|
|
73
|
+
- **`dialects:`** (optional): a list of mappings, each with a `regex` (compiled with `re.DOTALL`) that defines the named group `cmd` and optionally `stdin`. Custom dialects are tried after the built-in ones. Python users can also call `tether.register_dialect(fn)` with a `(str) -> ActionCall | None` function.
|
|
74
|
+
|
|
75
|
+
```yaml
|
|
76
|
+
mycli:
|
|
77
|
+
kind: command
|
|
78
|
+
argv: [mycli, "{conversation}"]
|
|
79
|
+
reply: result
|
|
80
|
+
dialects:
|
|
81
|
+
- regex: 'RUN\[(?P<cmd>.*?)\]WITH\[(?P<stdin>.*?)\]'
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Rules
|
|
85
|
+
|
|
86
|
+
- The built-in prefixes `ollama`, `openai`, `anthropic` and `claude` cannot be shadowed.
|
|
87
|
+
- An invalid file raises `ValueError: providers.yaml: ...` naming the offending entry.
|
|
88
|
+
|
|
89
|
+
### Measured example
|
|
90
|
+
|
|
91
|
+
On one small bug-fix task (v0.2.0 test), using `claude -p --tools ""` as the backend, as in the entry above, used about 15x fewer tokens than plain `claude -p` (21k vs 330k). This is a single small task, not a benchmark.
|
|
92
|
+
|
|
93
|
+
## `tether.providers.ollama`
|
|
94
|
+
|
|
95
|
+
::: tether.providers.ollama
|
|
96
|
+
options:
|
|
97
|
+
show_root_heading: false
|
|
98
|
+
members_order: source
|
|
99
|
+
|
|
100
|
+
## `tether.providers.openai`
|
|
101
|
+
|
|
102
|
+
::: tether.providers.openai
|
|
103
|
+
options:
|
|
104
|
+
show_root_heading: false
|
|
105
|
+
members_order: source
|
|
106
|
+
|
|
107
|
+
## `tether.providers.anthropic`
|
|
108
|
+
|
|
109
|
+
::: tether.providers.anthropic
|
|
110
|
+
options:
|
|
111
|
+
show_root_heading: false
|
|
112
|
+
members_order: source
|
|
113
|
+
|
|
114
|
+
## `tether.providers.declarative`
|
|
115
|
+
|
|
116
|
+
::: tether.providers.declarative
|
|
117
|
+
options:
|
|
118
|
+
show_root_heading: false
|
|
119
|
+
members_order: source
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Release Notes
|
|
2
2
|
|
|
3
|
+
## v0.2.2 (2026-10-19)
|
|
4
|
+
|
|
5
|
+
Dogfooding fixes: Tether implemented most of this release itself through the CLI and a `claude -p` provider.
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- `{schema}` placeholder for `command` providers (#51): constrain output to the act schema (e.g. `claude -p --json-schema '{schema}'`); replies may be structured objects with `cmd`/`stdin`.
|
|
10
|
+
- `tether misses [session_id] [--model M] [--limit N] [--json]` (#53): list unparsed model replies (nudges) as fixture-ready JSONL; paste lines into `tests/fixtures/dialects.jsonl` after filling `expected`.
|
|
11
|
+
|
|
12
|
+
### Fixes
|
|
13
|
+
|
|
14
|
+
- The context budget and observation fold window are now configurable (#54): `--context-tokens` / `--fold-after` on `tether run` and `tether pipeline`, `context_tokens` / `fold_after` on `tether_run`, and per-provider keys in `providers.yaml`.
|
|
15
|
+
- Tool-call dialect extraction is now table-driven (#52): built-in extractors for `<tool_call>` JSON, raw JSON, `<parameter name="cmd">` blocks and text `act(...)`, extendable with `tether.register_dialect` or a `dialects:` key on `command` entries in `providers.yaml`.
|
|
16
|
+
|
|
17
|
+
## v0.2.1 (2026-10-19)
|
|
18
|
+
|
|
19
|
+
Add model backends without code.
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
- Declarative providers (#49): add model backends without code via `~/.tether/providers.yaml` (override with `TETHER_PROVIDERS`, never read from the workspace). Kinds `openai`, `ollama`, `anthropic` and `command` (wrap any CLI that prints JSON, run without a shell). Works in `tether run`, `tether pipeline`, the SDK and `tether mcp`. See [Declarative providers](reference/providers.md#declarative-providers).
|
|
24
|
+
|
|
3
25
|
## v0.2.0 (2026-10-19)
|
|
4
26
|
|
|
5
27
|
Tether as a subagent for the harness you already use (Claude Code, Codex, Cursor, Gemini CLI, Copilot): MCP server, JSON output and exit codes (#46). See [Use Tether Inside Your Harness](tutorial/agent-interface.md).
|
|
@@ -10,6 +10,7 @@ Symptom, cause, fix. Most entries come from building the [course-builder example
|
|
|
10
10
|
| Files contain a literal `\n` | Model sent escaped newlines in stdin | Fixed in v0.1.2 for `edit write`, `edit replace` and `done` |
|
|
11
11
|
| Tool with multi-word argument fails (`unexpected keyword argument`, wrong value) | Unquoted words were not joined | Fixed in v0.1.2. See [argument parsing](tutorial/sdk-and-custom-tools.md#argument-parsing-v012) |
|
|
12
12
|
| `Error: unknown command` for a tool defined in `tools.py` | The tool is not in the skill `tools:` list ([#24](https://github.com/igormcsouza/tether/issues/24)) | Add its name to `tools:`. See [Skills](tutorial/skills.md#tools-py-and-the-tools-list) |
|
|
13
|
+
| Model answers are not understood (many nudges in `tether stats`) | The reply format is not parsed into an action | Run `tether misses`, then add a dialect: paste the lines into `tests/fixtures/dialects.jsonl` or add a `dialects:` regex to `providers.yaml` |
|
|
13
14
|
| Gate with inline `awk`/`sed` is blocked as "absolute path outside workspace" | Fixed in v0.1.4 ([#31](https://github.com/igormcsouza/tether/issues/31)); older versions saw a program starting with `/` as a path | Upgrade, or move the logic into a script (`python3 checks.py ...`). See [Security and Sandbox](tutorial/security-and-sandbox.md#example-awksed-programs-that-start-with) |
|
|
14
15
|
| `Ollama model … not found; installed: …` | The model name is not pulled; Tether no longer substitutes another one ([#21](https://github.com/igormcsouza/tether/issues/21)) | `ollama pull <model>`, fix the name, or set `TETHER_OLLAMA_FALLBACK=1` to opt in to a substitute |
|
|
15
16
|
| `tether mcp` prints "MCP support is not installed" | The optional extra is missing | `pip install "tether-sdk[mcp]"`. See [Use Tether Inside Your Harness](tutorial/agent-interface.md) |
|
|
@@ -104,6 +104,28 @@ Use `wait: false` for long jobs, then `tether_wait` and `tether_read`.
|
|
|
104
104
|
If status is `WAITING_INPUT`, answer with `tether_send`. Keep architecture and review for yourself.
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
+
## Any model, no code: providers.yaml
|
|
108
|
+
|
|
109
|
+
Tether can route to any backend you declare in `~/.tether/providers.yaml` (override the path with `TETHER_PROVIDERS`). The entry key is the model-id prefix, so `claude-cli/sonnet` uses the entry `claude-cli`. It works the same in `tether run`, `tether pipeline`, the SDK and `tether mcp`, and it is never read from the workspace.
|
|
110
|
+
|
|
111
|
+
The `command` kind wraps any program that reads a prompt and prints JSON, for example a `claude -p`, `codex exec` or `gemini -p` style command (check each tool's own docs for its flags). Tether runs `argv` directly, never through a shell.
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
claude-cli:
|
|
115
|
+
kind: command
|
|
116
|
+
argv: [claude, -p, --model, "{model}", --tools, "", --system-prompt, "{system}", --output-format, json, --no-session-persistence, --setting-sources, "", --disable-slash-commands, --strict-mcp-config]
|
|
117
|
+
stdin: "{conversation}"
|
|
118
|
+
reply: result
|
|
119
|
+
usage: {prompt: [usage.input_tokens, usage.cache_creation_input_tokens, usage.cache_read_input_tokens], completion: usage.output_tokens}
|
|
120
|
+
timeout: 180
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
tether run --model claude-cli/sonnet "fix the failing test"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
On one small bug-fix task, this backend used about 15x fewer tokens than plain `claude -p` (21k vs 330k). It is a single small task, not a benchmark. Full reference: [Declarative providers](../reference/providers.md#declarative-providers).
|
|
128
|
+
|
|
107
129
|
## Patterns
|
|
108
130
|
|
|
109
131
|
**Architect / executor.** The frontier model writes the spec and the tests. `tether_run` with `verify: ["pytest -q"]` implements it with a cheap model; the gate runs in the sandbox before `done` is accepted. The parent reads back only status, steps, tokens and the digest.
|
|
@@ -76,10 +76,22 @@ tether query "$SESSION_ID" "write-ahead log checkpointing" --top 3
|
|
|
76
76
|
tether list
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
## Context budget and fold window
|
|
80
|
+
|
|
81
|
+
By default Tether keeps an 8192-token context budget and folds observations older than 2 turns. Tune both per run with `--context-tokens N` and `--fold-after N` (on `tether run` and `tether pipeline`), or per model in `providers.yaml` with `context_tokens` / `fold_after`. Flags override the provider entry, which overrides the defaults.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
tether run --context-tokens 32000 --fold-after 8 "refactor the parser"
|
|
85
|
+
```
|
|
86
|
+
|
|
79
87
|
## Scripting: `--json` and exit codes
|
|
80
88
|
|
|
81
89
|
`run`, `pipeline`, `status`, `list`, `read`, `wait`, `send` and `stats` accept `--json`. Exit codes: `0` completed, `1` usage error, `2` waiting for input, `3` gate failed, `4` max steps, `5` worker error. A failure reason goes to stderr, never stdout. `--result-file <path>` writes the deliverable to a file, `--output-schema <schema.json>` rejects a `done` whose JSON does not match, and `tether events <id> --follow` streams turns as JSONL. Details: [Agent Interface reference](../reference/agent-interface.md).
|
|
82
90
|
|
|
91
|
+
### When a model's replies are not understood
|
|
92
|
+
|
|
93
|
+
If a model keeps answering in a format Tether cannot parse into an action (many nudges in `tether stats`), run `tether misses [session_id] [--model M] [--limit N] [--json]`. It lists the unparsed replies; with `--json` each line is fixture-ready. Paste lines into `tests/fixtures/dialects.jsonl` after filling `expected`, or add a `dialects:` regex to `providers.yaml`.
|
|
94
|
+
|
|
83
95
|
## Live Inbox Injection & Auto-Resume (`tether send`)
|
|
84
96
|
|
|
85
97
|
Every session has a persistent SQLite `inbox` queue:
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{"reply": "<tool_call>{\"name\": \"act\", \"arguments\": {\"cmd\": \"ls -la\"}}</tool_call>", "cmd": "ls -la", "stdin": null}
|
|
2
|
+
{"reply": "Sure: {\"cmd\": \"echo hi\"}", "cmd": "echo hi", "stdin": null}
|
|
3
|
+
{"reply": "act(\"done\", stdin=\"x\")", "cmd": "done", "stdin": "x"}
|
|
4
|
+
{"reply": "<invoke name=\"act\">\n<parameter name=\"cmd\"> pytest -q </parameter>\n</invoke>", "cmd": "pytest -q", "stdin": null}
|
|
5
|
+
{"reply": "<invoke name=\"act\">\n<parameter name=\"cmd\">edit write a.txt</parameter>\n<parameter name=\"stdin\">line1\nline2\n</parameter>\n</invoke>", "cmd": "edit write a.txt", "stdin": "line1\nline2\n"}
|
|
6
|
+
{"reply": "I will think about this and answer in plain prose.", "cmd": null, "stdin": null}
|