v1.43.1: sync README (tool counts, MCP resolver plus OAuth, research stats)

This commit is contained in:
2026-10-18 21:14:13 +02:00
parent 58786386cf
commit 609e5d8e91
2 changed files with 10 additions and 10 deletions
+9 -9
View File
@@ -4,7 +4,7 @@ retoor <retoor@molodetz.nl>
**Zero dependencies. A real agent.**
`tai` is an autonomous AI agent written in Python — the entire product lives in the standalone `tai.py` file (grouped into classes) and imports **only the Python standard library**. No `pip install`, no `requirements.txt`, no build step, no lockfile to rot. It runs as an interactive REPL or a one-shot command, reasons through an OpenAI-compatible backend, acts through 60 tools, keeps per-profile memory in a self-healing SQLite vault, seals that vault at rest, ranks every search with real BM25 (no vector database, no embeddings bill, no GPU), chunks and indexes anything worth remembering for retrieval, and can wall shell/file work off inside a disposable container sandbox.
`tai` is an autonomous AI agent written in Python — the entire product lives in the standalone `tai.py` file (grouped into classes) and imports **only the Python standard library**. No `pip install`, no `requirements.txt`, no build step, no lockfile to rot. It runs as an interactive REPL or a one-shot command, reasons through an OpenAI-compatible backend, acts through 61 tools, keeps per-profile memory in a self-healing SQLite vault, seals that vault at rest, ranks every search with real BM25 (no vector database, no embeddings bill, no GPU), chunks and indexes anything worth remembering for retrieval, and can wall shell/file work off inside a disposable container sandbox.
Nothing here is decorative. Every mechanism below exists because something specific was expensive — context tokens, API calls, attacker surface, disk writes, or your attention — and got engineered out.
@@ -153,7 +153,7 @@ This only ever activates as a genuine last resort: it's wired into `all_candidat
Every tool schema you hand an LLM costs real tokens on every single turn, whether or not it gets used, and a model drowning in fifty tool definitions picks worse than one offered the five that actually matter. tai treats this as a first-class engineering problem, not an afterthought, with three independent layers stacked on top of each other:
**1. Core vs. lazy.** Of the 60 tools, only `CORE_TOOLS` — `shell`, `read_file`, `write_file`, `remember`, `recall`, `load_skill`, `fork`, `poll` — are sent to the model on every turn. The other 51 are *lazy*: their full JSON schema only enters the request payload when they're judged relevant to what's actually being discussed.
**1. Core vs. lazy.** Of the 61 tools, only `CORE_TOOLS` — `shell`, `read_file`, `write_file`, `remember`, `recall`, `load_skill`, `fork`, `poll`, `ask_user` — are sent to the model on every turn. The other 52 are *lazy*: their full JSON schema only enters the request payload when they're judged relevant to what's actually being discussed.
**2. Relevance by three independent signals, not a fixed list.** `select_tools()` scans the last 6000 characters of conversation (user, assistant, and tool-result turns — `conversation_text()`) and pulls in a lazy tool's full schema the moment any one of three things matches:
@@ -167,7 +167,7 @@ Tool *results* feed this loop too: a spill pointer that mentions `record_read` l
A compact name-plus-one-line-summary catalog of every lazy tool (`tool_catalog()`) still rides in the system prompt at all times, so nothing is ever *hidden* — only its verbose, multi-field JSON schema is deferred. `/tools` shows the core/lazy split live (respecting the same environment gates a real turn would see), `/tools <name>` shows one tool with its full description and trigger tags.
Net effect: the model sees 8 schemas by default instead of 42, picks up exactly the ones a conversation actually needs, and never has a tool pushed on it that can't possibly apply in the current environment.
Net effect: the model sees 9 schemas by default instead of 61, picks up exactly the ones a conversation actually needs, and never has a tool pushed on it that can't possibly apply in the current environment.
## Context budget and compaction
@@ -276,9 +276,9 @@ tai speaks the [CLI Agent Protocol](https://cap-protocol.org/) (draft v1 plus th
tai speaks MCP in both directions, with no SDK: newline-delimited JSON-RPC over a spawned stdio child, or Streamable HTTP via `urllib` with a hand-rolled SSE parse, on the 2025-11-25 baseline. Full contract: [mcp.md](mcp.md); research: `research/mcp-client-support/`.
**As a client.** Paste any server definition into `mcp_add`: a JSON `mcpServers` block (URL form with headers, or command form with argv), a TOML `[mcp_servers.*]` table, or a bare GitHub URL. Configs normalize deterministically (`type` repair, `${VAR}`/`${VAR:-default}` expansion from env then vault secrets at use time, `enabled_tools` allow/deny lists); GitHub URLs resolve through registry, package files, and README run blocks, and every resolution is verified by a live handshake before anything is stored. Connections live in the vault (`mcp_servers`, headers sealed), enablement is per profile and defaults to off, and each server's tools enter the lazy system as `mcp_<server>_<tool>`. Servers are untrusted by design: registration shows the exact spawn command, tool descriptions are hash-pinned with re-approval on drift, calls ask like non-safe shell commands, results over 64KB spill to a record, and every call is audited.
**As a client.** Paste any server definition into `mcp_add`: a JSON `mcpServers` block (URL form with headers, or command form with argv), a TOML `[mcp_servers.*]` table, or a bare GitHub URL. Configs normalize deterministically (`type` repair, `${VAR}`/`${VAR:-default}` expansion from env then vault secrets at use time, `enabled_tools` allow/deny lists); GitHub URLs resolve in two passes (a deterministic file lookup over `mcp.json`, `package.json`, and the README run block, then a bounded resolver subagent that reads the repo and returns exactly one run entry), and every resolution is verified by a live handshake before anything is stored. Servers that answer 401 get OAuth instead of an error: `mcp_login` runs the browser flow (discovery, dynamic client registration, PKCE loopback, all stdlib), stores sealed per-profile tokens, and every later 401 refreshes once and retries before asking you to log in again. Connections live in the vault (`mcp_servers`, headers sealed), enablement is per profile and defaults to off, and each server's tools enter the lazy system as `mcp_<server>_<tool>`. Servers are untrusted by design: registration shows the exact spawn command, tool descriptions are hash-pinned with re-approval on drift, calls ask like non-safe shell commands, results over 64KB spill to a record, and every call is audited.
**As a server.** `./tai.py --serve-mcp KEY` exposes all 60 native tools plus `chat` (a full agent turn on a per-session agent, history kept) and `completions` (one stateless completion over a messages array, current model, no tools) over Streamable HTTP on `127.0.0.1:8765` (`--mcp-port`, `--mcp-bind`, `TAI_MCP_PORT`/`TAI_MCP_BIND` to move it). Clients send the key as a Bearer token; miss it and you get 401. The key is the authorization: serve mode runs yolo because no user is present to ask, which is stated loudly at startup. Bridged foreign tools are never re-served.
**As a server.** `./tai.py --serve-mcp KEY` exposes all 61 native tools plus `chat` (a full agent turn on a per-session agent, history kept) and `completions` (one stateless completion over a messages array, current model, no tools) over Streamable HTTP on `127.0.0.1:8765` (`--mcp-port`, `--mcp-bind`, `TAI_MCP_PORT`/`TAI_MCP_BIND` to move it). Clients send the key as a Bearer token; miss it and you get 401. The key is the authorization: serve mode runs yolo because no user is present to ask, which is stated loudly at startup. Bridged foreign tools are never re-served.
## Execution environment / sandbox
@@ -394,11 +394,11 @@ Snapshot as of 2026-10-18 (live counts always in [research/INDEX.md](research/IN
| Metric | Count |
|---|---|
| Subjects | 13 |
| Source documents | 691 (216 Tier 1 / 475 Tier 2) |
| Conclusion documents | 29 |
| Subjects | 14 |
| Source documents | 797 (254 Tier 1 / 543 Tier 2) |
| Conclusion documents | 32 |
Subjects range from SQLite BM25 retrieval and TUI framework design to goal verification, prompt scaling, scheduler bounds, sandbox caps, and the full CAP protocol survey. When this README says a technique was rejected (embeddings, GraphRAG, memory vendors, gRPC), that rejection is a researched decision with documents behind it, not an omission.
Subjects range from SQLite BM25 retrieval and TUI framework design to goal verification, prompt scaling, scheduler bounds, sandbox caps, the full CAP protocol survey, and MCP client support. When this README says a technique was rejected (embeddings, GraphRAG, memory vendors, gRPC), that rejection is a researched decision with documents behind it, not an omission.
## Testing
+1 -1
View File
@@ -105,7 +105,7 @@ def _fromisoformat(text):
return parsed.replace(tzinfo=tzinfo)
raise ValueError("invalid datetime, use ISO like 2026-10-08T09:00")
VERSION = "1.43.0"
VERSION = "1.43.1"
WORKER_STEPS = 12
CREATE_SKILL_STEPS = 40
FORK_MAX_DEPTH = 2