Metadata-Version: 2.5
Name: fava-trails
Version: 0.7.0
Summary: FAVA Trails 🫛👣 — Federated Agents Versioned Audit Trail. VCS-backed memory for AI agents via MCP.
Project-URL: Homepage, https://github.com/MachineWisdomAI/fava-trails
Project-URL: Repository, https://github.com/MachineWisdomAI/fava-trails
Project-URL: Issues, https://github.com/MachineWisdomAI/fava-trails/issues
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** — Git-native, curated memory for AI agents via MCP.

Every thought, decision, and observation is stored as a markdown file with YAML frontmatter in a Git repo you control, with durable persistence and a versioned audit trail. Agents interact through [MCP](https://modelcontextprotocol.io/) tools — they never see VCS commands.

## Governed recall

> **Release status:** The governed visibility model below describes the **current
> unreleased 0.7.0 tree** (release candidate on `main`). PyPI and GitHub Releases
> still list **0.6.0** as latest; that published build does **not** include the
> later 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 RC)** — 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 `llm-oneshot` (synchronous single-record rubric review). Non-LLM promotion is **not** a config toggle: on an operator endpoint use `propose_truth(..., approval="human")`. Rubric 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:** PyPI and GitHub Releases still list **0.6.0** as latest.
Main identifies as **0.7.0** with governed-recall, MCP registration, and later
fixes **merged but unreleased**. 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 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/) to access 300–500+ models from 60+ providers including Anthropic, OpenAI, Google, Qwen, and others. Get a free API key at [openrouter.ai/keys](https://openrouter.ai/keys). The default model (`google/gemini-2.5-flash`) costs ~$0.001 per review. **Provider selection is a data-egress choice:** under `llm-oneshot`, candidate content is transmitted to the configured destination *before* a verdict exists (a remote reject still means the text already left the process). 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`, 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` when using 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.

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

- [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.
