Skip to content

Latest commit

Β 

History

411 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

PyPI License Tests Python Views

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 tools β€” they never see VCS commands.

Governed recall

Release status: The governed visibility model below describes the current unreleased 0.6.1 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.

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

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.6.1 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 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.
  • 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) 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:

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.

From PyPI (recommended)

pip install fava-trails

Publication note: PyPI and GitHub Releases still list 0.6.0 as latest. Main identifies as 0.6.1 with governed-recall and MCP registration fixes merged but unreleased. Confirm what you actually loaded with fava-trails version (see 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)

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

Quick Start

Set up your data repo

New data repo (from scratch):

# Create an empty repo on GitHub (or any git remote), 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):

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.

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 and 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):

{
  "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):

{
  "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):

{
  "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

[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):

{
  "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 to access 300–500+ models from 60+ providers including Anthropic, OpenAI, Google, Qwen, and others. Get a free API key at 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 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) via ~/.config/fava-trails/config.yaml, or promote without LLM transmission using operator propose_truth(..., approval="human") β€” see 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.

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. Source Markdown remains canonical.

Cross-Machine Sync

FAVA Trails uses git remotes for cross-machine sync. 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.

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.

Setting up a second machine

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

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:

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:

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:

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:

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.

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.

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_SCOPE Agent Project-specific scope from .env file (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 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:

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 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, based on the SECOM paper (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.

pip install fava-trails[secom]

Add to your data repo's config.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:

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 for full config reference, model options, and the secom-skip opt-out. See AGENTS_SETUP_INSTRUCTIONS.md for the general hooks system.

Quick setup via CLI:

# 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) (Stanford, UC Berkeley, and SambaNova, ICLR 2026). Applies multiplicative scoring using rules stored in the preferences/ namespace.

pip install fava-trails  # included in base install

Add to your data repo's config.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:

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) MapReduce workflows. Validates mapper outputs, tracks batch progress, and sorts results deterministically for reducer consumption.

pip install fava-trails  # included in base install

Add to your data repo's config.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:

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

Development

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

Docs

Contributing

See CONTRIBUTING.md for setup instructions, how to run tests, and PR expectations.

See 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 for private artifacts, lifecycle and lineage blockers, crash recovery, and the real-data approval gate.

About

πŸ«›πŸ‘£ FAVA Trails β€” Git-native, curated memory for AI agents via MCP. Draft isolation, promotion gate, thought lifecycle hooks, memory curation protocols, supersession chains.

Topics

Resources

Contributing

Security policy

Stars

25 stars

Watchers

3 watching

Forks

Releases

Used by

Contributors

Languages