Metadata-Version: 2.5
Name: novirhiza-tether
Version: 0.3.2
Summary: An embedded Python agent harness built on Pydantic AI Agent
Author: FocusedDiversity LLC
License-Expression: MIT OR Apache-2.0
License-File: LICENSE-APACHE
License-File: LICENSE-MIT
License-File: NOTICE
Requires-Python: >=3.11
Requires-Dist: anyio<5,>=4.15.1
Requires-Dist: pydantic-ai-slim<2.63,>=2.59.0
Requires-Dist: pydantic<2.15,>=2.12
Provides-Extra: embedded-postgres
Requires-Dist: psycopg[binary,pool]<3.4,>=3.3.6; extra == 'embedded-postgres'
Provides-Extra: openai
Requires-Dist: openai==3.37.0; extra == 'openai'
Requires-Dist: pydantic-ai-slim[openai]<2.63,>=2.59.0; extra == 'openai'
Requires-Dist: tiktoken==0.14.0; extra == 'openai'
Provides-Extra: postgres
Requires-Dist: psycopg[binary,pool]<3.4,>=3.3.6; extra == 'postgres'
Provides-Extra: remote
Requires-Dist: httpx==0.28.1; extra == 'remote'
Provides-Extra: tui
Requires-Dist: textual==8.2.8; extra == 'tui'
Description-Content-Type: text/markdown

# Tether

[Microsite](https://DocumentDrivenDX.github.io/tether/) · [Scenario capture workflow](docs/microsite.md)

Install the `novirhiza-tether` distribution; the Python import remains `tether`.

A Python library for building agent harnesses. Tether integrates Pydantic AI Agent with
typed tools, execution policy and durable sessions. Its in-process async Client API exposes
session metadata, history, persisted changes and a partial command facade. An offline
reference CLI and terminal UI exercise the local profile.

**Status: qualified local runtime and Client profiles.** Pydantic AI Agent runs tools through
Tether's policy, validation, approval and commit gates. Model doubles and shared codecs remain
available. Local JSONL journal/history and pending-approval restart reconstruction are implemented;
Owner-bound Client metadata, history, call-detail and verified pending reads are available through an async `Tether` context with explicit `ClientLimits`. Bounded persisted follow/replay is also available. Task-owned `send`, shielded result handles, canonical request retries, current-source result reads, lease heartbeats and explicit cancellation are implemented. Explicit durable resume is implemented for the qualified local profile. Optional PostgreSQL18 session storage and independent content history are implemented for the qualified database profile. See the [build plan](docs/helix/02-design/solution-designs/SD-001-tether-library-design-and-build-plan.md).

The complete release gate (`make check`) has passed on macOS arm64 with Python 3.11.17, the
locked dependency versions, local POSIX JSONL storage and PostgreSQL 18, including the optional
embedded helper. That is the qualified platform. Base and extras installs of published 0.2.0
from PyPI also pass on Linux aarch64 and x86_64 with Python 3.11
([receipt](docs/helix/03-test/evidence/release-020-publication-20261106.md)), but the complete
suite has not yet passed on Linux; the `Release gate` workflow runs it there. Published base
dependency ranges and the versions that passed at each end are in the
[dependency matrix](docs/helix/03-test/evidence/dependency-matrix-20261106.md). Live
compatible-model evidence covers the recorded local DS4/Qwen text/function-tool profile,
with explicit response caps, approximate token estimates and configured safety margins.
The [P0 qualification receipt](docs/helix/03-test/evidence/p0-qualification-20261028.md)
names exact versions, workloads, boundary refusals and process-fault evidence. Additional
operating systems, Python versions, model servers, native media/reasoning, Gateway joins
and the OpenTelemetry exporter require separate qualification.

## Embeddable web interfaces (source only, not on PyPI)

The public `tether.client` boundary now supplies typed protocols, explicit
`ClientHost` construction and ownership, journal-backed turn results and targeted
cancellation. The terminal UI, HTTP API and Databricks carrier consume that
boundary. Sharing the same server and durable storage lets browser and remote
terminal clients view and operate the same authorized sessions.

Optional components are separate artifacts in this repository:

| Component | Python distribution or npm package |
|---|---|
| HTTP API with injected authentication and client provider | `novirhiza-tether-web-api` |
| Static browser assets and ASGI host | `novirhiza-tether-web-ui` |
| Databricks Apps composition | `novirhiza-tether-databricks` |
| Framework-free browser transport | `@tether/client` |
| Injected React hooks and replaceable components | `@tether/react`, `@tether/ui` |

These development artifacts are not published releases. Their source, installed
consumer, real browser and live platform evidence is tracked in the
[implementation plan](docs/helix/04-build/web-platform-implementation-plan.md).
The platform-neutral browser journey uses fabricated model/tool data. Databricks
Apps and Lakebase qualification remains separately recorded; local proof does
not qualify a hosted profile.

## Offline reference client

From a clone, `uv run --frozen --python 3.11 --no-dev --extra tui tether` starts a
Textual terminal UI with a scripted model, a
fabricated tool call and an approval. It creates a local JSONL store at
`~/.local/share/tether`. No account, credential or network call is needed for the demo.
Install the optional `tui` extra when running an installed distribution outside the
development environment. The terminal UI shows sessions and transcript history and
supports send, approve/deny and explicit resume. Click **New Session** or press
**Ctrl+N** to start a blank conversation using the current model profile. New
sessions retain manual tool approval and appear in the session list; select an
earlier session to return to its history and pending approvals. The same local demo profile has CLI
commands for `list`, `show`, `send`, `follow`, `confirm`, `resume` and fabricated cassette
`replay`; run `uv run tether --help` for arguments. Use `--root PATH` to select another
local store. These clients are a reference path. One fresh macOS/Python 3.11 clone with an isolated dependency cache reached
the offline UI in 5.51 seconds, including installation; download time varies with
network and package registry conditions.
For a deliberately volatile in-memory UI demo, use `uv run tether --volatile`;
its sessions disappear when the process exits and resume is disabled.

## Start with a local model

From this checkout:

```sh
uv run tether --openai-api-baseurl=http://localhost:18001/v1
```

For an installed release containing this feature, use the distribution's actual name:

```sh
uvx --from 'novirhiza-tether[tui,openai]' tether --openai-api-baseurl=http://localhost:18001/v1
```

`uvx tether` looks for a distribution named `tether`; this project installs as
`novirhiza-tether` and provides the `tether` executable.
To run the current checkout through uvx instead of the published package, use
`uvx --from '.[tui,openai]' tether --openai-api-baseurl=http://localhost:18001/v1`.
Use HTTP if your server serves HTTP; HTTPS keeps certificate verification enabled.

The launcher discovers `/models`, prefers an advertised `-chat` alias, and shows
its selection. Override with `--model MODEL`. Common tools use the current directory;
select another with `--workspace PATH`. Each tool call still requires approval.
Loopback startup needs no API key. Authenticated endpoints use
`--authorization-file PATH` (a file containing the full Authorization value).
No environment credentials or tokenizer downloads are used.

Context capacity comes from server metadata when available. Otherwise the launcher
shows an unverified **starter** request budget of 16,384 tokens; `--context-window N`
overrides it. Output defaults to 1,024 tokens, capped by an advertised maximum;
`--output-tokens N` overrides the request, with 1,024 tokens held as a safety margin.
The bundled byte-only estimate includes the whole native request and is approximate;
it is intentionally cautious about text size and cannot prove server framing or
capacity. Explicit tokenizer/server profiles remain available below.

## Start with a Databricks CLI profile

With the Databricks CLI installed and the profile already signed in:

```sh
uv run tether --databricks-profile=work
```

No URL or authorization file is needed. The launcher derives the workspace host,
uses the gateway's Chat Completions route, and opens a model picker. Select a model
and press **Start**, or skip the picker with `--model system.ai.MODEL`.
For the current checkout through uvx, use
`uvx --from '.[tui,openai]' tether --databricks-profile=work`.

Discovery uses the Databricks model-services API, currently scoped to `system.ai`
services advertising `mlflow/v1/chat/completions`. Listed metadata does not guarantee
invocation permission. The existing CLI supplies refreshed OAuth user credentials;
Tether does not copy tokens into profiles or history. Each authentication checks
that the profile still points to the original workspace. Missing CLI, expired login,
changed host, empty catalogs and discovery failures stop startup or the request.
PAT and service-principal CLI authentication are not qualified by this launcher.
The starter context budget and approximate estimate described above apply.

`--gateway URL` is an alias for the compatible API base URL, alongside `--base-url`;
it never interprets a value as a Databricks profile. A Databricks profile supplies
both URL and authentication, so combining it with an explicit URL or authorization
file is refused. Inference API compatibility, streaming, discovery and credentials
are separate capabilities; this shortcut does not imply discovery/auth support for
other gateway products.

### Calling the gateway on behalf of a signed-in user

An application that already holds the signed-in user's token for the current request
passes it as that request's caller credential. Tether never reads, stores or logs the
token; the application owns where it comes from and how long it lives.

```python
from tether.adapters.openai_compat import OpenAICompatModel
from tether.types.execution import Actor


class RequestToken:
    """One request's user token. Build a new one per request; never cache it."""

    def __init__(self, token: str) -> None:
        self._token = token

    async def authorization(self) -> str:
        return f"Bearer {self._token}"


model = OpenAICompatModel(
    base_url="https://WORKSPACE/serving-endpoints",
    tiers={"default": "ENDPOINT"},
    limits=limits,
    output_limit_field="max_tokens",
    request_tags_header="Databricks-Ai-Gateway-Request-Tags",
)
actor = Actor(id=user_email, principal=user_id, credential=RequestToken(user_token))
async with tether.client(actor) as client:
    handle = await client.send(session_id, text, request_id=request_id)
```

With the default `identity_mode="caller"`, each physical model attempt sends only that
actor's `Authorization` value; an application credential configured on the model is
never read for a caller request. The session, turn, call and principal identifiers travel
as string tags in `Databricks-Ai-Gateway-Request-Tags` (not in `User-Agent`), the current
`traceparent` is forwarded, and the gateway's `x-request-id` is recorded on the response
receipt and on `ModelUnavailable.request_id`. Gateway errors surface as `http <status>`
with no response body text. A continuation after approval uses the answering actor's
current credential, not the one that started the turn. The mock-transport wire tests in
`tests/test_databricks_caller_identity_wire.py` cover these properties, including five
concurrent callers. Live per-tier receipts against a development workspace are an
operator step that needs a signed-in user and are recorded separately.

## Optional compatible model reference

To use the same CLI or terminal UI with an explicitly selected OpenAI-compatible
text/tool model, install the `tui` and `openai` extras and run
`tether --model-config /path/to/model.json`. The JSON profile requires `id`,
`base_url`, `model_name`, `output_limit_field` (`max_tokens` or
`max_completion_tokens`), `context_window_tokens`, `output_tokens`,
`safety_margin_tokens`, a complete `TokenizerProfile` object in
`tokenizer_profile`, and paths named `vocabulary_path` and
`authorization_file`. The optional `max_response_bytes` defaults to 8 KiB and
may be set up to 1 MiB for a measured streaming profile. Relative paths resolve
beside the JSON file. Keep the
authorization file outside the checkout; it contains the complete HTTP
Authorization value and is read afresh for each physical attempt. The profile
contains no secret. Its tokenizer vocabulary and server framing values must be
qualified for the chosen model. The offline demo remains the default when
`--model-config` is absent. A controlled local HTTP peer and one local DS4
text/tool/approval run exercise this path; the selected DS4 run and its remaining
tokenizer/framing limits are recorded in the
[P0 qualification receipt](docs/helix/03-test/evidence/p0-qualification-20261028.md).
Broader provider compatibility and semantic summary fidelity remain open.

To enable common tools, add `"workspace": "/absolute/path/to/workspace"` to
that model profile (a relative path resolves beside the profile). The directory
must exist. Both reference clients then expose `bash`, `read_file`, `write_file`,
`web_search` and `web_page_load` alongside `record_demo`. Each invocation requires
approval; the TUI shows its exact arguments before Approve/Deny. Changing the
workspace changes the profile/session identity.

File tools accept workspace-relative or absolute paths resolving inside the
workspace, refuse escapes including existing symlinks, and use UTF-8 with a
64 KiB limit. Writes create or overwrite files; parent directories must exist.
Bash starts in the workspace, inherits the process environment and user permissions,
and is **not a sandbox**. Its default deadline is 10 seconds (maximum 20), with
64 KiB combined output; timeout/overflow kills its process group. Keep authorization
files outside the workspace. File paths are a convenience boundary, not protection
against concurrent filesystem changes or hard links.

Search uses DuckDuckGo's public Lite HTML endpoint without an API key and returns up to
five title/URL/snippet results. Availability and markup can change; no parsed results
explicitly reports that uncertainty. Page loading fetches HTTP(S) text/HTML, follows
HTTP(S) redirects, extracts text without JavaScript and labels truncation. Web reads
have a 10-second socket timeout, a 256 KiB input cap and a 32 Ki-character page-text
cap; decoding replaces invalid characters. These are trusted local example tools:
network access includes reachable private hosts and configured system proxies.
Their outputs enter session history and the selected model's context. The offline
profile has no common tools or network activity.

## Development

Install [uv](https://docs.astral.sh/uv/), then:

```sh
uv sync --locked
make check
uv build
uv run --locked python scripts/check_distribution.py dist
uv run --locked python scripts/check_distribution.py dist openai
make hooks  # optional: install the committed pre-commit hook
```

The base uses `pydantic-ai-slim`, Pydantic and AnyIO with a checked-in lock. Foundation-model
provider SDKs are excluded from the reachable base dependency graph. The optional `openai` extra supplies the native compatible adapter. Development dependencies
include that extra for its tests; the installed base excludes it. No hosted account or credential is needed for local checks.

`make check` is shared by local work, the hook and CI. It includes planted negative controls
for forbidden imports, private SDK modules, cycles, missing dependencies and raised ceilings.
It runs every publicly collected test in bounded subprocess groups and checks aggregate
branch coverage against the same 85% floor; the per-child deadline remains 180 seconds.

## Current model integration

`ModelRequest`/`ModelResponse` are Tether-owned types; `ModelPort.stream` and `tether.models.complete`
are the external boundary. `tether.models.testing` provides `ScriptedModel`, fabricated-only
`RecordingModel`, `ReplayModel` and the volatile `ListAttemptRecorder`. Cassettes exclude credentials
and callbacks, fix the principal to `recorded`, and verify versioned request keys before replay.

`tether.runtime.pydantic_ai` is internal. Its Agent bridge requires history authorization and
per-call preparation callbacks. `tether.engine.Engine` supplies the gated execution path through
fixed serial tools. Checkpoints are bound derivatives, not execution authority.

The `openai` extra supplies `tether.adapters.openai_compat.OpenAICompatModel`; select it locally
with `uv sync --locked --extra openai`. It requires an explicit base URL, tier map, output-limit field, execution
limits and an attempt recorder. Identity is per request. The current profile uses Pydantic AI's
native chat model, disables SDK retries/redirects, and owns bounded transport retries.
It requests identity encoding and refuses compressed bodies before decoding; decompression
resource limits require separate qualification.
Text/function-tool responses are checked over fabricated HTTPX2 peers and the selected live DS4 profile. Empty stop completions
produce a typed `empty_response` failure; they never trigger an unmediated correction. Provisional network
streaming has controlled local and selected DS4 evidence. Media/reasoning content, effort selection,
SDK validation feedback, structured output and additional server profiles require separate qualification or are explicitly refused. A supplied custom
transport is trusted and must not add hidden retries or redirects.

## Gated execution subset

`Engine.run_turn(actor, log, text, spec=spec)` accepts an explicit `AgentSpec`; `Engine.answer`
continues a pending approval through current policy and source checks. The registry pins tool
versions and schemas. Approvals bind normalized executable values to the actor, invocation,
session, tool and spec. An accepted assistant source precedes dispatch; an accepted intent
precedes every effectful or replay-unsafe tool. Unknown effects block new effectful calls.
Agent owns iteration and deferred continuation. Tether owns every execution permission.

Configure the model, history, limits, tools/providers and policy explicitly. The default
journal attempt recorder fences and authorizes physical attempts; an explicit recorder factory
is available for test/profile integrations. `AllowAll` is the permissive default policy; ownership and engine invariants
still apply. Tool and provider plain functions run off the event loop with bounded capacity.
Cancellation retains the worker's capacity and session marker until actual exit. `aclose()` seals
new work and returns `False` if work remains after the configured finite shutdown wait.

`MemoryTurnLog` and `MemoryHistory` in `tether.testing.execution` implement the slice 2 journal
and message-history subsets. Their durability is `none`. For local durability, explicitly open
`JsonlRoot`, `JsonlSessionStore` and `JsonlHistory`, and use `MessageHistoryAdapter` with Engine.
SessionStore contract4 exposes `store.for_actor(actor)` for caller-aware storage acquisition.
Client operations bind the present actor explicitly, including followers and retained results.
These scopes borrow the store lifetime: closing a scope does not close its root, and global
recovery requires explicit maintenance authority. Applications retain ownership of credential
refresh, connection pools, database roles and RLS. The optional PostgreSQL store preserves caller predicates and never falls back to maintenance authority.
The POSIX single-writer profile stores checksummed batches and accepted-prefix witnesses,
fences leased writes, and reconstructs pending approval from journal/source facts with the
original supplied specs. Unknown writes require reopening; close retains ownership while
actual I/O remains. Process-exit evidence does not prove power-cut behavior. The qualification receipt
records the selected recovery faults, scope/spec resolver integration and metadata-only telemetry.
User and guarded tool-result attachment refs are supported as metadata rendered
into text; the original refs remain in history and model requests. Artifact bytes, native media,
non-JSON executable argument leaves and lossy argument serializers remain outside this subset.
The fabricated examples and faults are in [the execution tests](tests/test_gated_execution.py)
and [the integration review](tests/test_execution_review.py).

The optional compatible adapter can supply an estimator with
`model.estimator(profile=tokenizer_profile, vocabulary=verified_local_bytes)`.
`TokenizerProfile` is exported by `tether.adapters.openai_compat`; it requires explicit tokenizer,
byte/capacity and framing settings. Counts cover native serialized JSON and remain approximate.
Vocabulary loading belongs to the application; the library performs no implicit tokenizer download.
Constructor setup is synchronous; Engine deadlines retain actual off-loop counting through cancellation.
See [the context contract](docs/helix/02-design/contracts/CONTRACT-004-context-projection-and-compaction.md)
for configuration and limits. `uv run --locked python scripts/qualify_tokenizer.py VOCABULARY PROFILE_JSON OUTPUT_JSON`
runs fabricated offline wire/BPE qualification with supplied data; it makes no live model request.

The pure `tether.engine.plan_resume(entries, declarations, session_id=...)` planner classifies
interrupted invocation states from scoped journal facts and immutable `ResumeTool` snapshots.
It preserves source-derived invocation ids, refuses canceled or superseded ancestry, and never
hydrates content or dispatches tools/models. `Client.resume(session_id)` now accepts an interrupted durable fenced session under current
read/resume policy. Agent resumes unfinished calls through all gates; safe retries keep the original
invocation id and intent, unsafe starts become unknown, pending approvals retain their bindings,
and step/resume-chain budgets survive reconstruction. Accepted final sources avoid another model call.
Storage-scoped actual worker ownership blocks replacement Engines until physical exit. This local
profile has restart and prefix evidence; broader process/storage fault qualification remains required.

## Client command subset

`client.create(ref=..., spec=..., title=..., tags=..., settings=...)` creates an owner-scoped
session; a repeated reference returns the existing session. `client.update(session_id, ...)`
merges supplied settings and replaces supplied tags. Omitting title preserves it; passing
`title=None` clears it. Public settings refuse binder-managed `held` context. Updates use the
store lease and refuse while another turn owns it. Current read policy governs command access
and returned summaries; a refused final disclosure cannot undo a known committed change.

Within an active `Tether` context, `client.send(session_id, text, request_id=...)`
returns a handle after durable input acceptance. Await `handle.result()` for completion;
canceling that waiter leaves Tether's turn running. Use `handle.cancel()` or
`client.cancel(session_id)` to request cancellation explicitly. An old handle cannot cancel
a newer turn. A waiting approval is not declined by cancellation.

Select a supplied unambiguous spec by the command's `spec`, the session's stored spec id,
or explicit `Tether(default_spec=...)`. A repeated request id must have identical canonical
text, attachment refs and requested spec. The existing turn is returned without another Agent
run, including after local root/composition restart. Each result delivery uses current caller
policy and verified original sources; unavailable or denied sources refuse content rather than
substituting an empty transcript. Result reads have a finite deadline after execution completes.
An external still-running turn without a local task reports `TurnInProgress` on result lookup;
cross-process following and automatic recovery remain separate qualification.

`client.confirm(session_id, invocation_id, Answer(approved=True))` returns a handle after
known decision acceptance, before the continuing Agent finishes. Late or superseded answers
return `None`; concurrent answers have one winner. Continuation uses the current actor's policy,
principal and credential, with the original logical turn id. Existing successful send handles
wait for an accepted continuation before returning its latest outcome. Optional answer notes
are bounded informational history, outside executable arguments and automatic model input.

`client.close(session_id)` seals pending calls with non-success results and closes the session
in one journal commit. It refuses active turns, live workers and unresolved effects; closed
sessions remain readable and repeat close is idempotent. Failed result-content preparation
cannot publish closure. Explicit cancellation returns the session to idle without advertising
resume; an actual interruption is distinct and may be resumable.

`client.resolve(session_id, invocation_id, "completed", reason="operator_checked")` records
an explicit operator decision under current `RESOLVE_EFFECT` policy and the session lease.
It clears that unknown-effect barrier without another tool or model call. Identical decisions
are idempotent; conflicting outcomes/reasons refuse. Missing terminal results are first closed
as unknown, preserving original evidence. Malformed partial groups refuse instead of clearing
the barrier. A live worker still prevents resolution. Resolution reasons are bounded operator
metadata, and credentials must never be included.

Tether bounds its owned jobs by `ClientLimits.max_active_turns`. Lease loss stops the Agent;
shutdown cancels owned turns and retains storage until actual workers and in-flight heartbeats
exit. An incomplete bounded close keeps ownership through deferred cleanup.

## What it is for

You are building an agent application or a multi-user platform in Python and want to own the
interface, the storage, and the policy. The intended runtime capabilities, delivered in later slices, are:

- **Agent and tools.** Pydantic AI Agent calls a model through the Tether integration, runs your tools (plain
  `def` or `async def` functions with typed arguments), and stops for approval when a tool
  asks.
- **Sessions.** A session is an append-only log committed step by step, so a UI can read it
  while a turn runs and a crashed turn can be recovered.
- **A UI boundary.** A client API lists sessions, reads transcripts in pages, and follows live
  changes with a resumable feed. It is a Python API, not a server; a web server is an adapter
  you write over it.
- **Telemetry.** Spans and audit entries for every turn, model call, and tool call, without
  message content by default.
- **Replaceable models.** Pydantic AI native/compatible model integration is selected. Local/self-hosted subsets and optional provider SDK extras need separate qualification; there is no required hosted-provider account.
- **Testing.** Every port has a dummy implementation and a conformance suite. A recorded model
  conversation replays through the loop deterministically.

## What it is not

Not a UI, not a server, not a multi-agent orchestration graph, not a coding agent, and not a
model provider SDK and not tied to a hosted provider: the base is provider-SDK-free and selected optional adapters may use native SDKs. See the
[non-goals](docs/helix/00-discover/product-vision.md#non-goals).

## License

Dual licensed under either of

- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
- MIT license ([LICENSE-MIT](LICENSE-MIT))

at your option. Copyright 2026 FocusedDiversity LLC.

## Contributing

You can contribute directly: open an issue or a pull request. There is no contributor license agreement to sign. Contributions are accepted as prompts: describe the change you want precisely enough that a maintainer's coding agent can implement it, and include any reference code as illustration. See [CONTRIBUTING.md](CONTRIBUTING.md).

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project, as defined in the Apache-2.0 license, is dual licensed as above, without any additional terms or conditions.


Optional archive reads use `Tether(..., archive=source)` and explicit `tier="archive"` on
`Client.list/get/entries/history`. Get/entries default to live first and fall back only on actual
absence. Archive failures stay distinct from empty results. Derived sources remain partial;
full history hydration checks current payload permission after the final metadata await.

`InMemoryArchive(ArchiveLimits(...))` is the bounded volatile reference source/sink. Configure
`archive_sink=sink` and explicitly call `app.archive_session(actor, session_id)` after idle/close;
this preserves live data and runs outside turns. Application retention policy schedules it.
`Client.restore(session_id)` requires a full restorable archive and current read/payload/restore
permission, copies verified content before atomic journal restoration, and preserves pending,
unknown effects and closure. A retry with conflicting reference or creation metadata refuses.
Reference-preserving restore currently requires compatible original provider/codec addresses.
See [archive conformance](tests/test_archive_ports.py) and [Client journeys](tests/test_client_archive.py).


Optional PostgreSQL18 session storage and content history use `novirhiza-tether[postgres]` and
`tether.stores.postgres.PostgresSessionStore` and `PostgresHistory`. Supply a `ConnectionSource(actor, session_id)` async
context manager that borrows an idle psycopg async connection. The application owns its pool,
credential refresh, roles and RLS. Tether owns each short transaction and refuses ambient ones.
Run `migrate(source, actor=maintenance_actor, schema="tether")` explicitly, then
`DatabaseRoot.open(source, actor=bootstrap_actor, schema="tether")`; ordinary opening verifies
schema/revision, namespace and durability settings without migration. Compose
`PostgresSessionStore(root, maintenance=maintenance_actor)` and `PostgresHistory(root)`, or use
independent database roots/history and journal backends. Bind every caller via `store.for_actor(actor)`;
unbound caller operations refuse. Root recovery requires explicit maintenance authority. A bound
PostgreSQL facade also offers `recover_owner(now=aware_datetime)` for deployments permitting only
caller-scoped recovery. Borrowed facade close is non-owning; close database roots explicitly.
Tether never closes your pool.
`migration_sql(schema)` exports the fresh-schema plain SQL statements; apply the complete sequence
in one transaction. `migrate` explicitly upgrades a verified revision1 schema to revision2,
preserving its namespace and immutable history bytes; repeat calls verify the current revision.
Ordinary opening refuses revision1 until that upgrade. Conflicting or drifted tables refuse
rather than receiving implicit repair. Revision2 includes indexed journal, metadata/paging,
materialized pending/effect state and persisted feeds. Leased writes enforce holder/generation
and server-time expiry inside the transaction. Uncertain acknowledgments seal the current handle;
reopening reconstructs from canonical facts. Global recovery is one bounded transaction: candidate
count/bytes or result-byte overflow refuses without partial publication. `page_limit` bounds its
candidate set; larger deployments must select an explicit bound and authority.

This checkpoint qualifies PostgreSQL18.6 with logged ordinary tables, `fsync=on`,
`full_page_writes=on` and `synchronous_commit=on`. It preserves canonical bytes, immutable
keys and ordered read statuses. Cancellation/timeouts retain actual connection work, and
release failures after possible commit remain uncertain. Independent roots share namespace
actual-worker tracking with separate root admission gates. Closing one root seals execution through
that root while a sibling remains usable. Real separate-client processes cover lease exclusion,
takeover, stale operations and killed-client recovery; paired SQL Agent/Client cases cover approval
restart, safe/unsafe resume, manual resolution and archive/restore. A fabricated application-owned
unprivileged-role RLS profile demonstrates credential refresh and denied-caller refusal. This does
not qualify arbitrary deployment roles, downstream exactly-once effects or OS power loss.
Optional `novirhiza-tether[embedded-postgres]` installs the database driver. The embedded helper also
requires an operator-installed `pgserver-pg18==18.6.1` binary wheel from the
[pinned PostgreSQL 18.6 release](https://github.com/DocumentDrivenDX/pgserver/releases/tag/pg18.6-pgv0.8.6-r2).
From this checkout, `uv sync --locked --extra embedded-postgres --group embedded-binaries`
installs the platform-matched wheel with the committed hash. For the qualified macOS arm64/Python 3.11 profile,
install the same prerequisite before using the published distribution:

```sh
uv pip install 'https://github.com/DocumentDrivenDX/pgserver/releases/download/pg18.6-pgv0.8.6-r2/pgserver_pg18-18.6.1-cp311-cp311-macosx_14_0_arm64.whl#sha256=fd42f07982693e16478ed42a297890d142d22250116e08887146e690f0dd2649'
```

The helper uses the same pinned PostgreSQL18 binaries as the
qualification suite. `EmbeddedPostgres.start(EmbeddedConfig(...))` requires an explicit bootstrap_user,
absolute data_dir/runtime_dir, startup_seconds/shutdown_seconds/poll_seconds and max_roots. Its
endpoint supplies Unix socket connection fields; create your own source, run migrations explicitly,
and register roots through `server.open_root(source, actor=actor, schema="tether")`. Close the helper
with `await server.aclose()`. False means retained cleanup is still draining physical work. Persistent
data is preserved; unmanaged directories and existing native servers refuse adoption. The helper
uses local trust authentication within its private runtime directory and disables TCP listening.
The selected binary profile is non-root macOS arm64 or Linux arm64/x86_64, Python 3.11–3.13; other
profiles refuse. Parent death can leave a server requiring explicit operator cleanup. Server binaries
stay out of the base and postgres-only runtime extras. See CONTRACT-003 for the lifecycle contract.
Broader release/profile coverage remains work.


Reusable port checks are available from `tether.testing.conformance`. Await
`live_store_suite(factory)` with an async context-manager factory yielding
`LiveStoreFixture(unbound_store, actor, foreign_actor)`. Use an isolated fabricated namespace;
a durable factory reopens that same namespace on its next entry. The factory owns explicit
migrations, connections and cleanup. The same suite runs against memory, JSONL and PostgreSQL.

`archive_suite(factory)` passes a fabricated immutable snapshot to an async factory yielding
`ArchiveFixture(source, actor, foreign_actor, sink=None)`. Supply current actors and credentials;
source and sink remain independent. A source-only fixture preloads the snapshot. A supplied sink
is exercised even when the source facade is read-only. Suite inputs use independent copies so
provider mutation cannot rewrite the expected result.

Each metadata-only SuiteReport lists executed checks, declared source/store and optional sink
capabilities, and `required_external` qualifications. These logical checks do not confer full
fault, process or release qualification. Keep backend corruption/unknown acknowledgment, physical
worker, contended snapshot/approval and process-fencing suites alongside them. The base package
adds no pytest dependency; a pytest/anyio test may simply await either suite. See CONTRACT-003
for fixture shapes, bounds and the complete-profile obligations.

## Development consumer operations

The development source adds model-free `Client.invoke_tool`, leased preparation, staged settlement/reconciliation, `Client.transition_spec`, immutable registry publication, caller-bound `Client.auxiliary`, edited approvals, and paired PostgreSQL history/journal transactions. These APIs are beyond the published **0.1.0** package.

Run the fully fabricated public API example from a source checkout:

```sh
uv sync --locked
uv run --locked python examples/consumer_capabilities.py
```

The [example](examples/consumer_capabilities.py) combines all of these capabilities without a model server or paid calls. [Contracts](docs/helix/02-design/contracts/) define identity, limits, refusal and recovery semantics. New Client/model/Engine contract revisions are 5/4/4. New readers accept legacy records; do not downgrade journals containing new generation or edited-approval records to an older Engine.

Client and model execution limits now have finite reference-derived defaults and per-field overrides. Model capacity/tokenizer settings remain explicit. Trace names accept independent Engine `span_prefix` and `attribute_namespace` settings; journal and audit names remain canonical. Published dependency pins retain the actually tested singleton versions.

Shared PostgreSQL composition requires the same verified `DatabaseRoot` object, including its caller acquisition policy. A shared participant receives bounded named SQL operations, never a connection in `ToolContext`. Independent settlement records its decision before the application action and retains uncertainty until current-authorized same-ID reconciliation. Applications own stage data, idempotency, scheduling and deployment-specific qualification.
