tether-sdk 0.2.8__tar.gz → 0.2.9__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.8 → tether_sdk-0.2.9}/.claude/gru/active/v0-2-bugs.md +2 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/AGENTS.md +3 -3
- tether_sdk-0.2.8/docs/testing.md → tether_sdk-0.2.9/CONTRIBUTING.md +34 -3
- tether_sdk-0.2.9/PKG-INFO +143 -0
- tether_sdk-0.2.9/README.md +122 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/README.md +1 -1
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/architecture.md +2 -0
- tether_sdk-0.2.9/docs/benchmarks.md +50 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/extending.md +2 -0
- tether_sdk-0.2.9/docs/glossary.md +29 -0
- tether_sdk-0.2.9/docs/guides/a-pipeline-with-checks.md +70 -0
- tether_sdk-0.2.9/docs/guides/add-your-own-tool.md +62 -0
- tether_sdk-0.2.9/docs/guides/fix-a-failing-test.md +65 -0
- tether_sdk-0.2.9/docs/guides/use-tether-from-your-agent.md +66 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/index.md +18 -0
- tether_sdk-0.2.9/docs/llms.txt +45 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/reference/agent-interface.md +2 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/reference/cli.md +2 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/reference/index.md +2 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/release-notes.md +17 -0
- tether_sdk-0.2.9/docs/troubleshooting.md +117 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/tutorial/agent-interface.md +2 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/tutorial/built-in-tools.md +2 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/tutorial/cli-and-headless.md +2 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/tutorial/index.md +3 -1
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/tutorial/multi-agent.md +2 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/tutorial/sdk-and-custom-tools.md +2 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/tutorial/security-and-sandbox.md +2 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/tutorial/skills.md +2 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/tutorial/workflows-and-telemetry.md +2 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/e2e/test_quirks.py +12 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/mkdocs.yml +11 -8
- tether_sdk-0.2.9/tests/test_benchmarks_docs.py +21 -0
- tether_sdk-0.2.9/tests/test_docs_structure.py +46 -0
- tether_sdk-0.2.9/tests/test_guides_docs.py +20 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_harness.py +26 -0
- tether_sdk-0.2.9/tests/test_llms_txt.py +31 -0
- tether_sdk-0.2.9/tests/test_readme.py +26 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_tether.py +1 -1
- tether_sdk-0.2.9/tests/test_troubleshooting_docs.py +25 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/__init__.py +1 -1
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/harness.py +12 -2
- tether_sdk-0.2.8/PKG-INFO +0 -374
- tether_sdk-0.2.8/README.md +0 -353
- tether_sdk-0.2.8/docs/benchmarks.md +0 -31
- tether_sdk-0.2.8/docs/releasing.md +0 -9
- tether_sdk-0.2.8/docs/troubleshooting.md +0 -40
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/.claude/gru/active/.gitkeep +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/.claude/gru/archive/.gitkeep +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/.claude/gru/archive/work-on-v0-2-4.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/.claude/gru.json +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/.github/workflows/ci.yml +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/.github/workflows/delivery.yml +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/.github/workflows/docs.yml +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/.gitignore +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/LICENSE +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/grade.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/harness-bench/README.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/harness-bench/tether-adapter.patch +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/naive_agent.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/report.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/run.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-hard/goal.txt +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-hard/hidden/test_hidden.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-hard/ref/calc/__init__.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-hard/ref/calc/errors.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-hard/ref/calc/evaluator.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-hard/ref/calc/parser.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-hard/ref/calc/tokenizer.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-hard/ws/calc/__init__.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-hard/ws/calc/errors.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-hard/ws/calc/evaluator.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-hard/ws/calc/parser.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-hard/ws/calc/tokenizer.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-hard/ws/tests/__init__.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-hard/ws/tests/test_calc.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-medium/goal.txt +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-medium/hidden/test_hidden.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-medium/ws/cli.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-medium/ws/ledger/__init__.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-medium/ws/ledger/parser.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-medium/ws/ledger/report.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-medium/ws/sample.csv +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-medium/ws/tests/__init__.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-medium/ws/tests/test_report.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-simple/goal.txt +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-simple/hidden/test_hidden.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-simple/ws/tests/__init__.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-simple/ws/tests/test_textutils.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/code-simple/ws/textutils.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/codereview/goal.txt +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/codereview/ws/inventory.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/content/goal.txt +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/content/ws/.keep +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/focus/goal.txt +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/focus/hidden/test_hidden.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/focus/ws/AGENTS.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/focus/ws/README.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/focus/ws/docs/architecture.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/focus/ws/docs/style.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/focus/ws/mini/__init__.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/focus/ws/mini/cli.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/focus/ws/mini/fmt.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/focus/ws/mini/stats.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/focus/ws/mini/textio.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/focus/ws/tests/__init__.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/focus/ws/tests/test_stats.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/plan/goal.txt +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/plan/ws/README.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/plan/ws/svc/__init__.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/plan/ws/svc/client.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/plan/ws/svc/config.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/qa/goal.txt +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/qa/ws/.keep +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/review/goal.txt +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/bench/tasks/review/ws/paper.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/javascripts/mathjax.js +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/reference/context-and-store.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/reference/harness.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/reference/multiplexer-and-decorator.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/reference/providers.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/reference/sandbox-and-skills.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/docs/reference/tools.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/e2e/__init__.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/e2e/conftest.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/e2e/proxy.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/e2e/test_agents.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/e2e/test_defaults.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/e2e/test_faults.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/e2e/test_lifecycle.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/e2e/test_limits.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/e2e/test_sandbox.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/e2e/test_surface.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/examples/course/README.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/examples/course/checks.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/examples/course/course.yaml +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/examples/course/ruff.toml +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/examples/course/skills/planner/SKILL.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/examples/course/skills/searcher/SKILL.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/examples/course/skills/searcher/tools.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/examples/course/skills/writer/SKILL.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/examples/course/skills/writer/tools.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/pyproject.toml +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/scripts/record_golden.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/skills/researcher/SKILL.md +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/__init__.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/conftest.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/fixtures/dialects.jsonl +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/golden/coding_edit_failure_modes.json +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/golden/coding_edit_failure_modes.snap.json +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/golden/done_nudge.json +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/golden/done_nudge.snap.json +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/golden/read_range_pingpong.json +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/golden/read_range_pingpong.snap.json +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/golden/research_web.json +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/golden/research_web.snap.json +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/golden/shell_wrappers.json +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/golden/shell_wrappers.snap.json +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/golden/waiting_input_resume.json +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/golden/waiting_input_resume.snap.json +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/providers/__init__.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/providers/test_anthropic.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/providers/test_base.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/providers/test_declarative.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/providers/test_dialects.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/providers/test_ollama.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/providers/test_openai.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/providers/test_retry.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_agent_interface.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_cli.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_cli_docs.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_context.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_context_config.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_decorator.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_edit_tool.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_golden.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_integration.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_misses.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_multiplexer.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_params.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_pipeline.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_providers_docs.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_release_hardening.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_retro.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_sandbox.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_security_hardening.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_skills.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_store.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/test_structured_history.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/tools/__init__.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/tools/test_agent.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/tools/test_bash.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/tools/test_done.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/tools/test_edit.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/tools/test_mem.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tests/tools/test_web.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/api.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/cli.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/context.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/decorator.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/mcp.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/multiplexer.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/pipeline.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/providers/__init__.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/providers/anthropic.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/providers/base.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/providers/declarative.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/providers/ollama.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/providers/openai.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/retro.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/sandbox.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/schema.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/skills.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/store.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/tools/__init__.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/tools/agent.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/tools/bash.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/tools/done.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/tools/edit.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/tools/mem.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/tether/tools/web.py +0 -0
- {tether_sdk-0.2.8 → tether_sdk-0.2.9}/uv.lock +0 -0
|
@@ -106,3 +106,5 @@ Track and fix bugs found in Tether v0.2.x. Exact bug list not yet given by PO.
|
|
|
106
106
|
- 2026-10-25 PO: docs plan. Audit -> epic #150 (steps: CLI ref #61, providers #60+#62, site structure #151, story guides #152, troubleshooting #153, benchmarks #154, README #63, llms.txt #64, polish #65/#66), all in milestone v0.2.10 (v0.2.11 closed, empty). AGENTS.md 5.6 now states the two audiences (docs site = users/integrators, no bug history; README = evaluate/build/contribute). #145 fix on branch fix/79-stats-offenders awaiting fast-set bench then PR.
|
|
107
107
|
- 2026-10-25 Tether friction (docs cards on Nemotron Ultra): (1) ses_f6f847e5 CLI page and ses_7fb5863b providers page both ended at max steps WITHOUT calling done after the work was finished (keeps verifying); (2) rewriting a docs page it invented API facts (wrong ModelResponse fields, register_provider signature, nonexistent settings) -> docs rewrites by tether need a gru fact-check or assemble-from-source approach; (3) 5 steps in 10 min on a 156-line page (slow generation of long outputs on the free endpoint). Merged: #155 policy, #156 CLI ref (#61). PR for #60/#62 open. #145 PR waits for fast-set bench.
|
|
108
108
|
- 2026-10-25 PO: milestones reshuffled: v0.2.8 = release now (#144 #145 + sandbox venv + CLI/providers docs), v0.2.9 = docs overhaul (#150-#154, #63-#66), v0.2.10 = #27 #79 #132-#137. Merged: #146 #148 #149 #155 #156 #157 #158. Release PR v0.2.8 (version bump, notes) prepared; GitHub Release only on PO yes.
|
|
109
|
+
- 2026-10-25 RELEASED v0.2.8 'Room to Answer' (release PR #159 -> f4370b1, GitHub Release, delivery.yml success, PyPI tether-sdk 0.2.8 live, milestone closed). Docs v0.2.9 started: #151 site structure PR (nav = Start here/Guides/Concepts/Reference/Help, 'For:' line per page, CONTRIBUTING.md replaces docs/testing.md + releasing.md, tests/test_docs_structure.py). Lesson: ruff format --check also formats Python blocks in docs; renamed anchors break the strict mkdocs build via old release-notes links -> run both before pushing docs.
|
|
110
|
+
- 2026-10-25 PO: 'merge all docs PRs and cut the next version'. Docs plan (#150) done: #155 #156 #157 #160 #161 #162 #165 #166 #167 merged, epic closed. Found #163 while writing guide 1 (qwen3.5:9b fixes the test, passes, never calls done -> FAILED Stalled); #164 merged on PO yes (Rule 3 reading: passing verify gates at stall time -> COMPLETED). Release PR v0.2.9 prepared. Next: v0.2.10 = #27 #79 #132-#137 (8 issues; pick by evidence, e.g. bench failure breakdown after v0.2.9). Tether friction this stretch: docs rewrites by Nemotron invent API facts and end without done; harness.py cards need 40+ steps.
|
|
@@ -46,7 +46,7 @@ Reviewers and the Team Leader cite the failing line when they reject a change.
|
|
|
46
46
|
- A task only completes when the agent explicitly calls `act("done", stdin="<final deliverable>")` (or `act("done '<short answer>'")`), pauses for human/parent input via `act("done --ask", stdin="<question>")` (transitioning the session to `WAITING_INPUT` and releasing the worker process until resumed via `send`), or hits `max_steps`.
|
|
47
47
|
- If the model emits plain text without calling `act`, the harness appends a brief deterministic nudge reminding it to either execute a command or call `done`.
|
|
48
48
|
- After 6 consecutive read-only/verification steps following a change, the harness appends a one-shot `[NUDGE]` to that step's observation reminding the model to call `done` if the task is complete (0 static prompt tokens, #74). Before any change, 12 read-only steps in a row (then every 8 more) append a one-line `[NUDGE]` to make the edit or call `done` (#127). Seven reads of one file in a row with no change are a `cycle_stall`, and 4 stall warnings without a change end the session `FAILED` as `Stalled` (#92); `tether stop <id>` terminates a detached worker (#89).
|
|
49
|
-
- When `verify` gate commands are configured (via `Harness(verify=[...])`, CLI `--verify`, or Skill YAML `verify: [...]`), `Harness.execute_session` deterministically executes each command inside the session's `SandboxPolicy` before allowing `done` to transition to `COMPLETED` (consuming 0 static prompt tokens until a gate fails). A gate that errors instead of failing (sandbox violation, exit 124/126/127) ends the session `FAILED` at once as `gate_error` ("gate misconfigured"), without counting toward the consecutive-failure abort.
|
|
49
|
+
- When `verify` gate commands are configured (via `Harness(verify=[...])`, CLI `--verify`, or Skill YAML `verify: [...]`), `Harness.execute_session` deterministically executes each command inside the session's `SandboxPolicy` before allowing `done` to transition to `COMPLETED` (consuming 0 static prompt tokens until a gate fails). A gate that errors instead of failing (sandbox violation, exit 124/126/127) ends the session `FAILED` at once as `gate_error` ("gate misconfigured"), without counting toward the consecutive-failure abort. When the stall guard is about to end a session as `Stalled` and gates are configured, it runs them first: a change was made and every gate passes, so the session ends `COMPLETED` ("Finished by the harness"), since the deterministic gate is the completion criterion the caller asked for (#163).
|
|
50
50
|
|
|
51
51
|
### Rule 4: Progressive `--help` (JIT Schema Disclosure)
|
|
52
52
|
- The system prompt lists at most **1 line per enabled tool namespace** (`- <name>: <short summary>`, target **≤ 12 tokens per entry**).
|
|
@@ -206,8 +206,8 @@ uv run mypy tether/
|
|
|
206
206
|
- All model calls and I/O-bound tool executions must be `async`/`await` native, while seamlessly wrapping synchronous user `@tool` functions.
|
|
207
207
|
3. **Graceful Degradation on 9B Hallucinations:**
|
|
208
208
|
- Small models occasionally emit `cmd` with minor quirks (e.g., leading `$`, wrapping backticks, or calling a tool name directly instead of `act`). The multiplexer must normalize trivial syntax quirks deterministically before failing. This includes unescaping literal `\n`/`\t` in one-line `stdin` for `edit write`/`edit replace` and `done` (never `bash`), turning a trailing shell heredoc in `cmd` (`edit write f <<'EOF' ... EOF`) into `stdin` (never for `bash`), unwrapping `bash -c/-lc '<cmd>'` and `sh -c` wrappers, running an unknown first word through the enabled `bash` tool (never for disabled built-ins), registering extra quirk fixers with `register_normalizer`, mapping a native tool call named other than `act` to `<name> --k 'v'` so the JIT usage answers it, inferring the `edit` verb (`edit <path>` -> view/replace/write) and accepting `apply_patch` (`*** Begin Patch` or unified-diff patches, also when a diff arrives as `edit <path>` stdin) as an `edit` verb.
|
|
209
|
-
4. **Golden traces:** behaviour the model sees (framing, folding, nudges, tool output) is locked by `tests/golden/*.snap.json`. A PR that changes it runs `UPDATE_GOLDEN=1 pytest tests/test_golden.py` and the snapshot diff is part of the review; unrelated traces must not change. See `
|
|
210
|
-
5. **E2E scenarios:** every feature or bug fix ships with a scenario in `e2e/` (real CLI, real ollama behind a fault-injecting proxy, local only, `TETHER_E2E=1 uv run pytest e2e`). Assert on what tether sends and records, never on the model's skill; script the reply or inject the fault so the test does not depend on model potency. **Before merging any PR, run `TETHER_E2E=1 uv run pytest e2e` locally and put the result in the PR description (CI cannot run it). Every failure must be mitigated before merge: fix the code, or fix a wrong scenario, or record in the PR why it is acceptable and what follows up. A PR is not merged with an unexplained red scenario.** See `
|
|
209
|
+
4. **Golden traces:** behaviour the model sees (framing, folding, nudges, tool output) is locked by `tests/golden/*.snap.json`. A PR that changes it runs `UPDATE_GOLDEN=1 pytest tests/test_golden.py` and the snapshot diff is part of the review; unrelated traces must not change. See `CONTRIBUTING.md`.
|
|
210
|
+
5. **E2E scenarios:** every feature or bug fix ships with a scenario in `e2e/` (real CLI, real ollama behind a fault-injecting proxy, local only, `TETHER_E2E=1 uv run pytest e2e`). Assert on what tether sends and records, never on the model's skill; script the reply or inject the fault so the test does not depend on model potency. **Before merging any PR, run `TETHER_E2E=1 uv run pytest e2e` locally and put the result in the PR description (CI cannot run it). Every failure must be mitigated before merge: fix the code, or fix a wrong scenario, or record in the PR why it is acceptable and what follows up. A PR is not merged with an unexplained red scenario.** See `CONTRIBUTING.md`.
|
|
211
211
|
6. **Documentation Integrity:**
|
|
212
212
|
- Keep `README.md` and `AGENTS.md` synchronized whenever architectural interfaces or built-in tool signatures change.
|
|
213
213
|
- **Always update documentation in the same PR** when introducing a new feature, changing an existing behavior, or altering a CLI/SDK interface. This covers `README.md`, `AGENTS.md`, the MkDocs pages under `docs/` (including `docs/reference/*` for new modules), and a `docs/release-notes.md` entry. A change without matching docs is incomplete.
|
|
@@ -1,4 +1,24 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Contributing to tether
|
|
2
|
+
|
|
3
|
+
Thanks for helping. Tether stays **lightweight, reliable, extensible, model-agnostic, easy to use, resourceful and a token saver**: that list (the *Philosophy Gate*) and the architectural rules live in [AGENTS.md](AGENTS.md), which is the authoritative guide for contributors and coding agents. Read its sections 1-3 before changing behaviour.
|
|
4
|
+
|
|
5
|
+
## Setup and checks
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv sync # environment
|
|
9
|
+
uv run ruff check . && uv run ruff format --check . # lint and format (also formats Python in docs)
|
|
10
|
+
uv run mypy tether/ # types
|
|
11
|
+
uv run pytest --cov=tether --cov-report=term-missing --cov-fail-under=100 # unit tests, 100% coverage (CI)
|
|
12
|
+
TETHER_E2E=1 uv run pytest e2e # e2e, local only (needs the tether-ollama container)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Pull requests
|
|
16
|
+
|
|
17
|
+
- One PR per task, Conventional Commits (`feat(scope): ... (#N)`, `fix(...)`, `docs(...)`, `release: vX.Y.Z ...`), squash-merged to `main`.
|
|
18
|
+
- Update the docs in the same PR (README, the MkDocs pages, `docs/release-notes.md`); see *Two audiences, two jobs* in AGENTS.md section 5.6 for what belongs where.
|
|
19
|
+
- A behaviour change carries a measured result in the PR body (bench or e2e delta), reported honestly even when it is flat. Put the `TETHER_E2E=1 uv run pytest e2e` result in the PR; CI cannot run it.
|
|
20
|
+
|
|
21
|
+
## Testing: golden traces, e2e scenarios and the model bench
|
|
2
22
|
|
|
3
23
|
Tether is a deterministic envelope around a random model, so a change to framing, folding,
|
|
4
24
|
nudges, tools or retries can silently change what the model is shown. Golden traces make that
|
|
@@ -20,7 +40,7 @@ Shipped traces: a real researcher run (web stubbed, plain-text `done` nudges), a
|
|
|
20
40
|
coding run covering the edit failure modes seen with small models, and a `done --ask` /
|
|
21
41
|
`send` resume, and a range ping-pong (alternating views of one file; the latest read of each range stays unfolded, older ones fold). Add one for every behaviour that must not regress.
|
|
22
42
|
|
|
23
|
-
|
|
43
|
+
### End-to-end scenarios (local, real processes, real ollama)
|
|
24
44
|
|
|
25
45
|
`e2e/` drives the real `tether` CLI in a throwaway workspace. A small fault-injecting proxy
|
|
26
46
|
(`e2e/proxy.py`) sits between tether and ollama, so a scenario can force the situation instead of
|
|
@@ -83,7 +103,7 @@ judge (answer quality).
|
|
|
83
103
|
**Rule:** a new feature or bug fix ships with a scenario here. If a model must misbehave to
|
|
84
104
|
trigger it, script the reply; if infrastructure must fail, add a fault to the proxy.
|
|
85
105
|
|
|
86
|
-
|
|
106
|
+
### Model conformance bench (developers only)
|
|
87
107
|
|
|
88
108
|
`bench/` holds nine tasks (the `focus` task checks that an exact single-file goal is not lost in a distractor-heavy repo) with hidden-test graders and three runners (Tether, a plain tool loop,
|
|
89
109
|
Claude Code) so the same model can be compared across harnesses and releases. It needs real
|
|
@@ -91,3 +111,14 @@ models, so it is not in CI and not in the package: use it when onboarding a mode
|
|
|
91
111
|
release to see friction that scripted tests cannot (the shell-wrapper, bare-command and
|
|
92
112
|
tool-name failures behind #109 were found this way). Anything it finds that can be scripted
|
|
93
113
|
becomes a unit, golden or e2e test. See `bench/README.md`.
|
|
114
|
+
|
|
115
|
+
## Releasing
|
|
116
|
+
|
|
117
|
+
PyPI is published only from a GitHub Release, never from a push or tag.
|
|
118
|
+
|
|
119
|
+
0. Re-run the commands in `docs/guides/` against the release candidate and fix any output that changed.
|
|
120
|
+
1. Bump `__version__` in `tether/__init__.py` (and the version assertion in `tests/test_tether.py`) and update `docs/release-notes.md`.
|
|
121
|
+
2. Merge to `main`.
|
|
122
|
+
3. Create a GitHub Release with tag `v<version>` (for example `v0.1.0`). The tag must match `__version__` (a leading `v` is ignored) or the workflow fails before building.
|
|
123
|
+
|
|
124
|
+
The `delivery.yml` workflow then verifies the tag, builds the sdist and wheel, publishes to PyPI with trusted publishing (no secrets) and attaches both files to the release. Pre-releases are skipped: nothing is published or attached.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: tether-sdk
|
|
3
|
+
Version: 0.2.9
|
|
4
|
+
Summary: General-purpose, headless-first LLM-Native micro-harness for small language models
|
|
5
|
+
Project-URL: Homepage, https://github.com/igormcsouza/tether
|
|
6
|
+
Project-URL: Repository, https://github.com/igormcsouza/tether
|
|
7
|
+
Project-URL: Documentation, https://igormcsouza.github.io/tether/
|
|
8
|
+
Author-email: Igor Souza <igormcsouza@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,cli,harness,llm,slm,sqlite
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Requires-Dist: httpx>=0.27.0
|
|
17
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
18
|
+
Provides-Extra: mcp
|
|
19
|
+
Requires-Dist: mcp<3,>=2.0; extra == 'mcp'
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# 🪢 Tether
|
|
23
|
+
|
|
24
|
+
**A small, headless-first harness that lets small local models (and frontier ones) do real work with a tiny prompt.**
|
|
25
|
+
|
|
26
|
+
[](https://pypi.org/project/tether-sdk/)
|
|
27
|
+
[](https://igormcsouza.github.io/tether/)
|
|
28
|
+
[](#install-and-first-run)
|
|
29
|
+
[](https://igormcsouza.github.io/tether/reference/providers/)
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv tool install tether-sdk
|
|
33
|
+
tether run --model ollama/qwen3.5:9b --tools bash,edit --verify "pytest -q" "fix the failing tests"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Why Tether
|
|
37
|
+
|
|
38
|
+
Most agent frameworks treat the context window as a dumping ground. Before your task is read, they inject **3,000 to 8,000 tokens** of JSON tool schemas and system prompt, then append every tool output forever. On a frontier model that is money. On a 9B model running on your laptop it is fatal: attention is diluted, small models fumble nested JSON, and changing the tool list between turns throws away the inference server's KV cache.
|
|
39
|
+
|
|
40
|
+
Tether is built the other way round:
|
|
41
|
+
|
|
42
|
+
- **One tool, not thirty.** The model sees a single `act(cmd, stdin)` schema. Everything else (`bash`, `edit`, `web`, `mem`, your own functions) is a command behind it, off by default. Fixed framing is under 150 tokens.
|
|
43
|
+
- **Old output folds away.** Tool results stay in full for two turns, then become a one-line tombstone. Everything lives in SQLite and comes back by search.
|
|
44
|
+
- **The envelope is deterministic.** Sandboxing, state, step limits, verification gates and termination are plain code. The model only reasons; it is never trusted with safety.
|
|
45
|
+
|
|
46
|
+
## How well does it work
|
|
47
|
+
|
|
48
|
+
Tether answers to one number: how many real coding exercises a small local model finishes. On the Polyglot-Python set (Aider's 34 Python exercises, graded by each exercise's own tests), `qwen3.5:9b` through Ollama, 12-exercise fast set, 2 trials each:
|
|
49
|
+
|
|
50
|
+
| | v0.2.7 | v0.2.8 |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| Passes | 10 of 24 | 11 of 24 |
|
|
53
|
+
| Mean score | 0.741 | 0.756 |
|
|
54
|
+
| "Tool call cut off" hints | 29 | 2 |
|
|
55
|
+
| Steps that did nothing | 19% | 2.2% |
|
|
56
|
+
|
|
57
|
+
One run is noisy (about 0.1), so read the pass count and the failure breakdown, not one decimal. Method, how to reproduce it and fair-comparison rules: [Benchmarks](https://igormcsouza.github.io/tether/benchmarks/). Every change that touches model-visible behaviour has to move this number or leave it alone.
|
|
58
|
+
|
|
59
|
+
## How it works
|
|
60
|
+
|
|
61
|
+
```mermaid
|
|
62
|
+
flowchart LR
|
|
63
|
+
You["CLI · SDK · MCP"] --> Loop
|
|
64
|
+
subgraph Envelope["Deterministic envelope"]
|
|
65
|
+
Loop["bounded loop<br/>steps · stall guard · gates"] --> Mux["act(cmd, stdin)<br/>command bus"]
|
|
66
|
+
Mux --> Sand["sandbox<br/>bwrap · workspace jail"]
|
|
67
|
+
Sand --> Tools["bash · edit · web · mem · agent · your @tool"]
|
|
68
|
+
Tools --> Fold["fold old output"]
|
|
69
|
+
Fold --> DB[("SQLite WAL + FTS5<br/>.tether/tether.db")]
|
|
70
|
+
DB --> Loop
|
|
71
|
+
end
|
|
72
|
+
Loop <-->|"messages"| Model["LLM<br/>ollama · openai · anthropic"]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
A session is a row in SQLite, so it survives crashes, runs detached (`--headless`), takes new instructions while running (`tether send`), and can be read by another agent as compact JSON. A task ends only when the model calls `done`, after your `--verify` commands pass.
|
|
76
|
+
|
|
77
|
+
## Why this and not that
|
|
78
|
+
|
|
79
|
+
| Decision | Instead of | Why |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| One `act` tool, commands behind it | A JSON schema per tool | CodeAct and SWE-agent results: one executable channel beats many schemas, most for 7-9B models; and the prompt prefix never changes, so the KV cache survives. |
|
|
82
|
+
| Progressive `--help` | Full usage up front | A tool costs about a dozen tokens until the model asks or gets it wrong. |
|
|
83
|
+
| SQLite (WAL, FTS5) as memory | An ever-growing transcript | Context stays a bounded working set; the rest is searchable. State is ACID, so sessions resume and agents share results. |
|
|
84
|
+
| Kernel sandbox (`bwrap`), env scrubbing, SSRF guard | "Please be careful" in the prompt | Safety that depends on the model obeying is not safety. |
|
|
85
|
+
| Explicit `done` plus verification gates | Letting the model declare success | Small models stop early or never stop. The gate is a command you wrote. |
|
|
86
|
+
| Thin `httpx` adapters, 2 runtime dependencies | Vendor SDKs, LangChain-style frameworks | Easy to read, easy to add a backend (`providers.yaml`, no code), nothing to upgrade around. |
|
|
87
|
+
|
|
88
|
+
The research behind it: Maia et al. (2026) on probabilistic core and deterministic envelope, Wang et al. (2024, CodeAct), Yang et al. (2024, SWE-agent) and Packer et al. (2023, MemGPT); see [References](#references) and [Architecture](https://igormcsouza.github.io/tether/architecture/).
|
|
89
|
+
|
|
90
|
+
## Install and first run
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
uv tool install tether-sdk # or: pip install tether-sdk
|
|
94
|
+
ollama pull qwen3.5:9b # any model works; see the providers page
|
|
95
|
+
tether run --model ollama/qwen3.5:9b --tools bash,edit "create hello.txt containing hi"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Everything else (headless sessions, skills, pipelines, MCP, the CLI reference, writing your own tool or provider) is in the docs: **[igormcsouza.github.io/tether](https://igormcsouza.github.io/tether/)**. Start with the guides: [fix a failing test](https://igormcsouza.github.io/tether/guides/fix-a-failing-test/), [use it from Claude Code or Codex](https://igormcsouza.github.io/tether/guides/use-tether-from-your-agent/).
|
|
99
|
+
|
|
100
|
+
## Build on it
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
import asyncio
|
|
104
|
+
|
|
105
|
+
from tether import Harness, tool
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@tool(name="price", description="Look up the unit price of a fruit")
|
|
109
|
+
def price(fruit: str) -> str:
|
|
110
|
+
return f"{fruit}: 0.50 EUR"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
async def main() -> None:
|
|
114
|
+
harness = Harness(model="ollama/qwen3.5:9b", tools=[price])
|
|
115
|
+
session = await harness.run("What does an apple cost?")
|
|
116
|
+
print(session.result)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
asyncio.run(main())
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`Harness`, `Pipeline`, `Skill`, `SandboxPolicy`, `@tool` and `register_provider` are the public surface. The agent-facing JSON and the optional `tether mcp` server (`tether-sdk[mcp]`) are specified in the [reference](https://igormcsouza.github.io/tether/reference/agent-interface/).
|
|
123
|
+
|
|
124
|
+
## Contributing
|
|
125
|
+
|
|
126
|
+
Tether stays lightweight, reliable, extensible, model-agnostic, easy to use, resourceful and a token saver; a change that fails one of those lines is reworked. Read [AGENTS.md](AGENTS.md) (the rules and the module map) and [CONTRIBUTING.md](CONTRIBUTING.md) (setup, checks, golden traces, e2e, the bench, releasing). Open an issue before a large change; small fixes with a test and a docs line are welcome as they are.
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
tether/ harness · multiplexer · context · store · sandbox · providers/ · tools/
|
|
130
|
+
tests/ unit, golden traces (what the model sees), 100% coverage
|
|
131
|
+
e2e/ real CLI and real Ollama behind a fault-injecting proxy (local)
|
|
132
|
+
bench/ Polyglot-Python and model checks (developers)
|
|
133
|
+
docs/ the user site (MkDocs)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## References
|
|
139
|
+
|
|
140
|
+
1. **Maia, P. H. M., et al. (2026).** *LLM-Native Applications as a Paradigm for AI-Driven Software Architecture*.
|
|
141
|
+
2. **Wang, X., Chen, Y., Yuan, L., Zhang, Y., Li, Y., Peng, H., & Ji, H. (2024).** *Executable Code Actions Elicit Better LLM Agents (CodeAct)*. In **Proceedings of the 41st International Conference on Machine Learning (ICML 2024)**. [arXiv:2402.01030](https://arxiv.org/abs/2402.01030)
|
|
142
|
+
3. **Yang, J., Jimenez, C. E., Wettig, A., Lieret, K., Yao, S., Narasimhan, K., & Press, O. (2024).** *SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering*. In **Advances in Neural Information Processing Systems (NeurIPS 2024)**. [arXiv:2405.15793](https://arxiv.org/abs/2405.15793)
|
|
143
|
+
4. **Packer, C., Wooders, S., Lin, K., Fang, V., Patil, S. G., Stoica, I., & Gonzalez, J. E. (2023).** *MemGPT: Towards LLMs as Operating Systems*. [arXiv:2310.08560](https://arxiv.org/abs/2310.08560)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# 🪢 Tether
|
|
2
|
+
|
|
3
|
+
**A small, headless-first harness that lets small local models (and frontier ones) do real work with a tiny prompt.**
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/tether-sdk/)
|
|
6
|
+
[](https://igormcsouza.github.io/tether/)
|
|
7
|
+
[](#install-and-first-run)
|
|
8
|
+
[](https://igormcsouza.github.io/tether/reference/providers/)
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
uv tool install tether-sdk
|
|
12
|
+
tether run --model ollama/qwen3.5:9b --tools bash,edit --verify "pytest -q" "fix the failing tests"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Why Tether
|
|
16
|
+
|
|
17
|
+
Most agent frameworks treat the context window as a dumping ground. Before your task is read, they inject **3,000 to 8,000 tokens** of JSON tool schemas and system prompt, then append every tool output forever. On a frontier model that is money. On a 9B model running on your laptop it is fatal: attention is diluted, small models fumble nested JSON, and changing the tool list between turns throws away the inference server's KV cache.
|
|
18
|
+
|
|
19
|
+
Tether is built the other way round:
|
|
20
|
+
|
|
21
|
+
- **One tool, not thirty.** The model sees a single `act(cmd, stdin)` schema. Everything else (`bash`, `edit`, `web`, `mem`, your own functions) is a command behind it, off by default. Fixed framing is under 150 tokens.
|
|
22
|
+
- **Old output folds away.** Tool results stay in full for two turns, then become a one-line tombstone. Everything lives in SQLite and comes back by search.
|
|
23
|
+
- **The envelope is deterministic.** Sandboxing, state, step limits, verification gates and termination are plain code. The model only reasons; it is never trusted with safety.
|
|
24
|
+
|
|
25
|
+
## How well does it work
|
|
26
|
+
|
|
27
|
+
Tether answers to one number: how many real coding exercises a small local model finishes. On the Polyglot-Python set (Aider's 34 Python exercises, graded by each exercise's own tests), `qwen3.5:9b` through Ollama, 12-exercise fast set, 2 trials each:
|
|
28
|
+
|
|
29
|
+
| | v0.2.7 | v0.2.8 |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| Passes | 10 of 24 | 11 of 24 |
|
|
32
|
+
| Mean score | 0.741 | 0.756 |
|
|
33
|
+
| "Tool call cut off" hints | 29 | 2 |
|
|
34
|
+
| Steps that did nothing | 19% | 2.2% |
|
|
35
|
+
|
|
36
|
+
One run is noisy (about 0.1), so read the pass count and the failure breakdown, not one decimal. Method, how to reproduce it and fair-comparison rules: [Benchmarks](https://igormcsouza.github.io/tether/benchmarks/). Every change that touches model-visible behaviour has to move this number or leave it alone.
|
|
37
|
+
|
|
38
|
+
## How it works
|
|
39
|
+
|
|
40
|
+
```mermaid
|
|
41
|
+
flowchart LR
|
|
42
|
+
You["CLI · SDK · MCP"] --> Loop
|
|
43
|
+
subgraph Envelope["Deterministic envelope"]
|
|
44
|
+
Loop["bounded loop<br/>steps · stall guard · gates"] --> Mux["act(cmd, stdin)<br/>command bus"]
|
|
45
|
+
Mux --> Sand["sandbox<br/>bwrap · workspace jail"]
|
|
46
|
+
Sand --> Tools["bash · edit · web · mem · agent · your @tool"]
|
|
47
|
+
Tools --> Fold["fold old output"]
|
|
48
|
+
Fold --> DB[("SQLite WAL + FTS5<br/>.tether/tether.db")]
|
|
49
|
+
DB --> Loop
|
|
50
|
+
end
|
|
51
|
+
Loop <-->|"messages"| Model["LLM<br/>ollama · openai · anthropic"]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
A session is a row in SQLite, so it survives crashes, runs detached (`--headless`), takes new instructions while running (`tether send`), and can be read by another agent as compact JSON. A task ends only when the model calls `done`, after your `--verify` commands pass.
|
|
55
|
+
|
|
56
|
+
## Why this and not that
|
|
57
|
+
|
|
58
|
+
| Decision | Instead of | Why |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| One `act` tool, commands behind it | A JSON schema per tool | CodeAct and SWE-agent results: one executable channel beats many schemas, most for 7-9B models; and the prompt prefix never changes, so the KV cache survives. |
|
|
61
|
+
| Progressive `--help` | Full usage up front | A tool costs about a dozen tokens until the model asks or gets it wrong. |
|
|
62
|
+
| SQLite (WAL, FTS5) as memory | An ever-growing transcript | Context stays a bounded working set; the rest is searchable. State is ACID, so sessions resume and agents share results. |
|
|
63
|
+
| Kernel sandbox (`bwrap`), env scrubbing, SSRF guard | "Please be careful" in the prompt | Safety that depends on the model obeying is not safety. |
|
|
64
|
+
| Explicit `done` plus verification gates | Letting the model declare success | Small models stop early or never stop. The gate is a command you wrote. |
|
|
65
|
+
| Thin `httpx` adapters, 2 runtime dependencies | Vendor SDKs, LangChain-style frameworks | Easy to read, easy to add a backend (`providers.yaml`, no code), nothing to upgrade around. |
|
|
66
|
+
|
|
67
|
+
The research behind it: Maia et al. (2026) on probabilistic core and deterministic envelope, Wang et al. (2024, CodeAct), Yang et al. (2024, SWE-agent) and Packer et al. (2023, MemGPT); see [References](#references) and [Architecture](https://igormcsouza.github.io/tether/architecture/).
|
|
68
|
+
|
|
69
|
+
## Install and first run
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
uv tool install tether-sdk # or: pip install tether-sdk
|
|
73
|
+
ollama pull qwen3.5:9b # any model works; see the providers page
|
|
74
|
+
tether run --model ollama/qwen3.5:9b --tools bash,edit "create hello.txt containing hi"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Everything else (headless sessions, skills, pipelines, MCP, the CLI reference, writing your own tool or provider) is in the docs: **[igormcsouza.github.io/tether](https://igormcsouza.github.io/tether/)**. Start with the guides: [fix a failing test](https://igormcsouza.github.io/tether/guides/fix-a-failing-test/), [use it from Claude Code or Codex](https://igormcsouza.github.io/tether/guides/use-tether-from-your-agent/).
|
|
78
|
+
|
|
79
|
+
## Build on it
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
import asyncio
|
|
83
|
+
|
|
84
|
+
from tether import Harness, tool
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@tool(name="price", description="Look up the unit price of a fruit")
|
|
88
|
+
def price(fruit: str) -> str:
|
|
89
|
+
return f"{fruit}: 0.50 EUR"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
async def main() -> None:
|
|
93
|
+
harness = Harness(model="ollama/qwen3.5:9b", tools=[price])
|
|
94
|
+
session = await harness.run("What does an apple cost?")
|
|
95
|
+
print(session.result)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
asyncio.run(main())
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`Harness`, `Pipeline`, `Skill`, `SandboxPolicy`, `@tool` and `register_provider` are the public surface. The agent-facing JSON and the optional `tether mcp` server (`tether-sdk[mcp]`) are specified in the [reference](https://igormcsouza.github.io/tether/reference/agent-interface/).
|
|
102
|
+
|
|
103
|
+
## Contributing
|
|
104
|
+
|
|
105
|
+
Tether stays lightweight, reliable, extensible, model-agnostic, easy to use, resourceful and a token saver; a change that fails one of those lines is reworked. Read [AGENTS.md](AGENTS.md) (the rules and the module map) and [CONTRIBUTING.md](CONTRIBUTING.md) (setup, checks, golden traces, e2e, the bench, releasing). Open an issue before a large change; small fixes with a test and a docs line are welcome as they are.
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
tether/ harness · multiplexer · context · store · sandbox · providers/ · tools/
|
|
109
|
+
tests/ unit, golden traces (what the model sees), 100% coverage
|
|
110
|
+
e2e/ real CLI and real Ollama behind a fault-injecting proxy (local)
|
|
111
|
+
bench/ Polyglot-Python and model checks (developers)
|
|
112
|
+
docs/ the user site (MkDocs)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## References
|
|
118
|
+
|
|
119
|
+
1. **Maia, P. H. M., et al. (2026).** *LLM-Native Applications as a Paradigm for AI-Driven Software Architecture*.
|
|
120
|
+
2. **Wang, X., Chen, Y., Yuan, L., Zhang, Y., Li, Y., Peng, H., & Ji, H. (2024).** *Executable Code Actions Elicit Better LLM Agents (CodeAct)*. In **Proceedings of the 41st International Conference on Machine Learning (ICML 2024)**. [arXiv:2402.01030](https://arxiv.org/abs/2402.01030)
|
|
121
|
+
3. **Yang, J., Jimenez, C. E., Wettig, A., Lieret, K., Yao, S., Narasimhan, K., & Press, O. (2024).** *SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering*. In **Advances in Neural Information Processing Systems (NeurIPS 2024)**. [arXiv:2405.15793](https://arxiv.org/abs/2405.15793)
|
|
122
|
+
4. **Packer, C., Wooders, S., Lin, K., Fang, V., Patil, S. G., Stoica, I., & Gonzalez, J. E. (2023).** *MemGPT: Towards LLMs as Operating Systems*. [arXiv:2310.08560](https://arxiv.org/abs/2310.08560)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Not part of the `tether` package, not run in CI. Use it to onboard a new model or to check a
|
|
4
4
|
release for regressions that deterministic tests cannot see (unit, golden and e2e tests stay the
|
|
5
|
-
gate; see `
|
|
5
|
+
gate; see `CONTRIBUTING.md`).
|
|
6
6
|
|
|
7
7
|
Nine tasks in `tasks/<name>/` (workspace in `ws/`, goal in `goal.txt`, hidden tests in `hidden/`):
|
|
8
8
|
`focus` (one exact edit in a distractor-heavy repo, scope-checked), `plan` (an implementation plan, checked for named files, failure handling and tests), `codereview` (eight seeded bugs to find, no edits), `code-simple`, `code-medium`, `code-hard` (graded by hidden unit tests; editing the visible tests
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Architecture and Literature
|
|
2
2
|
|
|
3
|
+
*For: people who want to understand why tether works the way it does, and what each design choice costs or saves.*
|
|
4
|
+
|
|
3
5
|
Most agent frameworks were designed around massive frontier models with 200k-token context windows. When you point those same frameworks at a local 8B or 9B model (`ollama/qwen3.5:9b`, `ollama/gemma4:e4b`), they immediately fail—not because the model cannot reason, but because **3,000 to 8,000 tokens of nested JSON tool schemas and raw tool outputs** flood the attention window before the agent takes its second step.
|
|
4
6
|
|
|
5
7
|
Tether's design translates four foundational works in recent AI systems research into a compact, zero-bloat Python micro-harness.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Benchmark Evaluation
|
|
2
|
+
|
|
3
|
+
*For: people comparing tether with other harnesses, and anyone checking that a release did not make small models worse. It shows what is measured, how, and the latest numbers.*
|
|
4
|
+
|
|
5
|
+
Tether is judged on one score: **how many real coding exercises a small local model finishes** when tether is the harness around it. That is the number every change has to improve, or at least not lower.
|
|
6
|
+
|
|
7
|
+
## What is measured
|
|
8
|
+
|
|
9
|
+
The **Polyglot-Python** set: the 34 Python exercises of Aider's [polyglot benchmark](https://github.com/Aider-AI/polyglot-benchmark) (poker hands, a Forth interpreter, a REST API, a reactive spreadsheet, ...). For each exercise the model gets a workspace with a stub and the exercise's own test file, and a goal: make the tests pass.
|
|
10
|
+
|
|
11
|
+
- **Graded by the exercise's own tests.** The test file is restored before grading, so editing it cannot pass.
|
|
12
|
+
- **Score** is the fraction of tests that pass (so a partial solution counts); a *pass* is every test green.
|
|
13
|
+
- The **fast set** is 12 exercises that mix passes, partial results and failures, run twice each (24 runs): about an hour and a half on one local GPU, enough to see a change without waiting for the full 34.
|
|
14
|
+
|
|
15
|
+
## Latest result
|
|
16
|
+
|
|
17
|
+
`qwen3.5:9b` through Ollama (`num_ctx` 8192), fast set, 2 trials per exercise:
|
|
18
|
+
|
|
19
|
+
| | v0.2.7 | v0.2.8 |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| Passes | 10 of 24 | 11 of 24 |
|
|
22
|
+
| Mean score | 0.741 | 0.756 |
|
|
23
|
+
| Mean score, runs without a provider outage | 0.772 | 0.775 |
|
|
24
|
+
| "Tool call cut off" hints (runs that had one) | 29 (9) | 2 (1) |
|
|
25
|
+
| Steps that did nothing (no tool call) | 19% | 2.2% |
|
|
26
|
+
|
|
27
|
+
What changed between them: tether now keeps a quarter of the context window free for the model's reply. Before, a prompt that filled the window cut off the longest tool calls (file writes), and every cut cost a step. Pass counts and scores moved little; the waste almost disappeared.
|
|
28
|
+
|
|
29
|
+
**Read these numbers with care.** One run of one exercise is noisy (about 0.1 of score), 24 runs are a small sample, and a local server can time out for reasons that have nothing to do with tether. A difference smaller than that noise is not a result. The *failure breakdown* in the report keeps provider outages and timeouts apart from harness outcomes (`max_steps`, `stalled`, `wrong_answer`, `worker_error`) so you can see which one moved.
|
|
30
|
+
|
|
31
|
+
## Run it yourself
|
|
32
|
+
|
|
33
|
+
The benchmark lives in `bench/` of the repository (not in the package, not in CI). It needs the exercises cloned once and a model to run:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# fast set, two trials, one local model
|
|
37
|
+
uv run python bench/run.py --runners tether --models ollama/qwen3.5:9b \
|
|
38
|
+
--tasks polyglot-fast --trials 2 --out /tmp/bench
|
|
39
|
+
|
|
40
|
+
# pass rate, mean score and the failure breakdown
|
|
41
|
+
python3 bench/report.py /tmp/bench
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Use `--tasks polyglot` for all 34 exercises, `--tether-bin /path/to/venv/bin/tether` to compare two versions on the same machine, and `--redo-infra` to rerun cells that failed on a provider outage. To attribute a gap to the harness rather than the model, add `--runners tether,naive`: the `naive` runner is a plain tool loop with the same model and the same exercises. Details and the smaller non-coding tasks: [`bench/README.md`](https://github.com/igormcsouza/tether/blob/main/bench/README.md).
|
|
45
|
+
|
|
46
|
+
## How to compare fairly
|
|
47
|
+
|
|
48
|
+
- Same model, same server, same `num_ctx`, same machine; run both versions in the same session (a busy GPU changes timeouts).
|
|
49
|
+
- Report pass count **and** mean score, with the failure breakdown.
|
|
50
|
+
- Do not draw a conclusion from a single trial of a single exercise.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Extending Tether
|
|
2
2
|
|
|
3
|
+
*For: developers adding tools or model backends. After this page you can extend tether without touching its core.*
|
|
4
|
+
|
|
3
5
|
Tether is designed around small, explicit protocols so you can plug in custom model gateways, local inference engines, or domain tool suites without subclassing framework base classes.
|
|
4
6
|
|
|
5
7
|
## 1. Custom Model Providers
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Glossary
|
|
2
|
+
|
|
3
|
+
*For: anyone meeting a tether term for the first time. Each entry is one sentence you can act on.*
|
|
4
|
+
|
|
5
|
+
**Act.** The single tool the model sees: `act(cmd, stdin)`. Every command (`bash`, `edit`, `done`, your own) goes through it.
|
|
6
|
+
|
|
7
|
+
**ACI (agent-computer interface).** The command line the model works with. Tether keeps it small and forgiving: compact usage, helpful errors, normalised quirks.
|
|
8
|
+
|
|
9
|
+
**Dialect.** A reply format a model uses instead of a proper tool call (for example a `<tool_call>` block). Tether parses known dialects into the same action; add your own with a `dialects:` regex.
|
|
10
|
+
|
|
11
|
+
**Fold / tombstone.** After a few turns, a tool output is replaced in the prompt by a one-line stand-in (the tombstone). The full text stays in SQLite and can be searched or recalled.
|
|
12
|
+
|
|
13
|
+
**FTS5 RAG.** Retrieval over SQLite's full-text index (BM25 ranking). `mem search` and `agent query` use it to pull only the relevant chunks, with no embedding model.
|
|
14
|
+
|
|
15
|
+
**Gate (verification gate).** A command you supply with `--verify` that tether runs itself before accepting `done`. A failing gate sends its output back to the model.
|
|
16
|
+
|
|
17
|
+
**Headless.** A session run detached in the background (`--headless`). You check it with `tether status`, `wait` and `read`, and steer it with `tether send`.
|
|
18
|
+
|
|
19
|
+
**Nudge.** A one-line reminder tether adds when the model answers in plain text, or keeps reading after a change, so it acts or calls `done`.
|
|
20
|
+
|
|
21
|
+
**Pipeline.** An ordered set of stages in YAML; each stage is its own session with its own model, tools and gate.
|
|
22
|
+
|
|
23
|
+
**Session.** One run of the loop, stored as a row in `.tether/tether.db`. It can be resumed, sent messages and read by another agent.
|
|
24
|
+
|
|
25
|
+
**Skill.** A Markdown file with YAML frontmatter that packages instructions, model, tools and gates for a repeatable job.
|
|
26
|
+
|
|
27
|
+
**Stall.** The model repeating a command, cycling, or re-reading one file without changing anything. Tether warns, and ends the session early if it continues.
|
|
28
|
+
|
|
29
|
+
**Workspace.** The directory a session may read and write. Everything outside it is blocked by the sandbox.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Run a pipeline where each stage has its own job
|
|
2
|
+
|
|
3
|
+
*For: people with a task that has an order: gather, then write, then check. After this page you can run a declarative pipeline, give each stage its own model, tools and gate, and read the results.*
|
|
4
|
+
|
|
5
|
+
A single long conversation gets confused. A **pipeline** splits the job into stages, each in its own clean session with its own tools and its own check, and passes a compact summary from one stage to the next.
|
|
6
|
+
|
|
7
|
+
## The job
|
|
8
|
+
|
|
9
|
+
Turn a list of changes into a short release note. Stage one only reads (and cannot write anything); stage two writes the file and must produce a non-empty `NOTE.md` before it may finish.
|
|
10
|
+
|
|
11
|
+
`changes.txt`:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
- csv export added to the reports page
|
|
15
|
+
- empty cart no longer crashes checkout
|
|
16
|
+
- install steps rewritten for Windows
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`release-note.yaml`:
|
|
20
|
+
|
|
21
|
+
```yaml
|
|
22
|
+
name: release-note
|
|
23
|
+
stages:
|
|
24
|
+
- id: facts
|
|
25
|
+
model: ollama/qwen3.5:9b
|
|
26
|
+
tools: [bash]
|
|
27
|
+
sandbox: read-only # this stage cannot change anything
|
|
28
|
+
goal: "Read changes.txt and call done with the list of changes, one per line. Task: {{goal}}"
|
|
29
|
+
- id: write
|
|
30
|
+
model: ollama/qwen3.5:9b # could be a different, stronger model
|
|
31
|
+
tools: [bash, edit]
|
|
32
|
+
verify: ["test -s NOTE.md"] # the stage cannot finish without this
|
|
33
|
+
goal: "Using the changes from the previous stage, write NOTE.md: a three-sentence plain-English release note. Then call done."
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Run it
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
tether pipeline release-note.yaml "write the release note"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
It finished in 19 seconds. `NOTE.md`:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
This release adds CSV export functionality to the reports page.
|
|
46
|
+
The checkout process now handles empty carts without crashing.
|
|
47
|
+
Install steps have been rewritten for better Windows compatibility.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`tether list` shows the three sessions: the pipeline itself and one per stage, each with its own status and step count.
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
ses_61eda63a COMPLETED ... 2/2 write the release note (the pipeline)
|
|
54
|
+
ses_3f575a5b COMPLETED ... 4/15 Using the changes from the previous stage, ...
|
|
55
|
+
ses_f6b0921b COMPLETED ... 2/15 Read changes.txt and call done with the list ...
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Why this helps
|
|
59
|
+
|
|
60
|
+
- **Clean context per stage.** The write stage did not see the reading stage's raw output, only a compact digest of its result (about 50 tokens), and it can query the earlier stage on demand.
|
|
61
|
+
- **A model per job.** Put a stronger or paid model only on the stage that needs it.
|
|
62
|
+
- **Narrowing only.** A stage can reduce the sandbox (`read-only`, fewer tools) but never widen what the pipeline allows.
|
|
63
|
+
|
|
64
|
+
## When it goes wrong
|
|
65
|
+
|
|
66
|
+
- **A stage ends `Max steps reached`.** Raise `max_steps` for that stage (or for the whole pipeline). There is no resume yet, so progress that lives in files is what you keep; re-run the stage by hand with `tether run --skill ...`.
|
|
67
|
+
- **A stage's gate keeps failing.** Run the gate command by hand; make the stage goal name the file it must produce.
|
|
68
|
+
- **Invalid manifest.** `Error: invalid pipeline ...` and exit code 1 name the field.
|
|
69
|
+
|
|
70
|
+
More: [Gates, memory, pipelines](../tutorial/workflows-and-telemetry.md#pipelines), [multi-agent](../tutorial/multi-agent.md).
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Give the model a tool of your own
|
|
2
|
+
|
|
3
|
+
*For: Python developers who want the model to call their code. After this page you can turn a function into a tool, run it with a local model, and know what the model sees.*
|
|
4
|
+
|
|
5
|
+
Tether exposes exactly one tool schema to the model, `act(cmd, stdin)`. Your function becomes a **command** behind it, so adding ten tools does not add ten schemas to the prompt.
|
|
6
|
+
|
|
7
|
+
## The function
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import asyncio
|
|
11
|
+
|
|
12
|
+
from tether import Harness, tool
|
|
13
|
+
|
|
14
|
+
PRICES = {"apple": 0.5, "pear": 0.75, "melon": 3.2}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@tool(name="price", description="Look up the unit price of a fruit")
|
|
18
|
+
def price(fruit: str) -> str:
|
|
19
|
+
"""Return the price of one `fruit` in euros. Known fruit: apple, pear, melon."""
|
|
20
|
+
if fruit not in PRICES:
|
|
21
|
+
return f"Unknown fruit `{fruit}`. Known: {', '.join(PRICES)}."
|
|
22
|
+
return f"{fruit}: {PRICES[fruit]:.2f} EUR"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def main() -> None:
|
|
26
|
+
harness = Harness(model="ollama/qwen3.5:9b", tools=[price])
|
|
27
|
+
session = await harness.run("What do 3 pears and 1 melon cost together? Use the price tool.")
|
|
28
|
+
print(session.status, "-", session.result)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
asyncio.run(main())
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Type hints and the docstring become the command's usage text. The model only sees the one-line description until it runs `price --help` or calls it wrongly, so a tool costs about a dozen prompt tokens until it is used.
|
|
35
|
+
|
|
36
|
+
## Run it
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
python shop.py
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
COMPLETED - 5.45 EUR
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Ten seconds, three steps: the model asked for the pear price, then the melon price, then did the sum (3 × 0.75 + 3.20 = 5.45) and finished. Note what the tool does on bad input: it returns the list of valid fruit and does not raise. A message that says what to do next is how a small model recovers on its own.
|
|
47
|
+
|
|
48
|
+
## Make the error message do the work
|
|
49
|
+
|
|
50
|
+
Write tool errors for a reader that cannot ask a follow-up question: say what was wrong, list the valid options and show the next call. A tool that raises a stack trace teaches the model nothing.
|
|
51
|
+
|
|
52
|
+
## Other ways to add things
|
|
53
|
+
|
|
54
|
+
- **In a skill.** Put the function in `skills/<name>/tools.py` and list its name under `tools:` in `SKILL.md`; tools are opt-in, so an unlisted tool stays unavailable.
|
|
55
|
+
- **A model backend, not a tool.** Declare it in `~/.tether/providers.yaml` or register a class: [Model providers](../reference/providers.md).
|
|
56
|
+
|
|
57
|
+
## When it goes wrong
|
|
58
|
+
|
|
59
|
+
- **`unknown command` for your tool.** It was not passed in `tools=[...]` (or not listed in the skill's `tools:`).
|
|
60
|
+
- **The model passes the wrong argument.** Tether answers with the tool's usage line; if it keeps happening, tighten the docstring and the error message.
|
|
61
|
+
|
|
62
|
+
More: [Python SDK and custom tools](../tutorial/sdk-and-custom-tools.md), [Extending tether](../extending.md).
|