tether-sdk 0.2.2__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. tether_sdk-0.2.4/.claude/feature-context/active/.gitkeep +0 -0
  2. tether_sdk-0.2.4/.claude/feature-context/active/v0-2-bugs.md +75 -0
  3. tether_sdk-0.2.4/.claude/feature-context/archive/.gitkeep +0 -0
  4. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/AGENTS.md +40 -10
  5. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/PKG-INFO +2 -2
  6. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/README.md +1 -1
  7. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/architecture.md +3 -2
  8. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/reference/agent-interface.md +2 -2
  9. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/reference/context-and-store.md +1 -0
  10. tether_sdk-0.2.4/docs/reference/multiplexer-and-decorator.md +17 -0
  11. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/reference/providers.md +8 -4
  12. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/release-notes.md +40 -0
  13. tether_sdk-0.2.4/docs/testing.md +84 -0
  14. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/troubleshooting.md +6 -4
  15. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/tutorial/built-in-tools.md +1 -1
  16. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/tutorial/cli-and-headless.md +4 -1
  17. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/tutorial/skills.md +1 -1
  18. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/tutorial/workflows-and-telemetry.md +3 -3
  19. tether_sdk-0.2.4/e2e/__init__.py +0 -0
  20. tether_sdk-0.2.4/e2e/conftest.py +159 -0
  21. tether_sdk-0.2.4/e2e/proxy.py +252 -0
  22. tether_sdk-0.2.4/e2e/test_agents.py +86 -0
  23. tether_sdk-0.2.4/e2e/test_defaults.py +89 -0
  24. tether_sdk-0.2.4/e2e/test_faults.py +66 -0
  25. tether_sdk-0.2.4/e2e/test_lifecycle.py +68 -0
  26. tether_sdk-0.2.4/e2e/test_limits.py +44 -0
  27. tether_sdk-0.2.4/e2e/test_quirks.py +127 -0
  28. tether_sdk-0.2.4/e2e/test_sandbox.py +50 -0
  29. tether_sdk-0.2.4/e2e/test_surface.py +159 -0
  30. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/mkdocs.yml +1 -0
  31. tether_sdk-0.2.4/scripts/record_golden.py +42 -0
  32. tether_sdk-0.2.4/tests/golden/coding_edit_failure_modes.json +51 -0
  33. tether_sdk-0.2.4/tests/golden/coding_edit_failure_modes.snap.json +483 -0
  34. tether_sdk-0.2.4/tests/golden/done_nudge.json +45 -0
  35. tether_sdk-0.2.4/tests/golden/done_nudge.snap.json +483 -0
  36. tether_sdk-0.2.4/tests/golden/read_range_pingpong.json +38 -0
  37. tether_sdk-0.2.4/tests/golden/read_range_pingpong.snap.json +258 -0
  38. tether_sdk-0.2.4/tests/golden/research_web.json +41 -0
  39. tether_sdk-0.2.4/tests/golden/research_web.snap.json +256 -0
  40. tether_sdk-0.2.4/tests/golden/waiting_input_resume.json +30 -0
  41. tether_sdk-0.2.4/tests/golden/waiting_input_resume.snap.json +78 -0
  42. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/providers/test_anthropic.py +12 -0
  43. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/providers/test_declarative.py +21 -1
  44. tether_sdk-0.2.4/tests/providers/test_retry.py +210 -0
  45. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_agent_interface.py +2 -1
  46. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_cli.py +15 -1
  47. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_context.py +116 -0
  48. tether_sdk-0.2.4/tests/test_edit_tool.py +142 -0
  49. tether_sdk-0.2.4/tests/test_golden.py +102 -0
  50. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_harness.py +239 -2
  51. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_multiplexer.py +34 -0
  52. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_release_hardening.py +1 -1
  53. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_skills.py +32 -0
  54. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_store.py +83 -0
  55. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_tether.py +1 -1
  56. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/__init__.py +1 -1
  57. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/api.py +1 -1
  58. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/cli.py +12 -3
  59. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/context.py +105 -7
  60. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/harness.py +152 -11
  61. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/multiplexer.py +21 -1
  62. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/providers/anthropic.py +9 -1
  63. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/providers/base.py +76 -1
  64. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/providers/declarative.py +18 -8
  65. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/providers/ollama.py +13 -2
  66. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/providers/openai.py +9 -1
  67. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/skills.py +9 -0
  68. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/store.py +64 -8
  69. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/tools/edit.py +83 -7
  70. tether_sdk-0.2.2/docs/reference/multiplexer-and-decorator.md +0 -15
  71. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/.github/workflows/ci.yml +0 -0
  72. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/.github/workflows/delivery.yml +0 -0
  73. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/.github/workflows/docs.yml +0 -0
  74. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/.gitignore +0 -0
  75. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/LICENSE +0 -0
  76. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/benchmarks.md +0 -0
  77. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/extending.md +0 -0
  78. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/index.md +0 -0
  79. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/javascripts/mathjax.js +0 -0
  80. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/reference/harness.md +0 -0
  81. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/reference/index.md +0 -0
  82. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/reference/sandbox-and-skills.md +0 -0
  83. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/reference/tools.md +0 -0
  84. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/releasing.md +0 -0
  85. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/tutorial/agent-interface.md +0 -0
  86. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/tutorial/index.md +0 -0
  87. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/tutorial/multi-agent.md +0 -0
  88. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/tutorial/sdk-and-custom-tools.md +0 -0
  89. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/docs/tutorial/security-and-sandbox.md +0 -0
  90. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/examples/course/README.md +0 -0
  91. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/examples/course/checks.py +0 -0
  92. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/examples/course/course.yaml +0 -0
  93. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/examples/course/ruff.toml +0 -0
  94. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/examples/course/skills/planner/SKILL.md +0 -0
  95. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/examples/course/skills/searcher/SKILL.md +0 -0
  96. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/examples/course/skills/searcher/tools.py +0 -0
  97. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/examples/course/skills/writer/SKILL.md +0 -0
  98. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/examples/course/skills/writer/tools.py +0 -0
  99. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/pyproject.toml +0 -0
  100. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/skills/researcher/SKILL.md +0 -0
  101. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/__init__.py +0 -0
  102. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/conftest.py +0 -0
  103. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/fixtures/dialects.jsonl +0 -0
  104. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/providers/__init__.py +0 -0
  105. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/providers/test_base.py +0 -0
  106. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/providers/test_dialects.py +0 -0
  107. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/providers/test_ollama.py +0 -0
  108. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/providers/test_openai.py +0 -0
  109. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_context_config.py +0 -0
  110. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_decorator.py +0 -0
  111. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_integration.py +0 -0
  112. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_misses.py +0 -0
  113. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_pipeline.py +0 -0
  114. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_retro.py +0 -0
  115. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_sandbox.py +0 -0
  116. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_security_hardening.py +0 -0
  117. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/test_structured_history.py +0 -0
  118. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/tools/__init__.py +0 -0
  119. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/tools/test_agent.py +0 -0
  120. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/tools/test_bash.py +0 -0
  121. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/tools/test_done.py +0 -0
  122. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/tools/test_edit.py +0 -0
  123. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/tools/test_mem.py +0 -0
  124. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tests/tools/test_web.py +0 -0
  125. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/decorator.py +0 -0
  126. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/mcp.py +0 -0
  127. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/pipeline.py +0 -0
  128. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/providers/__init__.py +0 -0
  129. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/retro.py +0 -0
  130. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/sandbox.py +0 -0
  131. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/schema.py +0 -0
  132. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/tools/__init__.py +0 -0
  133. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/tools/agent.py +0 -0
  134. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/tools/bash.py +0 -0
  135. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/tools/done.py +0 -0
  136. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/tools/mem.py +0 -0
  137. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/tether/tools/web.py +0 -0
  138. {tether_sdk-0.2.2 → tether_sdk-0.2.4}/uv.lock +0 -0
@@ -0,0 +1,75 @@
1
+ # v0.2 bugs
2
+ - Slug: v0-2-bugs
3
+ - Status: active
4
+ - Started: 2026-10-20 Updated: 2026-10-20 Closed: —
5
+ - Branches / PRs: branch `claude/keep-feature-context-resume-0ec918` (worktree); base `main` at 125b386 (release v0.2.2)
6
+
7
+ ## Goal
8
+ Track and fix bugs found in Tether v0.2.x. Exact bug list not yet given by PO.
9
+
10
+ ## Requests
11
+ - 2026-10-20: Close v0.2.2 milestone; implement v0.2.3 issues (#40 #39 #33 #24) via swe+tether(GLM), watch tether behavior and report bugs; open v0.2.4 issue for provider docs (YAML + Python, generic example, where providers.yaml lives); review docs overall and report.
12
+ - 2026-10-20: `/keep-feature-context new v0.2 bugs` (start log for v0.2 bugs work)
13
+
14
+ ## Decisions
15
+ - 2026-10-20: PO convention: v0.X.0 = feature releases (many issues OK), v0.X.Y = bug releases (small, 3-4). All 13 features now in single milestone v0.3 (v0.3.1-v0.3.3 deleted). Bug releases v0.2.4-v0.2.8 unchanged.
16
+ - 2026-10-20: PO: max 3-4 issues per release. Bugs by impact -> v0.2.4 (#85 #88 #75 #74), v0.2.5 (#86 #81 #92 #60), v0.2.6 (#76 #93 #27 #77), v0.2.7 (#79 #61 #62 #64), v0.2.8 (#63 #65 #66 docs). Features -> v0.3 (#89 #90 #78 #82), v0.3.1 (#34 #35 #32 #59), v0.3.2 (#7 #9 #8), v0.3.3 (#10 #11). Docs treated as lower-impact bugs; #60 (PO request) pulled to v0.2.5.
17
+ - 2026-10-20: PO approved: prepare release PR for v0.2.3 AND create the GitHub Release (tag v0.2.3, standard message like v0.2.2: title 'v0.2.3 <theme>', intro line, ### Features/### Fixes with (#N), Docs link) after the release PR merges. swe abb3128a53f9716b2 on release/v0.2.3.
18
+ - 2026-10-20: PO approved: after swe fixes reviewer minors on PR #73 (threshold 3x2-gram / 2x3-gram, softer nudge, offenders, tests) and CI is green -> post review comment then squash-merge #73, then start #33.
19
+ - 2026-10-20: PO: keep using GLM (small scoped runs, 1 retry then hand-finish) for #39 etc. to capture every tether issue, over switching to sonnet. #33 waits for #39 (harness.py).
20
+ - 2026-10-20: PO made me act as Team Leader (no product code; dispatch swe/qa/reviewer). swe delegates coding to tether; PO wants it run with GLM and observed for tether bugs.
21
+ - 2026-10-20: #24 -> opt-in kept + load-time warning + docs fix (maintainer lean, PO approved).
22
+ - 2026-10-20: Added `## Dev team specifics` to AGENTS.md (PR per task, hard stop = force push/history rewrite, store in-repo `.claude/feature-context/`).
23
+
24
+ ## Problems
25
+ - 2026-10-20: reviewer agent's own `gh pr review --comment` got blocked ('Unrequested Commit in a Connected App') and its merge condition needs a 'QA: GOOD TO GO' comment; TL posted the review+merge itself on PO instruction, works. Project has no QA layer beyond CI.
26
+ - 2026-10-20 TETHER+GLM BUGS (from swe #40, PR #68, 4 GLM runs, 0% pass, avg 25.5 steps, 2.8 syntax_errors/run): ses_32dbe34d max_steps 40, 102k prompt tokens, 0 changes: ~25 steps re-reading with `sed -n a,bp` (fold after 2 turns made it forget what it read); `bash -c "..."` errors, bare `ls` -> syntax_error+nudge; tried `mem recall` with mem disabled (exit 127). ses_b1816f3f (--fold-after 8 --context-tokens 16384): max_steps 50, 231k prompt tokens, produced mostly-correct edit.py but destructive edit (replaced `from __future__ import annotations` line instead of adding), never wrote tests/docs; 4 syntax_errors on `edit replace` (--help, bare --stdin, stdin after newline in cmd). ses_a8b72e53 ReadTimeout after 2 steps (132s). Loops show as repeated identical-ish `ok` reads, no loop_stall (#39). `tether read --full` invalid flag (docs/agent says it exists). Bash guard in swe worktree refuses compound commands.
27
+ - 2026-10-20 TETHER+GLM BUGS (from swe #24): (a) ses_bde86e86 glm-5.3 with --tools bash,edit: 38 steps/90k prompt tokens, read-loop alternating `sed -n 1,40p` / `40,100p` of same file, never edited; every assistant turn empty text + command only; no nudge/loop_stall recorded (= issue #39, cycle detection needed); ended `ReadTimeout` raised as tool_error/Worker error after ~5 min (httpx timeout not retried/handled). (b) ses_6176f368: without --tools bash,edit GLM had only `done`, spent 38 steps probing "unknown command", then WAITING_INPUT asking for a shell (tether default zero tools; unknown-command loops not nudged; could hint 'enabled tools: done' / tools to enable). (c) 401 shows raw HTTPStatusError, doesn't name NVIDIA_API_KEY. (d) swe worktree guard blocks eval/export/time in bash -> key loading needs a script file.
28
+ - 2026-10-20: swe #40 blocked: tether session ses_29d41cff (nvidia/z-ai/glm-5.3) -> 401 Unauthorized, 0 steps. NVIDIA_API_KEY missing/invalid in tether MCP server env. #24 swe confirmed same (ses_4a589057, 401, 686 ms). Both local branches empty, no PRs. Tether finding: 401 surfaces as raw HTTPStatusError, should name NVIDIA_API_KEY. Needs PO to supply key to MCP server env (not via me).
29
+ (none yet)
30
+
31
+ ## Progress log
32
+ - 2026-10-20: RELEASED v0.2.3: release PR #94 merged (ec32d63), GitHub Release v0.2.3 'Small-Model Robustness', delivery.yml success, PyPI tether-sdk 0.2.3 live, milestone v0.2.3 closed. Next: v0.2.4 (#27 #60 #74-#79 #81-#82 #85-#86 #88-#90 #92-#93) and v0.2.5 docs (#61-#66).
33
+ - 2026-10-20: PR #91 (#71) MERGED after minors fixed. v0.2.3 code-complete (all 8 issues merged). Release v0.2.3 (release: commit, PyPI via delivery.yml) needs PO OK. v0.2.4 open issues next.
34
+ - 2026-10-20: swe #71 -> PR #91 (95% hand; GLM ses_8ab3240d looped 40 steps on edit view ranges, cycle_stall missed). New issues #92 (varied-range view loops evade cycle_stall), #93 (tether query finds no chunks); comment on #76 (no `python` in sandbox). Reviewer on #91 running; standing PO ok: post findings + merge if reviewer clean.
35
+ - 2026-10-20: PR #87 (#70) MERGED. v0.2.3 merged: #24 #40 #39 #33 #69 #72 #70. swe dispatched for #71 (fix/71-clearer-errors) on GLM, last v0.2.3 issue. After it: release v0.2.3 needs PO OK (release commit/PyPI).
36
+ - 2026-10-20: PR #84 (#72) MERGED. #87 (#70) swe asked to merge main in; then review+merge, then dispatch #71 (extend providers/base.py helper; add release-notes bullet only after merging main).
37
+ - 2026-10-20: PO: 'if reviewer already looked, post findings and merge'. PR #83 (#69) MERGED. #84 and #87 now CONFLICTING (release-notes) -> swe #72 merging main into branch first; #87 after #84 merges. Then #71.
38
+ - 2026-10-20: PR #83 re-review CLEAN (CI green); minor: tombstone() duplicates store.py summary format. #84 green+clean. #87 minors being fixed. All three await PO merge yes.
39
+ - 2026-10-20: swe #70 -> PR #87 (post_with_retry + ProviderError; ~5% GLM; ses_2a4d5606 hung RUNNING 15+ min). PR #84 fixes pushed (830f4ca, CI green), PR #83 fixes pushed (re-review running). New issues #88 (total per-call deadline), #89 (tether stop), #90 (print session id to stderr at start). Reviewer on #87 running. #71 starts after #87 merges. PO yes needed for merges of #83/#84/#87 (asked for #84).
40
+ - 2026-10-20: Reviews: PR #83 (#69) BLOCKER (sed -i treated as read, path not normalized, bash writes leave stale reads, no cap on live reads) -> swe fixing. PR #84 (#72) clean + minors (mutually exclusive flags, stdout tests, doc) -> swe fixing. Merge needs PO yes per PR.
41
+ - 2026-10-20: swe #72 -> PR #84 (`tether read --full` alias, CLI default stays full; 100% hand-written, GLM ses_9ed228cd ignored goal for 31 steps). New issues: stale RUNNING after kill, goal-ignored/lost context. Reviewer for #84 next. #70 still running.
42
+ - 2026-10-20: swe #69 -> PR #83 (live_reads keeps latest unedited file read unfolded; mem_enabled hint). GLM ~30% of code. Reviewer dispatched review-only; PO merge OK needed.
43
+ - 2026-10-20: PR #80 (#33) MERGED after PO yes (review comment then squash). v0.2.3 left: #69 (context.py), #70 (providers), #72 (cli.py) in parallel on GLM; #71 after #70 (provider errors overlap).
44
+ - 2026-10-20: swe #33 -> PR #80 (gate_error), 100% hand-written (2 GLM runs: ReadTimeout at step 9 twice, 0 edits; ses_a82f3cb1, ses_458d2900). CI pending, reviewer dispatched (review-only). New issues #81 (context budget not hard limit, 16384 -> ~24k), #82 (gate dry-run, item 3 of #33); comment on #70. PO merge approval so far covered #67/#68/#73 only; ask for #80.
45
+ - 2026-10-20: PR #73 (#39) MERGED. v0.2.3 done: #24 (#67), #40 (#68), #39 (#73). swe dispatched for #33 (fix/33-gate-error-vs-failure) on GLM. Remaining v0.2.3: #33, #69-#72.
46
+ - 2026-10-20: PR #73 (#39 cycle_stall) opened by swe (GLM wrote ~70% of 116 additions; tests/docs partly by hand). CI green. Reviewer dispatched review-only. Issues opened from GLM runs: #74 no-done nudge, #75 max_steps counter 46/40, #76 verify gate can't run uv/ruff in sandbox, #77 stray ':memory:' file, #78 anchor-based edit replace, #79 stats offenders 'cd'/'unknown'; comments on #69, #70. PR #68 merged; AGENTS.md notes dropped per PO (only permanent facts).
47
+ - 2026-10-20: PR #68 (#40) MERGED (squash) after PO had me post the reviewer findings as a PR review comment first; classifier blocks bare merge ('Merge Without Review'). Remote merge fine; local branch delete failed (worktree holds it). Settings.json edit for gh pr review/merge allow rules still blocked (Self-Modification) -> PO to add if wanted.
48
+ - 2026-10-20: swe dispatched for #39 (fix/39-cycle-stall-detection) on GLM, small runs; #68 rebase in flight.
49
+ - 2026-10-20: PO approved squash-merge of #67/#68 (reviewer merges when CI green). #67 MERGED. #68 conflicts (release-notes, troubleshooting) -> swe resumed to merge main in + review nits (grammar, trailing-newline, 3 tests); reviewer re-merges after. Created v0.2.3 issues #69 (folding re-read loops), #70 (ReadTimeout retry), #71 (401/unknown-cmd errors), #72 (read --full mismatch); commented evidence on #39. PO: keep #66 bundled. Next: #39 then #33 (harness.py), model for them undecided (GLM unreliable: 0/4).
50
+ - 2026-10-20: swe #40 -> PR #68, hand-written after 2 GLM attempts; 188 tests pass. Awaiting CI check + review + PO merge OK.
51
+ - 2026-10-20: reviewer on PR #67: CLEAN, CI green (3.12/3.13, 100% cov), not merged (awaiting PO merge OK). Minor: no multi-tool test, 'they' wording, warning could name skill path.
52
+ - 2026-10-20: swe #24 -> PR #67 (c65a221), code written by swe itself after GLM failed twice. CI pending; reviewer dispatched (may merge if green). #40 swe still running.
53
+ - 2026-10-20: Wrapper ~/.tether/mcp-launch.sh + ~/.claude.json tether server repointed (backup ~/.claude.json.bak-tether); MCP reconnect is the PO's. Workaround that works now: swe uses tether CLI via Bash with key eval'd from ~/.profile (smoke test ses_279df4ad OK, GLM 1 step). Re-dispatched swe #40 and #24 on that route.
54
+ - 2026-10-20: PO approved all docs findings; created milestone v0.2.5 + issues #61 (CLI ref), #62 (dedupe providers docs), #63 (README restructure), #64 (llms.txt/index), #65 (tools.py opt-in wording), #66 (polish). Commented on #60. NVIDIA key is in ~/.profile but tether MCP processes lack it; waiting on PO to restart MCP with key.
55
+ - 2026-10-20: GLM id = `nvidia/z-ai/glm-5.3` (from public NVIDIA /v1/models; NVIDIA_API_KEY not in my env). Dispatched swe #40 (fix/40-edit-write-overwrite-summary) and swe #24 (fix/24-skill-tools-optin-warning), both via tether+GLM. Docs review report received (see Next steps for proposed issues). #39/#33 wait (both edit harness.py).
56
+ - 2026-10-20: milestone v0.2.2 closed. Issue #60 created (v0.2.4, provider docs). Docs review agent launched (read-only).
57
+ - 2026-10-20 (backfilled): preflight ran; AGENTS.md section + `.claude/feature-context/{active,archive}` created (uncommitted).
58
+ - Checks: `uv run ruff check . && uv run ruff format --check .`; `uv run mypy tether/`; `uv run pytest`.
59
+
60
+ ## Next steps
61
+ - [x] Release v0.2.3 done
62
+ - [ ] Start v0.2.4: #85 #88 #75 #74
63
+ - [ ] PO OK to merge #73 (post review comment first, then squash merge); then swe #33 (harness.py), then #69-#72, #74-#79 as scheduled
64
+ - [ ] Get GLM model id from PO (not in ~/.tether/providers.yaml; only claude-cli + nvidia entries; no 'glm' anywhere on disk). PO says it was added, maybe in another worktree/host: not found.
65
+ - [ ] Dispatch swe for #40 #39 #33 #24 (own PRs; #39/#33 both touch harness -> sequence), then qa+reviewer
66
+ - [ ] swe for #60 docs
67
+ - [ ] Relay docs review report to PO
68
+ - [ ] PO to list the v0.2 bugs
69
+ - [ ] Commit AGENTS.md + feature-context log on this branch
70
+
71
+ ## Sessions
72
+ | Date | Session note |
73
+ | 2026-10-20 | Current session: created log |
74
+
75
+ - 2026-10-20: PO asked: add Philosophy Gate to AGENTS.md (done, uncommitted; also fixed coverage line: CI enforces 100%). Switch swe tether model to `nvidia/nvidia/nemotron-3-ultra-550b-a55b` (exists on NVIDIA API, smoke test `done pong` passed via tether 0.2.3; no providers.yaml change needed, `nvidia` prefix covers it). Upgraded installed tether 0.2.2 -> 0.2.3 (`uv tool upgrade tether-sdk`). MCP server still on old process: needs reconnect to load 0.2.3 + key wrapper.
@@ -16,6 +16,18 @@ Tether implements the architectural principles from *"LLM-Native Applications as
16
16
  2. **Explicit Context Governance:** Context is treated as a bounded L1 working memory backed by an ACID SQLite store (`.tether/tether.db` in WAL mode)—never an unbounded append-only transcript.
17
17
  3. **First-Class AI Quality Attributes:** Every change must be evaluated against **Token Economy**, **KV-Cache Prefix Immutability**, **Self-Healing Probabilistic Execution**, and **Observability**.
18
18
 
19
+ ### Philosophy Gate (every issue, PR and review is judged against this)
20
+ Tether stays **lightweight, reliable, extensible, model-agnostic, easy to use, resourceful and a token saver**. A change that fails any line below is rejected or reworked, however useful it looks:
21
+ 1. **Lightweight:** no new heavy dependency (Rule 7); prefer stdlib and existing modules over new ones; no code for hypothetical needs.
22
+ 2. **Token saver:** no growth of the static prompt (< 150 tokens, Rule 1/4); new per-turn prompt content must be bounded and justified with a token-budget test.
23
+ 3. **Reliable:** failures self-heal or fail loudly with an actionable message, never hang or crash silently; the deterministic envelope (loop, SQLite, sandbox, `done`) never relies on the LLM.
24
+ 4. **Extensible:** new behaviour comes through providers, skills, `@tool` or config, not hardcoded special cases in the harness core.
25
+ 5. **Model-agnostic:** no behaviour tied to one vendor or model; small local models and frontier models take the same code path.
26
+ 6. **Easy to use:** zero-config defaults, short CLI/SDK surface, errors that say what to do next, docs in the same PR (5.4).
27
+ 7. **Resourceful:** reuse existing primitives (`act`, SQLite store, FTS5 recall) before adding a new one.
28
+
29
+ Reviewers and the Team Leader cite the failing line when they reject a change.
30
+
19
31
  ---
20
32
 
21
33
  ## 2. The Golden Rules (Non-Negotiable Invariants)
@@ -33,13 +45,15 @@ Tether implements the architectural principles from *"LLM-Native Applications as
33
45
  - 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).
34
46
  - A task only completes when the agent explicitly calls `act("done", stdin="<final deliverable>")` (or `act("done '<short answer>'")`), pauses for human/parent input via `act("done --ask", stdin="<question>")` (transitioning the session to `WAITING_INPUT` and releasing the worker process until resumed via `send`), or hits `max_steps`.
35
47
  - If the model emits plain text without calling `act`, the harness appends a brief deterministic nudge reminding it to either execute a command or call `done`.
36
- - 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).
48
+ - After 6 consecutive read-only/verification steps following a change, the harness appends a one-shot `[NUDGE]` to that step's observation reminding the model to call `done` if the task is complete (0 static prompt tokens, #74).
49
+ - When `verify` gate commands are configured (via `Harness(verify=[...])`, CLI `--verify`, or Skill YAML `verify: [...]`), `Harness.execute_session` deterministically executes each command inside the session's `SandboxPolicy` before allowing `done` to transition to `COMPLETED` (consuming 0 static prompt tokens until a gate fails). A gate that errors instead of failing (sandbox violation, exit 124/126/127) ends the session `FAILED` at once as `gate_error` ("gate misconfigured"), without counting toward the consecutive-failure abort.
37
50
 
38
51
  ### Rule 4: Progressive `--help` (JIT Schema Disclosure)
39
- - The system prompt lists at most **1 line per enabled tool namespace** (`<name> : <short summary>`, target **≤ 12 tokens per entry**).
52
+ - The system prompt lists at most **1 line per enabled tool namespace** (`- <name>: <short summary>`, target **≤ 12 tokens per entry**).
40
53
  - Full parameter signatures, defaults, and docstrings are revealed **Just-In-Time (JIT)** when:
41
54
  1. The model explicitly runs `<cmd> --help`, OR
42
55
  2. The model invokes `<cmd>` with missing/invalid arguments (self-healing error response, never an unhandled Python exception).
56
+ 3. The model invokes a built-in tool that is not enabled: the error says it is disabled, lists the enabled tools and how to enable more (error responses are history, never prefix).
43
57
  - Auto-generated `--help` output must remain ultra-compact (**≤ 60 tokens** per command).
44
58
 
45
59
  ### Rule 5: Ephemeral Observation Folding
@@ -48,6 +62,7 @@ Tether implements the architectural principles from *"LLM-Native Applications as
48
62
  ```text
49
63
  [obs_3 folded: `web fetch https://...` (3,420 chars) | use `mem recall obs_3` to expand]
50
64
  ```
65
+ - Exception: the latest read of each of the last 8 files (`edit view <p>`, `bash cat/head/tail/sed -n <p>`; paths normalized against the workspace) is not folded until `<p>` is edited (`edit write|replace`, or any other bash command naming it: `sed -i`, `tee`, redirects, `python -c`) or re-read. The 2,400-char head+tail elision still applies, and the hard context budget remains #81. The `mem recall` hint is shown only when `mem` is enabled (else "re-run the command").
51
66
  - History is replayed as structured `tool_calls` + `tool` results (providers with `structured_history = True`; textual `act('...')` form only as fallback), with `stdin` replayed as a real 400-char prefix, never a `<N chars>` placeholder. A call and its result are trimmed together.
52
67
 
53
68
  ### Rule 6: Deterministic Security Envelope (Never Trust the LLM for Safety)
@@ -82,7 +97,7 @@ tether/
82
97
  ├── multiplexer.py # Action Plane: `act(cmd, stdin)` parser, command registry, JIT --help
83
98
  ├── decorator.py # `@tool` introspection: converts Python type hints + docstrings to CLI specs
84
99
  ├── context.py # State Plane: ContextAssembler, EphemeralFolder, token estimator
85
- ├── skills.py # Skill loader: Markdown + YAML frontmatter + optional sibling `tools.py`
100
+ ├── skills.py # Skill loader: Markdown + YAML frontmatter + optional sibling `tools.py` (its tools must be listed in `tools:`)
86
101
  ├── providers/
87
102
  │ ├── base.py # Abstract async ModelProvider protocol (`generate(messages, tools)`)
88
103
  │ ├── ollama.py # Native Ollama `/api/chat` httpx adapter (`num_ctx`, `keep_alive`)
@@ -103,16 +118,18 @@ tether/
103
118
  - Default DB path: `.tether/tether.db` (configurable via `TETHER_DB` or `Harness(db_path=...)`) with `PRAGMA journal_mode=WAL;`.
104
119
  - Tables:
105
120
  - `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`.
106
- - `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` | `syntax_error` | `tool_error` | `gate_fail` | `completed`), `created_at`.
121
+ - `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`), `created_at`.
107
122
  - `observations`: `id`, `session_id`, `step`, `cmd`, `raw_output`, `summary`, `char_len`.
108
123
  - `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>`).
109
124
  - `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>`).
110
125
  - `inbox`: `id`, `session_id`, `message`, `consumed` (`0` or `1`), `created_at`.
111
126
  - **Agent-to-Agent Interface (`api.py`, `mcp.py`):** `--json` outputs (schema `v: 1`), exit codes `0` completed / `1` usage / `2` waiting / `3` gate / `4` max steps / `5` worker error, failure text on stderr, `--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`.
112
127
  - **Telemetry & Failure-Mode Inspector (`tether stats`):**
113
- - `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`, `syntax_error`, `tool_error`, `gate_fail`), plus a top-offenders breakdown by tool command.
114
- - **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`/`syntax_error`/`tool_error`/`gate_fail` 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).
128
+ - `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.
129
+ - **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).
115
130
  - **Headless Worker & Live `send` Contract:**
131
+ - `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).
132
+ - `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).
116
133
  - `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.
117
134
  - Before each step, the running worker queries `inbox WHERE session_id = ? AND consumed = 0` and injects any new user/parent messages into the conversation.
118
135
  - If `tether send <session_id> "msg"` (or `agent send`) is called when the session is `WAITING_INPUT`, `COMPLETED`, or `FAILED`, it inserts the message into `inbox`, sets status back to `RUNNING`/`PENDING`, and spawns/resumes the worker from the persisted SQLite state.
@@ -123,7 +140,7 @@ tether/
123
140
  - `agent status <id>` $\rightarrow$ returns 1-line status, step count, and last command.
124
141
  - `agent wait <id1> [id2 ...]` $\rightarrow$ asynchronously polls SQLite until the specified child session(s) finish and returns a **compact ~50-token digest** (status, total chars/chunks, 2-line preview) so the parent's context window is never flooded.
125
142
  - `agent query <id|--all> <query> [--top 3]` $\rightarrow$ queries `chunks_fts` (BM25 ranking) across the sub-agent's final deliverable and raw observations, returning only the top-K relevant chunks (~200–300 tokens).
126
- - `agent read <id> [--full]` $\rightarrow$ reads the child session's digest or full `result`.
143
+ - `agent read <id> [--full]` $\rightarrow$ reads the child session's digest or full `result`. The CLI `tether read <id>` prints the full result by default (`--full` accepted for parity; `--digest` for the digest).
127
144
  - `agent send <id> <message>` $\rightarrow$ queues a message into the child session's inbox (or resumes it if completed).
128
145
  - **Deterministic Multi-Stage Pipelines (`tether pipeline <pipeline.yaml> "<goal>"` & `Harness.run_pipeline`):**
129
146
  - 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)`).
@@ -142,7 +159,7 @@ tether/
142
159
  - pytest -q
143
160
  ---
144
161
  ```
145
- - If `skills/<name>/tools.py` exists, any `@tool`-decorated functions inside it are loaded with the skill and registered into the session's multiplexer when their name is also listed in the skill's `tools:` (otherwise: `unknown command`).
162
+ - If `skills/<name>/tools.py` exists, any `@tool`-decorated functions inside it are loaded with the skill and registered into the session's multiplexer when their name is also listed in the skill's `tools:` (otherwise: `unknown command`). A skill's own custom tools are opt-in like any other, so they must be listed in `tools:`; loading a skill whose `tools.py` defines an unlisted `@tool` emits a `UserWarning` (0 prompt tokens).
146
163
 
147
164
  #### D. 3 Thin `httpx` Model Providers & SDK Provider Routing (`providers/`)
148
165
  - Model identifier format: `<provider>/<model_name>` (e.g., `ollama/qwen3.5:9b`, `openai/gpt-4o-mini`, `anthropic/claude-3-7-sonnet-latest`).
@@ -155,6 +172,7 @@ tether/
155
172
  - `ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL` (default `https://api.anthropic.com`)
156
173
 
157
174
  ---
175
+ - **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).
158
176
  - **Context config (#54):** `max_context_tokens` / `fold_observations_after` resolve as explicit argument (`--context-tokens` / `--fold-after`, MCP `context_tokens` / `fold_after`) > `providers.yaml` entry keys `context_tokens` / `fold_after` > defaults 8192 / 2.
159
177
 
160
178
  ## 4. Development Workflow & Commands
@@ -186,7 +204,19 @@ uv run mypy tether/
186
204
  2. **Async-First Design:**
187
205
  - All model calls and I/O-bound tool executions must be `async`/`await` native, while seamlessly wrapping synchronous user `@tool` functions.
188
206
  3. **Graceful Degradation on 9B Hallucinations:**
189
- - Small models occasionally emit `cmd` with minor quirks (e.g., leading `$`, wrapping backticks, or calling a tool name directly instead of `act`). The multiplexer must normalize trivial syntax quirks deterministically before failing. This includes unescaping literal `\n`/`\t` in one-line `stdin` for `edit write`/`edit replace` and `done` (never `bash`).
190
- 4. **Documentation Integrity:**
207
+ - Small models occasionally emit `cmd` with minor quirks (e.g., leading `$`, wrapping backticks, or calling a tool name directly instead of `act`). The multiplexer must normalize trivial syntax quirks deterministically before failing. This includes unescaping literal `\n`/`\t` in one-line `stdin` for `edit write`/`edit replace` and `done` (never `bash`), and turning a trailing shell heredoc in `cmd` (`edit write f <<'EOF' ... EOF`) into `stdin` (never for `bash`).
208
+ 4. **Golden traces:** behaviour the model sees (framing, folding, nudges, tool output) is locked by `tests/golden/*.snap.json`. A PR that changes it runs `UPDATE_GOLDEN=1 pytest tests/test_golden.py` and the snapshot diff is part of the review; unrelated traces must not change. See `docs/testing.md`.
209
+ 5. **E2E scenarios:** every feature or bug fix ships with a scenario in `e2e/` (real CLI, real ollama behind a fault-injecting proxy, local only, `TETHER_E2E=1 uv run pytest e2e`). Assert on what tether sends and records, never on the model's skill; script the reply or inject the fault so the test does not depend on model potency. **Before merging any PR, run `TETHER_E2E=1 uv run pytest e2e` locally and put the result in the PR description (CI cannot run it). Every failure must be mitigated before merge: fix the code, or fix a wrong scenario, or record in the PR why it is acceptable and what follows up. A PR is not merged with an unexplained red scenario.** See `docs/testing.md`.
210
+ 6. **Documentation Integrity:**
191
211
  - Keep `README.md` and `AGENTS.md` synchronized whenever architectural interfaces or built-in tool signatures change.
192
212
  - **Always update documentation in the same PR** when introducing a new feature, changing an existing behavior, or altering a CLI/SDK interface. This covers `README.md`, `AGENTS.md`, the MkDocs pages under `docs/` (including `docs/reference/*` for new modules), and a `docs/release-notes.md` entry. A change without matching docs is incomplete.
213
+
214
+ ## Dev team specifics
215
+ - Stack: Python 3.12+, package `tether/`, tests in `tests/`, docs in `docs/` (MkDocs); `uv`, ruff, mypy
216
+ - Checks: lint `uv run ruff check . && uv run ruff format --check .`; types `uv run mypy tether/`; tests `uv run pytest --cov=tether --cov-report=term-missing --cov-fail-under=100` (CI enforces 100% coverage); **e2e `TETHER_E2E=1 uv run pytest e2e` run locally before every merge, result in the PR, failures mitigated** (not in CI)
217
+ - E2E / QA: `e2e/` scenarios (real CLI + ollama behind a fault-injecting proxy, local only); shared resource: the `tether-ollama` container (port 11435)
218
+ - Branches/commits/PRs: Conventional Commits (`feat(scope): ... (#N)`, `release: vX.Y.Z ...`); PR per task, squash-merged to `main`; docs updated in the same PR (see section 5.4)
219
+ - Delivery: PR per task
220
+ - Hard stops (ask first): force push / history rewrite
221
+ - Design docs: `AGENTS.md` sections 1-3, `docs/architecture.md`, `docs/reference/*`
222
+ - feature-context store: .claude/feature-context/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tether-sdk
3
- Version: 0.2.2
3
+ Version: 0.2.4
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
@@ -109,7 +109,7 @@ Every Tether execution is a stateful session persisted in `.tether/tether.db` (S
109
109
  - If **`WAITING_INPUT` / `COMPLETED` / `IDLE`**: appends the message and automatically spawns a worker to resume the session with its pinned memory and folded context intact.
110
110
 
111
111
  ### 3. Progressive Skills (`skills/`)
112
- Skills are portable capability bundles stored in `./skills/<name>/SKILL.md` (or `./skills/<name>.md`) with YAML frontmatter (including optional `verify` gates) and an optional sibling `tools.py`:
112
+ Skills are portable capability bundles stored in `./skills/<name>/SKILL.md` (or `./skills/<name>.md`) with YAML frontmatter (including optional `verify` gates) and an optional sibling `tools.py` (every `@tool` in it must also be listed in `tools:`, otherwise it is unavailable and loading the skill emits a `UserWarning`):
113
113
  ```markdown
114
114
  ---
115
115
  name: researcher
@@ -88,7 +88,7 @@ Every Tether execution is a stateful session persisted in `.tether/tether.db` (S
88
88
  - If **`WAITING_INPUT` / `COMPLETED` / `IDLE`**: appends the message and automatically spawns a worker to resume the session with its pinned memory and folded context intact.
89
89
 
90
90
  ### 3. Progressive Skills (`skills/`)
91
- Skills are portable capability bundles stored in `./skills/<name>/SKILL.md` (or `./skills/<name>.md`) with YAML frontmatter (including optional `verify` gates) and an optional sibling `tools.py`:
91
+ Skills are portable capability bundles stored in `./skills/<name>/SKILL.md` (or `./skills/<name>.md`) with YAML frontmatter (including optional `verify` gates) and an optional sibling `tools.py` (every `@tool` in it must also be listed in `tools:`, otherwise it is unavailable and loading the skill emits a `UserWarning`):
92
92
  ```markdown
93
93
  ---
94
94
  name: researcher
@@ -58,6 +58,7 @@ Tether implements a deterministic three-tier memory hierarchy backed by `.tether
58
58
  ```text
59
59
  [obs_1 folded: `web fetch https://...` (3,420 chars) | use `mem recall obs_1` or `mem search <query>`]
60
60
  ```
61
+ Exception (#69): the latest read of each of the last 8 files (`edit view <p>`, `bash cat/head/tail/sed -n <p>`; paths normalized against the workspace) is not folded until `<p>` is edited (`edit write|replace`, or any other bash command naming it: `sed -i`, `tee`, redirects, `python -c`) or re-read. The 2,400-char head+tail elision still applies, and the hard context budget remains #81. The `mem recall` hint is shown only when `mem` is enabled (else "re-run the command").
61
62
  3. **Sub-Millisecond SQLite FTS5 BM25 RAG**: Every raw observation and final deliverable is automatically chunked (~150 tokens per paragraph/section) and indexed into an FTS5 virtual table (`chunks_fts`) so the agent (or a parent agent) can retrieve exact passages via `mem search` or `agent query`.
62
63
 
63
64
  ---
@@ -74,8 +75,8 @@ Local inference engines (`Ollama`, `llama.cpp`, `vLLM`) and cloud providers (`An
74
75
  ### Deterministic Loop-Stall Breaker & Pre-`done` Verification Gates
75
76
  Small 8B/9B models occasionally fall into a probability trap where they emit the exact same command (`web search "..."` or `bash ...`) twice in a row, or prematurely call `done` while tests or linters are failing:
76
77
 
77
- - **Loop-Stall Breaker:** `Harness.execute_session` tracks the `(cmd, stdin)` signature of the previous step and intercepts consecutive duplicates before executing the tool, returning a `[LOOP DETECTED]` nudge that forces the model to advance.
78
- - **Pre-`done` Verification Gates:** When configured via `Harness(verify=[...])`, CLI `--verify`, or Skill YAML `verify: [...]`, Tether intercepts `done` and runs each verification command inside the session's `SandboxPolicy`. It consumes **0 static prompt tokens** unless a gate exits non-zero—at which point termination is rejected and a compact `[VERIFICATION GATE FAILED]` observation is injected so the model can self-heal.
78
+ - **Loop-Stall Breaker:** `Harness.execute_session` tracks the `(cmd, stdin)` signature of the previous step and intercepts consecutive duplicates before executing the tool, returning a `[LOOP DETECTED]` nudge that forces the model to advance. A companion cycle detector (`detect_cycle`) catches a 2-command cycle repeated 3 times or a 3-command cycle repeated twice (6 actions, within the last 8), skips the repeated command, nudges with `[CYCLE DETECTED]` naming the commands, and records `cycle_stall`.
79
+ - **Pre-`done` Verification Gates:** When configured via `Harness(verify=[...])`, CLI `--verify`, or Skill YAML `verify: [...]`, Tether intercepts `done` and runs each verification command inside the session's `SandboxPolicy`. It consumes **0 static prompt tokens** unless a gate exits non-zero—at which point termination is rejected and a compact `[VERIFICATION GATE FAILED]` observation is injected so the model can self-heal. A gate that errors rather than fails (sandbox violation, exit 124/126/127) ends the session `FAILED` immediately as `gate_error` instead of consuming retries.
79
80
 
80
81
  ### Connection Pooling & Thread-Local SQLite WAL Reuse
81
82
  - Model providers reuse persistent `httpx.AsyncClient` connection pools across all turns in a session, eliminating repeated TCP/TLS handshakes.
@@ -15,7 +15,7 @@ Machine-readable surface for calling Tether from another agent. Setup per harnes
15
15
  - `failure` is `{"kind": "gate|max_steps|worker_error", "gate": "<cmd>|output-schema|null", "message": "…"}`.
16
16
  - Keys are stable; new keys may be added.
17
17
 
18
- `--json` is available on `run`, `pipeline`, `status`, `list`, `read` (also `--digest`, `--steps`), `wait`, `send` and `stats`. With `--headless` it prints `{"v":1,"id":"ses_…"}`.
18
+ `--json` is available on `run`, `pipeline`, `status`, `list`, `read` (also `--digest`, `--steps`, `--full`), `wait`, `send` and `stats`. With `--headless` it prints `{"v":1,"id":"ses_…"}`.
19
19
 
20
20
  ## Exit codes
21
21
 
@@ -24,7 +24,7 @@ Machine-readable surface for calling Tether from another agent. Setup per harnes
24
24
  | 0 | completed |
25
25
  | 1 | usage error (bad flag, unknown session, invalid pipeline) |
26
26
  | 2 | waiting for input (`done --ask`) |
27
- | 3 | verification gate failed (including `--output-schema`) |
27
+ | 3 | verification gate failed or misconfigured (including `--output-schema`) |
28
28
  | 4 | max steps reached |
29
29
  | 5 | worker or provider error |
30
30
 
@@ -22,6 +22,7 @@ When the provider sets `structured_history = True` (Ollama, OpenAI-compatible, A
22
22
 
23
23
  Raw tool outputs stay in the prompt for $K=2$ turns, then fold into a one-line tombstone (see [Built-in Tools](../tutorial/built-in-tools.md#mem-pinned-scratchpad-bm25-observation-recall)). For small models this has consequences ([#27](https://github.com/igormcsouza/tether/issues/27)), seen in the [course-builder example](https://github.com/igormcsouza/tether/tree/main/examples/course):
24
24
 
25
+ - **Fix (#69).** Folding exception: the latest read of each of the last 8 files (`edit view <p>`, `bash cat/head/tail/sed -n <p>`; paths normalized against the workspace) is not folded until `<p>` is edited (`edit write|replace`, or any other bash command naming it: `sed -i`, `tee`, redirects, `python -c`) or re-read. The 2,400-char head+tail elision still applies, and the hard context budget remains #81. The `mem recall` hint is shown only when `mem` is enabled (else "re-run the command"). (`ContextAssembler(mem_enabled=..., workspace_root=...)`, `MAX_LIVE_READS`).
25
26
  - **Re-viewing files loops, then invents data.** The model runs `edit view`, the output folds two turns later, it views again, and eventually writes from memory.
26
27
  - **What worked: one compact joined view, used right before the write, that ends with the exact next call.** The `course-source <module>` tool returns a module's plan and chosen videos in one output and finishes with `NOW WRITE THIS MODULE: act("edit write part-N.md", ...)`, so the data is still in the prompt when the write happens.
27
28
  - **Write long outputs in small pieces.** One big write dropped a whole section. The writer skill writes one `part-N.md` per module with `edit write`, then a `course-build` tool concatenates them into `course.md`.
@@ -0,0 +1,17 @@
1
+ # Multiplexer and Decorator
2
+
3
+ ## `tether.multiplexer`
4
+
5
+ A command that is not enabled returns a self-healing error (`unknown_cmd`, no exception) listing the enabled tools and how to enable more (`--tools`, SDK `tools=[...]`, skill `tools:`). A known built-in that is merely disabled (`bash`, `web`, `edit`, `mem`, `skill`, `agent`) is reported as disabled, not unknown. The text lives in the error response (history), never in the system prompt prefix.
6
+
7
+ ::: tether.multiplexer
8
+ options:
9
+ show_root_heading: false
10
+ members_order: source
11
+
12
+ ## `tether.decorator`
13
+
14
+ ::: tether.decorator
15
+ options:
16
+ show_root_heading: false
17
+ members_order: source
@@ -46,16 +46,20 @@ tether run --model claude-cli/sonnet "fix the failing test"
46
46
 
47
47
  Every entry, of any kind, may set two optional positive integers: `context_tokens` (the context budget, default 8192) and `fold_after` (how many recent turns keep full observations, default 2). The `--context-tokens` / `--fold-after` flags and the Harness arguments override them.
48
48
 
49
+ ### Timeout and retry
50
+
51
+ Every entry may set `timeout` (positive seconds). It applies to the HTTP kinds (`openai`, `ollama`, `anthropic`; defaults 120 / 120 / 180 s) and to `command` (default 300 s). The HTTP providers retry automatically, at most 2 times with 1 s then 2 s backoff, on timeouts, network errors and HTTP 429/500/502/503/504. A 401/403 is never retried: it raises `ProviderError` naming the provider and the credential variable (`openai: HTTP 401: `NVIDIA_API_KEY` is missing or rejected; set it to a valid key`; the value is never printed), and a declared `api_key_env` that is unset or empty fails the session before any request with the same message. Other 4xx responses are never retried. When retries run out the call raises `ProviderError`, so the session fails with a readable `Worker error: ProviderError: openai model `gpt-4o` unavailable after 3 attempts (timeout 120s, last error: ReadTimeout)`. The retry is invisible to the prompt (0 tokens, no prefix change).
52
+
49
53
  ### Kinds
50
54
 
51
55
  | `kind` | Fields |
52
56
  |---|---|
53
- | `openai` | `base_url`, `api_key_env` |
54
- | `ollama` | `host` |
55
- | `anthropic` | `base_url`, `api_key_env` |
57
+ | `openai` | `base_url`, `api_key_env`, optional `timeout` |
58
+ | `ollama` | `host`, optional `timeout` |
59
+ | `anthropic` | `base_url`, `api_key_env`, optional `timeout` |
56
60
  | `command` | `argv` (list), `reply` (JSON path), optional `stdin` template, optional `usage`, optional `timeout` (seconds, default 300) |
57
61
 
58
- `api_key_env` is the **name** of an environment variable, never the key itself.
62
+ `api_key_env` is the **name** of an environment variable, never the key itself. A declared `api_key_env` must be set and non-empty (otherwise the session fails before any request); omit it to declare a keyless local server. A 403 reads `access denied (HTTP 403)` with the first 120 characters of the response body instead of "missing or rejected".
59
63
 
60
64
  ### The `command` kind
61
65
 
@@ -1,5 +1,45 @@
1
1
  # Release Notes
2
2
 
3
+ ## v0.2.4 (2026-10-21)
4
+
5
+ Dogfooding fixes: the four milestone bugs (#74 #75 #85 #88) plus the friction found by running Tether on its own backlog with `nvidia/nemotron-3-ultra-550b-a55b` (two of the fixes were first written by Tether itself from scoped briefs), and two new regression layers: golden-trace replay in CI and 54 local end-to-end scenarios.
6
+
7
+ ### Features
8
+
9
+ - Tests: local end-to-end scenarios (`e2e/`, `TETHER_E2E=1 uv run pytest e2e`): the real CLI against a fault-injecting proxy in front of ollama; 54 scenarios cover the Golden Rules, lifecycle and exit codes, gates, faults (429, trickling hang, dead worker, auth), the sandbox, small-model quirks, sub-agents and pipelines, the OpenAI/Anthropic/command providers, MCP, web, skills' own tools, telemetry, `--no-network` and the context budget. Hard assertions are about tether's behaviour, so they hold for any model. See `docs/testing.md`.
10
+ - Tests: golden-trace replay (`tests/golden/`, `tests/test_golden.py`, `scripts/record_golden.py`). Recorded model steps run through the real harness and tools; the exact messages, events, tokens and files are snapshotted, so any behaviour change shows as a reviewable diff. See `docs/testing.md`.
11
+ - Edit: `edit replace <path>` also takes `<<<<<<< SEARCH` / `=======` / `>>>>>>> REPLACE` blocks in `stdin` (the format models know from Aider): exact-text match, each SEARCH must hit exactly once, all blocks apply atomically, no line numbers. A missing final `>>>>>>> REPLACE` is tolerated (models stop after the new text); a truncated block in the middle gets a specific error. Line-numbered `--lines` still works. Motivation: `cat | sed` reads carry no line numbers, so models re-read ranges forever trying to count lines.
12
+ - Harness: deterministic one-shot `[NUDGE]` appended to the observation after 6 consecutive read-only/verification steps following a file change, reminding the model to call `done` if the task is complete (0 static prompt tokens, #74).
13
+
14
+ ### Fixes
15
+
16
+ - Store: a `RUNNING` session whose worker pid no longer exists is flipped to `FAILED` (`Worker died (pid N) before finishing`) when read via `get_session`/`list_sessions`, so `status`, `list`, `wait` and `read` no longer report a killed worker as running forever (#85). A pid owned by another user counts as alive.
17
+ - Providers: `429` retries wait `Retry-After` (else 10s doubling, capped 30s) and get up to 5 attempts instead of 3 quick ones, so rate-limited hosted models (NVIDIA free tier) no longer fail the session after 3 quick attempts.
18
+ - Providers: each `post_with_retry` attempt is now bounded by a total `asyncio.wait_for(timeout)` deadline, so a trickling/stalled response cannot hang past the configured timeout; the deadline is retried like other transient errors and shows as `TimeoutError` (vs `ReadTimeout`) in the final `ProviderError` (#88).
19
+ - Harness: `max_steps` is now the absolute session ceiling. `send` on a session that used its steps extends the ceiling to `step_count + <Harness budget>`, and a session paused earlier keeps its remaining budget, so `status` never shows `step_count` above `max_steps` (e.g. `46/40`) (#75).
20
+ - Context: unfolded reads are tracked per path **and line range**. Viewing `f:55:65` used to evict the live `f:65:120` read, so the model ping-ponged between two ranges forever (found with `nvidia/nemotron-3-ultra-550b-a55b`, #69 follow-up).
21
+ - Context: the unfolded-read window is now the last 8 files (was 4); a model cycling through 5 files evicted each read just before needing it and re-read forever (#69 follow-up).
22
+ - Multiplexer: a trailing heredoc in `cmd` (`edit replace f --lines 1:1 <<'EOF'`) is now passed as `stdin` instead of failing with `requires replacement block in stdin` (never for `bash`). Found dogfooding `nvidia/nemotron-3-ultra-550b-a55b`, which never filled the `stdin` parameter in 150+ steps.
23
+ - Edit: `edit replace|write` on a `.py` file that stops parsing (e.g. a one-line replacement that lost its indentation) now ends the tool result with `WARNING: ... no longer parses: <msg> at line N. Fix it now.` so the model self-heals next step instead of discovering it ten steps later via pytest. 0 prompt tokens.
24
+
25
+ ## v0.2.3 (2026-10-20)
26
+
27
+ Dogfooding fixes: the issues came from running Tether with a small model (GLM-5.3) on its own backlog.
28
+
29
+ ### Features
30
+
31
+ - `tether read` now accepts `--full` (the full final result, already its default) so the `agent read <id> --full` form works on the CLI too; `--digest` still gives the compact card (#72).
32
+
33
+ ### Fixes
34
+
35
+ - Verification gate errors (sandbox violation, exit 124/126/127) are now told apart from gate failures: the session fails immediately with a `Verification gate ... misconfigured: <reason>` result (exit code 3) instead of burning the 3-failure abort budget, and `tether stats` shows a `GATE_ERR` column (#33).
36
+ - `edit write` over an existing file now returns a deterministic summary (`Replaced N lines with M (X removed, Y added; first removed: ...)`) and, when the file shrank, points to `edit replace --lines a:b` (#40).
37
+ - Skills now warn (`UserWarning`, 0 prompt tokens) at load when `tools.py` defines a `@tool` not listed in the skill `tools:` (#24).
38
+ - Repeated multi-command cycles (e.g. `cat` / `sed` / `pytest`, or two alternating `sed -n` ranges) now trigger a deterministic `[CYCLE DETECTED]` nudge and a new `cycle_stall` event type, shown in `tether stats` as `CYCLES` (#39).
39
+ - HTTP providers now retry timeouts, network errors and 429/5xx twice with backoff, honour `timeout` from `providers.yaml` for all kinds, and fail with a `ProviderError` naming provider, model and timeout instead of a bare `ReadTimeout` (#70).
40
+ - Observation folding no longer makes small models re-read files in a loop: the latest read (`edit view <p>` / `bash cat|head|tail|sed -n <p>`) of each of the last 4 files stays unfolded until that path is edited (`edit write|replace`, or a bash write such as `sed -i`/`tee`) or re-read, and the fold/elision hint only mentions `mem recall` when `mem` is enabled (otherwise "re-run the command"). Static prompt prefix unchanged (#69).
41
+ - Clearer errors (#71): a provider 401/403 now fails with `ProviderError` naming the provider and the `api_key_env` / default key variable as missing or rejected (never its value), a declared `api_key_env` that is empty fails before the first request, and `unknown command` for a built-in tool that is not enabled says it is disabled and how to enable it (`--tools`, `tools=[...]`, skill `tools:`). Static prompt prefix unchanged.
42
+
3
43
  ## v0.2.2 (2026-10-19)
4
44
 
5
45
  Dogfooding fixes: Tether implemented most of this release itself through the CLI and a `claude -p` provider.
@@ -0,0 +1,84 @@
1
+ # Testing: golden traces
2
+
3
+ Tether is a deterministic envelope around a random model, so a change to framing, folding,
4
+ nudges, tools or retries can silently change what the model is shown. Golden traces make that
5
+ visible without a model, a network or any judgment.
6
+
7
+ Each `tests/golden/<name>.json` is a scripted session: workspace files, stubbed network
8
+ tools, the model's recorded steps and optional `send` phases. `tests/test_golden.py` replays
9
+ it through the **real** harness, multiplexer, `edit`/`done`/`mem` tools, context assembly and
10
+ folding, and compares everything against `<name>.snap.json`: the exact messages sent at every
11
+ step, event types, token estimates, final status/result and final workspace files.
12
+
13
+ - A PR that changes nothing the model sees keeps every snapshot green. A PR that does shows a
14
+ JSON diff to approve or reject in review.
15
+ - Intended change: `UPDATE_GOLDEN=1 uv run pytest tests/test_golden.py`, then review the diff.
16
+ - Record a real run: `python scripts/record_golden.py <db> <session_id> tests/golden/<name>.json --stub web`
17
+ (trim or sanitise third-party text first).
18
+
19
+ Shipped traces: a real researcher run (web stubbed, plain-text `done` nudges), a synthetic
20
+ coding run covering the edit failure modes seen with small models, and a `done --ask` /
21
+ `send` resume, and a range ping-pong (alternating views of one file; the latest read of each range stays unfolded, older ones fold). Add one for every behaviour that must not regress.
22
+
23
+ ## End-to-end scenarios (local, real processes, real ollama)
24
+
25
+ `e2e/` drives the real `tether` CLI in a throwaway workspace. A small fault-injecting proxy
26
+ (`e2e/proxy.py`) sits between tether and ollama, so a scenario can force the situation instead of
27
+ hoping a model produces it: answer `429`/`503`, trickle a never-ending body, stall, or **script the
28
+ reply** (a fixed tool call, no model). Scenarios assert on what tether sent and recorded (requests,
29
+ events, DB state, exit codes, files), so they hold for any model. Task success with a real model is
30
+ only a *soft* signal (a warning), never a gate.
31
+
32
+ ```bash
33
+ TETHER_E2E=1 uv run pytest e2e # ollama at localhost:11435 by default
34
+ TETHER_E2E_OLLAMA=http://localhost:11434 TETHER_E2E_MODELS=qwen3.5:9b,gemma4:e4b uv run pytest e2e
35
+ ```
36
+
37
+ It is local only (needs ollama, never runs in CI) and skips itself when `TETHER_E2E` is unset or
38
+ ollama does not answer. A scenario tagged *model* runs once per model in `TETHER_E2E_MODELS`;
39
+ *scripted* ones do not use the model at all, so they take a second.
40
+
41
+ | Feature / bug | Scenario | Kind |
42
+ |---|---|---|
43
+ | Zero optional tools by default, single `act` schema (Rule 2) | `test_defaults::no_optional_tools_by_default` | model |
44
+ | One short index line per enabled tool (Rule 4) | `enabled_tools_are_one_short_line_each` | model |
45
+ | Static prefix, KV-cache immutability (Rule 1) | `prefix_is_immutable_across_steps` | model |
46
+ | Static framing within the token budget (Rule 1/4) | `static_framing_fits_the_token_budget` (ollama's own `prompt_eval_count`) | model |
47
+ | Disabled built-in teaches how to enable it; JIT `--help` (Rule 4) | `disabled_builtin_tool_error...`, `missing_args_return_compact_help` | scripted |
48
+ | `done`, `done --ask` + `send`, exit codes 0/2/3/4/5 | `test_lifecycle` | scripted |
49
+ | `max_steps` is an absolute ceiling, resume never overshoots (#75) | `max_steps_exit_4_and_resume...` | scripted |
50
+ | Verify gates: fail + circuit breaker, broken gate = `gate_error` (#33), pass | `gate_*`, `broken_gate_*`, `passing_gate_*` | scripted |
51
+ | `--output-schema` rejects then accepts | `output_schema_rejects_then_accepts` | scripted |
52
+ | Headless run / wait / read / status | `headless_run_wait_read_status` | scripted |
53
+ | `429` retried, honoring `Retry-After` (#98) | `test_faults::429_is_retried...` | model |
54
+ | Persistent 5xx fails loudly, exit 5 (#70) | `persistent_5xx_fails_loudly...` | scripted |
55
+ | Trickling response hits the per-attempt deadline (#88) | `trickling_response_hits_the_per_attempt_deadline` | scripted |
56
+ | Auth error names the variable, never the value (#71) | `auth_error_names_the_variable...` | scripted |
57
+ | Killed worker reported `FAILED`, not `RUNNING` (#85) | `killed_worker_is_reported_failed...` | scripted |
58
+ | Sandbox: no writes outside the workspace, protected files, env scrub, SSRF guard, read-only profile (Rule 6) | `test_sandbox` | scripted |
59
+ | Heredoc in `cmd` becomes `stdin` (#97), bare shell command redirected to `bash`, `$`/backtick wrappers, literal `\n` | `test_quirks` | scripted |
60
+ | Plain-text reply nudged, never completes; loop and cycle stalls (#39) | `plain_text_reply...`, `identical_repeat...`, `two_command_cycle...` | scripted |
61
+ | `.py` parse warning, SEARCH/REPLACE blocks (#100) | `lost_indentation...`, `search_replace_block...` | scripted |
62
+ | One done-nudge after a quiet stretch (#74) | `quiet_stretch_after_a_change...` | scripted |
63
+ | Folding to a recallable tombstone (Rule 5), per-range reads (#99) | `old_observations_fold...`, `two_ranges_of_one_file...` | scripted |
64
+ | `mem` pin/search; skills enable tools without touching the schema | `mem_pins_...`, `skill_enables_its_tools...` | scripted |
65
+ | Sub-agents: spawn is async, `wait` digest is compact, `read --full`, `query`, `send` resumes a finished child | `test_agents::agent_*` | scripted |
66
+ | Child sandbox never wider than its parent | `child_sandbox_is_never_wider_than_the_parent` | scripted |
67
+ | Pipelines: stages in order, upstream digest reaches the next stage, `act` stays the only schema | `pipeline_runs_stages_in_order...` | scripted |
68
+ | OpenAI and Anthropic wire formats, declared `command` provider | `test_surface::*_wire_format`, `declared_command_provider` | scripted |
69
+ | `web search` via `SEARXNG_URL`, `web fetch` (private hosts allowed explicitly) | `web_search_uses_searxng_override`, `web_fetch_reads_a_page...` | scripted |
70
+ | A skill's own `tools.py`: listed tool runs, unlisted stays unknown and warns | `skill_tools_py_runs_when_listed`, `skill_tool_not_listed...` | scripted |
71
+ | `stats` / `events` / `misses`; `retro` never edits a skill without `--apply` | `stats_events_and_misses...`, `retro_proposes_rules...` | scripted |
72
+ | MCP server over stdio runs a session | `mcp_server_runs_a_session_over_stdio` | scripted |
73
+ | `--no-network` cuts what the default allows (with a control), hard context budget trims old history (with a control) | `test_limits::*` | scripted |
74
+
75
+ **Not covered yet** (add a scenario when touching them): the `claude`/`vllm` flavours of the HTTP
76
+ providers beyond the wire format, Anthropic prompt caching, `web fetch` against the real SSRF
77
+ list beyond loopback and metadata, `tether pipeline` verify gates and per-stage model routing,
78
+ `mem pin --project` across sessions, `retro --apply`, and the real-model-only checks that need a
79
+ judge (answer quality).
80
+
81
+ **Merge gate (manual, because CI cannot run it):** before merging a PR run `TETHER_E2E=1 uv run pytest e2e`, paste the summary into the PR, and mitigate every failure: fix the code, fix a wrong scenario, or write down in the PR why it is acceptable and what follows up. No unexplained red scenario is merged.
82
+
83
+ **Rule:** a new feature or bug fix ships with a scenario here. If a model must misbehave to
84
+ trigger it, script the reply; if infrastructure must fail, add a fault to the proxy.
@@ -6,16 +6,18 @@ Symptom, cause, fix. Most entries come from building the [course-builder example
6
6
  |---|---|---|
7
7
  | Model keeps running `edit view` on the same file, then writes invented data | Old observations fold after 2 turns ([#27](https://github.com/igormcsouza/tether/issues/27)) | Give it one compact tool view right before the write, ending with the exact next call. See [Context and SQLite Store](reference/context-and-store.md#observation-folding-and-small-models) |
8
8
  | A long `edit write` is missing a whole section | One big generation drops content | Write one small file per section (`part-N.md`) and join them with a tool. [Same page](reference/context-and-store.md#observation-folding-and-small-models) |
9
- | Earlier file content vanished after a second `edit write` | `edit write` replaces the whole file ([#34](https://github.com/igormcsouza/tether/issues/34)) | Tell the model to rewrite the complete file, or use `edit replace --lines`. See [Built-in Tools](tutorial/built-in-tools.md#example-course-builder) |
9
+ | Earlier file content vanished after a second `edit write` | `edit write` replaces the whole file ([#34](https://github.com/igormcsouza/tether/issues/34)) | `edit write` now reports what an overwrite removed (#40). Tell the model to rewrite the complete file, or use `edit replace --lines`. See [Built-in Tools](tutorial/built-in-tools.md#example-course-builder) |
10
10
  | Files contain a literal `\n` | Model sent escaped newlines in stdin | Fixed in v0.1.2 for `edit write`, `edit replace` and `done` |
11
11
  | Tool with multi-word argument fails (`unexpected keyword argument`, wrong value) | Unquoted words were not joined | Fixed in v0.1.2. See [argument parsing](tutorial/sdk-and-custom-tools.md#argument-parsing-v012) |
12
- | `Error: unknown command` for a tool defined in `tools.py` | The tool is not in the skill `tools:` list ([#24](https://github.com/igormcsouza/tether/issues/24)) | Add its name to `tools:`. See [Skills](tutorial/skills.md#tools-py-and-the-tools-list) |
12
+ | `Error: unknown command` for a tool defined in `tools.py` | The tool is not in the skill `tools:` list ([#24](https://github.com/igormcsouza/tether/issues/24)) | Add its name to `tools:` (loading the skill emits a `UserWarning` naming unlisted tools). See [Skills](tutorial/skills.md#tools-py-and-the-tools-list) |
13
+ | `Worker error: ProviderError: ... `NVIDIA_API_KEY` is missing or rejected` | The provider answered 401/403, or the `api_key_env` variable is unset/empty ([#71](https://github.com/igormcsouza/tether/issues/71)) | Export a valid key under that variable name (Tether prints the name, never the value). An empty `api_key_env` variable fails the session before any request |
14
+ | `Error: \`bash\` is disabled` / `unknown command` | The tool is not enabled for this session; the message lists the enabled tools | Add it with `--tools bash,edit`, SDK `tools=[...]` or the skill `tools:` list ([#71](https://github.com/igormcsouza/tether/issues/71)) |
13
15
  | Model answers are not understood (many nudges in `tether stats`) | The reply format is not parsed into an action | Run `tether misses`, then add a dialect: paste the lines into `tests/fixtures/dialects.jsonl` or add a `dialects:` regex to `providers.yaml` |
14
16
  | Gate with inline `awk`/`sed` is blocked as "absolute path outside workspace" | Fixed in v0.1.4 ([#31](https://github.com/igormcsouza/tether/issues/31)); older versions saw a program starting with `/` as a path | Upgrade, or move the logic into a script (`python3 checks.py ...`). See [Security and Sandbox](tutorial/security-and-sandbox.md#example-awksed-programs-that-start-with) |
15
17
  | `Ollama model … not found; installed: …` | The model name is not pulled; Tether no longer substitutes another one ([#21](https://github.com/igormcsouza/tether/issues/21)) | `ollama pull <model>`, fix the name, or set `TETHER_OLLAMA_FALLBACK=1` to opt in to a substitute |
16
18
  | `tether mcp` prints "MCP support is not installed" | The optional extra is missing | `pip install "tether-sdk[mcp]"`. See [Use Tether Inside Your Harness](tutorial/agent-interface.md) |
17
19
  | Gate exits 0 although work is missing | Missing input file means nothing to check | Make the gate fail closed. See [Gates](tutorial/workflows-and-telemetry.md#gates) |
18
- | Session `FAILED` after 3 gate failures, none of them real | A gate environment error counts as a failure ([#33](https://github.com/igormcsouza/tether/issues/33)) | Run the gate command by hand first |
20
+ | Session `FAILED` with `Verification gate ... misconfigured` | The gate itself errored (sandbox violation, command not found, timeout), not your work; it fails fast and shows as `gate_error` in `tether stats` | Fix the gate command and run it by hand first |
19
21
  | Model repeats a tool call with a rejected value | Gate or tool message says what failed, not what to do | Return the valid options and the next call in the message. See [Custom Tools](tutorial/sdk-and-custom-tools.md#example-course-builder) |
20
22
  | A pipe-delimited file gets extra or shifted columns | A delimiter (` | `) appeared inside a value | Sanitize values in the tool that writes the file |
21
23
  | Pipeline stage ends "Max steps reached" | `max_steps` too small for the work ([#35](https://github.com/igormcsouza/tether/issues/35)) | Raise `max_steps` (pipeline or stage), or re-run the stage's skill by hand. See [Pipelines](tutorial/workflows-and-telemetry.md#pipelines) |
@@ -23,7 +25,7 @@ Symptom, cause, fix. Most entries come from building the [course-builder example
23
25
 
24
26
  ## Known limitations
25
27
 
26
- - [#24](https://github.com/igormcsouza/tether/issues/24) skill `tools.py` tools must also be listed in `tools:`.
28
+ - [#24](https://github.com/igormcsouza/tether/issues/24) skill `tools.py` tools must also be listed in `tools:` (a `UserWarning` is emitted at load otherwise).
27
29
  - [#27](https://github.com/igormcsouza/tether/issues/27) observation folding hurts small models.
28
30
  - [#32](https://github.com/igormcsouza/tether/issues/32) model reasoning is not in the trace.
29
31
  - [#33](https://github.com/igormcsouza/tether/issues/33) gate infrastructure errors count toward the 3-failure abort.
@@ -82,7 +82,7 @@ act(cmd="mem recall obs_3")
82
82
 
83
83
  The [course-builder example](https://github.com/igormcsouza/tether/tree/main/examples/course) uses `edit` and custom tools to turn a plan into a course. Three limits shaped it:
84
84
 
85
- - **`edit write` replaces the whole file.** Small models assume it appends and silently lose earlier content. The planner skill says so explicitly: "write the COMPLETE plan in ONE call". (An `edit append` is proposed in [#34](https://github.com/igormcsouza/tether/issues/34).)
85
+ - **`edit write` replaces the whole file.** Overwriting an existing file now reports `Replaced N lines with M (X removed, Y added; first removed: ...)` and, when the file shrank, points to `edit replace --lines a:b` ([#40](https://github.com/igormcsouza/tether/issues/40)). Small models assume it appends and silently lose earlier content. The planner skill says so explicitly: "write the COMPLETE plan in ONE call". (An `edit append` is proposed in [#34](https://github.com/igormcsouza/tether/issues/34).)
86
86
  - **`web search` cannot reliably find a specific YouTube video.** Use a [custom tool](sdk-and-custom-tools.md#pattern-numbered-candidates) that returns numbered candidates instead.
87
87
  - **Tools are opt-in.** The searcher skill lists `tools: [yt-todo, yt-candidates, yt-save]` and nothing else; `edit` is only loaded by the stages that write files.
88
88
 
@@ -63,9 +63,12 @@ tether status "$SESSION_ID"
63
63
  # Block until the session completes and print a compact ~50-token digest
64
64
  tether wait "$SESSION_ID"
65
65
 
66
- # Read the full final deliverable
66
+ # Read the full final deliverable (`--full` is optional: it is the default)
67
67
  tether read "$SESSION_ID"
68
68
 
69
+ # Or just the compact digest
70
+ tether read "$SESSION_ID" --digest
71
+
69
72
  # Inspect the step-by-step action log
70
73
  tether read "$SESSION_ID" --steps
71
74
 
@@ -91,7 +91,7 @@ verify:
91
91
 
92
92
  ### <a id="tools-py-and-the-tools-list"></a>`tools.py` and the `tools:` list
93
93
 
94
- Defining a tool in `tools.py` is not enough: name it in the skill `tools:` as well, or the model gets `unknown command` ([#24](https://github.com/igormcsouza/tether/issues/24)). See [Python SDK and Custom Tools](sdk-and-custom-tools.md).
94
+ Defining a tool in `tools.py` is not enough: name it in the skill `tools:` as well, or the model gets `unknown command` ([#24](https://github.com/igormcsouza/tether/issues/24)). A skill's own custom tools are opt-in like built-in ones, so list them in `tools:`. Loading a skill whose `tools.py` defines an unlisted `@tool` emits a `UserWarning` naming it (costs no prompt tokens). See [Python SDK and Custom Tools](sdk-and-custom-tools.md).
95
95
 
96
96
  ### Let the model decide, let code enforce hard requirements
97
97