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.
Files changed (110) hide show
  1. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/AGENTS.md +3 -1
  2. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/PKG-INFO +23 -1
  3. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/README.md +22 -0
  4. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/reference/agent-interface.md +1 -1
  5. tether_sdk-0.2.2/docs/reference/providers.md +119 -0
  6. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/release-notes.md +22 -0
  7. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/troubleshooting.md +1 -0
  8. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/agent-interface.md +22 -0
  9. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/cli-and-headless.md +12 -0
  10. tether_sdk-0.2.2/tests/fixtures/dialects.jsonl +6 -0
  11. tether_sdk-0.2.2/tests/providers/test_declarative.py +400 -0
  12. tether_sdk-0.2.2/tests/providers/test_dialects.py +79 -0
  13. tether_sdk-0.2.2/tests/test_context_config.py +101 -0
  14. tether_sdk-0.2.2/tests/test_misses.py +108 -0
  15. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_tether.py +1 -1
  16. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/__init__.py +3 -1
  17. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/cli.py +55 -0
  18. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/harness.py +14 -4
  19. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/mcp.py +4 -0
  20. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/providers/__init__.py +6 -0
  21. tether_sdk-0.2.2/tether/providers/base.py +183 -0
  22. tether_sdk-0.2.2/tether/providers/declarative.py +291 -0
  23. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/store.py +26 -0
  24. tether_sdk-0.2.0/docs/reference/providers.md +0 -42
  25. tether_sdk-0.2.0/tether/providers/base.py +0 -140
  26. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/.github/workflows/ci.yml +0 -0
  27. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/.github/workflows/delivery.yml +0 -0
  28. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/.github/workflows/docs.yml +0 -0
  29. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/.gitignore +0 -0
  30. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/LICENSE +0 -0
  31. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/architecture.md +0 -0
  32. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/benchmarks.md +0 -0
  33. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/extending.md +0 -0
  34. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/index.md +0 -0
  35. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/javascripts/mathjax.js +0 -0
  36. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/reference/context-and-store.md +0 -0
  37. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/reference/harness.md +0 -0
  38. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/reference/index.md +0 -0
  39. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/reference/multiplexer-and-decorator.md +0 -0
  40. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/reference/sandbox-and-skills.md +0 -0
  41. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/reference/tools.md +0 -0
  42. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/releasing.md +0 -0
  43. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/built-in-tools.md +0 -0
  44. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/index.md +0 -0
  45. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/multi-agent.md +0 -0
  46. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/sdk-and-custom-tools.md +0 -0
  47. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/security-and-sandbox.md +0 -0
  48. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/skills.md +0 -0
  49. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/docs/tutorial/workflows-and-telemetry.md +0 -0
  50. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/README.md +0 -0
  51. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/checks.py +0 -0
  52. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/course.yaml +0 -0
  53. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/ruff.toml +0 -0
  54. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/skills/planner/SKILL.md +0 -0
  55. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/skills/searcher/SKILL.md +0 -0
  56. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/skills/searcher/tools.py +0 -0
  57. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/skills/writer/SKILL.md +0 -0
  58. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/examples/course/skills/writer/tools.py +0 -0
  59. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/mkdocs.yml +0 -0
  60. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/pyproject.toml +0 -0
  61. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/skills/researcher/SKILL.md +0 -0
  62. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/__init__.py +0 -0
  63. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/conftest.py +0 -0
  64. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/providers/__init__.py +0 -0
  65. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/providers/test_anthropic.py +0 -0
  66. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/providers/test_base.py +0 -0
  67. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/providers/test_ollama.py +0 -0
  68. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/providers/test_openai.py +0 -0
  69. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_agent_interface.py +0 -0
  70. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_cli.py +0 -0
  71. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_context.py +0 -0
  72. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_decorator.py +0 -0
  73. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_harness.py +0 -0
  74. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_integration.py +0 -0
  75. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_multiplexer.py +0 -0
  76. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_pipeline.py +0 -0
  77. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_release_hardening.py +0 -0
  78. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_retro.py +0 -0
  79. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_sandbox.py +0 -0
  80. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_security_hardening.py +0 -0
  81. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_skills.py +0 -0
  82. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_store.py +0 -0
  83. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/test_structured_history.py +0 -0
  84. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/tools/__init__.py +0 -0
  85. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/tools/test_agent.py +0 -0
  86. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/tools/test_bash.py +0 -0
  87. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/tools/test_done.py +0 -0
  88. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/tools/test_edit.py +0 -0
  89. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/tools/test_mem.py +0 -0
  90. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tests/tools/test_web.py +0 -0
  91. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/api.py +0 -0
  92. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/context.py +0 -0
  93. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/decorator.py +0 -0
  94. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/multiplexer.py +0 -0
  95. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/pipeline.py +0 -0
  96. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/providers/anthropic.py +0 -0
  97. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/providers/ollama.py +0 -0
  98. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/providers/openai.py +0 -0
  99. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/retro.py +0 -0
  100. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/sandbox.py +0 -0
  101. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/schema.py +0 -0
  102. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/skills.py +0 -0
  103. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/tools/__init__.py +0 -0
  104. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/tools/agent.py +0 -0
  105. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/tools/bash.py +0 -0
  106. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/tools/done.py +0 -0
  107. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/tools/edit.py +0 -0
  108. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/tools/mem.py +0 -0
  109. {tether_sdk-0.2.0 → tether_sdk-0.2.2}/tether/tools/web.py +0 -0
  110. {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.0
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}