tether-sdk 0.2.18__tar.gz → 0.3.0__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.3.0/.claude/gru/archive/real-world-v0218.md +97 -0
- tether_sdk-0.3.0/.minion-err.txt +1 -0
- tether_sdk-0.3.0/.minion-out.json +1 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/AGENTS.md +24 -9
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/PKG-INFO +15 -9
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/README.md +14 -8
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/history.jsonl +1 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/ladder/baseline/harness.jsonl +24 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/benchmarks.md +8 -6
- tether_sdk-0.3.0/docs/guides/own-branch.md +95 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/index.md +2 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/ladder/img/harness-cost.svg +1 -1
- tether_sdk-0.3.0/docs/ladder/img/harness-pass.svg +1 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/ladder/results.md +2 -2
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/llms.txt +2 -1
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/reference/agent-interface.md +8 -2
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/reference/cli.md +132 -3
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/reference/context-and-store.md +1 -1
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/reference/providers.md +1 -0
- tether_sdk-0.3.0/docs/release-notes.md +143 -0
- tether_sdk-0.3.0/docs/tutorial/security-and-sandbox.md +208 -0
- tether_sdk-0.3.0/e2e/test_command_retry.py +39 -0
- tether_sdk-0.3.0/e2e/test_diff_other_writers.py +48 -0
- tether_sdk-0.3.0/e2e/test_doctor.py +50 -0
- tether_sdk-0.3.0/e2e/test_edit_write_blocks.py +17 -0
- tether_sdk-0.3.0/e2e/test_empty_reply.py +37 -0
- tether_sdk-0.3.0/e2e/test_escalation.py +51 -0
- tether_sdk-0.3.0/e2e/test_export.py +37 -0
- tether_sdk-0.3.0/e2e/test_fold_window.py +44 -0
- tether_sdk-0.3.0/e2e/test_gate_precheck.py +24 -0
- tether_sdk-0.3.0/e2e/test_mcp_workspace.py +59 -0
- tether_sdk-0.3.0/e2e/test_network_pattern.py +20 -0
- tether_sdk-0.3.0/e2e/test_pipeline_resume.py +54 -0
- tether_sdk-0.3.0/e2e/test_replay.py +57 -0
- tether_sdk-0.3.0/e2e/test_replay_worktree.py +61 -0
- tether_sdk-0.3.0/e2e/test_sandbox_allowlist.py +40 -0
- tether_sdk-0.3.0/e2e/test_sandbox_conformance.py +164 -0
- tether_sdk-0.3.0/e2e/test_sandbox_fail_closed.py +52 -0
- tether_sdk-0.3.0/e2e/test_sandbox_limits.py +37 -0
- tether_sdk-0.3.0/e2e/test_sandbox_mask.py +39 -0
- tether_sdk-0.3.0/e2e/test_sandbox_refusals.py +31 -0
- tether_sdk-0.3.0/e2e/test_sandbox_stats.py +14 -0
- tether_sdk-0.3.0/e2e/test_session_diff.py +38 -0
- tether_sdk-0.3.0/e2e/test_thinking.py +66 -0
- tether_sdk-0.3.0/e2e/test_token_budget.py +35 -0
- tether_sdk-0.3.0/e2e/test_wait_timeout.py +17 -0
- tether_sdk-0.3.0/e2e/test_web_allowlist.py +27 -0
- tether_sdk-0.3.0/e2e/test_worker_isolation.py +36 -0
- tether_sdk-0.3.0/e2e/test_worker_reap_e2e.py +46 -0
- tether_sdk-0.3.0/e2e/test_worktree.py +59 -0
- tether_sdk-0.3.0/e2e/test_worktree_commit_excludes.py +40 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/mkdocs.yml +1 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/conftest.py +8 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/providers/test_declarative.py +6 -2
- tether_sdk-0.3.0/tests/providers/test_ollama_empty.py +118 -0
- tether_sdk-0.3.0/tests/test_cli_columns.py +169 -0
- tether_sdk-0.3.0/tests/test_cli_wait_timeout.py +166 -0
- tether_sdk-0.3.0/tests/test_command_provider_retry.py +168 -0
- tether_sdk-0.3.0/tests/test_diff.py +188 -0
- tether_sdk-0.3.0/tests/test_diff_other_writers.py +296 -0
- tether_sdk-0.3.0/tests/test_doctor.py +117 -0
- tether_sdk-0.3.0/tests/test_doctor_extra.py +70 -0
- tether_sdk-0.3.0/tests/test_edit_write_search_replace.py +108 -0
- tether_sdk-0.3.0/tests/test_empty_reply_docs.py +11 -0
- tether_sdk-0.3.0/tests/test_empty_reply_review.py +19 -0
- tether_sdk-0.3.0/tests/test_escalation.py +492 -0
- tether_sdk-0.3.0/tests/test_escalation_fix.py +136 -0
- tether_sdk-0.3.0/tests/test_export.py +81 -0
- tether_sdk-0.3.0/tests/test_export_review.py +41 -0
- tether_sdk-0.3.0/tests/test_fold_window.py +240 -0
- tether_sdk-0.3.0/tests/test_fold_window_cli.py +62 -0
- tether_sdk-0.3.0/tests/test_gate_precheck.py +81 -0
- tether_sdk-0.3.0/tests/test_gate_precheck_edges.py +26 -0
- tether_sdk-0.3.0/tests/test_gate_precheck_review.py +34 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_harness.py +23 -2
- tether_sdk-0.3.0/tests/test_mcp_limits.py +54 -0
- tether_sdk-0.3.0/tests/test_mcp_workspace.py +96 -0
- tether_sdk-0.3.0/tests/test_pipeline_resume.py +193 -0
- tether_sdk-0.3.0/tests/test_read_thinking.py +63 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_release_hardening.py +2 -2
- tether_sdk-0.3.0/tests/test_replay.py +142 -0
- tether_sdk-0.3.0/tests/test_replay_empty_model.py +25 -0
- tether_sdk-0.3.0/tests/test_sandbox_allowlist.py +374 -0
- tether_sdk-0.3.0/tests/test_sandbox_allowlist_cov.py +86 -0
- tether_sdk-0.3.0/tests/test_sandbox_conformance.py +150 -0
- tether_sdk-0.3.0/tests/test_sandbox_fail_closed.py +399 -0
- tether_sdk-0.3.0/tests/test_sandbox_limits.py +291 -0
- tether_sdk-0.3.0/tests/test_sandbox_limits_cov.py +60 -0
- tether_sdk-0.3.0/tests/test_sandbox_mask.py +228 -0
- tether_sdk-0.3.0/tests/test_sandbox_mask_sockets.py +18 -0
- tether_sdk-0.3.0/tests/test_sandbox_refusals.py +160 -0
- tether_sdk-0.3.0/tests/test_sandbox_stats.py +162 -0
- tether_sdk-0.3.0/tests/test_sandbox_stats_cov.py +124 -0
- tether_sdk-0.3.0/tests/test_security_docs.py +54 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_security_hardening.py +2 -1
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_tether.py +1 -1
- tether_sdk-0.3.0/tests/test_thinking.py +38 -0
- tether_sdk-0.3.0/tests/test_thinking_cap.py +29 -0
- tether_sdk-0.3.0/tests/test_token_budget.py +118 -0
- tether_sdk-0.3.0/tests/test_token_budget_crossing.py +48 -0
- tether_sdk-0.3.0/tests/test_web_allowlist.py +250 -0
- tether_sdk-0.3.0/tests/test_web_allowlist_cov.py +100 -0
- tether_sdk-0.3.0/tests/test_worker_isolation.py +75 -0
- tether_sdk-0.3.0/tests/test_worker_reap.py +134 -0
- tether_sdk-0.3.0/tests/test_worktree.py +203 -0
- tether_sdk-0.3.0/tests/test_worktree_commit_excludes.py +103 -0
- tether_sdk-0.3.0/tests/test_worktree_docs.py +24 -0
- tether_sdk-0.3.0/tests/test_worktree_errors.py +24 -0
- tether_sdk-0.3.0/tests/test_worktree_review.py +110 -0
- tether_sdk-0.3.0/tests/tools/test_agent_git_inherit.py +52 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/__init__.py +1 -1
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/api.py +32 -10
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/cli.py +433 -31
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/context.py +76 -16
- tether_sdk-0.3.0/tether/diff.py +134 -0
- tether_sdk-0.3.0/tether/doctor.py +79 -0
- tether_sdk-0.3.0/tether/export.py +47 -0
- tether_sdk-0.3.0/tether/gates.py +98 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/harness.py +454 -24
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/mcp.py +81 -11
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/multiplexer.py +1 -1
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/providers/base.py +2 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/providers/declarative.py +57 -29
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/providers/ollama.py +35 -0
- tether_sdk-0.3.0/tether/sandbox.py +1265 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/store.py +114 -15
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/tools/bash.py +15 -5
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/tools/edit.py +32 -11
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/tools/web.py +100 -16
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/unchanged.py +57 -0
- tether_sdk-0.3.0/tether/worktree.py +89 -0
- tether_sdk-0.2.18/docs/ladder/img/harness-pass.svg +0 -1
- tether_sdk-0.2.18/docs/release-notes.md +0 -427
- tether_sdk-0.2.18/docs/tutorial/security-and-sandbox.md +0 -75
- tether_sdk-0.2.18/tether/sandbox.py +0 -560
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/.claude/gru/active/.gitkeep +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/.claude/gru/active/north-star.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/.claude/gru/active/run-progress.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/.claude/gru/active/why-qwen-fails.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/.claude/gru/archive/.gitkeep +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/.claude/gru/archive/v0-2-bugs.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/.claude/gru/archive/work-on-v0-2-4.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/.claude/gru/why-qwen-fails/reg_ab.sh +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/.claude/gru/why-qwen-fails/regcount.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/.claude/gru/why-qwen-fails/whack.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/.claude/gru.json +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/.github/workflows/ci.yml +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/.github/workflows/delivery.yml +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/.github/workflows/docs.yml +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/.gitignore +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/CONTRIBUTING.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/LICENSE +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/README.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/canary.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/canary_baseline.json +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/compare.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/grade.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/harness-bench/README.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/harness-bench/tether-adapter.patch +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/ladder/base.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/ladder/baseline/ladder.jsonl +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/ladder/charts.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/ladder/general.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/ladder/probe.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/ladder/refs.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/ladder/report.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/ladder/run.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/ladder/snapshot.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/ladder/tasks.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/ladder/validate.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/little_coder_recipe.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/naive_agent.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/report.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/run.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-hard/goal.txt +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-hard/hidden/test_hidden.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-hard/ref/calc/__init__.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-hard/ref/calc/errors.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-hard/ref/calc/evaluator.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-hard/ref/calc/parser.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-hard/ref/calc/tokenizer.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-hard/ws/calc/__init__.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-hard/ws/calc/errors.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-hard/ws/calc/evaluator.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-hard/ws/calc/parser.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-hard/ws/calc/tokenizer.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-hard/ws/tests/__init__.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-hard/ws/tests/test_calc.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-medium/goal.txt +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-medium/hidden/test_hidden.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-medium/ws/cli.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-medium/ws/ledger/__init__.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-medium/ws/ledger/parser.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-medium/ws/ledger/report.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-medium/ws/sample.csv +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-medium/ws/tests/__init__.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-medium/ws/tests/test_report.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-simple/goal.txt +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-simple/hidden/test_hidden.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-simple/ws/tests/__init__.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-simple/ws/tests/test_textutils.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/code-simple/ws/textutils.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/codereview/goal.txt +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/codereview/ws/inventory.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/content/goal.txt +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/content/ws/.keep +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/focus/goal.txt +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/focus/hidden/test_hidden.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/focus/ws/AGENTS.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/focus/ws/README.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/focus/ws/docs/architecture.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/focus/ws/docs/style.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/focus/ws/mini/__init__.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/focus/ws/mini/cli.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/focus/ws/mini/fmt.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/focus/ws/mini/stats.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/focus/ws/mini/textio.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/focus/ws/tests/__init__.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/focus/ws/tests/test_stats.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/plan/goal.txt +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/plan/ws/README.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/plan/ws/svc/__init__.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/plan/ws/svc/client.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/plan/ws/svc/config.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/qa/goal.txt +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/qa/ws/.keep +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/review/goal.txt +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/bench/tasks/review/ws/paper.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/architecture.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/extending.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/glossary.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/guides/a-pipeline-with-checks.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/guides/add-your-own-tool.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/guides/fix-a-failing-test.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/guides/use-tether-from-your-agent.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/javascripts/mathjax.js +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/ladder/claude-haiku.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/ladder/general.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/ladder/img/task-grid.svg +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/ladder/img/tiers.svg +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/ladder/index.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/reference/harness.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/reference/index.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/reference/multiplexer-and-decorator.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/reference/sandbox-and-skills.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/reference/tools.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/troubleshooting.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/tutorial/agent-interface.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/tutorial/built-in-tools.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/tutorial/cli-and-headless.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/tutorial/index.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/tutorial/multi-agent.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/tutorial/sdk-and-custom-tools.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/tutorial/skills.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/docs/tutorial/workflows-and-telemetry.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/e2e/__init__.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/e2e/conftest.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/e2e/proxy.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/e2e/test_agents.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/e2e/test_defaults.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/e2e/test_faults.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/e2e/test_lifecycle.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/e2e/test_limits.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/e2e/test_notes.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/e2e/test_progress.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/e2e/test_quirks.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/e2e/test_restart.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/e2e/test_sandbox.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/e2e/test_surface.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/examples/course/README.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/examples/course/checks.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/examples/course/course.yaml +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/examples/course/ruff.toml +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/examples/course/skills/planner/SKILL.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/examples/course/skills/searcher/SKILL.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/examples/course/skills/searcher/tools.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/examples/course/skills/writer/SKILL.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/examples/course/skills/writer/tools.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/pyproject.toml +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/scripts/record_golden.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/skills/researcher/SKILL.md +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/__init__.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/fixtures/dialects.jsonl +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/golden/coding_edit_failure_modes.json +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/golden/coding_edit_failure_modes.snap.json +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/golden/done_nudge.json +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/golden/done_nudge.snap.json +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/golden/read_range_pingpong.json +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/golden/read_range_pingpong.snap.json +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/golden/research_web.json +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/golden/research_web.snap.json +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/golden/shell_wrappers.json +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/golden/shell_wrappers.snap.json +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/golden/waiting_input_resume.json +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/golden/waiting_input_resume.snap.json +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/providers/__init__.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/providers/test_anthropic.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/providers/test_base.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/providers/test_dialects.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/providers/test_ollama.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/providers/test_openai.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/providers/test_retry.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_agent_interface.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_auto_test.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_bench_littlecoder.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_benchmarks_docs.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_cli.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_cli_docs.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_context.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_context_config.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_decorator.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_docs_structure.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_edit_only_shell.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_edit_tool.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_failing_test_source.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_golden.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_guides_docs.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_integration.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_ladder_general.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_ladder_run.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_llms_txt.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_misses.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_multiplexer.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_params.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_pipeline.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_progress.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_providers_docs.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_quiet_and_fold.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_readme.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_regression_note.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_repeat_reads.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_restart.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_retro.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_sandbox.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_skills.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_small_rewrite.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_store.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_structured_history.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_test_reasoning.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_trace.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_troubleshooting_docs.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/test_unchanged_write.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/tools/__init__.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/tools/test_agent.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/tools/test_bash.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/tools/test_done.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/tools/test_edit.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/tools/test_mem.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tests/tools/test_web.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/decorator.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/pipeline.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/progress.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/providers/__init__.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/providers/anthropic.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/providers/openai.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/retro.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/schema.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/skills.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/tools/__init__.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/tools/agent.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/tools/done.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/tools/mem.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/tether/trace.py +0 -0
- {tether_sdk-0.2.18 → tether_sdk-0.3.0}/uv.lock +0 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Real-world test: tether v0.2.18 as the only minion
|
|
2
|
+
- Slug: real-world-v0218
|
|
3
|
+
- Status: done
|
|
4
|
+
- Started: 2026-11-08 Updated: 2026-11-08 Closed: 2026-11-08
|
|
5
|
+
- Branches / PRs: #259 #260 #261 #262 #263 #264 #265 #266 #274 #275 #276 (all merged); issues tether #267-#273, gru#7
|
|
6
|
+
- Budget: warn 300k / stop 400k paid tokens (gru + haiku), wall-clock overnight-ok, max escalations: none (no rung above haiku allowed) Used: gru session context ~620k at the end (Opus; 5-hour plan window hit 82%); haiku ~9M prompt tokens (claude-softmedium); opus planning ~2M; qwen local
|
|
7
|
+
|
|
8
|
+
## Goal
|
|
9
|
+
Implement 10 features using only tether v0.2.18 as the minion: tether+ollama/qwen3.5:9b for easy cards (sequential), tether+claude-softmedium/haiku for hard cards (parallel, own worktrees). Card kinds swe/qa/review/plan. Gru plans, writes acceptance tests, verifies, commits, opens PRs; never implements, no Claude Code minions. Then report: success rate, issues, improvements, what was good, junk tether features.
|
|
10
|
+
|
|
11
|
+
## Requests
|
|
12
|
+
- 2026-11-08 PO: list features; then "Implement all 10 ideas, the haiku ones can be done in parallel, the qwen ones should be one by one". Close already-covered issues with a note.
|
|
13
|
+
|
|
14
|
+
## Decisions
|
|
15
|
+
- Closed as covered: #220 (hand-back after restarts shipped), #78 and #134 (SEARCH/REPLACE blocks).
|
|
16
|
+
- qwen (sequential): F1 doctor, F2 gate precheck #82, F3 export.
|
|
17
|
+
- haiku (parallel, one worktree each): F4 empty reply #135, F5 --max-tokens, F6 thinking #32, F7 pipeline resume #35, F8 mcp worktree #242, F9 replay #7, F10 diff.
|
|
18
|
+
- No escalation rung: a card that fails twice on its minion is reported as a failure (that is the data), not escalated to Claude Code.
|
|
19
|
+
- PO 2026-11-08 (mid-turn): if a task fails, tether+opus (claude-softmedium/opus) writes a plan aimed at the implementing model, then retry on the same tether+model with that plan. Opus rung OK'd for this use only.
|
|
20
|
+
- Haiku runs with --context-tokens 100000; qwen at default 32768.
|
|
21
|
+
|
|
22
|
+
## Problems
|
|
23
|
+
## Plan (task cards)
|
|
24
|
+
## Ledger
|
|
25
|
+
| Task | Tier | Model | Harness | Session | Attempt | Tokens | Time | Result |
|
|
26
|
+
|---|---|---|---|---|---|---|---|---|
|
|
27
|
+
## Tether friction
|
|
28
|
+
## Progress log
|
|
29
|
+
- 2026-11-08 log created; issues #220 #78 #134 closed.
|
|
30
|
+
## Next steps
|
|
31
|
+
- [ ] selftest qwen + haiku via tether
|
|
32
|
+
- [ ] plan cards (haiku) for F4-F10, acceptance tests by gru
|
|
33
|
+
## Sessions
|
|
34
|
+
| Date | Session note |
|
|
35
|
+
- 2026-11-08 plans F4-F10 by tether+haiku: 7/7 COMPLETED, 59-109 s, 5-13 steps, 33k-109k prompt tok; scope clean (only PLAN.md). Reviewed: good quality; decisions F7 usage exit 1, F9 verify-gate limitation documented.
|
|
36
|
+
- Deviation: haiku swe cards write the plan's tests themselves (TDD), gate = full suite 100% cov + ruff + mypy; tether+haiku review checks tests vs plan. qwen cards get gru-written acceptance tests.
|
|
37
|
+
- Isolation: session can only write its own worktree; minions now run in plain copies scratchpad/ws/<id> (no git); rw-f1..rw-f10 git worktrees were created by mistake via script, unused except PLAN.md (to remove at done, PO OK).
|
|
38
|
+
- F1 qwen attempt 1 (ses_d89d4ab0): FAIL out of steps 30, 202k prompt tok, 113 s, never ran tests; broke cli.py with sed -i insert inside try:. Opus plan + qwen retry started.
|
|
39
|
+
- swe F4-F10 haiku dispatched (4 parallel, 40 steps).
|
|
40
|
+
- 2026-11-08 PO: needs a durable fix so tether works on its own worktree and delivers a branch, documented (docs + MCP). Added F11 `tether run --worktree` (harness creates git worktree at ~/.cache/tether/worktrees/<repo>-<sid>, optional --setup, commits on tether/<sid> at stop; MCP worktree:true; guide docs/guides/own-branch.md). F8 stays (explicit workspace arg). F11 plan dispatched to haiku; implementation merges after F5/F8.
|
|
41
|
+
- F1 qwen attempt 2 (opus plan ses_377958aa 103 s; qwen ses_43ab8679): FAIL out of steps; qwen never opened PLAN.md (gru card bug: plan mentioned only at the end after the old reading list). Attempt 3 with card = 'edit view PLAN.md first' running.
|
|
42
|
+
- Friction: tether wait default --timeout 120 (not shown in --help) returns while RUNNING; minion.sh waited 120 s and read RUNNING. Fixed caller side (--timeout 3600).
|
|
43
|
+
- Friction: qwen writes `sed -n '45,90p file'` (file inside the quotes) repeatedly: 6 times across 2 runs; a quirk normalizer could split it.
|
|
44
|
+
- F11 plan (haiku ses_41a4d3b2, 76 s) reviewed: ok; decisions: MCP no setup, commit at WAITING_INPUT, ship worktree-from. F11 swe dispatched.
|
|
45
|
+
- F1 qwen attempt 3 (ses_2033bfb8, card = follow opus PLAN.md): COMPLETED at step 30, 284k prompt tok, 137 s. Acceptance tests pass unchanged, 100% cov, mypy ok. Left: CLI docs section (test_cli_docs), formatting. Our #79 cd-skip fired in the wild (`cd /testbed && pytest`). Finish card (fin-f1) on qwen running.
|
|
46
|
+
- gru fault: my acceptance tests had 3 E501 lines + import order; fixed in acc/ (gru-owned).
|
|
47
|
+
- swe haiku F4-F10 (40 steps, 4 parallel): COMPLETED+verified green: F5 (no release note), F8, F9, F10. F7 green except 1 ruff (out of steps). FAIL: F4 (broke test_no_second_ask_when_the_caller_set_the_budget + mypy), F6 (edit write with SEARCH/REPLACE stdin wiped ollama.py 216->22 lines; 20 steps hunting the original). 357-543 s, 497k-720k prompt tok each.
|
|
48
|
+
- F1 fin (qwen): green all (docs section + format).
|
|
49
|
+
- TETHER BUG #1 (F6 ses_d64c80e5 step 19): `edit write <existing file>` with a SEARCH/REPLACE block in stdin overwrites the file with the block. Should apply as replace or refuse. No undo -> model hunted the disk 20 steps.
|
|
50
|
+
- Retries: F4, F6 = opus plan for haiku + fresh haiku (PO rule). F7, F5 = tether send resume (small leftovers; also tests send). F2 qwen started.
|
|
51
|
+
- tether send resume: F5 +5 steps 53 s, F7 +2 steps 33 s -> both verified green (send works well for leftovers). Reviews F5, F7 dispatched.
|
|
52
|
+
- Reviews (haiku, 37-94 s, 5-9 steps): 6/6 CHANGES. Real findings: F10 symlink escape in diff (security), F5 action dropped at budget cap, F8 TypeError on non-string workspace, F9 README + empty --model, F1 release note/:latest/host scheme. False alarm: F7 'EXIT_USAGE is 2' (it is 1). Fixes sent via tether send (F5 F7 F8 F9 F10); F1 via qwen fix card.
|
|
53
|
+
- F11 haiku (45 steps, 549 s, 725k tok): FAIL out of steps; commit at stop never lands, 3 tests fail, cov 99, docs missing. opus-finish (keep work) running.
|
|
54
|
+
- F2 qwen (30 steps, 222 s): FAIL; hook added but `gates` never imported -> NameError, 114 tests fail. opus-finish for qwen queued, then F1 fixes, then F3.
|
|
55
|
+
- Review fixes via tether send: all 5 green (27-133 s). Spot-check: F8, F10 really fixed (symlink check + size cap + tests). GREEN-BUT-WRONG: F9 did none of its blocking fixes (README, empty --model), F5 did not move the budget check (my new test fails), F6 lacks read --thinking + release note, F4 lacks release note. Sessions said done anyway: a fix request without a deterministic check is skipped.
|
|
56
|
+
- Response: gru wrote failing tests for each gap (acc/f4,f5,f6,f9 round 2, incl. docs-presence tests); fresh haiku fix2 sessions with those tests as verify gates.
|
|
57
|
+
- F4/F6 retries: opus plan sessions hit 30-step cap again (WAITING_INPUT) but had written PLAN.md; haiku then COMPLETED green (F4 19 steps 264 s, F6 24 steps 165 s).
|
|
58
|
+
- Opus F11 plan attempt 1: 30 steps, never wrote PLAN.md; re-read same 3 regions 6x (cause to investigate: folding vs command-provider token accounting). Retry with 45 steps + 'write PLAN.md early' running.
|
|
59
|
+
- PRs opened (unit-verified, reviewed, fixed; e2e pending): #259 F8 mcp workspace, #260 F10 diff, #261 F7 pipeline resume, #262 F5 max-tokens, #263 F9 replay. Branches feat/mcp-workspace, feat/session-diff, feat/pipeline-resume, feat/max-tokens, feat/replay (from main 2891ab5).
|
|
60
|
+
- fix2 (gru tests as gates): F4 8 steps, F5 18 steps green; F6/F9 ended FAILED (exit 3) only because gru's own new test files broke ruff E501 (minion correctly would not edit them); after gru fixed its tests: green. Lesson for gru: lint acceptance tests before dispatch.
|
|
61
|
+
- Reviews F4, F6 dispatched; QA (e2e scenario) card F10 dispatched as a probe for e2e inside tether's sandbox.
|
|
62
|
+
- F4 review: real (ModelResponse field order shifts positional args; provider docs missing). F6 review: mostly FALSE because gru's review diff raced with mkpr F5 in the same worktree (base had --max-tokens); real: 4000-char cap missing, empty message text. gru pinned both with tests (acc/f4 test_empty_reply_review, acc/f6 test_thinking_cap); fix3 sessions running. gru's new lint guard caught gru's own long docstring before dispatch.
|
|
63
|
+
- QA F10 (haiku, 9 steps, 72 s): e2e/test_session_diff.py, 3 real scenarios, pass; e2e runs fine inside tether's sandbox. Pushed to #260. QA F5 F7 F8 F9 dispatched. Full e2e for F10 running in ws/f10.
|
|
64
|
+
- F4/F6 fix3 (gru tests): green (10 / 9 steps, ~70 s). F4 README wrongly says retry uses think=false -> tether send correction running.
|
|
65
|
+
- F11 after opus plan #2 (45 steps, WAITING but PLAN.md written) + haiku 40 steps: commit now lands; left: not-a-repo test, 10 uncovered lines, cli docs, ruff, guide. gru wrote tests/test_worktree_docs.py; fix4 card running.
|
|
66
|
+
- QA F5 F7 F8 F9 (haiku 7-9 steps, 55-83 s): real e2e scenarios, gates pass; pushed to PR branches; full e2e running.
|
|
67
|
+
- MERGED #260 (F10 tether diff): e2e 96 passed, CI green.
|
|
68
|
+
- MERGED #259 (F8). e2e F5 96, F7 95, F8 96, F9 96 passed.
|
|
69
|
+
- F4 README corrected via send (15 steps total, 52 s): green.
|
|
70
|
+
- TETHER BUG #2: command provider timeout (180 s) is not retried; one slow Claude CLI call (writing a long guide) ended the whole F11 session FAILED exit 5 (ses_ee26e6ca step 24). HTTP providers retry twice. Floor #171 violation. F11 resumed via send.
|
|
71
|
+
- MERGED #262 (F5) and #261 (F7). F7 needed: CI on py3.12 caught an argparse break (local 3.14 passed) -> tether+haiku fix card with a 3.12 venv (code fixed; its .venv312 gate failed only inside the sandbox on SSL CA, a gru setup issue); two merge-with-main rounds (additive conflicts, gru resolved); e2e on merged branch 104 passed.
|
|
72
|
+
- Note: GitHub skips CI on a conflicting PR; after each merge to main the next PR needs main merged in first.
|
|
73
|
+
- F11: green after send-resume (55 steps total). QA e2e card 6 steps 43 s. Review: 5 real blocking bugs (status line never printed, commit failure crashes session, stale sha on resume, docs keys location, README) + minors incl. '-' ref option injection. gru pinned them in tests/test_worktree_review.py; fix5 running.
|
|
74
|
+
- MERGED #263 (F9) after merge-with-main (additive conflict) + e2e 107 passed. Merged so far: F10 #260, F8 #259, F5 #262, F7 #261, F9 #263.
|
|
75
|
+
- QA cards F4, F6 dispatched; F11 fix5 running; qwen queue (F2 finish, F1 fixes, F3) still running.
|
|
76
|
+
- MERGED #264 (F4, e2e 110), #265 (F6, e2e 113; provider field conflict additive), #266 (F11, e2e 115; hand-resolved create_session/from_session interleave). 8 of 11 merged.
|
|
77
|
+
- Dogfood --worktree with qwen in a throwaway repo: caller untouched, fix committed on tether/<id>, status line ok. Session ran out of steps: (a) [BEFORE WRITING] note made qwen re-read the test 6x (5 loop_stall, 6/15 steps lost) -> report item; (b) gru's gate used system python3 without pytest (gru error).
|
|
78
|
+
- TETHER BUG #5: headless worker died before marking RUNNING (pid 3540688 gone); status stays PENDING forever (reaper only handles RUNNING); tether wait blocked 3600 s; worker stderr discarded so the cause is lost (F2 opus plan ses_e6c13419).
|
|
79
|
+
- F1 qwen review-fix: 25 steps out; host-prefix test fails + ruff. Queued: opus plan + qwen retry for F2 finish, then F1 finish (after F3 qwen run).
|
|
80
|
+
- F3 qwen attempt 1 (ses_7a0d8b34, 30 steps, 267 s): FAIL; export.py written, CLI not wired, cut missing; 3/5 tests fail. Queued opus finish + qwen retry after F2/F1.
|
|
81
|
+
- PO: file issues. Filed tether #267 (edit write + SEARCH/REPLACE wipes file), #268 (command provider timeout not retried), #269 (wait 120 s default), #270 (sed quote quirk), #271 (PENDING dead worker, stderr lost), #272 ([BEFORE WRITING] loop on qwen). gru guide filed as igormcsouza/gru#7.
|
|
82
|
+
- ROOT CAUSE of F2 opus 'never wrote PLAN.md' x2: TETHER BUG #273 (security): headless worker = python -m tether.cli with cwd=workspace -> imports the workspace's tether/ (minion's broken F2 code) -> NameError -> PENDING forever. Reproduced. Workaround: minion.sh runs foreground (session id from stderr, #90). Commented on #271. qwen queue restarted (qwen-queue4: F2, F1, F3).
|
|
83
|
+
- qwen-queue4: F1 review fixes via opus plan (5 steps 48 s) + qwen (9 steps 49 s): GREEN. F3 via opus plan (11 steps) + qwen (30 steps): feature works, all 5 acceptance tests pass; left cli docs + ruff -> fin-f3 qwen card. F2: no clean try yet (old queue2 survived pkill and deleted ws/f2-plan mid-run -> FileNotFoundError; gru orchestration error). qwen-queue5: clean opus+qwen for F2, then fin-f3. QA F1 (haiku) dispatched.
|
|
84
|
+
- QA F1 (haiku 9 steps 64 s): 3 e2e scenarios. MERGED #274 (F1 doctor, qwen-built): e2e 118, CI green. 9 of 11 merged.
|
|
85
|
+
- F2 clean opus plan (14 steps 281 s) + qwen (30 steps 108 s): all tests pass; left 3 uncovered lines + ruff. F3 fin (qwen 20 steps): docs done, tests pass; ruff left. qwen-queue6: fin-f2 (exact test file given), fin2-f3 (ruff only).
|
|
86
|
+
- fin-f2 (qwen 6 steps 42 s, exact test file in card) and fin2-f3 (qwen 4 steps 15 s): both GREEN; acceptance-test diffs are import order only (ruff isort with ws config). Reviews + QA for F2, F3 dispatched (haiku).
|
|
87
|
+
- Reviews F2/F3 (haiku): F2 chained gates only check first program, no release note, edited test_harness parametrization; F3 release note under v0.2.18, backtick fences break, stray .minion-* files (gru runner artifacts, now excluded). QA F2 (5 steps 29 s, 2 scenarios), F3 (7 steps 46 s, 3 scenarios). gru pinned fixes in tests; qwen fix2 cards (sequential) running.
|
|
88
|
+
- qwen fix2 F2 (25 steps 578 s) and F3 (25 steps 2491 s, runaway thinking): both out of steps. F2: review tests pass; stale edge test + 1 line left. F3: both review tests fail. opus-finish + qwen retry queued (qwen-queue7).
|
|
89
|
+
- PO asked steering cost vs self-implementing: for qwen cards ~same (context size per turn dominates; ~45 turns at ~450k cached each); haiku cards clearly cheaper to steer. Session ctx 616k, 5h window 82%.
|
|
90
|
+
- F2/F3 review fixes via opus plan + qwen: F2 green (27 steps); F3 green after gru fixed its own wrong test regex (4th gru test bug). MERGED #275 (F2, e2e 120) and #276 (F3, e2e 123). ALL 11 FEATURES MERGED.
|
|
91
|
+
|
|
92
|
+
## Wrap-up
|
|
93
|
+
- Outcome: 11/11 features merged (F1 doctor, F2 gate pre-check #82, F3 export, F4 empty reply #135, F5 --max-tokens, F6 thinking #32, F7 pipeline --resume #35, F8 MCP workspace #242, F9 replay #7, F10 diff, F11 run --worktree), each with 100% coverage, a QA-written e2e scenario, full local e2e, CI 3.12/3.13 and a Haiku review. Issues #220 #78 #134 closed as covered.
|
|
94
|
+
- Per tier: Haiku plans 8/8 good; Haiku swe first try 4/8 green; after an Opus plan 3/3. qwen swe first try 0/3; with Opus plans and gru-pinned tests 3/3 after 2-5 rounds each. Reviews 11/11 found real bugs (1 false alarm + 1 gru-caused false set). QA 11/11 first try.
|
|
95
|
+
- Lessons (also in gru#7): a minion's done is not evidence, pin every requested fix with a failing test as a gate; lint acceptance tests with the workspace config; gates must run inside the sandbox; one workspace per minion (now `tether run --worktree`); run minions in the foreground until tether#273; never switch branches in the orchestrator checkout while background jobs read it; kill old queues fully before restarting; steering qwen costs about as much as typing it yourself on a long gru session, steering Haiku is clearly cheaper.
|
|
96
|
+
- Tether bugs found: #267 edit write + SEARCH/REPLACE, #268 command provider no retry, #269 wait 120 s, #270 sed quote quirk, #271 PENDING dead worker, #272 [BEFORE WRITING] loop on qwen, #273 headless worker imports the workspace's tether (security).
|
|
97
|
+
- Cleanup: rw-f1..rw-f10 worktrees and feat/rw-* branches removed (empty, created by mistake); scratch workspaces (8.7 GB) and the dogfood repo deleted; 11 local PR branches deleted (merged upstream); remote branches kept.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
session ses_9b520dc1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"v": 1, "id": "ses_9b520dc1", "status": "COMPLETED", "model": "claude-softmedium/haiku", "steps": 9, "max_steps": 30, "tokens": {"prompt": 113508, "completion": 6562}, "duration_ms": 63721, "failure": null, "exit_code": 0, "result": "Added e2e/test_doctor.py with 3 scripted e2e tests for `tether doctor`: (1) against the proxy with OLLAMA_HOST set to chaos.url, exit 0 and `ok` lines for ollama and model; (2) against a closed localhost port, exit 1 and a FAIL line naming OLLAMA_HOST; (3) with an unpulled model name, exit 1, ollama ok, model FAIL, and the `ollama pull <name>` hint. Verified: `TETHER_E2E=1 .venv/bin/python -m pytest e2e/test_doctor.py -q -p no:cacheprovider` gives 3 passed; `ruff check e2e` and `ruff format --check e2e` both pass. No other files were changed."}
|
|
@@ -29,7 +29,7 @@ Tether stays **lightweight, reliable, extensible, model-agnostic, easy to use, r
|
|
|
29
29
|
Reviewers and the Team Leader cite the failing line when they reject a change.
|
|
30
30
|
|
|
31
31
|
### North Star (the one number; see tracker #173)
|
|
32
|
-
**Polyglot-Python pass rate on `ollama/qwen3.5:9b` (our weakest model), default config, full 34 exercises x 2 runs: 74% (50 of 68) by v0.4.0.** 74% is what the model can already do: 25 of 34 exercises
|
|
32
|
+
**Polyglot-Python pass rate on `ollama/qwen3.5:9b` (our weakest model), default config, full 34 exercises x 2 runs: 74% (50 of 68) by v0.4.0.** 74% is what the model can already do: 25 of 34 exercises had passed at least once from scratch when it was set, and 27 have now (connect and hangman first passed in the v0.2.18 run), so the goal is that each of them passes every run (reliability), not new capability. 7 exercises never passed from scratch (bowling, forth, react, rest-api, scale-generator, sgf-parsing, transpose); they are not planned for. Nothing important is set aside for it, but everything is ranked by it:
|
|
33
33
|
1. Every issue, PR and benchmark must say how it moves this number (or tokens per solved exercise at the same pass rate). A benchmark that cannot show a change in it is not run; an issue that cannot name the link waits behind one that can.
|
|
34
34
|
1b. Two lanes, both tracked: the **general-purpose default** (no role) is the official number and must reach 74%; the shipped **`swe` role** (#172) is reported next to it, must never score below the default, and gets its own target once its baseline is pinned.
|
|
35
35
|
2. Weakest-model rule: every stronger model must score at least what `qwen3.5:9b` scores (and at least 74% once reached). If we plateau, stronger models may not fall below the plateau.
|
|
@@ -53,9 +53,10 @@ Reviewers and the Team Leader cite the failing line when they reject a change.
|
|
|
53
53
|
### Rule 3: Explicit Termination (`act("done", stdin="...")`), Interactive Inquiry (`done --ask`), & Pre-`done` Verification Gates
|
|
54
54
|
- Sessions do **not** terminate simply because the model emitted a text response without a tool call (small 9B models frequently "think out loud" before acting).
|
|
55
55
|
- 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` (handed back as `WAITING_INPUT`, `send` resumes it with a fresh budget). A bare `done` (no answer, no stdin) completes with the result `Done.` and still runs the `verify` gates; `done --ask` with no question is an error (#169).
|
|
56
|
+
- A bash command that needs approval to run outside the jail (#59) also pauses as `WAITING_INPUT` (`[APPROVAL NEEDED] <command>`, JSON `approval: {command, prefix}`); `send approve` runs it and resumes, any other reply refuses it.
|
|
56
57
|
- 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`. A reply of 80 or more characters may be the answer: the nudge says it was not delivered and to pass it to `done`, and a session that still ends `WAITING_INPUT` (stuck or out of steps) carries the last such reply in its result, marked "never confirmed with `done`" (at most 2,000 chars, #173). Lookups by built-in tools (`web search|fetch`, `mem search|recall`, `agent status|wait|read|query`) count as reads, not changes.
|
|
57
58
|
- After 6 consecutive read-only/verification steps following a change, the harness appends a `[NUDGE]` to that step's observation reminding the model to call `done` if the task is complete, repeated every 8 more quiet steps until the next change (0 static prompt tokens, #74, #132). Once per session, at 75% of `max_steps` (limits of 8 or more), a one-line `[NUDGE]` says how many steps are left (#133). 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 (#92) first get one `[WRAP UP]` turn (a read-only session is asked for its final answer, a session that already changed something is asked to call `done` with what is done and what is left, unless its `verify` gates or green test already finish it). Being stuck any way (the stall guard's warnings still unanswered after the wrap-up, or the same check command failing 3 times with an edit in between) restarts the session: one `restart` turn (event_type `restart`, at most 6000 chars: the files as the model left them, the last failing output, counts of what was tried; failed commands are not replayed) and the history continues from that turn alone. After 2 restarts, being stuck again hands the task back: the session ends `WAITING_INPUT` (exit code 2) with a `[STUCK ...]` summary as its result, so the caller can refine the request and `send` it. 0 static prompt tokens, no extra model call; `tether stop <id>` terminates a detached worker (#89). When that restart comes from the same check command failing 3 times, the files the model changed go back to how they started (a fresh attempt, Olausson et al. ICLR 2024) and the message names the failing tests instead of quoting the failed code; files it created stay. A run counts as near a pass when at most 3 tests fail (counted from the runner's summary line, so `| head` cannot hide failures) and more than twice that many pass: the harness keeps that version, and once 3 later runs fail to beat it, its files go back in place with one `[RESTORED]` line and the history stays (no restart turn, #173).
|
|
58
|
-
- 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 restart or hand back a session 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). With no gates, the model's own last passing test run (runner-recognised, nothing changed since, still green when re-run) is the implicit gate, at a stall and at the step cap; a passing run after a change also appends a one-line `[NUDGE]` to call `done` (#177).
|
|
59
|
+
- 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. Before the first model call every gate is pre-checked (`tether/gates.py`): an empty gate, one that cannot be parsed, or a program that does not exist (every command of a `&&`/`||`/`;`/`|` chain; relative paths are looked up in the workspace) ends the session the same way with no tokens spent (#82). When the stall guard is about to restart or hand back a session 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). With no gates, the model's own last passing test run (runner-recognised, nothing changed since, still green when re-run) is the implicit gate, at a stall and at the step cap; a passing run after a change also appends a one-line `[NUDGE]` to call `done` (#177).
|
|
59
60
|
|
|
60
61
|
### Rule 4: Progressive `--help` (JIT Schema Disclosure)
|
|
61
62
|
- The system prompt lists at most **1 line per enabled tool namespace** (`- <name>: <short summary>`, target **≤ 12 tokens per entry**).
|
|
@@ -66,8 +67,8 @@ Reviewers and the Team Leader cite the failing line when they reject a change.
|
|
|
66
67
|
- Auto-generated `--help` output must remain ultra-compact (**≤ 60 tokens** per command).
|
|
67
68
|
|
|
68
69
|
### Rule 5: Ephemeral Observation Folding
|
|
69
|
-
- Raw tool outputs (`bash` stdout/stderr, `web` page contents, `--help` outputs) are stored in full inside SQLite (`observations` table)
|
|
70
|
-
- After $K$ turns, the `ContextAssembler` deterministically folds old observations into a 1-line tombstone:
|
|
70
|
+
- Raw tool outputs (`bash` stdout/stderr, `web` page contents, `--help` outputs) are stored in full inside SQLite (`observations` table). While the history fits in half of `max_context_tokens` (the target) nothing is folded by age. Above it, by default (auto mode, #205) observations older than $K$ turns ($K = 2$) fold oldest-first only until the history is back under the target, so crossing it by a little folds a little. ANY unedited re-read of a file (not only a re-read of a folded observation) lengthens $K$ by one for the session, bounded by 8 (`AUTO_FOLD_MAX_K`). An explicit `--fold-after N` (or `fold_after` in providers.yaml/MCP) keeps the fixed rule: above the target every observation older than $N$ turns folds.
|
|
71
|
+
- After $K$ turns, the `ContextAssembler` deterministically folds old observations (when selected by the rule above) into a 1-line tombstone:
|
|
71
72
|
```text
|
|
72
73
|
[obs_3 folded: `web fetch https://...` (3,420 chars) | use `mem recall obs_3` to expand]
|
|
73
74
|
```
|
|
@@ -83,8 +84,11 @@ Reviewers and the Team Leader cite the failing line when they reject a change.
|
|
|
83
84
|
- All `edit` and `bash` paths are canonicalized (`Path.resolve()`) and confined to `workspace_root`.
|
|
84
85
|
- Sensitive files/directories (`.env*`, `.git/config`, `.git/hooks`, `~/.ssh`, `.tether/`) are deterministically protected from mutation or read exfiltration.
|
|
85
86
|
- `bash` subprocesses get an allowlisted environment only (secrets with any name are dropped; opt-in via `TETHER_ENV_PASSTHROUGH`). `web fetch` blocks loopback/private/metadata addresses (SSRF guard). `SandboxPolicy(network=False)` / `--no-network` adds `--unshare-net` (fails closed without `bwrap`; children can only narrow it).
|
|
86
|
-
- On Linux, `bash` automatically uses **`bwrap` (Bubblewrap) / `unshare`** when available (read-only `/`, hidden `/home` and `/var/tmp` tmpfs (existing `PATH` dirs under them, e.g. `~/.local/bin`, are re-bound read-only so `uv`/`node` still run, #76), `/run` unix sockets such as `docker.sock` masked, read-write bind on `workspace_root` with secret-looking files at any depth (`.env*`, `*.pem`, `*.key`, ...), `.tether/`, `.git/config` and `.git/hooks` masked at the mount level, `PR_SET_NO_NEW_PRIVS`), falling back to a strict deterministic command allowlist/denylist (path, `..` and `.env` text rules run only in this fallback, the mounts enforce them under `bwrap`, #125; `.env.example`-style templates are not secrets) (`rm -rf /`, `sudo`, `dd`, `mkfs`, `chmod 777`, etc. blocked).
|
|
87
|
+
- On Linux, `bash` automatically uses **`bwrap` (Bubblewrap) / `unshare`** when available (read-only `/`, hidden `/home` and `/var/tmp` tmpfs (existing `PATH` dirs under them, e.g. `~/.local/bin`, are re-bound read-only so `uv`/`node` still run, #76), `/run` unix sockets such as `docker.sock` masked, read-write bind on `workspace_root` with secret-looking files at any depth (`.env*`, `*.pem`, `*.key`, ...), `.tether/`, `.git/config` and `.git/hooks` masked at the mount level, `PR_SET_NO_NEW_PRIVS`), falling back to a strict deterministic command allowlist/denylist (path, `..` and `.env` text rules run only in this fallback, the mounts enforce them under `bwrap`, #125; `.env.example`-style templates are not secrets) (`rm -rf /`, `sudo`, `dd`, `mkfs`, `chmod 777`, etc. blocked). Without `bwrap`, write sessions fail closed unless the caller opts out (`--allow-unsandboxed`, `allow_unsandboxed=True`, MCP `allow_unsandboxed`, or `TETHER_SANDBOX=container`); children inherit the opt-out and can only drop it.
|
|
88
|
+
- Optional rlimits on `bash` (and `--verify` gates): `SandboxPolicy(limits=)` / `--limit cpu=S mem=MB procs=N fsize=MB` / MCP `limits`; off by default, Linux only, validated by `parse_limits` (unknown key, non-integer, <1 or too large is a ValueError); a hit appends one `[limit hit: ...] Instead:` line; children can only lower limits.
|
|
87
89
|
- Sub-agents spawned via `agent spawn` strictly inherit or narrow their parent session's `SandboxPolicy` (no privilege escalation).
|
|
90
|
+
- Commands that run outside the jail come only from the allowlist (`--allow`, `.tether/allow.json`, MCP `allow`, #59). Matching is by whole tokens, with no shell (metacharacters are refused), never in read-only or `--no-network` sessions, and git options such as `-c`/`-C`/`--receive-pack` stay jailed. MCP `allow` is an exception to "callers can only narrow", like `allow_unsandboxed`.
|
|
91
|
+
- Escalation (#59): a command whose prefix is in `SandboxPolicy.escalate` (default `git push|pull|fetch|clone|worktree|config`, `gh`, `docker`; replaceable) and not allowlisted pauses the session as `WAITING_INPUT` with `[APPROVAL NEEDED]`. Only the caller approves (`Harness.send`, `tether send <id> approve`, `tether allow`, MCP `tether_send`), never a model's `agent send`; the approved prefix (2 words for git, else 1) goes to the project's `.tether/allow.json`. Never offered for metacharacters, unsafe git options, read-only or `--no-network` sessions.
|
|
88
92
|
|
|
89
93
|
### Rule 7: Zero Framework Bloat
|
|
90
94
|
- **Do NOT add heavy dependencies** like `langchain`, `llamaindex`, `crewai`, `litellm`, `chromadb`, or vendor SDKs (`openai`, `anthropic`).
|
|
@@ -97,7 +101,7 @@ Reviewers and the Team Leader cite the failing line when they reject a change.
|
|
|
97
101
|
```text
|
|
98
102
|
tether/
|
|
99
103
|
├── __init__.py # Public SDK exports: Harness, Pipeline, Session, Skill, SandboxPolicy, tool
|
|
100
|
-
├── cli.py # Headless-capable CLI: `tether run|pipeline|status|read|query|send|list|wait|stop|stats|retro|events|misses|mcp` (`--json` + documented exit codes)
|
|
104
|
+
├── cli.py # Headless-capable CLI: `tether run|pipeline|status|read|query|send|list|wait|stop|stats|retro|diff|events|misses|mcp|export|doctor|replay` (`--json` + documented exit codes)
|
|
101
105
|
├── harness.py # Control Plane: bounded agent loop, pipelines, SQLite inbox, worker spawn
|
|
102
106
|
├── pipeline.py # Workflow Plane: declarative multi-stage YAML pipelines & model routing
|
|
103
107
|
├── api.py # Agent-facing JSON session view, exit codes, events (`--json`)
|
|
@@ -110,6 +114,12 @@ tether/
|
|
|
110
114
|
├── decorator.py # `@tool` introspection: converts Python type hints + docstrings to CLI specs
|
|
111
115
|
├── context.py # State Plane: ContextAssembler, EphemeralFolder, token estimator
|
|
112
116
|
├── trace.py # Runtime trace of the smallest failing test (calls, arguments, results)
|
|
117
|
+
├── gates.py # Pre-check of `--verify` gates before the first model call (empty, unparsable or missing program -> `gate_error`)
|
|
118
|
+
├── diff.py # `tether diff`: what a session changed, from the `file_snapshots` table
|
|
119
|
+
├── export.py # `tether export`: a session as a Markdown transcript
|
|
120
|
+
├── doctor.py # `tether doctor`: checks Ollama, model, bwrap, providers.yaml and the DB, and says what to fix
|
|
121
|
+
├── progress.py # Live progress on stderr for a foreground `run` (dots, `--verbose`)
|
|
122
|
+
├── worktree.py # Git worktrees for `tether run --worktree` (create, setup, commit; only harness code runs git)
|
|
113
123
|
├── skills.py # Skill loader: Markdown + YAML frontmatter + optional sibling `tools.py` (its tools must be listed in `tools:`)
|
|
114
124
|
├── providers/
|
|
115
125
|
│ ├── base.py # Abstract async ModelProvider protocol (`generate(messages, tools)`)
|
|
@@ -131,17 +141,21 @@ tether/
|
|
|
131
141
|
- Default DB path: `.tether/tether.db` (configurable via `TETHER_DB` or `Harness(db_path=...)`) with `PRAGMA journal_mode=WAL;`.
|
|
132
142
|
- Tables:
|
|
133
143
|
- `sessions`: `id`, `parent_id`, `status` (`PENDING`, `RUNNING`, `WAITING_INPUT`, `COMPLETED`, `FAILED`), `model`, `skill`, `tools_json`, `sandbox_json`, `goal`, `result`, `step_count`, `max_steps`, `peak_tokens`, `total_prompt_tokens`, `total_completion_tokens`, `pid`, `created_at`, `updated_at`.
|
|
134
|
-
- `turns`: `id`, `session_id`, `step`, `role`, `content`, `tool_cmd`, `tool_stdin`, `obs_id`, `prompt_tokens`, `completion_tokens`, `duration_ms`, `event_type` (`ok` | `nudge` | `loop_stall` | `cycle_stall` | `syntax_error` | `tool_error` | `gate_fail` | `gate_error` | `completed` | `restart`), `created_at`.
|
|
144
|
+
- `turns`: `id`, `session_id`, `step`, `role`, `content`, `tool_cmd`, `tool_stdin`, `obs_id`, `prompt_tokens`, `completion_tokens`, `duration_ms`, `event_type` (`ok` | `nudge` | `loop_stall` | `cycle_stall` | `syntax_error` | `tool_error` | `gate_fail` | `gate_error` | `sandbox_violation` | `completed` | `restart`), `rule` (refused rule for `sandbox_violation`, else `''`), `created_at`.
|
|
135
145
|
- `observations`: `id`, `session_id`, `step`, `cmd`, `raw_output`, `summary`, `char_len`.
|
|
136
146
|
- `chunks_fts` *(SQLite FTS5 Virtual Table)*: `session_id`, `source_type` (`result` | `observation` | `pin`), `source_id`, `chunk_index`, `content` — automatically indexes ~150-token paragraph/heading chunks of all observations, pins, and final `done` deliverables for sub-millisecond BM25 RAG (including cross-session project search via `mem search --project <query>`).
|
|
137
147
|
- `pins`: `session_id`, `key`, `value` (supports session-scoped working memory and workspace-wide semantic facts under `GLOBAL_SESSION_ID = "__project__"` via `mem pin --project <key> <val>`).
|
|
138
148
|
- `inbox`: `id`, `session_id`, `message`, `consumed` (`0` or `1`), `created_at`.
|
|
139
|
-
- **Agent-to-Agent Interface (`api.py`, `mcp.py`):** `--json` outputs (schema `v: 1`), exit codes `0` completed / `1` usage / `2` waiting (also stuck or out of steps, handed back) / `3` gate / `5` worker error, failure text on stderr, a foreground `run` writes `session ses_<id>` to stderr at creation (#90), `--result-file`, `--output-schema`, `tether events`. `tether mcp` serves the same JSON over stdio and requires the optional `tether-sdk[mcp]` extra (official `mcp` SDK, imported lazily so core stays dependency-light; Rule 7 allows it as an extra only); its sandbox is always rooted at `--workspace` and callers can only narrow it. Details: `docs/reference/agent-interface.md`.
|
|
149
|
+
- **Agent-to-Agent Interface (`api.py`, `mcp.py`):** `--json` outputs (schema `v: 1`), exit codes `0` completed / `1` usage / `2` waiting (also stuck or out of steps, handed back) / `3` gate / `5` worker error / `6` wait timed out, failure text on stderr, a foreground `run` writes `session ses_<id>` to stderr at creation (#90), `--result-file`, `--output-schema`, `tether events`. `tether mcp` serves the same JSON over stdio and requires the optional `tether-sdk[mcp]` extra (official `mcp` SDK, imported lazily so core stays dependency-light; Rule 7 allows it as an extra only); its sandbox is always rooted at `--workspace` and callers can only narrow it (exceptions: `allow_unsandboxed`, see #124; `allow`, the command-prefix allowlist that runs outside the jail, see #59). Details: `docs/reference/agent-interface.md`.
|
|
140
150
|
- **Telemetry & Failure-Mode Inspector (`tether stats`):**
|
|
141
151
|
- `tether stats [--model <m>] [--skill <s>] [--limit <n>]` aggregates per-model pass rates, cumulative token economy (`total_prompt_tokens + total_completion_tokens`), step latency, and self-healing ACI friction (`nudge`, `loop_stall`, `cycle_stall`, `syntax_error`, `tool_error`, `gate_fail`, `gate_error`), plus a top-offenders breakdown by tool command (a leading `cd <dir> &&` is skipped; no command is `(no command)`, #79).
|
|
142
152
|
- **Post-Mortem Distillation (`tether retro <session_id> [--apply] [--model <m>]`):** offline only (never mutates a live prompt). `store.extract_friction_trace` pulls `inbox` corrections and `loop_stall`/`cycle_stall`/`syntax_error`/`tool_error`/`gate_fail`/`gate_error` turns (+ the fixing command); one bounded LLM pass yields ≤2 rules (≤30 tokens each, deduped against existing rules). Prints a unified diff of `SKILL.md`; `--apply` appends under `## Learned Rules` (or pins to project memory when no skill was used).
|
|
143
153
|
- **Headless Worker & Live `send` Contract:**
|
|
144
154
|
- `max_steps` is the absolute session ceiling. `send` on a session that used its steps extends it to `step_count + <Harness budget>` (a session paused earlier keeps its remaining budget), so `step_count` never exceeds `max_steps` (#75).
|
|
155
|
+
- `max_tokens` (CLI `--max-tokens`, `Harness(max_tokens=)`, MCP `max_tokens`) is an optional budget of prompt + completion tokens, checked after each step: when reached the session ends `WAITING_INPUT` (exit 2) with `[TOKEN BUDGET] used N of M tokens`, and `send` grants the same amount again. No budget, no change.
|
|
156
|
+
- `tether run --worktree [--worktree-from <ref>] [--setup <cmd>]` runs the session in a git worktree on branch `tether/<id>` and commits the result there when it stops (`worktree.py`; the model never gets git). The session JSON carries `worktree`, `branch` and `commit`; the MCP `tether_run` accepts `worktree` (no setup command) and `workspace` (an absolute directory inside the server's workspace, anything else is refused, #242).
|
|
157
|
+
- `file_snapshots(session_id, path, content)` keeps the first bytes the session saw of each file its `edit write|replace|append`, redirect or `tee` commands change (NULL for a file it created), and of each existing file named as an argument on a command's first line (`sed -i f`, `perl -i f`, `mv new f`, `cp new f`, `python3 -c` with a quoted path), at most 32 per command and 256 kB each. `tether diff` compares them with the current files, and names files whose mtime falls in the session window with no snapshot as changed by commands it could not track (#280). A file deleted by an opaque command leaves no mtime, so it is not detected.
|
|
158
|
+
- `tether replay <id>` re-runs a session's goal in the current directory with its saved tools, skill, sandbox, output schema and params (not its gates) and prints both runs side by side; `tether export <id>` prints a session as Markdown; `tether doctor` checks the setup and exits 1 when a check fails.
|
|
145
159
|
- `SQLiteStore.get_session`/`list_sessions` reap dead workers: a `RUNNING` session whose stored `pid` is gone becomes `FAILED` with `Worker died (pid N) before finishing` (#85).
|
|
146
160
|
- `tether run --headless ...` creates the session row in SQLite and spawns `python -m tether.cli _worker <session_id>` via `subprocess.Popen(..., start_new_session=True)`, printing `<session_id>` immediately to stdout.
|
|
147
161
|
- Before each step, the running worker queries `inbox WHERE session_id = ? AND consumed = 0` and injects any new user/parent messages into the conversation.
|
|
@@ -157,6 +171,7 @@ tether/
|
|
|
157
171
|
- `agent send <id> <message>` $\rightarrow$ queues a message into the child session's inbox (or resumes it if completed).
|
|
158
172
|
- **Deterministic Multi-Stage Pipelines (`tether pipeline <pipeline.yaml> "<goal>"` & `Harness.run_pipeline`):**
|
|
159
173
|
- Each stage runs in its own isolated `SessionRecord` (`parent_id = pipeline_session_id`) with heterogeneous model routing (`stage.model`), stage-level `verify` gates, and strict sandbox monotonicity (`root_sandbox.attenuate(stage.sandbox)`).
|
|
174
|
+
- Resume (`tether pipeline --resume <id>`) re-runs from the first stage not `COMPLETED` and reuses earlier completed stages; the manifest path is stored under the `pipeline_source` sandbox_config key.
|
|
160
175
|
- Downstream stages receive a compact ~50-token digest of upstream stages plus automatic `agent query` / `agent read` access to upstream `chunks_fts` deliverables.
|
|
161
176
|
|
|
162
177
|
#### C. Skills System (`skills.py`)
|
|
@@ -187,7 +202,7 @@ tether/
|
|
|
187
202
|
|
|
188
203
|
---
|
|
189
204
|
- **Timeout/retry (#70):** `providers.yaml` `timeout` applies to every kind; HTTP providers retry timeouts/network errors/429/5xx twice (`post_with_retry` in `providers/base.py`) then raise `ProviderError` naming provider, model and timeout; each attempt is also bounded by a total `asyncio.wait_for(timeout)` deadline (#88).
|
|
190
|
-
- **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 32768 / 2 (32k keeps `qwen3.5:9b` fully on an 8 GB GPU and passed 7 of 18 wall-capped Polyglot runs vs 2 at 8k, #173).
|
|
205
|
+
- **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 32768 / auto (K=2, #205); any explicit value = fixed rule (32k keeps `qwen3.5:9b` fully on an 8 GB GPU and passed 7 of 18 wall-capped Polyglot runs vs 2 at 8k, #173).
|
|
191
206
|
- **Ollama thinking (#183, #173):** `think=false` when under 3072 tokens of `num_ctx` are free; a thinking turn gets `num_predict` 2560 and, if cut there with no command or with a half tool call Ollama rejects (HTTP 500), is asked once more with `think=false` (tokens summed; a second rejection is the resend hint). A caller's `think`/`num_predict` param wins.
|
|
192
207
|
|
|
193
208
|
## 4. Development Workflow & Commands
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tether-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
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
|
|
@@ -45,15 +45,15 @@ Tether is built the other way round:
|
|
|
45
45
|
|
|
46
46
|
## How well does it work
|
|
47
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
|
|
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, default config, 2 trials each (68 runs). The context was 8192 tokens up to v0.2.15 and is 32768 (the default since v0.2.16) for v0.2.18:
|
|
49
49
|
|
|
50
|
-
| | before v0.2.10 | v0.2.10 | v0.2.13 | v0.2.15 |
|
|
51
|
-
|
|
52
|
-
| Passes | 35 of 68 (51.5%) | 35 of 68 (51.5%) | 37 of 68 (54.4%) | 40 of 68 (58.8%) |
|
|
53
|
-
| Mean score | 0.718 | 0.763 | 0.783 | 0.756 |
|
|
54
|
-
| Runs killed by the wall cap | 11 | 14 | 13 | 12 |
|
|
50
|
+
| | before v0.2.10 | v0.2.10 | v0.2.13 | v0.2.15 | v0.2.18 |
|
|
51
|
+
|---|---|---|---|---|---|
|
|
52
|
+
| Passes | 35 of 68 (51.5%) | 35 of 68 (51.5%) | 37 of 68 (54.4%) | 40 of 68 (58.8%) | 45 of 68 (66.2%) |
|
|
53
|
+
| Mean score | 0.718 | 0.763 | 0.783 | 0.756 | 0.830 |
|
|
54
|
+
| Runs killed by the wall cap | 11 | 14 | 13 | 12 | 17 |
|
|
55
55
|
|
|
56
|
-
The target is 74% (50 of 68) by v0.4.0, so this is the number every change is ranked by. It is what qwen3.5:9b can already do:
|
|
56
|
+
The target is 74% (50 of 68) by v0.4.0, so this is the number every change is ranked by. It is what qwen3.5:9b can already do: 27 of the 34 exercises have passed at least once from scratch, so the work is making each of them pass every run. For comparison, the best published harness for this model, [little-coder](https://github.com/itayinbarr/little-coder), reports 52.9% on the same 34 Python exercises (its own setup). v0.2.18 passes 5 more runs than v0.2.15, and this time the paired score delta clears the noise: +0.074, 95% interval +0.003 to +0.147 (pass flips +7/-2). It comes with the larger default context, so the gain belongs to both. connect and hangman passed for the first time; more runs now end at the wall cap (17, against 12) instead of running out of steps. One run is noisy (about 0.1), so read the pass count and the failure breakdown, not one decimal. The second yardstick is the [Coding Ladder](https://igormcsouza.github.io/tether/ladder/): 24 tasks, 6 tiers of difficulty times write, bugfix, plan and review, which shows how far up a model climbs and what stops it. Method, how to reproduce both and fair-comparison rules: [Benchmarks](https://igormcsouza.github.io/tether/benchmarks/).
|
|
57
57
|
|
|
58
58
|
## How it works
|
|
59
59
|
|
|
@@ -71,7 +71,7 @@ flowchart LR
|
|
|
71
71
|
Loop <-->|"messages"| Model["LLM<br/>ollama · openai · anthropic"]
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
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.
|
|
74
|
+
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. To re-run a finished session's goal and compare the two runs, use `tether replay <session-id>`.
|
|
75
75
|
|
|
76
76
|
## Why this and not that
|
|
77
77
|
|
|
@@ -92,8 +92,14 @@ The research behind it: Maia et al. (2026) on probabilistic core and determinist
|
|
|
92
92
|
uv tool install tether-sdk # or: pip install tether-sdk
|
|
93
93
|
ollama pull qwen3.5:9b # any model works; see the providers page
|
|
94
94
|
tether run --model ollama/qwen3.5:9b --tools bash,edit "create hello.txt containing hi"
|
|
95
|
+
tether run --worktree --setup "uv sync" --verify "uv run pytest -q" "fix the failing test"
|
|
95
96
|
```
|
|
96
97
|
|
|
98
|
+
On Linux install `bubblewrap` for write sessions; on macOS/Windows pass `--allow-unsandboxed`, in Docker set `TETHER_SANDBOX=container`.
|
|
99
|
+
|
|
100
|
+
An empty Ollama reply is resent once, unchanged; if three empty replies come in a row, the session stops and suggests `think=false`. See the [providers page](https://igormcsouza.github.io/tether/reference/providers/).
|
|
101
|
+
Resume a failed pipeline from its first unfinished stage with `tether pipeline --resume <pipeline_session_id>`.
|
|
102
|
+
|
|
97
103
|
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/).
|
|
98
104
|
|
|
99
105
|
## Build on it
|
|
@@ -24,15 +24,15 @@ Tether is built the other way round:
|
|
|
24
24
|
|
|
25
25
|
## How well does it work
|
|
26
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
|
|
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, default config, 2 trials each (68 runs). The context was 8192 tokens up to v0.2.15 and is 32768 (the default since v0.2.16) for v0.2.18:
|
|
28
28
|
|
|
29
|
-
| | before v0.2.10 | v0.2.10 | v0.2.13 | v0.2.15 |
|
|
30
|
-
|
|
31
|
-
| Passes | 35 of 68 (51.5%) | 35 of 68 (51.5%) | 37 of 68 (54.4%) | 40 of 68 (58.8%) |
|
|
32
|
-
| Mean score | 0.718 | 0.763 | 0.783 | 0.756 |
|
|
33
|
-
| Runs killed by the wall cap | 11 | 14 | 13 | 12 |
|
|
29
|
+
| | before v0.2.10 | v0.2.10 | v0.2.13 | v0.2.15 | v0.2.18 |
|
|
30
|
+
|---|---|---|---|---|---|
|
|
31
|
+
| Passes | 35 of 68 (51.5%) | 35 of 68 (51.5%) | 37 of 68 (54.4%) | 40 of 68 (58.8%) | 45 of 68 (66.2%) |
|
|
32
|
+
| Mean score | 0.718 | 0.763 | 0.783 | 0.756 | 0.830 |
|
|
33
|
+
| Runs killed by the wall cap | 11 | 14 | 13 | 12 | 17 |
|
|
34
34
|
|
|
35
|
-
The target is 74% (50 of 68) by v0.4.0, so this is the number every change is ranked by. It is what qwen3.5:9b can already do:
|
|
35
|
+
The target is 74% (50 of 68) by v0.4.0, so this is the number every change is ranked by. It is what qwen3.5:9b can already do: 27 of the 34 exercises have passed at least once from scratch, so the work is making each of them pass every run. For comparison, the best published harness for this model, [little-coder](https://github.com/itayinbarr/little-coder), reports 52.9% on the same 34 Python exercises (its own setup). v0.2.18 passes 5 more runs than v0.2.15, and this time the paired score delta clears the noise: +0.074, 95% interval +0.003 to +0.147 (pass flips +7/-2). It comes with the larger default context, so the gain belongs to both. connect and hangman passed for the first time; more runs now end at the wall cap (17, against 12) instead of running out of steps. One run is noisy (about 0.1), so read the pass count and the failure breakdown, not one decimal. The second yardstick is the [Coding Ladder](https://igormcsouza.github.io/tether/ladder/): 24 tasks, 6 tiers of difficulty times write, bugfix, plan and review, which shows how far up a model climbs and what stops it. Method, how to reproduce both and fair-comparison rules: [Benchmarks](https://igormcsouza.github.io/tether/benchmarks/).
|
|
36
36
|
|
|
37
37
|
## How it works
|
|
38
38
|
|
|
@@ -50,7 +50,7 @@ flowchart LR
|
|
|
50
50
|
Loop <-->|"messages"| Model["LLM<br/>ollama · openai · anthropic"]
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
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.
|
|
53
|
+
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. To re-run a finished session's goal and compare the two runs, use `tether replay <session-id>`.
|
|
54
54
|
|
|
55
55
|
## Why this and not that
|
|
56
56
|
|
|
@@ -71,8 +71,14 @@ The research behind it: Maia et al. (2026) on probabilistic core and determinist
|
|
|
71
71
|
uv tool install tether-sdk # or: pip install tether-sdk
|
|
72
72
|
ollama pull qwen3.5:9b # any model works; see the providers page
|
|
73
73
|
tether run --model ollama/qwen3.5:9b --tools bash,edit "create hello.txt containing hi"
|
|
74
|
+
tether run --worktree --setup "uv sync" --verify "uv run pytest -q" "fix the failing test"
|
|
74
75
|
```
|
|
75
76
|
|
|
77
|
+
On Linux install `bubblewrap` for write sessions; on macOS/Windows pass `--allow-unsandboxed`, in Docker set `TETHER_SANDBOX=container`.
|
|
78
|
+
|
|
79
|
+
An empty Ollama reply is resent once, unchanged; if three empty replies come in a row, the session stops and suggests `think=false`. See the [providers page](https://igormcsouza.github.io/tether/reference/providers/).
|
|
80
|
+
Resume a failed pipeline from its first unfinished stage with `tether pipeline --resume <pipeline_session_id>`.
|
|
81
|
+
|
|
76
82
|
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/).
|
|
77
83
|
|
|
78
84
|
## Build on it
|
|
@@ -5,3 +5,4 @@
|
|
|
5
5
|
{"date": "2026-11-03", "version": "v0.2.13", "model": "ollama/qwen3.5:9b", "runner": "tether", "tasks": "polyglot-34", "trials": 2, "context": 8192, "note": "v0.2.13 full run, --poly-verify, released wheel; flat vs v0.2.10 (paired score delta +0.020, CI -0.060..+0.101, pass flips +7/-5, p=0.77)", "runs": 68, "passed": 37, "pass_rate": 0.544, "mean_score": 0.783, "fail_max_steps": 18, "fail_wall_cap": 13, "mean_steps": 17.9, "mean_prompt_tokens": 77190, "mean_wall_s": 287}
|
|
6
6
|
{"date": "2026-11-04", "version": "v0.2.14", "model": "haiku (Claude CLI provider)", "runner": "tether", "tasks": "polyglot-34", "trials": 2, "context": 32000, "note": "v0.2.14 released wheel, full run, --poly-verify, Claude Haiku; every exercise passed in both trials, including the 11 that qwen3.5:9b never passes; no restart or hand-back fired (max 6 steps)", "runs": 68, "passed": 68, "pass_rate": 1.0, "mean_score": 1.0, "fail_max_steps": 0, "fail_wall_cap": 0, "mean_steps": 3.6, "mean_prompt_tokens": 17325, "mean_wall_s": 27}
|
|
7
7
|
{"date": "2026-11-06", "version": "v0.2.15", "model": "ollama/qwen3.5:9b", "runner": "tether", "tasks": "polyglot-34", "trials": 2, "context": 8192, "note": "v0.2.15 release candidate (main + #231-#233, same code as the tag), full run, --poly-verify; +3 vs v0.2.13 but inside the noise (pass flips +7/-4, p=0.55; paired score delta -0.026, CI -0.093..+0.041); 30 min less wall time", "runs": 68, "passed": 40, "pass_rate": 0.588, "mean_score": 0.756, "fail_max_steps": 16, "fail_wall_cap": 12, "mean_steps": 17.6, "mean_prompt_tokens": 75366, "mean_wall_s": 260}
|
|
8
|
+
{"date": "2026-11-08", "version": "v0.2.18", "model": "ollama/qwen3.5:9b", "runner": "tether", "tasks": "polyglot-34", "trials": 2, "context": 32768, "note": "v0.2.18 released wheel, full run, --poly-verify, default config (first full run at the 32k default; v0.2.15 ran at 8k, so context and the v0.2.16-v0.2.18 changes are confounded); vs v0.2.15: paired score delta +0.074, CI +0.003..+0.147 (BETTER), pass flips +7/-2, p=0.18; connect and hangman pass for the first time; wall cap 17 vs 12, mean wall 302 s vs 260 s", "runs": 68, "passed": 45, "pass_rate": 0.662, "mean_score": 0.830, "fail_max_steps": 6, "fail_wall_cap": 17, "mean_steps": 11.4, "mean_prompt_tokens": 64480, "mean_wall_s": 302}
|
|
@@ -190,3 +190,27 @@
|
|
|
190
190
|
{"model": "qwen2.5-coder:7b", "harness": "naive", "label": "naive", "task": "review-4", "kind": "review", "tier": 4, "attempt": 0, "think": false, "ctx": 32768, "wall_s": 10, "score": 0.0, "pass": false, "steps": 1, "delivered_steps": null, "prompt_tokens": null, "completion_tokens": null, "run_status": "ok", "session_status": "n/a"}
|
|
191
191
|
{"model": "qwen2.5-coder:7b", "harness": "naive", "label": "naive", "task": "review-5", "kind": "review", "tier": 5, "attempt": 0, "think": false, "ctx": 32768, "wall_s": 10, "score": 0.0, "pass": false, "steps": 1, "delivered_steps": null, "prompt_tokens": null, "completion_tokens": null, "run_status": "ok", "session_status": "n/a"}
|
|
192
192
|
{"model": "qwen2.5-coder:7b", "harness": "naive", "label": "naive", "task": "review-6", "kind": "review", "tier": 6, "attempt": 0, "think": false, "ctx": 32768, "wall_s": 10, "score": 0.0, "pass": false, "steps": 1, "delivered_steps": null, "prompt_tokens": null, "completion_tokens": null, "run_status": "ok", "session_status": "n/a"}
|
|
193
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "write-1", "kind": "write", "tier": 1, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 32, "score": 1.0, "pass": true, "steps": 7, "delivered_steps": 7, "prompt_tokens": 6049, "completion_tokens": 442, "run_status": "delivered", "session_status": "RUNNING"}
|
|
194
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "write-2", "kind": "write", "tier": 2, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 22, "score": 1.0, "pass": true, "steps": 8, "delivered_steps": 8, "prompt_tokens": 7470, "completion_tokens": 635, "run_status": "delivered", "session_status": "RUNNING"}
|
|
195
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "write-3", "kind": "write", "tier": 3, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 22, "score": 1.0, "pass": true, "steps": 7, "delivered_steps": 7, "prompt_tokens": 7100, "completion_tokens": 592, "run_status": "delivered", "session_status": "RUNNING"}
|
|
196
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "write-4", "kind": "write", "tier": 4, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 70, "score": 0.667, "pass": false, "steps": 14, "delivered_steps": null, "prompt_tokens": 28961, "completion_tokens": 2162, "run_status": "ok", "session_status": "COMPLETED"}
|
|
197
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "write-5", "kind": "write", "tier": 5, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 42, "score": 1.0, "pass": true, "steps": 14, "delivered_steps": 14, "prompt_tokens": 25956, "completion_tokens": 1222, "run_status": "delivered", "session_status": "RUNNING"}
|
|
198
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "write-6", "kind": "write", "tier": 6, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 121, "score": 0.833, "pass": false, "steps": 28, "delivered_steps": null, "prompt_tokens": 102580, "completion_tokens": 3917, "run_status": "ok", "session_status": "COMPLETED"}
|
|
199
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "bugfix-1", "kind": "bugfix", "tier": 1, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 10, "score": 1.0, "pass": true, "steps": 4, "delivered_steps": null, "prompt_tokens": 2534, "completion_tokens": 224, "run_status": "ok", "session_status": "COMPLETED"}
|
|
200
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "bugfix-2", "kind": "bugfix", "tier": 2, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 22, "score": 1.0, "pass": true, "steps": 6, "delivered_steps": 6, "prompt_tokens": 5409, "completion_tokens": 527, "run_status": "delivered", "session_status": "RUNNING"}
|
|
201
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "bugfix-3", "kind": "bugfix", "tier": 3, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 82, "score": 1.0, "pass": true, "steps": 21, "delivered_steps": 21, "prompt_tokens": 51765, "completion_tokens": 2587, "run_status": "delivered", "session_status": "RUNNING"}
|
|
202
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "bugfix-4", "kind": "bugfix", "tier": 4, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 80, "score": 0.667, "pass": false, "steps": 27, "delivered_steps": null, "prompt_tokens": 82330, "completion_tokens": 2303, "run_status": "ok", "session_status": "COMPLETED"}
|
|
203
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "bugfix-5", "kind": "bugfix", "tier": 5, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 22, "score": 1.0, "pass": true, "steps": 9, "delivered_steps": 9, "prompt_tokens": 10979, "completion_tokens": 581, "run_status": "delivered", "session_status": "RUNNING"}
|
|
204
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "bugfix-6", "kind": "bugfix", "tier": 6, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 72, "score": 1.0, "pass": true, "steps": 29, "delivered_steps": 29, "prompt_tokens": 91364, "completion_tokens": 2098, "run_status": "delivered", "session_status": "RUNNING"}
|
|
205
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "plan-1", "kind": "plan", "tier": 1, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 22, "score": 1.0, "pass": true, "steps": 6, "delivered_steps": 6, "prompt_tokens": 4866, "completion_tokens": 619, "run_status": "delivered", "session_status": "RUNNING"}
|
|
206
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "plan-2", "kind": "plan", "tier": 2, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 42, "score": 1.0, "pass": true, "steps": 12, "delivered_steps": 12, "prompt_tokens": 18763, "completion_tokens": 1183, "run_status": "delivered", "session_status": "RUNNING"}
|
|
207
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "plan-3", "kind": "plan", "tier": 3, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 50, "score": 0.857, "pass": true, "steps": 17, "delivered_steps": 17, "prompt_tokens": 34656, "completion_tokens": 1524, "run_status": "delivered", "session_status": "COMPLETED"}
|
|
208
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "plan-4", "kind": "plan", "tier": 4, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 62, "score": 0.9, "pass": true, "steps": 16, "delivered_steps": 16, "prompt_tokens": 30603, "completion_tokens": 1920, "run_status": "delivered", "session_status": "RUNNING"}
|
|
209
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "plan-5", "kind": "plan", "tier": 5, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 92, "score": 0.9, "pass": true, "steps": 28, "delivered_steps": 28, "prompt_tokens": 81275, "completion_tokens": 2832, "run_status": "delivered", "session_status": "RUNNING"}
|
|
210
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "plan-6", "kind": "plan", "tier": 6, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 72, "score": 0.7, "pass": true, "steps": 9, "delivered_steps": 9, "prompt_tokens": 13031, "completion_tokens": 2343, "run_status": "delivered", "session_status": "RUNNING"}
|
|
211
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "review-1", "kind": "review", "tier": 1, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 10, "score": 1.0, "pass": true, "steps": 5, "delivered_steps": null, "prompt_tokens": 3395, "completion_tokens": 237, "run_status": "ok", "session_status": "COMPLETED"}
|
|
212
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "review-2", "kind": "review", "tier": 2, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 20, "score": 0.85, "pass": true, "steps": 6, "delivered_steps": 6, "prompt_tokens": 4446, "completion_tokens": 385, "run_status": "delivered", "session_status": "COMPLETED"}
|
|
213
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "review-3", "kind": "review", "tier": 3, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 20, "score": 0.667, "pass": false, "steps": 4, "delivered_steps": null, "prompt_tokens": 2862, "completion_tokens": 401, "run_status": "ok", "session_status": "COMPLETED"}
|
|
214
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "review-4", "kind": "review", "tier": 4, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 80, "score": 0.333, "pass": false, "steps": 30, "delivered_steps": null, "prompt_tokens": 48719, "completion_tokens": 2545, "run_status": "ok", "session_status": "WAITING_INPUT"}
|
|
215
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "review-5", "kind": "review", "tier": 5, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 30, "score": 0.75, "pass": true, "steps": 6, "delivered_steps": 6, "prompt_tokens": 6757, "completion_tokens": 962, "run_status": "delivered", "session_status": "COMPLETED"}
|
|
216
|
+
{"model": "qwen3.5:9b", "harness": "tether", "label": "tether-v0218", "task": "review-6", "kind": "review", "tier": 6, "attempt": 0, "think": false, "ctx": 40960, "wall_s": 32, "score": 0.833, "pass": true, "steps": 6, "delivered_steps": 6, "prompt_tokens": 5474, "completion_tokens": 842, "run_status": "delivered", "session_status": "RUNNING"}
|
|
@@ -14,13 +14,15 @@ The **Polyglot-Python** set: the 34 Python exercises of Aider's [polyglot benchm
|
|
|
14
14
|
|
|
15
15
|
## Latest result
|
|
16
16
|
|
|
17
|
-
`qwen3.5:9b` through Ollama
|
|
17
|
+
`qwen3.5:9b` through Ollama, default config, all 34 exercises, 2 trials per exercise (68 runs). `num_ctx` was 8192 up to v0.2.15 and 32768 (the default since v0.2.16) for v0.2.18:
|
|
18
18
|
|
|
19
|
-
| | before v0.2.10 | v0.2.10 | v0.2.13 | v0.2.15 |
|
|
20
|
-
|
|
21
|
-
| Passes | 35 of 68 (51.5%) | 35 of 68 (51.5%) | 37 of 68 (54.4%) | 40 of 68 (58.8%) |
|
|
22
|
-
| Mean score | 0.718 | 0.763 | 0.783 | 0.756 |
|
|
23
|
-
| Runs killed by the wall cap | 11 | 14 | 13 | 12 |
|
|
19
|
+
| | before v0.2.10 | v0.2.10 | v0.2.13 | v0.2.15 | v0.2.18 |
|
|
20
|
+
|---|---|---|---|---|---|
|
|
21
|
+
| Passes | 35 of 68 (51.5%) | 35 of 68 (51.5%) | 37 of 68 (54.4%) | 40 of 68 (58.8%) | 45 of 68 (66.2%) |
|
|
22
|
+
| Mean score | 0.718 | 0.763 | 0.783 | 0.756 | 0.830 |
|
|
23
|
+
| Runs killed by the wall cap | 11 | 14 | 13 | 12 | 17 |
|
|
24
|
+
|
|
25
|
+
**v0.2.18** (the repair loop around failing tests, the 32k default context) passes 45 of 68, 5 more than v0.2.15, and is the first release whose gain clears the noise: the paired score delta is +0.074 with a 95% interval of +0.003 to +0.147, and 7 runs flipped to a pass against 2 the other way (sign test p = 0.18). The larger context and the harness changes arrived together, so the gain belongs to both. connect and hangman passed for the first time, poker in one trial; book-store lost its one pass. Runs take fewer steps (11.4 against 17.6) and fewer prompt tokens (64,500 against 75,400 per run, 110,000 against 141,000 tokens per pass), but longer (302 s against 260 s): of the 23 failures, 17 hit the 600 s wall cap and 6 were handed back. 25 of the 34 exercises passed at least once and 20 in both trials. Command: `uv run python bench/run.py --runners tether --models ollama/qwen3.5:9b --tasks polyglot --trials 2 --poly-verify --tether-bin <venv with tether-sdk==0.2.18>/bin/tether --out <dir>`.
|
|
24
26
|
|
|
25
27
|
The change from v0.2.10 to v0.2.13 is inside the noise: the paired score delta is +0.020 with a 95% interval of -0.060 to +0.101, and 7 exercises flipped up against 5 down. The runs that still fail either use all 30 steps (18 of 31) or hit the 600 s wall cap (13 of 31), and 11 of the 34 exercises (bowling, connect, forth, hangman, poker, pov, react, scale-generator, sgf-parsing, transpose, wordy) did not pass in either trial. v0.2.15 (thinking capped per turn, a call cut by that cap asked again without thinking, short notes after a write or test run) passes 40 of 68, 3 more than v0.2.13, and the 68 runs take 295 minutes instead of 325. That is inside the noise as well: 7 runs flipped to a pass against 4 the other way (sign test p = 0.55) and the paired score delta is -0.026 (95% interval -0.093 to +0.041). Of the 28 failures, 16 were handed back stuck or out of steps and 12 hit the wall cap; the 11 exercises that passed in neither trial are the same as before, except that wordy now passes and rest-api does not. The [Coding Ladder](ladder/index.md) is the second yardstick: it shows where a model stops climbing instead of one pass rate. For Claude Haiku with and without tether, see the [Coding Ladder results](ladder/claude-haiku.md).
|
|
26
28
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Run a session on its own branch
|
|
2
|
+
|
|
3
|
+
*For: people and orchestrators running several tether sessions at once. After this page you can give each run its own branch, read where its work went, and merge or remove it.*
|
|
4
|
+
|
|
5
|
+
This guide is for people, and for orchestrators that start several `tether` sessions at once. `tether run --worktree` gives each session its own git worktree and branch, so parallel runs cannot overwrite each other's files, and the result is a normal commit you can review and merge.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
By default a run edits the directory you started it in, so two runs in the same checkout will collide. With `--worktree`, each run:
|
|
10
|
+
|
|
11
|
+
- gets a new worktree under `~/.cache/tether/worktrees/` on the branch `tether/<id>`;
|
|
12
|
+
- edits only that worktree;
|
|
13
|
+
- commits its changes on that branch when it stops, if anything changed.
|
|
14
|
+
|
|
15
|
+
Your own checkout is left alone until you decide to merge.
|
|
16
|
+
|
|
17
|
+
To run dependency installation with the network and the model's own commands without it, see [Install with the network, work without it](../tutorial/security-and-sandbox.md#install-with-the-network-work-without-it).
|
|
18
|
+
|
|
19
|
+
## The flags
|
|
20
|
+
|
|
21
|
+
| Flag | Meaning |
|
|
22
|
+
|------|---------|
|
|
23
|
+
| `--worktree` | Run in a git worktree on branch `tether/<id>`; the run commits its changes there when it stops |
|
|
24
|
+
| `--worktree-from` | Base ref for `--worktree` (default HEAD) |
|
|
25
|
+
| `--setup` | Shell command run in the worktree before the model starts; runs outside the sandbox, not offered over MCP |
|
|
26
|
+
|
|
27
|
+
`--worktree` must be started inside a git repository and needs `git` on PATH. Otherwise the run exits with a usage error and does not start.
|
|
28
|
+
|
|
29
|
+
## Example
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
tether run --worktree --setup "uv sync" --verify "uv run pytest -q" "fix the failing test"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This creates a worktree, installs dependencies with `uv sync`, lets the model work, runs the verify gate before `done`, and commits the result on `tether/<id>`. Add `--headless` to start it in the background and get the session id back.
|
|
36
|
+
|
|
37
|
+
To replay a session onto its own branch, run `tether replay <id> --worktree`. It takes the same `--worktree-from` and `--setup` flags, and the replay gets a worktree and branch of its own.
|
|
38
|
+
|
|
39
|
+
## Read the branch and commit
|
|
40
|
+
|
|
41
|
+
The session record tells you where the work went:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
tether status <id> --json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The relevant part of the output looks like this:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{"id": "a1b2c3", "status": "done", "worktree": "/home/you/.cache/tether/worktrees/a1b2c3", "branch": "tether/a1b2c3", "commit": "9f3e2d1c0b7a6e5f4d3c2b1a0987654321fedcba"}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The session JSON holds `worktree` (the path), `branch` (`tether/<id>`), and `commit` (the full sha, or `null` when the run changed nothing). The commit uses `git add -A --`, so it commits every tracked or new file except the following, which are always excluded regardless of `.gitignore`:
|
|
54
|
+
|
|
55
|
+
- `__pycache__/`
|
|
56
|
+
- `*.pyc`
|
|
57
|
+
- `.pytest_cache/`
|
|
58
|
+
- `.ruff_cache/`
|
|
59
|
+
- `.mypy_cache/`
|
|
60
|
+
- `.tether/`
|
|
61
|
+
|
|
62
|
+
Everything else not ignored by `.gitignore` is committed.
|
|
63
|
+
|
|
64
|
+
## Merge the branch
|
|
65
|
+
|
|
66
|
+
Review the commit, then merge it like any other branch:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
git log tether/<id>
|
|
70
|
+
git merge tether/<id>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Or cherry-pick the commit with `git cherry-pick <commit>`.
|
|
74
|
+
|
|
75
|
+
## Clean up
|
|
76
|
+
|
|
77
|
+
When you are done with a session, remove its worktree and branch:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
git worktree remove <path>
|
|
81
|
+
git branch -D tether/<id>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Use `<path>` from the `worktree` field. Delete the branch only after its commit is merged or you no longer need it.
|
|
85
|
+
|
|
86
|
+
## Setup runs outside the sandbox
|
|
87
|
+
|
|
88
|
+
`--setup` is a shell command that runs on the host, outside the model sandbox, before the model starts. Treat it as code you trust. It is not offered over MCP: an MCP caller can create a worktree but cannot supply a setup command.
|
|
89
|
+
|
|
90
|
+
## When it goes wrong
|
|
91
|
+
|
|
92
|
+
- **`needs a git repository` and exit code 1.** The run was started outside a git checkout, or `git` is not on PATH. Change into the repository and run again.
|
|
93
|
+
- **`git worktree add failed`.** The `--worktree-from` ref does not exist, or the branch `tether/<id>` is already taken. Check the ref with `git rev-parse <ref>`.
|
|
94
|
+
- **`--setup` timed out or failed.** The setup command ran on the host and did not finish. Run it by hand in the worktree path from the `worktree` field to see the error.
|
|
95
|
+
- **`commit` is `null`.** The run changed nothing, so there is no commit to merge. Read the result text to see what it concluded.
|
|
@@ -19,6 +19,8 @@ Most agent frameworks drown small models in 3,000–8,000 tokens of nested JSON
|
|
|
19
19
|
|---|---|
|
|
20
20
|
| Have a small local model fix a failing test | [Fix a failing test](guides/fix-a-failing-test.md) |
|
|
21
21
|
| Delegate work from Claude Code, Codex or Cursor | [Use tether from your agent](guides/use-tether-from-your-agent.md) |
|
|
22
|
+
| Check my setup before the first run | `tether doctor` in the [CLI reference](reference/cli.md) |
|
|
23
|
+
| Let a session work on its own branch | [Run a session on its own branch](guides/own-branch.md) |
|
|
22
24
|
| Run a multi-stage job with checks | [A pipeline where each stage has its own job](guides/a-pipeline-with-checks.md) |
|
|
23
25
|
| Give the model my own function | [Give the model a tool of your own](guides/add-your-own-tool.md) |
|
|
24
26
|
| Look up a command or flag | [CLI reference](reference/cli.md) |
|