tether-sdk 0.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. tether_sdk-0.0.2/.github/workflows/ci.yml +45 -0
  2. tether_sdk-0.0.2/.github/workflows/delivery.yml +69 -0
  3. tether_sdk-0.0.2/.github/workflows/docs.yml +46 -0
  4. tether_sdk-0.0.2/.gitignore +30 -0
  5. tether_sdk-0.0.2/AGENTS.md +173 -0
  6. tether_sdk-0.0.2/LICENSE +20 -0
  7. tether_sdk-0.0.2/PKG-INFO +346 -0
  8. tether_sdk-0.0.2/README.md +327 -0
  9. tether_sdk-0.0.2/docs/architecture.md +88 -0
  10. tether_sdk-0.0.2/docs/benchmarks.md +31 -0
  11. tether_sdk-0.0.2/docs/extending.md +94 -0
  12. tether_sdk-0.0.2/docs/index.md +121 -0
  13. tether_sdk-0.0.2/docs/javascripts/mathjax.js +19 -0
  14. tether_sdk-0.0.2/docs/reference/context-and-store.md +15 -0
  15. tether_sdk-0.0.2/docs/reference/harness.md +15 -0
  16. tether_sdk-0.0.2/docs/reference/index.md +13 -0
  17. tether_sdk-0.0.2/docs/reference/multiplexer-and-decorator.md +15 -0
  18. tether_sdk-0.0.2/docs/reference/providers.md +36 -0
  19. tether_sdk-0.0.2/docs/reference/sandbox-and-skills.md +15 -0
  20. tether_sdk-0.0.2/docs/reference/tools.md +43 -0
  21. tether_sdk-0.0.2/docs/release-notes.md +23 -0
  22. tether_sdk-0.0.2/docs/releasing.md +19 -0
  23. tether_sdk-0.0.2/docs/tutorial/built-in-tools.md +72 -0
  24. tether_sdk-0.0.2/docs/tutorial/cli-and-headless.md +90 -0
  25. tether_sdk-0.0.2/docs/tutorial/index.md +10 -0
  26. tether_sdk-0.0.2/docs/tutorial/multi-agent.md +70 -0
  27. tether_sdk-0.0.2/docs/tutorial/sdk-and-custom-tools.md +68 -0
  28. tether_sdk-0.0.2/docs/tutorial/security-and-sandbox.md +59 -0
  29. tether_sdk-0.0.2/docs/tutorial/skills.md +67 -0
  30. tether_sdk-0.0.2/mkdocs.yml +121 -0
  31. tether_sdk-0.0.2/pyproject.toml +69 -0
  32. tether_sdk-0.0.2/skills/researcher/SKILL.md +10 -0
  33. tether_sdk-0.0.2/tests/__init__.py +1 -0
  34. tether_sdk-0.0.2/tests/conftest.py +29 -0
  35. tether_sdk-0.0.2/tests/providers/__init__.py +1 -0
  36. tether_sdk-0.0.2/tests/providers/test_anthropic.py +61 -0
  37. tether_sdk-0.0.2/tests/providers/test_base.py +101 -0
  38. tether_sdk-0.0.2/tests/providers/test_ollama.py +97 -0
  39. tether_sdk-0.0.2/tests/providers/test_openai.py +56 -0
  40. tether_sdk-0.0.2/tests/test_cli.py +99 -0
  41. tether_sdk-0.0.2/tests/test_context.py +143 -0
  42. tether_sdk-0.0.2/tests/test_decorator.py +140 -0
  43. tether_sdk-0.0.2/tests/test_harness.py +155 -0
  44. tether_sdk-0.0.2/tests/test_integration.py +167 -0
  45. tether_sdk-0.0.2/tests/test_multiplexer.py +41 -0
  46. tether_sdk-0.0.2/tests/test_sandbox.py +127 -0
  47. tether_sdk-0.0.2/tests/test_skills.py +80 -0
  48. tether_sdk-0.0.2/tests/test_store.py +51 -0
  49. tether_sdk-0.0.2/tests/test_tether.py +12 -0
  50. tether_sdk-0.0.2/tests/tools/__init__.py +1 -0
  51. tether_sdk-0.0.2/tests/tools/test_agent.py +84 -0
  52. tether_sdk-0.0.2/tests/tools/test_bash.py +19 -0
  53. tether_sdk-0.0.2/tests/tools/test_done.py +24 -0
  54. tether_sdk-0.0.2/tests/tools/test_edit.py +45 -0
  55. tether_sdk-0.0.2/tests/tools/test_mem.py +38 -0
  56. tether_sdk-0.0.2/tests/tools/test_web.py +106 -0
  57. tether_sdk-0.0.2/tether/__init__.py +35 -0
  58. tether_sdk-0.0.2/tether/cli.py +235 -0
  59. tether_sdk-0.0.2/tether/context.py +170 -0
  60. tether_sdk-0.0.2/tether/decorator.py +280 -0
  61. tether_sdk-0.0.2/tether/harness.py +435 -0
  62. tether_sdk-0.0.2/tether/multiplexer.py +124 -0
  63. tether_sdk-0.0.2/tether/providers/__init__.py +66 -0
  64. tether_sdk-0.0.2/tether/providers/anthropic.py +135 -0
  65. tether_sdk-0.0.2/tether/providers/base.py +136 -0
  66. tether_sdk-0.0.2/tether/providers/ollama.py +112 -0
  67. tether_sdk-0.0.2/tether/providers/openai.py +89 -0
  68. tether_sdk-0.0.2/tether/sandbox.py +342 -0
  69. tether_sdk-0.0.2/tether/skills.py +157 -0
  70. tether_sdk-0.0.2/tether/store.py +660 -0
  71. tether_sdk-0.0.2/tether/tools/__init__.py +22 -0
  72. tether_sdk-0.0.2/tether/tools/agent.py +190 -0
  73. tether_sdk-0.0.2/tether/tools/bash.py +29 -0
  74. tether_sdk-0.0.2/tether/tools/done.py +33 -0
  75. tether_sdk-0.0.2/tether/tools/edit.py +107 -0
  76. tether_sdk-0.0.2/tether/tools/mem.py +93 -0
  77. tether_sdk-0.0.2/tether/tools/web.py +222 -0
  78. tether_sdk-0.0.2/uv.lock +1188 -0
@@ -0,0 +1,45 @@
1
+ name: CI (Lint, Typecheck, Docs, Unit & Integration Tests + 100% Coverage)
2
+
3
+ on:
4
+ push:
5
+ branches: ["*"]
6
+ pull_request:
7
+ branches: ["*"]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ ci:
14
+ name: Ruff, Mypy, MkDocs & Pytest (100% Coverage)
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ matrix:
18
+ python-version: ["3.12", "3.13"]
19
+ steps:
20
+ - name: Checkout repository
21
+ uses: actions/checkout@v4
22
+
23
+ - name: Install uv
24
+ uses: astral-sh/setup-uv@v6
25
+ with:
26
+ python-version: ${{ matrix.python-version }}
27
+ enable-cache: true
28
+
29
+ - name: Sync dependencies
30
+ run: uv sync --all-groups
31
+
32
+ - name: Run Ruff Linter
33
+ run: uv run ruff check .
34
+
35
+ - name: Run Ruff Format Check
36
+ run: uv run ruff format --check .
37
+
38
+ - name: Run Mypy Strict Type Checking
39
+ run: uv run mypy tether/
40
+
41
+ - name: Run Unit & Integration Tests with 100% Coverage Gate
42
+ run: uv run pytest --cov=tether --cov-report=term-missing --cov-fail-under=100
43
+
44
+ - name: Build Documentation (Strict Mode)
45
+ run: uv run mkdocs build --strict
@@ -0,0 +1,69 @@
1
+ name: Publish on PyPI
2
+
3
+ # PyPI is published ONLY from a published GitHub Release. Pre-releases are
4
+ # skipped (no PyPI upload, no assets). The tag must match __version__.
5
+ on:
6
+ release:
7
+ types: [published]
8
+ workflow_dispatch:
9
+ inputs:
10
+ tag:
11
+ description: "Release tag (e.g. v0.0.2)"
12
+ required: true
13
+ type: string
14
+
15
+ jobs:
16
+ build:
17
+ if: ${{ !github.event.release.prerelease }}
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: astral-sh/setup-uv@v6
22
+ with:
23
+ python-version: "3.12"
24
+ - name: Verify release tag matches package version
25
+ env:
26
+ TAG: ${{ github.event.inputs.tag || github.event.release.tag_name }}
27
+ run: |
28
+ version=$(python3 -c 'import re,pathlib; print(re.search(r"__version__ = [\x27\"]([^\x27\"]+)[\x27\"]", pathlib.Path("tether/__init__.py").read_text()).group(1))')
29
+ if [ "${TAG#v}" != "$version" ]; then
30
+ echo "::error::Release tag '$TAG' does not match package version '$version'"
31
+ exit 1
32
+ fi
33
+ - name: Build package (.whl and sdist)
34
+ run: uv build
35
+ - uses: actions/upload-artifact@v4
36
+ with:
37
+ name: dist
38
+ path: dist/*
39
+ if-no-files-found: error
40
+
41
+ publish:
42
+ needs: build
43
+ runs-on: ubuntu-latest
44
+ environment: pypi
45
+ permissions:
46
+ id-token: write # PyPI trusted publishing (OIDC), no secrets needed
47
+ steps:
48
+ - uses: actions/download-artifact@v4
49
+ with:
50
+ name: dist
51
+ path: dist
52
+ - uses: pypa/gh-action-pypi-publish@release/v1
53
+
54
+ release-assets:
55
+ needs: build
56
+ runs-on: ubuntu-latest
57
+ permissions:
58
+ contents: write # attach assets to the release
59
+ steps:
60
+ - uses: actions/download-artifact@v4
61
+ with:
62
+ name: dist
63
+ path: dist
64
+ - name: Attach sdist and wheel to the release
65
+ env:
66
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
67
+ GH_REPO: ${{ github.repository }}
68
+ TAG: ${{ github.event.inputs.tag || github.event.release.tag_name }}
69
+ run: gh release upload "$TAG" dist/*.tar.gz dist/*.whl --clobber
@@ -0,0 +1,46 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "docs/**"
8
+ - mkdocs.yml
9
+ - README.md
10
+ - "tether/**"
11
+ workflow_dispatch:
12
+
13
+ concurrency:
14
+ group: pages
15
+ cancel-in-progress: false
16
+
17
+ jobs:
18
+ build:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: astral-sh/setup-uv@v6
23
+ with:
24
+ python-version: "3.12"
25
+ - uses: actions/configure-pages@v5
26
+ - name: Install dependencies
27
+ run: uv sync --all-groups
28
+ - name: Build documentation
29
+ run: uv run mkdocs build --strict
30
+ - uses: actions/upload-pages-artifact@v3
31
+ with:
32
+ path: site
33
+
34
+ deploy:
35
+ if: github.ref == 'refs/heads/main'
36
+ needs: build
37
+ runs-on: ubuntu-latest
38
+ permissions:
39
+ pages: write
40
+ id-token: write
41
+ environment:
42
+ name: github-pages
43
+ url: ${{ steps.deployment.outputs.page_url }}
44
+ steps:
45
+ - id: deployment
46
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,30 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .eggs/
9
+ site/
10
+
11
+ # Virtual environments
12
+ .venv/
13
+ venv/
14
+
15
+ # Tool caches
16
+ .pytest_cache/
17
+ .ruff_cache/
18
+ .mypy_cache/
19
+ .coverage
20
+ coverage.xml
21
+
22
+ # Tether runtime state
23
+ .tether/
24
+ *.db
25
+ *.db-shm
26
+ *.db-wal
27
+
28
+ # Environment files
29
+ .env
30
+ .env.*
@@ -0,0 +1,173 @@
1
+ # AGENTS.md — Tether Architectural & Engineering Guide
2
+
3
+ This document is the authoritative specification for AI coding agents and human contributors working on **Tether**. Read this file before planning, designing, or modifying any code in this repository.
4
+
5
+ ---
6
+
7
+ ## 1. Mission & Core Philosophy
8
+
9
+ **Tether** is a general-purpose, headless-first **LLM-Native Micro-Harness** written in Python (`3.12+`), designed to orchestrate both Small Language Models (SLMs like **`ollama/qwen3.5:9b`**) and frontier models (`Claude`, `GPT`, `OpenRouter`, `vLLM`) with minimal context overhead.
10
+
11
+ Most agent frameworks drown small models in thousands of tokens of JSON schemas and raw tool outputs. Tether flips this model: **it loads zero optional tools by default, keeps fixed framing overhead under 150 tokens, and reserves the context window for business logic.**
12
+
13
+ ### Architectural Grounding: Maia et al. (2026)
14
+ Tether implements the architectural principles from *"LLM-Native Applications as a Paradigm for AI-Driven Software Architecture"* (Maia et al., 2026):
15
+ 1. **Probabilistic Core, Deterministic Envelope:** The LLM is the central reasoning kernel, while tool routing, CLI argument parsing, context eviction, SQLite state transitions, and task termination (`done`) are strictly deterministic.
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
+ 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
+
19
+ ---
20
+
21
+ ## 2. The Golden Rules (Non-Negotiable Invariants)
22
+
23
+ ### Rule 1: Static Prefix & KV-Cache Immutability
24
+ - **NEVER** dynamically mutate the system prompt prefix or the JSON `tools` array sent to the model provider mid-session.
25
+ - Local inference engines (`Ollama`, `llama.cpp`, `vLLM`) rely on exact prefix matching for KV-cache reuse.
26
+
27
+ ### Rule 2: The Single Action Primitive (`act`) & Zero Default Tool Bloat
28
+ - Tether exposes **only one JSON tool schema** to the LLM: `act(cmd: str, stdin: str | None = None)`.
29
+ - **By default, no optional tools are loaded**—only the mandatory `done` termination command is active unless the caller explicitly loads tools via CLI (`--tools web,bash` or `--tools all`), SDK (`tools=[...]`), or a Skill (`tools: [...]` in YAML frontmatter).
30
+ - Multiline payloads (file contents, code patches, final markdown reports, scripts) **must** be passed via `stdin` rather than complex shell-escaped strings inside `cmd`.
31
+
32
+ ### Rule 3: Explicit Termination (`act("done", stdin="...")`)
33
+ - 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
+ - A task only completes when the agent explicitly calls `act("done", stdin="<final deliverable>")` (or `act("done '<short answer>'")`), or hits `max_steps`.
35
+ - 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
+
37
+ ### Rule 4: Progressive `--help` (JIT Schema Disclosure)
38
+ - The system prompt lists at most **1 line per enabled tool namespace** (`<name> : <short summary>`, target **≤ 12 tokens per entry**).
39
+ - Full parameter signatures, defaults, and docstrings are revealed **Just-In-Time (JIT)** when:
40
+ 1. The model explicitly runs `<cmd> --help`, OR
41
+ 2. The model invokes `<cmd>` with missing/invalid arguments (self-healing error response, never an unhandled Python exception).
42
+ - Auto-generated `--help` output must remain ultra-compact (**≤ 60 tokens** per command).
43
+
44
+ ### Rule 5: Ephemeral Observation Folding
45
+ - Raw tool outputs (`bash` stdout/stderr, `web` page contents, `--help` outputs) are stored in full inside SQLite (`observations` table) and kept in the active LLM prompt for $K$ turns (default $K = 2$).
46
+ - After $K$ turns, the `ContextAssembler` deterministically folds old observations into a 1-line tombstone:
47
+ ```text
48
+ [obs_3 folded: `web fetch https://...` (3,420 chars) | use `mem recall obs_3` to expand]
49
+ ```
50
+
51
+ ### Rule 6: Deterministic Security Envelope (Never Trust the LLM for Safety)
52
+ - Safety boundaries around `bash` and `edit` are enforced **deterministically by `sandbox.py` and the OS kernel**, never by system prompt instructions.
53
+ - Default profile is `workspace-write`:
54
+ - All `edit` and `bash` paths are canonicalized (`Path.resolve()`) and confined to `workspace_root`.
55
+ - Sensitive files/directories (`.env*`, `.git/config`, `.git/hooks`, `~/.ssh`, `.tether/`) are deterministically protected from mutation or read exfiltration.
56
+ - Secret environment variables (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GITHUB_TOKEN`, `AWS_*`, etc.) are scrubbed from all `bash` subprocesses.
57
+ - On Linux, `bash` automatically uses **`bwrap` (Bubblewrap) / `unshare`** when available (read-only `/`, hidden `$HOME` tmpfs, read-write bind on `workspace_root`, `PR_SET_NO_NEW_PRIVS`), falling back to a strict deterministic command allowlist/denylist (`rm -rf /`, `sudo`, `dd`, `mkfs`, `chmod 777`, etc. blocked).
58
+ - Sub-agents spawned via `agent spawn` strictly inherit or narrow their parent session's `SandboxPolicy` (no privilege escalation).
59
+
60
+ ### Rule 7: Zero Framework Bloat
61
+ - **Do NOT add heavy dependencies** like `langchain`, `llamaindex`, `crewai`, `litellm`, `chromadb`, or vendor SDKs (`openai`, `anthropic`).
62
+ - Use Python standard library (`asyncio`, `sqlite3` with FTS5, `shlex`, `inspect`, `dataclasses`, `typing`, `argparse`, `subprocess`) + ultra-lightweight essentials (`httpx`, `pyyaml`).
63
+
64
+ ---
65
+
66
+ ## 3. `v0` Architecture & Module Specifications
67
+
68
+ ```text
69
+ tether/
70
+ ├── __init__.py # Public SDK exports: Harness, Session, Skill, SandboxPolicy, tool
71
+ ├── cli.py # Headless-capable CLI: `tether run|status|read|send|list|wait`
72
+ ├── harness.py # Control Plane: bounded agent loop, SQLite inbox polling, worker spawn
73
+ ├── store.py # SQLite WAL + FTS5 RAG (.tether/tether.db): sessions, turns, inbox, obs, chunks_fts
74
+ ├── sandbox.py # Security Plane: workspace-write jail, env scrubbing, Linux bwrap/unshare
75
+ ├── multiplexer.py # Action Plane: `act(cmd, stdin)` parser, command registry, JIT --help
76
+ ├── decorator.py # `@tool` introspection: converts Python type hints + docstrings to CLI specs
77
+ ├── context.py # State Plane: ContextAssembler, EphemeralFolder, token estimator
78
+ ├── skills.py # Skill loader: Markdown + YAML frontmatter + optional sibling `tools.py`
79
+ ├── providers/
80
+ │ ├── base.py # Abstract async ModelProvider protocol (`generate(messages, tools)`)
81
+ │ ├── ollama.py # Native Ollama `/api/chat` httpx adapter (`num_ctx`, `keep_alive`)
82
+ │ ├── openai.py # OpenAI-compatible `/v1/chat/completions` httpx adapter (GPT, vLLM, Groq, OpenRouter)
83
+ │ └── anthropic.py # Native Anthropic `/v1/messages` httpx adapter (Claude)
84
+ └── tools/
85
+ ├── done.py # Mandatory explicit completion command (`done`)
86
+ ├── bash.py # Kernel-sandboxed, env-scrubbed, workspace-bounded shell execution
87
+ ├── web.py # Zero-config DuckDuckGo Lite httpx search (+ Brave/Tavily/SearXNG env override) & reader
88
+ ├── edit.py # Workspace-jailed file viewing (`view`) & stdin patching (`write`, `replace`)
89
+ ├── mem.py # Working scratchpad (`pin`/`unpin`), FTS5 search (`search`), & recall (`recall`)
90
+ └── agent.py # Async multi-agent orchestration (`spawn`, `status`, `wait`, `query`, `read`, `send`)
91
+ ```
92
+
93
+ ### Key Subsystem Contracts
94
+
95
+ #### A. SQLite WAL State + FTS5 RAG Store (`store.py`) & Headless Execution (`cli.py` / `harness.py`)
96
+ - Default DB path: `.tether/tether.db` (configurable via `TETHER_DB` or `Harness(db_path=...)`) with `PRAGMA journal_mode=WAL;`.
97
+ - Tables:
98
+ - `sessions`: `id`, `parent_id`, `status` (`PENDING`, `RUNNING`, `COMPLETED`, `FAILED`), `model`, `skill`, `tools_json`, `sandbox_json`, `goal`, `result`, `step_count`, `max_steps`, `peak_tokens`, `pid`, `created_at`, `updated_at`.
99
+ - `turns`: `id`, `session_id`, `step`, `role`, `content`, `tool_cmd`, `tool_stdin`, `obs_id`, `created_at`.
100
+ - `observations`: `id`, `session_id`, `step`, `cmd`, `raw_output`, `summary`, `char_len`.
101
+ - `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 and final `done` deliverables for sub-millisecond BM25 RAG.
102
+ - `pins`: `session_id`, `key`, `value`.
103
+ - `inbox`: `id`, `session_id`, `message`, `consumed` (`0` or `1`), `created_at`.
104
+ - **Headless Worker & Live `send` Contract:**
105
+ - `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.
106
+ - Before each step, the running worker queries `inbox WHERE session_id = ? AND consumed = 0` and injects any new user/parent messages into the conversation.
107
+ - If `tether send <session_id> "msg"` (or `agent send`) is called when the session is `COMPLETED` or `FAILED`, it inserts the message into `inbox`, sets status back to `RUNNING`, and spawns/resumes the worker from the persisted SQLite state.
108
+
109
+ #### B. Always-Async Sub-Agent Tool & BM25 RAG (`tools/agent.py`)
110
+ - Sub-agents share the same `.tether/tether.db` with `parent_id` set to the caller's `session_id`:
111
+ - `agent spawn [--skill <name>] [--model <m>] [--tools <t1,t2>] <goal>` $\rightarrow$ spawns a detached headless session and immediately returns `ses_<id>`.
112
+ - `agent status <id>` $\rightarrow$ returns 1-line status, step count, and last command.
113
+ - `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.
114
+ - `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).
115
+ - `agent read <id> [--full]` $\rightarrow$ reads the child session's digest or full `result`.
116
+ - `agent send <id> <message>` $\rightarrow$ queues a message into the child session's inbox (or resumes it if completed).
117
+
118
+ #### C. Skills System (`skills.py`)
119
+ - Discovers skills in `./skills/` and `~/.tether/skills/`.
120
+ - Supports either `skills/<name>.md` or `skills/<name>/SKILL.md` with YAML frontmatter:
121
+ ```yaml
122
+ ---
123
+ name: researcher
124
+ description: Fast web research and synthesis
125
+ model: ollama/qwen3.5:9b
126
+ tools: [web, mem]
127
+ ---
128
+ ```
129
+ - If `skills/<name>/tools.py` exists, any `@tool`-decorated functions inside it are automatically registered into the session's multiplexer when the skill is loaded.
130
+
131
+ #### D. 3 Thin `httpx` Model Providers & SDK Provider Routing (`providers/`)
132
+ - Model identifier format: `<provider>/<model_name>` (e.g., `ollama/qwen3.5:9b`, `openai/gpt-4o-mini`, `anthropic/claude-3-7-sonnet-latest`).
133
+ - If no provider prefix is given, defaults to `ollama/<model>`.
134
+ - Custom SDK transports (e.g., internal corporate gateways, custom local runners, or test harnesses) can be injected via `Harness(provider=...)` or registered under a custom `<prefix>/<model>` via `register_provider(prefix, factory)`.
135
+ - Environment variables:
136
+ - `OLLAMA_HOST` (default `http://localhost:11434`)
137
+ - `OPENAI_API_KEY`, `OPENAI_BASE_URL` (default `https://api.openai.com/v1` — works with OpenRouter, vLLM, Groq, etc.)
138
+ - `ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL` (default `https://api.anthropic.com`)
139
+
140
+ ---
141
+
142
+ ## 4. Development Workflow & Commands
143
+
144
+ We use **`uv`** for fast, reproducible Python environment and package management.
145
+
146
+ ```bash
147
+ # Install dependencies and sync environment
148
+ uv sync
149
+
150
+ # Run unit and token-budget tests
151
+ uv run pytest
152
+
153
+ # Run linter and formatter
154
+ uv run ruff check .
155
+ uv run ruff format --check .
156
+
157
+ # Run static type checking
158
+ uv run mypy tether/
159
+ ```
160
+
161
+ ---
162
+
163
+ ## 5. Engineering & Testing Standards for Agents
164
+
165
+ 1. **Token Budget Assertions in Tests:**
166
+ - Every core feature test must assert that prompt framing and `--help` outputs stay within strict character/token budgets.
167
+ - Base system prompt + `act` tool schema + `done` index line must consume **< 150 tokens total**.
168
+ 2. **Async-First Design:**
169
+ - All model calls and I/O-bound tool executions must be `async`/`await` native, while seamlessly wrapping synchronous user `@tool` functions.
170
+ 3. **Graceful Degradation on 9B Hallucinations:**
171
+ - 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.
172
+ 4. **Documentation Integrity:**
173
+ - Keep `README.md` and `AGENTS.md` synchronized whenever architectural interfaces or built-in tool signatures change.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2026 Igor Souza
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.