{
  "markdown": "# Horizon Fidelity Monitor\n\n> **\"Quality is not a model property — it is a conversation property.\"**\n\n<p align=\"center\">\n  <a href=\"https://github.com/leocelis/horizon/actions/workflows/ci.yml\"><img src=\"https://img.shields.io/github/actions/workflow/status/leocelis/horizon/ci.yml?branch=main&style=flat-square&label=tests\" alt=\"Tests\"></a>\n  <a href=\"https://trust.complyedge.io/horizon\" rel=\"noopener noreferrer\">\n    <img src=\"https://api.complyedge.io/v1/public/badge/horizon.svg\" alt=\"ComplyEdge — runtime enforcement status\" height=\"26\">\n  </a>\n</p>\n\nHorizon is a real-time conversation health monitor for AI agents. It tracks the **structural dynamics** of multi-turn conversations — semantic drift, information gain, ontological gap width, temporal desynchronisation, circadian cognitive load, conversation velocity, and causal reachability — dimensions that LLMs do not reliably surface from inside the conversation.\n\nHorizon ships **two measurement planes**. The **conversation plane** (above, always\npresent) measures the health of a dialogue turn by turn. The optional **mission plane**\n— *Memento Mori* — measures elapsed **calendar** time against goals: ages, deadlines,\nstalls, per-entity latency, and share of a finite horizon. It is inert until you\nconfigure a store. See [Mission plane](#mission-plane-memento-mori).\n\nHorizon is **not** a manipulation, sycophancy, or human-influence detector — it measures conversation *dynamics*, not whether an agent is steering or flattering the user. See [LEGAL.md §1](LEGAL.md#1-what-horizon-is--and-is-not).\n\nWhy an external monitor? LLMs have *limited and unreliable* self-knowledge: introspection research shows partial self-access that is brittle and degrades on complex or out-of-distribution tasks ([Binder et al. 2024](https://arxiv.org/abs/2410.13787); [arXiv:2512.12411](https://arxiv.org/abs/2512.12411)). So rather than depend on a model reporting its own conversation dynamics, Horizon measures them externally with cheap, deterministic, always-on arithmetic that does not call the model at all.\n\n---\n\n## Why this exists\n\nMulti-turn AI agents lose accuracy. The ICLR 2026 Outstanding Paper [\"LLMs Get Lost In Multi-Turn Conversation\"](https://iclr.cc/virtual/2026/poster/10009146) (Laban, Hayashi, Zhou & Neville — Microsoft Research / Salesforce Research) reports **39% average accuracy degradation** across multi-turn evaluation — a structural property that standard observability tools (LangSmith, RAGAS, DeepEval) cannot see because they measure responses, not conversations.\n\nHorizon was built to close that gap. **It is observability first:** it surfaces conversation dynamics that response-level tools miss, using cheap deterministic arithmetic with zero model calls. In four controlled A/B scenarios where Horizon events drove a re-grounding intervention we measured a **+15.7% composite quality lift** and **87% fewer hallucination events** — but those are *synthetic, scripted scenarios with a hand-tuned controller*, not a production result. Treat them as promising in-house evidence, not a guaranteed outcome (see [Validation](#validation) and [LEGAL.md §5](LEGAL.md#5-performance-claims--scope-and-substantiation)). Every signal — information gain, divergence, estimated ontological gap width, causal reachability — is a standard information-theory or arithmetic measure computed on text embeddings and timestamps; see [4D Spacetime Signals](#4d-spacetime-signals) for the full definitions.\n\n- Read the demand proof → [ICLR 2026 Outstanding Paper (Laban et al.)](https://iclr.cc/virtual/2026/poster/10009146)\n- Read the category argument → [`docs/content/naming-the-category-conversation-dynamics-monitoring.md`](docs/content/naming-the-category-conversation-dynamics-monitoring.md)\n- Read the engineering case → [`docs/content/why-every-production-agent-needs-conversation-dynamics-monitoring.md`](docs/content/why-every-production-agent-needs-conversation-dynamics-monitoring.md)\n\n---\n\n## Getting started\n\nThree paths — pick the one that fits your workflow:\n\n### Path 1 — Hosted MCP (fastest, zero install)\n\nThe fastest way to add Horizon to any Cursor, VS Code, or Claude Desktop workspace. No Python required.\n\nRequest an alpha key → [open a Discussion](https://github.com/leocelis/horizon/discussions/new?category=q-a), then add the config for your client:\n\n**Cursor** (`~/.cursor/mcp.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"horizon\": {\n      \"url\": \"https://horizon.leocelis.com/sse\",\n      \"headers\": { \"Authorization\": \"Bearer YOUR_KEY_HERE\" }\n    }\n  }\n}\n```\n\n**VS Code / GitHub Copilot** (`.vscode/mcp.json` in your workspace):\n\n```json\n{\n  \"servers\": {\n    \"horizon\": {\n      \"type\": \"http\",\n      \"url\": \"https://horizon.leocelis.com/sse\",\n      \"headers\": { \"Authorization\": \"Bearer YOUR_KEY_HERE\" }\n    }\n  }\n}\n```\n\n> **VS Code note:** Use `\"servers\"` (not `\"mcpServers\"`) and `\"type\": \"http\"` — VS Code tries Streamable HTTP first and falls back to SSE automatically, so `\"type\": \"http\"` works with the `/sse` URL.\n\n**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"horizon\": {\n      \"url\": \"https://horizon.leocelis.com/sse\",\n      \"headers\": { \"Authorization\": \"Bearer YOUR_KEY_HERE\" }\n    }\n  }\n}\n```\n\nThat's it. Reload your MCP client and three tools appear: `new_conversation`, `process_turn`, `configure_session`.\n\n> **Alpha access:** Horizon's hosted endpoint is in private alpha. Keys are distributed to agent developers who want to monitor real projects. [Open a Discussion](https://github.com/leocelis/horizon/discussions/new?category=q-a) to request one — describe your use case and we'll send a key.\n\n### Path 2 — pip install (library integration)\n\n**Not yet published to PyPI — until it is, use [Path 3](#path-3--mcp-server-from-source) (install from source) below.**\n\n```bash\npip install horizon-monitor\n```\n\nVerify your install (exercises the full pipeline on 5 canonical scenarios, ~25s):\n\n```bash\nhorizon-validate\n```\n\n### Path 3 — MCP server from source\n\n```bash\npip install 'horizon-monitor[mcp]'\nhorizon serve                             # stdio — for Cursor, Claude Desktop\nhorizon serve --transport sse --port 3847 # SSE — for web/team deployments\n```\n\nAdd to `~/.cursor/mcp.json`:\n```json\n{\n  \"mcpServers\": {\n    \"horizon\": { \"command\": \"horizon\", \"args\": [\"serve\"] }\n  }\n}\n```\n\nFull Cursor and Claude Desktop setup guides: [`docs/integrations/`](docs/integrations/)\n\n---\n\n## What it monitors\n\nStandard observability tools evaluate individual response quality. Horizon evaluates **conversation quality** — a structurally different problem:\n\n| Tool | What it sees | What it misses |\n|---|---|---|\n| LangSmith, Braintrust | Latency, cost, per-response quality | Deterministic, every-turn structural signals |\n| RAGAS, DeepEval | Faithfulness, relevance per turn (DeepEval also has sampled multi-turn LLM-judge metrics) | Zero-LLM-call, real-time scoring on every turn |\n| Langfuse, Arize Phoenix | Session-level LLM-judge evaluation | Deterministic, always-on scoring at sub-50ms |\n| Human raters | Subjective quality | Systematic structural decay |\n| **Horizon** | **Conversation dynamics** | Intentionally nothing |\n\nHorizon does not replace per-response or LLM-judge quality tools. The differentiator is *how* it measures: deterministic, zero-LLM-call arithmetic on every single turn — effectively free and always-on — versus the alternative of sampled LLM-judge evaluations, which cost per sample and typically run offline or async rather than in real time.\n\n---\n\n## Quickstart\n\n```python\nfrom horizon_monitor import FidelityMonitor\nfrom datetime import datetime, timezone\n\nmonitor = FidelityMonitor()\nsession_id = monitor.new_conversation(metadata={\"domain\": \"technical\"})\n\nresult = monitor.process_turn(\n    session_id,\n    human_message=\"How does Python handle memory management?\",\n    agent_response=\"Python uses reference counting and a cyclic garbage collector...\",\n    timestamp=datetime.now(timezone.utc).isoformat(),\n)\n\nprint(f\"Fidelity:         {result.fidelity_score:.2f}\")\nprint(f\"Health:           {result.health_status}\")\nprint(f\"Circadian factor: {result.circadian_factor:.2f}\")\nprint(f\"Causal horizon:   {result.reachable_turns} reachable turns\")\nfor event in result.events:\n    print(f\"  Event: {event.type} (confidence={event.confidence:.2f})\")\n```\n\n---\n\n## Framework integrations\n\n### OpenAI SDK\n\n```python\nfrom openai import OpenAI\nfrom horizon_monitor import FidelityMonitor\n\nmonitor = FidelityMonitor()\nsession_id = monitor.new_conversation()\nclient = monitor.wrap(OpenAI(), session_id)\n\nresponse = client.chat.completions.create(\n    model=\"gpt-4o\",\n    messages=[{\"role\": \"user\", \"content\": \"Tell me about quantum computing.\"}]\n)\n\ntraj = monitor.get_trajectory(session_id)\nprint(f\"Fidelity: {traj.current_fidelity:.2f}  T*: {traj.estimated_t_star}\")\n```\n\n`monitor.wrap()` accepts custom timestamp and context providers for testing and replay.\n\n### Anthropic SDK\n\n```python\nfrom anthropic import Anthropic\nfrom horizon_monitor import FidelityMonitor\n\nmonitor = FidelityMonitor()\nsession_id = monitor.new_conversation()\nclient = monitor.wrap(Anthropic(), session_id)\n\nresponse = client.messages.create(\n    model=\"claude-3-5-sonnet-20241022\",\n    max_tokens=1024,\n    messages=[{\"role\": \"user\", \"content\": \"Explain RLHF.\"}]\n)\n```\n\n### LangChain\n\n```python\nfrom langchain_openai import ChatOpenAI\nfrom horizon_monitor import FidelityMonitor\nfrom horizon_monitor.integrations.langchain import HorizonCallback\n\nmonitor = FidelityMonitor()\nsession_id = monitor.new_conversation()\ncallback = HorizonCallback(monitor, session_id)\n\nllm = ChatOpenAI(callbacks=[callback])\nllm.invoke(\"Explain the CAP theorem.\")\nprint(f\"Fidelity: {callback.last_result.fidelity_score:.2f}\")\n```\n\n### OpenAI Agents SDK\n\n```python\nfrom agents import Agent, Runner\nfrom horizon_monitor import FidelityMonitor\n\nmonitor = FidelityMonitor()\nsession_id = monitor.new_conversation()\nagent = Agent(name=\"assistant\", model=\"gpt-4o-mini\", instructions=\"You are helpful.\")\n\nfor user_message in conversation:\n    result = Runner.run_sync(agent, user_message)\n    monitor.process_turn(session_id, human_message=user_message,\n        agent_response=result.final_output, timestamp=datetime.now(timezone.utc).isoformat())\n```\n\n---\n\n## 4D Spacetime Signals\n\n> **\"Spacetime\" here is a metaphor, not physics.** The relativity vocabulary (Minkowski interval,\n> light cone, proper time) is *design inspiration* — it shaped which quantities we compute. Every\n> signal below reduces to a standard information-theory or arithmetic measure on text embeddings and\n> timestamps, listed in the **Plain definition** column. Nothing in Horizon's behavior or validation\n> depends on the analogy being literally true, and the Lorentzian `interval_class` is emitted as\n> descriptive metadata only — no event or score depends on it.\n\nEvery `process_turn()` returns a `TurnResult` with 32 fields across five signal families:\n\n### Core (always present)\n\n| Signal | Description |\n|---|---|\n| `fidelity_score` | Composite conversation health [0, 1] |\n| `igt_value` | Information Gain per Turn — semantic novelty |\n| `divergence_score` | Jensen-Shannon proxy for intent/response gap |\n| `twr_value` | Token Waste Ratio — semantic redundancy |\n| `consistency_score` | Bipredictability — structural coherence |\n| `epsilon_t` | Estimated ontological gap width [0, 1] |\n| `health_status` | `healthy` / `degrading` / `critical` / `converged` |\n| `conversation_mode` | `execute` / `explore` / `refine` / `learn` (auto-detected) |\n\n### Temporal (requires `timestamp`)\n\n| Signal | Description |\n|---|---|\n| `gap_seconds` | Wall-clock gap since last turn |\n| `estimated_retention` | Human memory retention (Ebbinghaus half-life model) |\n| `circadian_factor` | Human cognitive capacity at this hour [0.3, 1.0] |\n| `temporal_asymmetry` | Penalty for temporal desync |\n| `resumption_cost` | `none` / `low` / `medium` / `high` / `extreme` |\n| `temporal_references` | Resolved deictic expressions (\"yesterday\", \"last week\") |\n\n### Pace (requires `timestamp` + turn ≥ 2)\n\n| Signal | Description |\n|---|---|\n| `conversation_velocity` | Semantic displacement / proper time |\n| `conversation_acceleration` | Velocity delta (requires turn ≥ 3) |\n\n### Spacetime (requires `timestamp` + turn ≥ 2) — descriptive metadata only\n\n| Signal | Description (metaphor) | Plain definition (what it computes) |\n|---|---|---|\n| `spacetime_interval` | ds² with Minkowski-like signature (−,+,+,+) | A 4-term weighted distance: `ds² = −α·log(1+Δt)² + β·ΔD_JS² + γ·Δε² + δ·ΔC²`. The minus sign on the time term is a convention, not a physical law. |\n| `interval_class` | `timelike` / `spacelike` / `lightlike` | The sign bucket of `ds²` (`< −ε`, `> ε`, else lightlike). Emitted as metadata only — **no event or fidelity score consumes it**. |\n\n### Causal (requires `timestamp`)\n\n| Signal | Description (metaphor) | Plain definition (what it computes) |\n|---|---|---|\n| `reachable_turns` | Turns still inside the causal light cone | Count of prior turns where `in_context × retention(Δt) × cosine_similarity > θ` — still in-window, not yet memory-decayed, and topically related. |\n| `reachable_fraction` | Fraction of history still causally reachable | `reachable_turns / (turn − 1)`. |\n\n### Spatial (requires `client_context`)\n\n| Signal | Description |\n|---|---|\n| `location_class` | `home` / `office` / `mobile_transit` / `unknown` |\n| `spatial_constraint` | Attention budget, screen capacity, max response length |\n| `spatial_frame_shift` | Context switch magnitude |\n\n---\n\n## 16 Event Types (conversation plane)\n\nAll events default to **observe mode** (emitted, not acted on). Enable active mode via `configure()` once your event achieves ≥ 0.7 precision/recall on your domain.\n\n| Event | Fires when |\n|---|---|\n| `checkpoint.clarification` | D_JS above clarification threshold |\n| `checkpoint.comprehension` | Consistency drops below threshold |\n| `alert.drift` | Fidelity declining for `drift_window` consecutive turns |\n| `alert.contradiction` | Bipredictability below consistency threshold |\n| `alert.verbosity` | Token Waste Ratio above verbosity threshold |\n| `signal.convergence` | IGT trend consistently low — natural endpoint approaching |\n| `signal.optimal_length` | T* (estimated optimal length) reached |\n| `signal.horizon_widening` | IGT trend strongly positive — conversation expanding |\n| `signal.session_reset` | Large temporal gap with low retention |\n| `signal.temporal_desync` | Gap + retention drop below desync threshold |\n| `signal.broken_reference` | Reachable fraction drops below broken-reference threshold |\n| `signal.frame_shift` | Spatial constraint shifts significantly |\n| `signal.pace_shift` | Conversation acceleration above pace threshold |\n| `signal.light_cone_collapse` | Reachable fraction below light-cone threshold |\n| `signal.grounding_required` | Heuristic grounding-need score crosses threshold — agent should hedge or cite grounding evidence |\n| `signal.pace_premature_report` | User replied faster than a previously flagged deferred action could plausibly complete, with no completion signal |\n\n---\n\n\n## Mission plane (Memento Mori)\n\n> **\"Progress is not a turn property — it is a calendar property.\"**\n\nThe conversation plane answers *is this dialogue degrading?* The mission plane answers a\ndifferent question: *is this goal still moving, and against what clock?* A month of\nindividually healthy conversations that advance nothing is, to a conversation monitor, a\nmonth of perfect health.\n\n**The name is the design.** Every store has exactly one **root horizon** — a finite end\ndate you choose. Everything else hangs off it, so every day an item spends is a share of\na budget that is visibly running out. Without a finite root, deferring work costs\nnothing and \"later\" is free forever. That is the failure this plane exists to make\nvisible.\n\n**It is off by default.** With no store configured, its six tools do not register and\nnothing in your integration changes.\n\n### The problem it catches\n\nA task was given until 20 July. It is now 18 August and nobody has touched the mission\nsince 2 July. A decision was parked \"until things calm down\" with a revisit date of\n10 August that has quietly passed. The work has been sitting with one party for three\nweeks. None of that is visible in any conversation, in any tracker's status column, or\nin a model's context window — and each turn of each conversation about it looks healthy.\n\nThe mission plane reports it as: mission 78 days old, 47 days since progress, task\nlifespan expired, park 8 days overdue, currently blocked on `operator` for 21 days and\ncounting.\n\n### Vocabulary\n\nEverything is an **item** in a tree under the root horizon. There are eight kinds:\n\n| Kind | What it is |\n|---|---|\n| `horizon` | the finite root — exactly one per store, and the denominator for every share |\n| `mission` | a goal with a clock; the thing that can stall |\n| `task` | a unit of work with a **TTL** — an agreed window whose expiry means *investigate*, never *you estimated badly* |\n| `deadline` | an external date (regulatory, contractual, market), ideally linked to the internal work it gates |\n| `gate` | a checkpoint with an age budget |\n| `entity` | something the work passes through and waits on — a queue, a vendor, a system, you |\n| `deferral` | a park. **Requires a revisit date**; the store refuses one without it |\n| `probe` | a small, dated trial of an alternative way of working, so routes are compared by measurement rather than opinion |\n\nTwo more terms appear in the outputs: a **sojourn** is one recorded stay in a stage\n(enter → exit), and the **incumbent** is the way you are working today, as opposed to a\nprobe of some alternative.\n\n### Quickstart\n\n```bash\nexport HORIZON_MEMENTO_STORE_PATH=~/.horizon/missions.db   # the default: one local file\n```\n\n<details>\n<summary>Running it somewhere durable and multi-tenant (MySQL)</summary>\n\nA file-backed store is the right default, but it is the wrong choice on any host whose\nfilesystem resets between deploys — the plane would look correct and silently forget\neverything, which is worse than not running at all. For those, point it at MySQL 8:\n\n```bash\npip install \"horizon-monitor[mysql]\"\nexport HORIZON_MEMENTO_STORE_DSN='mysql://user:pass@host:3306/horizon'  # wins over _PATH\nexport HORIZON_MYSQL_SSL_CA=/path/to/server-ca.pem   # or ..._CA_B64 for a PEM in an env var\n```\n\nTLS verification is mandatory — the backend refuses to connect without a CA. Each API key\nmaps to an **assigned** tenant id (`scripts/provision_tenant.py`), so rotating a key keeps\nthat tenant's history; unknown or revoked keys get no mission access at all.\n\n</details>\n\n<details>\n<summary>Feeding it from work you already do (artifact ingestion)</summary>\n\nA clock is only as good as what reaches it, and a record that depends on\nremembering to write is worth nothing on the day you forget. So the plane can\nderive events from append-only sources you already produce:\n\n```bash\npython scripts/ingest_artifacts.py --store ~/.horizon/missions.db \\\n    --repo /path/to/repo --item-id <mission-id>\n```\n\nEach commit becomes an `ARTIFACT` event carrying the source's own provenance, and\nthe event's `valid_time` is the commit's timestamp — not the moment you ingested\nit. Safe to run from cron: it dedupes on the source's native id and asks only for\nwhat is new.\n\nTwo things it will not do. It will not guess which mission an artifact belongs to\n— `--item-id` is required, and the adapter interface has no parameter capable of\nattaching one. And it will not judge what counts as progress. Those are yours.\n\nNot sure what to register in the first place? Ask what your history suggests:\n\n```bash\npython scripts/ingest_artifacts.py --store ~/.horizon/missions.db \\\n    --repo /path/to/repo --propose\n```\n\nIt reports the shape — how many artifacts, over what span, starting when — and\nproposes a `created_valid` equal to the earliest one. It proposes no title,\nbecause what the work *is* cannot be read off a commit log. Nothing is written;\nregistering the mission is your call.\n\n`GitLocalAdapter` is the reference implementation; trackers and mail metadata fit\nthe same `ArtifactAdapter` interface.\n\n</details>\n\n```python\nfrom datetime import date, datetime, timezone\n\nfrom horizon_monitor.memento import (\n    EventKind, ItemKind, MementoConfig, MementoStore, evaluate,\n)\n\nstore = MementoStore(\"missions.db\")   # a real file; set this up once\n\nroot = store.register_item(\n    kind=ItemKind.HORIZON, title=\"engagement horizon\",\n    created_valid=datetime(2026, 1, 1, tzinfo=timezone.utc),\n    end_date=date(2030, 1, 1),\n)\nmission = store.register_item(\n    kind=ItemKind.MISSION, title=\"ship-the-thing\", parent_id=root,\n    stall_days=14,                    # silence longer than this is a stall\n    created_valid=datetime(2026, 6, 1, tzinfo=timezone.utc),\n)\nstore.record_event(                   # progress: a side-effect of the work\n    item_id=mission, kind=EventKind.PROGRESS,\n    valid_time=datetime(2026, 7, 2, tzinfo=timezone.utc),\n)\n\n# The evaluation instant is always a parameter — the engine never reads a clock,\n# so the same store at the same instant always yields the same report.\nreport = evaluate(\n    store.snapshot(), datetime(2026, 8, 18, 12, tzinfo=timezone.utc), MementoConfig()\n)\n\nrow = next(r for r in report.items if r.item_id == mission)\nprint(f\"age:             {row.age_days} days\")            # 78 days\nprint(f\"since progress:  {row.days_since_progress} days\") # 47 days\nprint(f\"recording path:  {row.recording_path}\")           # no recent work\nprint(f\"horizon share:   {row.horizon_share:.4f}\")        # 0.0595\n```\n\nThe store is a real database, so run the setup once — registering a second root raises\n`DuplicateRootError` by design, which is the one-finite-root guarantee working, not a\nbug. For the full picture — an expired task, an overdue park, the blocking entity, a\nrefused write and a fired signal — run\n[`examples/memento_mori_mission_clock.py`](examples/memento_mori_mission_clock.py) (no\narguments, no network, no API key; it uses a fresh temporary store each time).\n\n### What it measures\n\n| Output | Meaning |\n|---|---|\n| Age, days-remaining, TTL state | how old work is, how long is left, whether a task outlived its window |\n| Days-since-progress + recording-path check | a stall — and whether it is *no work* or *no records*, never conflated |\n| Slowest entity / blocking entity | the longest recorded wait, and separately what the work waits on **right now** |\n| Horizon share | what fraction of the remaining root horizon this item has consumed |\n| Cost-of-delay, break-even date | only when *you* declare an hourly rate and amounts |\n| Path comparison | a probe's recorded sojourn beside the incumbent's accrued delay |\n\n### 12 signal types (mission plane)\n\nSeparate from the conversation plane's [16 event types](#16-event-types-conversation-plane),\nnot an extension of them. Each fires **once on an edge** — when its predicate becomes\ntrue — never again while the condition persists, and at most one new signal per turn, so\na bad week cannot flood you. Tiers order that cap: **P1** is time-critical, **P2**\nstructural, **P3** informational.\n\n| Signal | Fires when | Tier |\n|---|---|---|\n| `signal.deadline_window` | an external deadline enters its warning window | P1 |\n| `signal.ttl_expired` | a task outlives its ratified lifespan — *investigate the blocker* | P1 |\n| `signal.deferral_expired` | a deferral passes its revisit date | P2 |\n| `signal.gate_aging` | a gate exceeds its age budget with no progress | P2 |\n| `signal.mission_stalled` | no progress events for the mission's threshold (paired with the recording-path check) | P2 |\n| `signal.slowest_entity` | the identity of a mission's slowest recorded entity changes | P2 |\n| `signal.clock_unpaired` | a deadline exists with no linked internal state | P2 |\n| `signal.horizon_share` | an item's elapsed time crosses a threshold share of the remaining root horizon | P3 |\n| `signal.cost_of_delay` | accrued cost-of-delay crosses an operator threshold (rate + amount + threshold all declared) | P3 |\n| `signal.probe_ready` | a probe sojourn completes — enough to *compare numbers*, never a powered test | P3 |\n| `signal.path_ahead` | a probe's recorded sojourn is shorter than the incumbent's accrued delay (descriptive only) | P3 |\n| `signal.breakeven_passed` | a ratified break-even date passes without the measured improvement | P3 |\n\nThese ride the existing `process_turn` contract for sessions bound with\n`associate_mission`. Every event carries `plane: \"mission\"`, and the contract is\ndeliberately **loud** — mission signals are surfaced to the operator with their numbers,\nunlike conversation signals, which apply silently. See\n[agent rules](docs/integrations/MEMENTO_MORI_AGENTS.md) for the block to paste into your\nhost.\n\n### What it refuses\n\nAccounting, never estimation. The engine never invents a duration, date, or amount:\n\n- no forecasts, no completion predictions, no counterfactual \"what the other path would\n  have cost\"\n- no NPV/IRR/DCF, no discount rates, no currency conversion — money only ever multiplies\n  measured time\n- no p-values, confidence intervals, or sequential tests on path latencies: at\n  single-operator sample sizes no dominance claim survives audit, so comparison is\n  descriptive only\n- no people analytics — entity latency is reported on functional **slots**; a person's\n  wait is measured but never becomes a score, a ranking, or a resolvable identifier\n- missing inputs degrade **by omission with an explanatory field**, never by substitution\n\nEvery row carries a `derivation` string spelling out the arithmetic it came from, and\nany summary statistic additionally carries the `n` it summarised. Identical store plus\nidentical evaluation instant produces a byte-identical report.\n\n**Docs:** [product requirements](docs/product/MEMENTO_MORI_PRD.md) ·\n[technical spec](docs/spec/MEMENTO_MORI_TECH_SPEC.md) ·\n[agent rules](docs/integrations/MEMENTO_MORI_AGENTS.md) ·\n[acceptance test plan](docs/spec/MEMENTO_MORI_TEST_PLAN.md)\n\n---\n\n## Configure\n\n```python\n# Per-session override\nmonitor.configure(\n    session_id=session_id,\n    clarification_threshold=0.25,           # tighter D_JS gate\n    event_modes={\"alert.drift\": \"active\"},  # activate one event\n)\n\n# Compound weight override\nmonitor.configure(\n    fidelity_weights={\"alpha\": 0.35, \"lambda_r\": 0.12, \"lambda_i\": 0.28, \"beta\": 0.25},\n    temporal_weights={\"gamma\": 0.08, \"delta\": 0.04},\n    spacetime_coefficients={\"alpha\": 1.0, \"beta\": 1.0, \"gamma\": 0.8, \"delta_st\": 0.5},\n)\n```\n\n---\n\n## Export\n\n```python\n# JSON\nresult = monitor.export_to(session_id, target=\"json\")\n\n# LangSmith / Langfuse / OpenTelemetry / Arize\nresult = monitor.export_to(session_id, target=\"langsmith\",\n    connection={\"api_key\": \"ls__...\"})\n```\n\n**Not yet published to PyPI — see [Path 3](#path-3--mcp-server-from-source) for a source install in the meantime.**\n\n```bash\npip install horizon-monitor[langsmith]   # or langfuse, otel, arize\n```\n\n---\n\n## Architecture\n\n```\nInput: plain strings (human_message, agent_response, optional timestamp, optional client_context)\n\nCore pipeline (< 50ms on CPU):\n  1. Embed both turns (local sentence-transformers, lazy-loaded)\n  2–6.  IGT · D_JS · TWR · Bipredictability · Epsilon\n  7. Temporal signals  — gap, retention, circadian, deictic\n  8. Fidelity dynamics — composite score\n  9. Health classification\n 10. Pace signals       — velocity, acceleration\n 11. Spacetime interval — ds² and interval class\n 12. Causal reachability — light-cone membership\n 13. Spatial signals    — device, location, frame shift\n 14. Mode detection     — auto-classify conversation type\n 15. Event evaluation   — 16 threshold checks\n 16. Optional: SQLite persistence\n\nOutput: TurnResult dataclass (32 fields)\n```\n\n**Design constraints (test-enforced):**\n- Zero LLM calls — pure arithmetic and local embeddings\n- Zero external network calls by default — fully local\n- Zero transitive framework dependencies in core\n- < 50ms core pipeline on CPU — soft target (CI flags regressions past 50ms and hard-fails at 150ms)\n- < 100MB memory for 100-turn conversations — hard-enforced at the claimed value\n- All events observe-by-default — never interferes unless explicitly configured\n\n---\n\n## Validation\n\n**What is proven, and what is not.** Horizon's signals are *correlational, in-domain*\nmeasurements that track human quality ratings well. They are **observability**, not a\nproven outcome guarantee. Here is the honest status of each claim:\n\n| Claim | Status | Where |\n|---|---|---|\n| Fidelity correlates with human ratings (in-domain) | ✅ measured (ρ ≈ 0.6–0.7) | gates below |\n| Signal beats naive heuristics | ✅ measured | V3 |\n| Holds on a **third-party** corpus (out-of-domain) | ❌ tested — ρ = **0.039** on MT-Bench expert judgments (n=80; below 0.3 floor); needs direct quality labels | [`V0_2_0_EVIDENCE.md` §Fix 4](docs/reviews/V0_2_0_EVIDENCE.md#fix-4--cross-domain-in-repo), [`adapt_external_corpus.py`](scripts/adapt_external_corpus.py) |\n| Events **predict** degradation (leading, not lagging) | ⚠️ tested on MT-Bench — **insufficient-data** (2-turn chats; events rarely fire); tool works | [`leading_indicator.json`](docs/reviews/leading_indicator.json), [`measure_leading_indicator.py`](scripts/measure_leading_indicator.py) |\n| Acting on events **improves outcomes** (+15.7%) | ⚠️ synthetic A/B only; needs an independent corpus | [`run_interventional_ab.py`](scripts/run_interventional_ab.py), [LEGAL.md §5](LEGAL.md#5-performance-claims--scope-and-substantiation) |\n\nThe four gates below pass on a **labelled** 5,602-record corpus (not bundled — see the\n[evidence pack](docs/reviews/V0_2_0_EVIDENCE.md); `scripts/build_validation_corpus.py`\nregenerates a *synthetic* corpus that exercises the gate logic, not these exact numbers):\n\n| Gate | Constraint | v0.2.0 |\n|---|---|---|\n| V1 — proxy correlation | per-conv ρ ≥ 0.6, per-turn ρ ≥ 0.5 | **0.685 / 0.659** |\n| V2 — per-event P/R | every event P ≥ 0.7 AND R ≥ 0.7 | **all 16 events ≥ 0.70 / 0.70** |\n| V3 — beats heuristics | rho lift > 25%, structural P ≥ 0.6 | **+202.4% lift, P=R=1.00** |\n| V5 — cross-domain | per-turn ρ ≥ 0.4 AND per-conv ρ ≥ 0.48 | **min 0.517 / 0.718** |\n\nCross-embedding stability: ρ_conv spread **0.026**, ρ_turn spread **0.018** across three sentence-transformer backends (22M / 33M / 110M params). The fidelity signal lives in conversational structure, not in the embedding manifold. (Note: cross-*embedding* stability on the same corpus is distinct from cross-*corpus* OOD — first third-party run on MT-Bench pairwise labels gave ρ = 0.039; see evidence pack §Fix 4.)\n\nRemediation gaps source: [`DESIGN_FIXES_redteam_remediation.md`](docs/reviews/DESIGN_FIXES_redteam_remediation.md)\n\nFull evidence pack: [`docs/reviews/V0_2_0_EVIDENCE.md`](docs/reviews/V0_2_0_EVIDENCE.md)\n\n---\n\n## Deployment\n\n### Self-hosted Docker (MCP server on port 3847)\n\n```bash\ncd deploy/docker\ndocker compose up\n```\n\nHorizon serves the MCP API via SSE. Point `.cursor/mcp.json` to `http://localhost:3847/sse`. The Dockerfile pre-caches the `all-MiniLM-L6-v2` weights at build time — zero cold start.\n\n### Hosted (DigitalOcean App Platform)\n\nThe official hosted endpoint is live at `https://horizon.leocelis.com`. It runs on DigitalOcean App Platform (single instance, in-process session state — sessions do not survive a restart) and requires a Bearer token, rate-limited and isolated per key. See [Path 1](#path-1--hosted-mcp-fastest-zero-install) above.\n\n---\n\n## Development\n\n```bash\ngit clone https://github.com/leocelis/horizon.git\ncd horizon\npython -m venv .venv && source .venv/bin/activate\npip install -r requirements-dev.txt\n\npytest tests/ -v                         # full suite\npytest tests/unit tests/integration tests/e2e -v   # fast path (~6 min)\nruff check src/ tests/\nblack --check src/ tests/\n./scripts/compliance/check.sh              # EU AI Act offline gate\n```\n\n### ComplyEdge TrustLint — EU AI Act\n\nHorizon integrates **[ComplyEdge](https://complyedge.io)** TrustLint on LLM-facing artifacts — same offline + runtime + trust pattern as [IVD](https://github.com/leocelis/ivd).\n\n| Layer | What |\n|-------|------|\n| **Offline (required)** | `./scripts/compliance/check.sh` — scans `horizon_intent.yaml` + `horizon-monitor.mdc` |\n| **Runtime (BYOK)** | `./scripts/compliance/runtime_check.sh` — feeds [live seal](https://api.complyedge.io/v1/public/badge/horizon.svg) + [trust page](https://trust.complyedge.io/horizon) |\n| **CI gate** | `.github/workflows/ci.yml` jobs `compliance` + optional `compliance-runtime` |\n| **Agent rule** | `<BEGIN-COMPLYEDGE v1.0>` in `docs/cursor-rules/horizon-monitor.mdc` |\n\nIntegration guide: [`docs/integrations/COMPLYEDGE.md`](docs/integrations/COMPLYEDGE.md). Public CE embed docs: [trust badge](https://complyedge.io/docs/trust-badge.html).\n\n---\n\n## Repository layout\n\n```\nhorizon/\n├── src/horizon/         # package source (PEP 517/518 src/ layout)\n│   ├── engines/         # IGT, D_JS, TWR, coherence, fidelity, epsilon, mode\n│   ├── spacetime/       # temporal, circadian, deictic, velocity, interval, light cone, spatial\n│   ├── events/          # 16-event evaluator\n│   ├── integrations/    # OpenAI, Anthropic, LangChain, export targets\n│   ├── mcp/             # MCP server + CLI\n│   └── storage/         # optional SQLite persistence\n├── tests/               # unit / integration / e2e / perf / validation\n├── examples/            # runnable framework demos\n├── deploy/              # Procfile, build.sh, runtime.txt, docker/\n├── docs/\n│   ├── product/         # public product overview\n│   ├── content/         # published pieces on conversation dynamics monitoring\n│   ├── integrations/    # Cursor / Claude Desktop / Copilot setup guides\n│   ├── cursor-rules/    # horizon-monitor.mdc (canonical Cursor agent rule)\n│   ├── spec/            # HORIZON_TECH_SPEC.md + intent.yaml\n│   └── reviews/         # E2E reviews, validation evidence\n└── pyproject.toml\n```\n\n---\n\n## Background\n\nHorizon's design was *inspired by* the Trans-Horizon Communication Protocol (THCP), a speculative framework that maps human–AI communication onto general-relativity metaphors. The five THCP \"conjectures\" are **design intuitions, not proven laws** — each is useful only because it pointed at a concrete, computable signal:\n\n| THCP conjecture (metaphor) | Computable signal it inspired |\n|---|---|\n| **THCP-1** — irreducible ontological loss ε > 0 | `epsilon_t` — estimated intent/response gap width [0, 1] |\n| **THCP-2** — an optimal length T\\* exists beyond which fidelity decays | IGT-trend convergence detection (`signal.convergence`, `estimated_t_star`) |\n| **THCP-3** — communication requires encode/decode adjunction | `consistency_score` — bidirectional embedding predictability |\n| **THCP-4** — global coherence requires \"sheaf gluing\" across turns | cross-turn contradiction / claim-consistency checks |\n| **THCP-5** — optimal trajectories lie near the \"light cone\" | `reachable_fraction` — retention × similarity over prior turns |\n\nTHCP is **design motivation only** — see [`docs/product/THCP_FIDELITY_MONITOR_PRD.md`](docs/product/THCP_FIDELITY_MONITOR_PRD.md) for the full conjecture-to-signal mapping.\n\n---\n\n## Community\n\n- **Request alpha access:** [Open a Discussion →](https://github.com/leocelis/horizon/discussions/new?category=q-a)\n- **Ask a question:** [GitHub Discussions](https://github.com/leocelis/horizon/discussions/new?category=q-a)\n- **Bug reports:** [GitHub Issues](https://github.com/leocelis/horizon/issues/new)\n- **Contributing:** [CONTRIBUTING.md](CONTRIBUTING.md)\n\n---\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n\n---\n\n## Legal\n\n| Document | Purpose |\n|----------|---------|\n| [LEGAL.md](LEGAL.md) | Full legal notices: what Horizon is/is not, high-stakes domain warnings, performance claim scope, EU AI Act classification, grounding hook privacy, limitation of liability |\n| [TERMS_OF_SERVICE.md](TERMS_OF_SERVICE.md) | Binding terms governing hosted server access and commercial use |\n| [PRIVACY_POLICY.md](PRIVACY_POLICY.md) | GDPR Art. 13 compliant privacy notice — what data is collected and your rights |\n| [DATA_PROCESSING_AGREEMENT.md](DATA_PROCESSING_AGREEMENT.md) | GDPR Art. 28 DPA template for EU enterprise users (request via email) |\n| [SECURITY.md](SECURITY.md) | Responsible disclosure policy; known self-hosted security considerations |\n\n**Performance claims:** The +15.7% quality lift and 87% fewer hallucination events\nfigures in this README are from **synthetic, scripted controlled A/B scenarios** with\nhand-tuned reference controllers — not production traffic and not the in-domain\nvalidation corpus (V1–V5 gates use a separate labelled set). Results may vary by domain,\nmodel, and deployment configuration. Do not use these figures in external marketing\nwithout conducting your own domain-specific evaluation. See\n[LEGAL.md §5](LEGAL.md#5-performance-claims--scope-and-substantiation) for full scope\nand evidentiary basis.\n\n**High-stakes domains:** Do not enable event types in `active` mode in healthcare,\nlegal, financial, or emergency service contexts without domain-specific validation and\nhuman oversight. See [LEGAL.md §4](LEGAL.md#4-high-stakes-domain-warning).\n\n<!-- mcp-name: io.github.leocelis/horizon-fidelity-monitor -->\n",
  "bytes": 37343,
  "sha": "fb2a4a92d6a7d4ecfd5764280f02d17cddd3deec4c2c9f716eaa4688733d678c",
  "repo_slug": "leocelis/horizon",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_leocelis_horizon_fidelity_moni_5e7a5c30/readme"
}