Metadata-Version: 2.5
Name: fava-trails
Version: 0.8.0
Summary: FAVA Trails 🫛👣 — Federated Agents Versioned Audit Trail. VCS-backed memory for AI agents via MCP.
Project-URL: Homepage, https://fava-trails.org/
Project-URL: Documentation, https://github.com/MachineWisdomAI/fava-trails#readme
Project-URL: Repository, https://github.com/MachineWisdomAI/fava-trails
Project-URL: Issues, https://github.com/MachineWisdomAI/fava-trails/issues
Project-URL: Machine Wisdom AI, https://machine-wisdom.ai/
Author: Machine Wisdom Solutions Inc.
License: Apache-2.0
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.11
Requires-Dist: any-llm-sdk<2.0,>=1.10.0
Requires-Dist: httpx<1.0,>=0.27.0
Requires-Dist: jsonschema<5.0,>=4.26.0
Requires-Dist: mcp<3.0,>=2.2.0
Requires-Dist: pydantic<3.0,>=2.0.0
Requires-Dist: python-ulid<4.0,>=3.0.0
Requires-Dist: pyyaml<7.0,>=6.0
Requires-Dist: starlette<2.0,>=1.3.1
Requires-Dist: uvicorn<1.0,>=0.41.0
Provides-Extra: all
Requires-Dist: any-llm-sdk[anthropic,bedrock,gemini,groq,mistral,ollama]<2.0,>=1.10.0; extra == 'all'
Requires-Dist: llmlingua<1.0,>=0.2.0; extra == 'all'
Provides-Extra: anthropic
Requires-Dist: any-llm-sdk[anthropic]<2.0,>=1.10.0; extra == 'anthropic'
Provides-Extra: bedrock
Requires-Dist: any-llm-sdk[bedrock]<2.0,>=1.10.0; extra == 'bedrock'
Provides-Extra: gemini
Requires-Dist: any-llm-sdk[gemini]<2.0,>=1.10.0; extra == 'gemini'
Provides-Extra: groq
Requires-Dist: any-llm-sdk[groq]<2.0,>=1.10.0; extra == 'groq'
Provides-Extra: mistral
Requires-Dist: any-llm-sdk[mistral]<2.0,>=1.10.0; extra == 'mistral'
Provides-Extra: ollama
Requires-Dist: any-llm-sdk[ollama]<2.0,>=1.10.0; extra == 'ollama'
Provides-Extra: secom
Requires-Dist: llmlingua<1.0,>=0.2.0; extra == 'secom'
Description-Content-Type: text/markdown

[![PyPI](https://img.shields.io/pypi/v/fava-trails)](https://pypi.org/project/fava-trails/)
[![License](https://img.shields.io/github/license/MachineWisdomAI/fava-trails)](LICENSE)
[![Tests](https://img.shields.io/github/actions/workflow/status/MachineWisdomAI/fava-trails/test.yml?label=tests)](https://github.com/MachineWisdomAI/fava-trails/actions)
[![Python](https://img.shields.io/pypi/pyversions/fava-trails)](https://pypi.org/project/fava-trails/)
[![Views](https://raw.githubusercontent.com/MachineWisdomAI/fava-trails/traffic/traffic-fava-trails/views.svg)](https://github.com/MachineWisdomAI/fava-trails)

# FAVA Trails

**Federated Agents Versioned Audit Trail**, an open-source agent memory system by [Machine Wisdom AI](https://machine-wisdom.ai/).

FAVA Trails helps agents share reviewed decisions across sessions and environments. Agents save drafts, submit them for review, and recall approved records with a history of what changed. Records are Markdown files with YAML frontmatter in a Git-compatible repository you control, exposed through [MCP](https://modelcontextprotocol.io/) tools.

[Get started](#install) · [Product website](https://fava-trails.org/) · [Two-agent case study](https://fava-trails.org/case-study/) · [Machine Wisdom AI's engineering writing](https://machine-wisdom.ai/writing/)

## New in 0.8: Jev at the memory boundary

An agent can write a plausible conclusion in seconds. Deciding whether the next
agent should inherit it deserves a separate step. FAVA Trails 0.8 uses
[TypeSafe Jev through OpenRouter Decisions](https://openrouter.ai/blog/insights/what-is-jev/)
as its default Trust Gate for new installs: one typed question, an explicit
approval threshold, and a review record you can inspect.

Jev scores the candidate against the supplied memory policy. FAVA applies the
threshold, records the returned model and probability, and makes approved
records available through governed recall. Failed reviews stop promotion.
Your records remain versioned Markdown in your own repository.

[Read the 0.8.0 release notes](docs/releases/v0.8.0.md) for the calibration
results, existing-install configuration, and data-egress details. Review is a
quality control step; it does not establish that a claim is true.

## Why we built FAVA Trails

An observation from one agent can become an assumption for the next. Machine Wisdom AI built FAVA Trails to give that transition an explicit review boundary: saving a thought does not make it accepted knowledge, and an approved correction preserves the record it replaces. The result is a shared history that agents can query and operators can inspect.

This is useful when several agents reuse decisions, when work continues across sessions, or when you need to trace why a remembered conclusion changed. Review remains a quality control step; it does not independently verify the facts. The [governed recall guide](docs/governed-recall.md) describes the identity and visibility boundaries.

## Governed recall

> **Release status:** The governed visibility model below ships in
> [FAVA Trails 0.7.0](https://github.com/MachineWisdomAI/fava-trails/releases/tag/v0.7.0),
> published to PyPI and GitHub on September 13, 2026. Older **0.6.0** does **not**
> include these governed-read isolation / MCP registration fixes. Confirm what
> you loaded with `fava-trails version` — see [docs/runtime-and-upgrade.md](docs/runtime-and-upgrade.md).

FAVA is the governed institutional record for decisions, observations, validation,
and lineage. It is not the operational working-context store. Default `recall`
and `get_thought` expose current approved records only. Explicit `mode="authoring"`
retrieves only the server-configured agent's draft/proposed records; operator-only
`mode="history"` selects lifecycle statuses and superseded records. Neither a
namespace nor a supplied `agent_id` grants access. See [governed-recall.md](docs/governed-recall.md)
for identity setup, compatibility, approval provenance, and interrupted-write recovery.

The operator configures `FAVA_TRAILS_AGENT_ID` on a dedicated process; caller
`agent_id` must match it. A shared endpoint is one identity boundary. Configure
`FAVA_TRAILS_OPERATOR=1` only on a separate operator-controlled endpoint.

For a long-lived private ChatGPT connection, follow the deployment-neutral
[Secure MCP Tunnel operator guide](docs/secure-mcp-tunnel.md).

## Why

- **Supersession tracking** — a proposed correction leaves the original current; approved replacements make predecessors historical in default recall. Lineage is recorded; supersession does **not** prove the replacement is true.
- **Draft isolation (0.7.0)** — working thoughts stay in `drafts/`. Default governed `recall`/`get_thought` expose approved current records only; own drafts need explicit `mode="authoring"` on a configured identity. A shared MCP endpoint or shared data filesystem is one boundary, not per-caller crypto isolation. Published **0.6.0** does not match this isolation model — upgrade/check the loaded version before relying on it.
- **Trust Gate** — default policy is `decisions` (OpenRouter TypeSafe Jev: one typed Noul question and a calibrated threshold). Explicit `llm-oneshot` rubric review and operator `propose_truth(..., approval="human")` remain supported. Review is process control with limited context — **not** independent verification of project facts, and not a guarantee that hallucinations never enter shared truth. A reject does not mean the draft was never stored or sent for review. A separate [bounded obvious-secret preflight](docs/secret-preflight.md) refuses a small set of high-confidence credential shapes before normal write and promotion paths persist or transmit them. It is not complete DLP and does not erase already-stored records.
- **Lexical recall** — `recall` matches lowercased whitespace-separated query tokens as substrings across content and selected metadata (AND). It is not semantic similarity search. See [docs/retrieval-baseline.md](docs/retrieval-baseline.md).
- **Full lineage** — every thought carries who wrote it, when, and why it changed.
- **Durable writes** — a successful tool return means the thought file and JJ commit path finished for that operation. File write still precedes several awaited JJ steps, so interruption can leave a recoverable dirty or incomplete working copy; it is not a guarantee of fully atomic multi-step commits or “no dirty working copy.”
- **Engine/Fuel split** — this repo is the engine MCP process (retains managers/hooks in memory; durable corpus is not embedded). Your data lives in a separate Fuel repo you control.

## Install

### Prerequisites

FAVA Trails uses [Jujutsu (JJ)](https://jj-vcs.github.io/jj/) as its storage engine, running in colocate mode alongside Git. Your repo remains a standard Git repo (GitHub and CI/CD see normal commits). Publishing local commits uses `push_strategy: immediate` (auto-push after successful writes) or the full manual protocol `jj bookmark set main -r @-` then `jj git push --bookmark main` (completed writes sit at `@-`). The `sync` MCP tool only fetches/rebases shared truth — it does not push. One-time install:

```bash
fava-trails install-jj
```

This reuses any already-installed JJ at or above the supported minimum (**0.28.0**), including newer versions. It never silently downgrades or overwrites a user-managed `jj`. When installation is needed, it resolves the current official GitHub stable release (override with `--version` / `JJ_VERSION` for reproducible environments). See [docs/jj-compatibility.md](docs/jj-compatibility.md).

### From PyPI (recommended)

```bash
pip install fava-trails
```

**Publication note:** [0.7.0 is published on PyPI](https://pypi.org/project/fava-trails/0.7.0/)
with governed-recall, MCP registration, and the feedback-driven fixes below.
Confirm what you actually loaded with
`fava-trails version` (see [docs/runtime-and-upgrade.md](docs/runtime-and-upgrade.md)).
Local `uv run --directory …` or vendor checkout selectors can keep an older tree
active after a package upgrade — restart the MCP client registration after
changing the install.

### From source (for development)

```bash
git clone https://github.com/MachineWisdomAI/fava-trails.git
cd fava-trails
uv sync
```

## Quick Start

### Set up your data repo

**Local-only evaluation (no git remote):**

A separate local JJ/Git repository is a valid evaluation setup. Save, recall,
review, and supersession work without a remote. FAVA never creates a hosted
repository, pushes private content, or changes remotes automatically.

```bash
fava-trails bootstrap fava-trails-data
```

The `sync` tool reports status `not_configured` until an operator adds a
reachable remote. That is not a broken remote. Do not enable tunnel
`--sync-on-start` on a local-only repository; required startup sync stays
fail-closed.

**New shared data repo (from scratch):**

```bash
# Create an empty repo on a git host you already operate, then clone it
git clone https://github.com/YOUR-ORG/fava-trails-data.git

# Bootstrap it (creates config, .gitignore, initializes JJ in colocate mode)
fava-trails bootstrap fava-trails-data
```

**Existing data repo (clone from remote):**

```bash
fava-trails clone https://github.com/YOUR-ORG/fava-trails-data.git fava-trails-data
```

### Register the MCP server

FAVA uses MCP SDK 2.2 or later within the 2.x series. Existing stdio client
configuration and the private Streamable HTTP endpoint remain supported, including
legacy `initialize` clients. Tool input/output schemas, annotations, and structured
responses are preserved. Restart a configured server after updating its package;
installing the package alone does not update a running process.

`initialize` advertises FAVA's **product** version in `serverInfo.version`. That
value is not the MCP SDK distribution version. Use `fava-trails version` to print
both, plus the loaded module path, without credentials.

Direct stdio testing is not native registration. Print current instructions that
use an ordinary server-configured agent identity, the resolved `fava-trails-server`
executable, and the intended data repository:

```bash
fava-trails register --agent-id claude-code
```

Optional client-config write is explicit (`--write`). It preserves unrelated client
settings, writes atomically, keeps a `.bak` backup, opens backup and replacement
files with the final mode before writing, caps that mode at `0600` (stricter
existing modes are kept), refuses non-writable existing configs, and reports
permission denial without bypassing client controls. Unresolved `fava-trails-server`
is an error unless `--executable` names an existing executable. `--verify` runs a
direct MCP smoke test, inspects the client config, and loads that config through
MCP Inspector. Diagnostics label which ran. Inspector success is
`inspector_config_load`, not a Claude Code/Desktop session. Missing Inspector is
`inspector_unavailable`; invocation/download failures, config load failures, server
spawn failures, MCP initialize failures, stale runtime paths, and registration not
loaded stay distinct. Native-session evidence that a client loaded Claude-shaped
`mcpServers` config is `tests/test_mcp_protocol.py::test_native_client_registration_loads_and_initializes`.

```bash
fava-trails register --write --verify --config ~/.claude.json --agent-id claude-code
```

Set `FAVA_TRAILS_AGENT_ID` on each ordinary authoring process. A shared endpoint
is one identity boundary; `FAVA_TRAILS_OPERATOR=1` belongs only on a separate
operator endpoint. Details:
[docs/governed-recall.md](docs/governed-recall.md) and
[docs/runtime-and-upgrade.md](docs/runtime-and-upgrade.md).

Add to your MCP client config:
- **Claude Code CLI**: `~/.claude.json` (top-level `mcpServers` key)
- **Claude Desktop**: `claude_desktop_config.json`

Authoring endpoints must set a stable process identity (`FAVA_TRAILS_AGENT_ID`).
Without it the server rejects writes. Omit the identity only for deliberate
read-only / governed-read setups.

**If installed from PyPI (authoring):**

```json
{
  "mcpServers": {
    "fava-trails": {
      "command": "fava-trails-server",
      "env": {
        "FAVA_TRAILS_DATA_REPO": "/path/to/fava-trails-data",
        "FAVA_TRAILS_AGENT_ID": "claude-code",
        "OPENROUTER_API_KEY": "sk-or-v1-..."
      }
    }
  }
}
```

**If installed from source (authoring):**

```json
{
  "mcpServers": {
    "fava-trails": {
      "type": "stdio",
      "command": "uv",
      "args": ["run", "--directory", "/path/to/fava-trails", "fava-trails-server"],
      "env": {
        "FAVA_TRAILS_DATA_REPO": "/path/to/fava-trails-data",
        "FAVA_TRAILS_AGENT_ID": "claude-code",
        "OPENROUTER_API_KEY": "sk-or-v1-..."
      }
    }
  }
}
```

For Claude Desktop on Windows (accessing WSL, authoring):

```json
{
  "mcpServers": {
    "fava-trails": {
      "command": "wsl.exe",
      "args": [
        "-e", "bash", "-lc",
        "FAVA_TRAILS_DATA_REPO=/path/to/fava-trails-data FAVA_TRAILS_AGENT_ID=claude-code OPENROUTER_API_KEY=sk-or-v1-... fava-trails-server"
      ]
    }
  }
}
```

**OpenAI Codex CLI** (authoring): `~/.codex/config.toml`

```toml
[mcp_servers.fava-trails]
command = "fava-trails-server"

[mcp_servers.fava-trails.env]
FAVA_TRAILS_DATA_REPO = "/path/to/fava-trails-data"
FAVA_TRAILS_AGENT_ID = "codex-cli"
OPENROUTER_API_KEY = "sk-or-v1-..."
```

**Other MCP clients** (Crush, OpenCode, etc.): check your client's MCP config docs — most accept this JSON format (authoring):

```json
{
  "mcpServers": {
    "fava-trails": {
      "type": "stdio",
      "command": "fava-trails-server",
      "env": {
        "FAVA_TRAILS_DATA_REPO": "/path/to/fava-trails-data",
        "FAVA_TRAILS_AGENT_ID": "my-agent",
        "OPENROUTER_API_KEY": "sk-or-v1-..."
      }
    }
  }
}
```

> **The Trust Gate uses an LLM (or explicit human) review step:** Thoughts can be reviewed before promotion. The reviewer applies a configured rubric or a typed Noul question with limited context — it does **not** independently verify project facts, your agent safety policy, or ground truth. A convincing false claim can still be approved. By default, FAVA Trails uses [OpenRouter](https://openrouter.ai/) Decisions with TypeSafe Jev (`~typesafe/jev-latest`). Get a free API key at [openrouter.ai/keys](https://openrouter.ai/keys). **Provider selection is a data-egress choice:** under `decisions`, the full prompt, candidate content, selected metadata, and Noul question are transmitted to the configured destination *before* a verdict exists (a remote reject still means the text already left the process). Jev returns no reasoning. A local [obvious-secret preflight](docs/secret-preflight.md) refuses a bounded set of known credential shapes before persist or transmit; it is not complete DLP and does not erase already-stored drafts. Run `fava-trails doctor` to see the effective destination/model and which candidate fields are sent (API secrets are never printed). You can point Trust Gate at a local OpenAI-compatible endpoint (e.g. [Unsloth Studio](https://unsloth.ai/docs/new/studio)) via `~/.config/fava-trails/config.yaml` with explicit `llm-oneshot`, or promote without LLM transmission using operator `propose_truth(..., approval="human")` — see [AGENTS_SETUP_INSTRUCTIONS.md](AGENTS_SETUP_INSTRUCTIONS.md).

### Use it

Agents call MCP tools. Core workflow:

```
save_thought(trail_name="myorg/eng/my-project", content="My finding about X", source_type="observation")
  → creates a draft in drafts/ (not visible under default governed recall)

propose_truth(trail_name="myorg/eng/my-project", thought_id=thought_id)
  → Trust Gate / operator review, then promotes to observations/ when approved

recall(trail_name="myorg/eng/my-project", query="X")
  → finds the promoted thought because token "x" appears in the content
  → query "finding about X" also matches (whitespace tokens, AND)
  → query "discovery regarding X" misses unless those words appear in the record
```

Agents interact through MCP tools — they never see VCS commands. Matching rules and
a shareable synthetic matrix: [docs/retrieval-baseline.md](docs/retrieval-baseline.md).

## Local scope reader

Generate a private, read-only dashboard from a FAVA scope and its descendants, then follow thought provenance, lineage, and typed relationships. [Generate and serve Rich Views locally](docs/rich-views.md). Source Markdown remains canonical.

## Cross-Machine Sync

Cross-machine sharing is optional. It requires a configured, reachable git
remote that every machine can fetch and, when using push, write to. Before
encouraging `sync`, plan for that remote as an operational dependency:

- Hosting and access: who can read private thoughts, how credentials rotate,
  and how you revoke a machine.
- Maintenance: keep the remote URL reachable, monitor disk/hosting cost, and
  recover from permission or connectivity failures yourself.
- Failure modes: a missing remote is `not_configured` (local-only). A
  configured remote that is unreachable or denies permission is an error, not
  local-only. Required startup sync (`--sync-on-start`) stays fail-closed in
  every non-`ok` case.

FAVA does not create hosted repositories, push private content, or change
remote settings automatically. Add a remote yourself (`git remote add origin
<url>`) or clone an existing shared repository with `fava-trails clone`.

`fava-trails bootstrap` writes `push_strategy: manual` by default — local
commits stay local until you publish them. Publishing is **not** what the
`sync` MCP tool does:

| Path | Behavior |
|------|----------|
| `push_strategy: immediate` | After each successful write, the server advances `main` and runs `jj git push` (push failures are non-fatal warnings). |
| `push_strategy: manual` (bootstrap default) | No auto-push. Operator must `jj bookmark set main -r @-` then `jj git push --bookmark main` (or set `immediate`). |
| `sync` MCP tool | Fetches/rebases from the remote only. Does **not** commit dirty local files and does **not** publish local commits. Missing remotes return `not_configured`. |

For multi-machine authoring, set `push_strategy: immediate` in the data repo `config.yaml` (or publish manually after writes). Peers still call `sync` to pull. Local-only repositories should keep `push_strategy: manual`.

### Setting up a second machine

```bash
# 1. Install FAVA Trails
pip install fava-trails

# 2. Install JJ (storage engine; runs alongside Git in colocate mode)
fava-trails install-jj

# 3. Clone the SAME data repo (handles colocated mode + bookmark tracking)
fava-trails clone https://github.com/YOUR-ORG/fava-trails-data.git fava-trails-data

# 4. Register MCP (same config as above, with local paths + FAVA_TRAILS_AGENT_ID)
```

Both machines share the same git remote. The writing machine must publish before peers can fetch (`immediate`, or manual `jj bookmark set main -r @-` then `jj git push --bookmark main`); the reading machine calls `sync` to fetch/rebase.

### ChatGPT tunnel freshness

`fava-trails-tunnel start` runs the private MCP runtime behind the OpenAI Secure
MCP Tunnel. Tunnel-managed data repo sync is disabled by default; use the
normal `sync` MCP tool when you want to fetch/rebase shared trail data.

```bash
fava-trails-tunnel start --data-repo /path/to/fava-trails-data --profile fava-trails
```

The `/healthz` readiness probe is bounded and non-mutating. It validates the
data repository config and required `trails` directory, traverses at most
100,000 scope-tree entries within two seconds, and parses one representative
thought (up to 512 KiB) when records exist. A structurally valid empty repository
is ready. Missing, unreadable, over-limit, timed-out, or malformed data returns
HTTP 503 with a stable reason code. The response contains counts and status only;
it never includes filesystem paths, credentials, thought bodies, or repository
content.

Readiness proves local data readability by the runtime identity. It does **not**
prove remote freshness, perform a sync, contact the network, or validate that a
write would succeed. A deployment that requires fresh startup data can request
one bounded, fail-closed sync inside the gateway before it exposes the tunnel:

```bash
fava-trails-tunnel start --data-repo /path/to/fava-trails-data --profile fava-trails --sync-on-start
```

With `--sync-on-start`, a non-ok result, timeout, or exception prevents HTTP and
tunnel exposure. Keep `--sync-interval-seconds` at its default `0` when no
later autosync is wanted; a positive interval additionally starts the recurring
worker without repeating the initial sync.

Before starting the external tunnel, deployments can validate the private
runtime without exposing it:

```bash
fava-trails-tunnel preflight --data-repo /path/to/fava-trails-data --profile fava-trails
```

`preflight` starts only the loopback HTTP runtime, waits for `/healthz`, and
always stops the private runtime before returning. It never starts the external
tunnel.

The tunnel startup wait and `status` command consume `/healthz`; `status` exits
non-zero when the supervisor is running but its data is not ready:

```bash
curl http://127.0.0.1:8765/healthz
fava-trails-tunnel status --data-repo /path/to/fava-trails-data --profile fava-trails --json
```

If ChatGPT guessed scopes before the read-only guard was installed, remove only
verified scaffolding-only scopes with an explicit dry run first:

```bash
fava-trails cleanup-empty-scopes --scope mw/headspace --scope mw
fava-trails cleanup-empty-scopes --scope mw/headspace --scope mw --apply
```

### Manual push (required when `push_strategy: manual`)

Bootstrap defaults to `manual`. Under that setting, approved local records stay on the writing machine until an operator publishes. The `sync` tool will not push them.

```bash
cd /path/to/fava-trails-data
jj bookmark set main -r @-
jj git push --bookmark main
```

Prefer setting `push_strategy: immediate` for multi-machine authoring so successful writes auto-publish. **NEVER use `git push origin main`** after JJ colocates — it misses thought commits. See [AGENTS_SETUP_INSTRUCTIONS.md](AGENTS_SETUP_INSTRUCTIONS.md#pushing-to-remote).

## Architecture

```
fava-trails (this repo)        fava-trails-data (your repo)
├── src/fava_trails/           ├── config.yaml
│   ├── server.py  ←── MCP ──→├── .gitignore
│   ├── cli.py                 └── trails/
│   ├── trail.py                   └── myorg/eng/project/
│   ├── config.py                      └── thoughts/
│   ├── trust_gate.py                      ├── drafts/
│   ├── hook_manifest.py                   ├── decisions/
│   ├── protocols/                         ├── observations/
│   │   └── secom/                         └── preferences/
│   └── vcs/
│       └── jj_backend.py
└── tests/
```

- **Engine** (`fava-trails`) — MCP server process, Apache-2.0. Install via `pip install fava-trails`. Runtime retains managers, backend handles, locks, and loaded hooks between calls; it does not store the durable corpus in-package.
- **Fuel** (`fava-trails-data`) — your organization's trail data (the durable memory graph), private.

## Configuration

Environment variables:

| Variable | Read by | Purpose | Default |
|----------|---------|---------|---------|
| `FAVA_TRAILS_DATA_REPO` | Server | Root directory for trail data (monorepo root) | `~/.fava-trails` |
| `FAVA_TRAILS_DIR` | Server | Override trails directory location (absolute path) | `$FAVA_TRAILS_DATA_REPO/trails` |
| `FAVA_TRAILS_SCOPE_HINT` | Server | Broad scope hint baked into tool descriptions | *(none)* |
| `FAVA_TRAILS_MCP_SURFACE` | Server | `full` (default) or `compact` advertised instructions/tool text | `full` |
| `FAVA_TRAILS_SCOPE` | Agent | Optional process override for project scope. Read if set; `fava-trails init` does not write application `.env` files unless `--write-env` is passed. | *(none)* |
| `OPENROUTER_API_KEY` | Server | Default Trust Gate API key env (OpenRouter). Override the env var *name* via `trust_gate_api_key_env` / legacy `openrouter_api_key_env` in `config.yaml`. | *(none — required for `propose_truth` under the default `decisions` policy, and for explicit `llm-oneshot`)* |

**LLM Provider:** FAVA Trails uses [any-llm-sdk](https://github.com/mozilla-ai/any-llm) for unified LLM access. OpenRouter is the default Trust Gate provider. To use a local OpenAI-compatible server (Unsloth Studio, vLLM, etc.) on one machine, put its Trust Gate runtime fields in `$XDG_CONFIG_HOME/fava-trails/config.yaml` (default `~/.config/fava-trails/config.yaml`). A credential file configured with `trust_gate_api_key_file` takes precedence over the environment and must be a regular, non-symlink, owner-only file. Slow local quantized models may need a higher `trust_gate_timeout_secs` (still below `tool_timeout_secs`). There is no automatic fallback between providers — misconfigured or unavailable endpoints fail closed without auto-approving. `fava-trails doctor` prints a secret-free Data egress notice; successful LLM or operator `propose_truth` paths (and credential/timeout failures after disclosure begins) include `trust_gate_egress` describing destination, model, and which candidate fields are sent. Early validation failures omit that field.

**Decisions policy (OpenRouter Jev, new-install default; local Unsloth Laya optional):** New installs review promotions through [OpenRouter's Decisions API](https://openrouter.ai/blog/insights/what-is-jev/) using TypeSafe's Jev (`~typesafe/jev-latest` tracking, or pin `typesafe/jev-1.13`). Supply an existing OpenRouter credential; you do not have to design a Noul question. One typed **Noul** question — the probability that a yes/no proposition holds — is submitted per review with the full scope-resolved Trust Gate prompt, full thought body, and the selected metadata (`agent_id` and `metadata.extra` excluded). Probabilities at or above `trust_gate_noul_threshold` approve; lower probabilities reject. Jev returns no reasoning, so provenance records the calibrated probability and threshold — never fabricated rationale. Invalid responses, missing answers, out-of-range probabilities, authentication/connection failures, and timeouts fail closed; there is no fallback to another reviewer. Optional reasoning is not generated and must not be invented. Select exactly one backend; FAVA does not probe localhost or switch providers.

Shipped question and threshold come from the [ticket 03 aggregate calibration report](https://github.com/MachineWisdomAI/fava-trails/issues/126#issuecomment-5894849724). The private calibration corpus is available upon request.

```yaml
trust_gate: decisions
trust_gate_provider: openrouter
trust_gate_model: ~typesafe/jev-latest
trust_gate_decisions_config:
  trust_gate_noul_question: "Should this candidate be promoted as durable, high-quality institutional memory under the supplied Trust Gate policy?"
  trust_gate_noul_threshold: 0.45
```

Local Unsloth Laya is the no-per-request-cost alternative for the same `decisions` policy. Reuse the existing local `openai` provider, credential file or env, and timeout used by local `llm-oneshot`. Documentation recommends `laya-typed-decisions`; do not select the generic `laya` alias. Requests go to the configured Unsloth base at `/v1/systemone`:

```yaml
trust_gate: decisions
trust_gate_provider: openai
trust_gate_model: laya-typed-decisions
trust_gate_api_base: http://127.0.0.1:<unsloth-api-port>/v1
trust_gate_api_key_file: /path/to/owner-only/runtime/api-key
trust_gate_timeout_secs: 240
```

The Decisions endpoint defaults to `https://openrouter.ai/api/alpha/decisions`; `trust_gate_api_base` overrides the API root (for a test double, or for local Unsloth the loopback Decision API). Credentials reuse `trust_gate_api_key_env` / `trust_gate_api_key_file`. `trust_gate_decisions_config` is a Trust Gate runtime field: the per-machine config may override the whole block, while the data repo owns durable defaults. Explicit `trust_gate: llm-oneshot` remains fully operational (set a chat-completions model such as `google/gemini-2.5-flash`); operator `propose_truth(..., approval="human")` is unchanged. `fava-trails doctor` and tunnel gateway startup disclose the destination (OpenRouter as `remote_provider`, loopback Unsloth as `local_endpoint`) without secrets. Missing OpenRouter credentials for Decisions review also present local Unsloth Laya as an alternative.

The server reads `$FAVA_TRAILS_DATA_REPO/config.yaml` for global settings. Minimal `config.yaml`:

```yaml
trails_dir: trails          # relative to FAVA_TRAILS_DATA_REPO
remote_url: null            # git remote URL (optional)
push_strategy: manual       # manual | immediate
```

The standard per-machine config overrides only Trust Gate runtime fields. Repository settings such as `trails_dir`, `remote_url`, `push_strategy`, hooks, and trail definitions remain owned by the data repo. Effective precedence is machine config, then data-repo config, then defaults.

When `push_strategy: immediate`, the server auto-pushes after every successful write (advances `main` to `@-` then pushes). Push failures are non-fatal. When `manual` (bootstrap default), writes commit locally only; use the full manual protocol above (`jj bookmark set main -r @-` then `jj git push --bookmark main`). The `sync` tool never substitutes for push.

See [AGENTS_SETUP_INSTRUCTIONS.md](AGENTS_SETUP_INSTRUCTIONS.md) for full config reference including trust gate and per-trail overrides.

## Protocols

FAVA Trails supports optional **lifecycle protocols** — hook modules that run custom logic at key points in the thought lifecycle (save, promote, recall). Protocols are registered in your data repo's `config.yaml` and loaded at server startup.

### SECOM — Compression at Promote Time

Extractive token-level compression via [LLMLingua-2](https://github.com/microsoft/LLMLingua), based on the [SECOM paper](https://arxiv.org/abs/2502.05589) (Tsinghua University and Microsoft, ICLR 2025). Thoughts are compressed once at promote time (WORM pattern), reducing storage and boosting recall density. Purely extractive — only original tokens survive, no paraphrasing or rewriting.

```bash
pip install fava-trails[secom]
```

Add to your data repo's `config.yaml`:

```yaml
hooks:
  - module: fava_trails.protocols.secom
    points: [before_propose, before_save, on_recall]
    order: 20
    fail_mode: open
    config:
      compression_threshold_chars: 500
      target_compress_rate: 0.6
      compression_engine:
        type: llmlingua
```

**Structured data**: SECOM's token-level compression has no notion of syntactic validity — JSON objects, YAML blocks, and fenced code blocks may be silently destroyed at promote time. Tag thoughts with `secom-skip` to opt out:

```python
save_thought(trail_name="my/scope", content='{"phases": [...]}', metadata={"tags": ["secom-skip"]})
```

The `before_save` hook warns when structured content is detected without `secom-skip`.

See [protocols/secom/README.md](src/fava_trails/protocols/secom/README.md) for full config reference, model options, and the `secom-skip` opt-out. See [AGENTS_SETUP_INSTRUCTIONS.md](AGENTS_SETUP_INSTRUCTIONS.md#lifecycle-hooks) for the general hooks system.

**Quick setup via CLI:**

```bash
# Print default config (copy-paste into config.yaml)
fava-trails secom setup

# Write config directly + commit with jj
fava-trails secom setup --write

# Pre-download model to avoid first-use delay
fava-trails secom warmup
```

### ACE — Agentic Context Engineering

Playbook-driven reranking and anti-pattern detection, based on [ACE (arXiv:2510.04618)](https://arxiv.org/abs/2510.04618) (Stanford, UC Berkeley, and SambaNova, ICLR 2026). Applies multiplicative scoring using rules stored in the `preferences/` namespace.

```bash
pip install fava-trails  # included in base install
```

Add to your data repo's `config.yaml`:

```yaml
hooks:
  - module: fava_trails.protocols.ace
    points: [on_startup, on_recall, before_save, after_save, after_propose, after_supersede]
    order: 10
    fail_mode: open
    config:
      playbook_namespace: preferences
      telemetry_max_per_scope: 10000
```

**Quick setup via CLI:**

```bash
fava-trails ace setup           # print default config
fava-trails ace setup --write   # write + jj commit
```

### RLM — MapReduce Orchestration

Lifecycle hooks for [MIT RLM (arXiv:2512.24601)](https://arxiv.org/abs/2512.24601) MapReduce workflows. Validates mapper outputs, tracks batch progress, and sorts results deterministically for reducer consumption.

```bash
pip install fava-trails  # included in base install
```

Add to your data repo's `config.yaml`:

```yaml
hooks:
  - module: fava_trails.protocols.rlm
    points: [before_save, after_save, on_recall]
    order: 15
    fail_mode: closed
    config:
      expected_mappers: 5
      min_mapper_output_chars: 20
```

**Quick setup via CLI:**

```bash
fava-trails rlm setup           # print default config
fava-trails rlm setup --write   # write + jj commit
```

## Development

```bash
uv run pytest -v          # run tests
uv run pytest --cov       # with coverage
```

## Docs

- [FAVA Trails and Machine Wisdom AI](docs/attribution.md) — Project authorship, engineering articles, and attribution guidance
- [AGENTS.md](AGENTS.md) — Agent-facing: MCP tools reference, scope discovery, thought lifecycle, agent conventions
- [AGENTS_USAGE_INSTRUCTIONS.md](AGENTS_USAGE_INSTRUCTIONS.md) — Canonical usage: scope discovery, session protocol, agent identity
- [AGENTS_SETUP_INSTRUCTIONS.md](AGENTS_SETUP_INSTRUCTIONS.md) — Data repo setup, config reference, trust gate prompts, lifecycle hooks
- [protocols/secom/README.md](src/fava_trails/protocols/secom/README.md) — SECOM compression protocol: config, models, WORM architecture
- [docs/fava_trails_faq.md](docs/fava_trails_faq.md) — Detailed FAQ for framework authors and ML engineers
- [docs/mcp-context-overhead.md](docs/mcp-context-overhead.md) — Measured MCP session-init overhead, compact surface, enforcement vs prompt
- [docs/secret-preflight.md](docs/secret-preflight.md) — Bounded credential preflight: data flow, detection limits, false positives

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions, how to run tests, and PR expectations.

See [CHANGELOG.md](CHANGELOG.md) for release history.

### Duplicate maintenance

Use `fava-trails duplicates` for a read-only exact-body report and a digest-bound,
reviewed migration plan. Applying or rolling back requires an explicit operator
command and preserves a JJ recovery point and before images. See
[reviewed duplicate maintenance](docs/duplicate-migration.md) for private artifacts,
lifecycle and lineage blockers, crash recovery, and the real-data approval gate.

## Built by Machine Wisdom AI

FAVA Trails is part of [Machine Wisdom AI's open-source work on production agent systems](https://machine-wisdom.ai/). Read the [agent memory architecture analysis](https://machine-wisdom.ai/writing/agent-memory-landscape/) and [context engineering protocol guide](https://machine-wisdom.ai/writing/agent-memory-protocols/) for the reasoning behind the implementation.

To use the project, start with the installation and setup above. For hands-on help applying these ideas to a production system, see [Machine Wisdom AI's engagement options](https://machine-wisdom.ai/engage/).
