Skip to main content
Glama

Iris

A self-hosted personal AI assistant that runs my life over Telegram — calendar, tasks, daily planning, memory — built as a custom MCP server and hook-plugin suite on top of the open-source OpenClaw agent harness, with Claude as the intelligence.

The design bet: an assistant you trust with write access to your life cannot be a prompt. Everything that must never go wrong — action gating, priority ranking, conflict resolution, provenance, "what day is it" — is deterministic, unit-tested code. The model supplies judgment inside an envelope that code enforces.

Why it exists

Calendar, task app, and notes are three disconnected surfaces, each claiming to be the truth. Iris inverts that: a small git-backed "brain" (mission → goals → priorities) that I own is the single source of truth, and the calendar/task apps are projection surfaces rendered from it. The assistant reads all of it, ranks the day deterministically, delivers a morning digest, and acts — creating events, moving tasks, updating memory — behind approval gates that scale with blast radius.

It has been my daily driver since June 2026.

Related MCP server: mnemory

Architecture

flowchart LR
    TG[Telegram]

    subgraph BOX["Ubuntu box — Docker, Tailscale-only, no public ports"]
        GW["OpenClaw gateway<br/>(rented: agent loop, channels, cron)"]
        subgraph OWNED["Owned core — this repo"]
            MCP["MCP server (src/)<br/>planner · brain · memory · taxonomy<br/>reconciliation · security envelope"]
            PLUG["Hook plugins (plugins/)<br/>confirm-gate · verify-gate<br/>live-state · traits · prompt-capture"]
        end
        BRAIN[("Markdown brain + memory<br/>git-backed workspace")]
    end

    TG <--> GW
    GW <-->|"tools"| MCP
    GW -.->|"before_tool_call · prompt hooks"| PLUG
    MCP <--> BRAIN
    GW <--> GCAL["Google Calendar MCP<br/>(rented, OAuth scope-narrowed)"]
    GW <--> TODO["Todoist MCP<br/>(rented, tool-allowlisted)"]
    GW <--> ANTH["Anthropic API<br/>Haiku default (live) · /model escalation"]

Own the brain, rent the surfaces. The repo builds only the differentiator — planning, memory, authority, safety. The agent loop is rented (OpenClaw), the calendar and task surfaces are rented MCP servers pinned and narrowed to least privilege (OAuth scope patch on the calendar server; tool allowlists on both). The security envelope wraps the rented servers too.

Component map

Path

Responsibility

src/planner/

rank_day — pure, LLM-free day ranking: weighted-additive score + Eisenhower quadrant + time-block-aware plan

src/reconciliation/

Authority engine: a brain-vs-live conflict is decided by the slot's type-authority prior (state→live surface, intent→brain), downgrading to flag-and-ask when the prior's loser is fresher; same-side conflicts use a recency → provenance → ask ladder. No model on the path

src/taxonomy/

Action classifier: every tool call maps to autonomous / auto+notify / confirm-first / quarantine+confirm by reversibility + blast radius

src/security-envelope/

Quarantine (untrusted-content sanitize + envelope), provenance tagging, Rule-of-Two invariant, web egress gate

src/brain/, src/memory/

Git-backed Markdown SSOT: read tools + gated write tools (propose/commit_brain_edit, memory_promote/memory_forget) — every durable write is one git revert from undone

src/traits/

Owner-tuneable personality dials (humor, verbosity, bluntness…) with a non-tuneable safety/honesty floor

src/grounding/, src/projection/

Verify-before-act helpers; calendar-as-projection write path (tagged, idempotent by record id)

plugins/confirm-gate/

before_tool_call hook → runs the taxonomy classifier → fail-closed Telegram approval via a text /approve <id> allow-once|deny command (300s timeout → deny); also hosts the web-egress gate

plugins/verify-gate/

Hard-blocks destructive calendar and Todoist writes whose target wasn't read in the same turn

plugins/live-state/

Injects fresh calendar/task state + an authoritative local Now: line every turn — time is an injected fact, never a model inference

plugins/traits/, plugins/prompt-capture/

Persona rendering into the system prompt; wire-level capture feeding the eval harness

scripts/eval/

Offline model-eval harness: wire-exact corpus, replay across providers, deterministic scorers, cross-family LLM judge

deploy/

Hardened gateway config, compose, vendored patches (pinned + hash-checked)

prs/, docs/

The development process: one research-gated PR file per change, architecture SSOT, design logs with the full decision trail

The gated write path

flowchart TD
    A["Proposed tool call"] --> V{"verify-gate:<br/>destructive write —<br/>target read this turn?"}
    V -- "no" --> B["Blocked → model told to read first"]
    V -- "yes / not destructive" --> T["taxonomy classifier<br/>(deterministic, unit-tested)"]
    T --> C{"action class"}
    C -- "autonomous /<br/>auto+notify" --> X["Execute"]
    C -- "confirm-first /<br/>quarantine+confirm" --> P["Telegram approval — /approve command<br/>fail-closed, 300s timeout → deny"]
    P -- "approve" --> X
    P -- "deny / timeout" --> D["Denied"]

Governing invariant (Rule of Two): no single operation may combine reads untrusted content + accesses sensitive data + changes external state without a mandatory human gate. In practice it's enforced as a system rather than one choke-point function: session taint escalates every same-turn write after untrusted input, and the sensitive store is read through its own gated tool. Confirm-gate additionally runs a web-egress gate — a web_fetch to a domain this turn's searches never surfaced is confirm-first (kills the classic exfil-by-URL channel). Prompt injection is unsolved; the dangerous combination stays permanently gated no matter how good the model is.

Key design decisions

Each of these had a live alternative that was researched and rejected — the full trail is in docs/*/DESIGN-log.md and the prs/ files.

  1. Deterministic gates, not prompt-based safety. System-prompt instructions like "verify before acting" were live-tested and proven insufficient — the assistant still moved real calendar events off a wrong-day inference. The fix each time was a code-level hook (verify-gate, confirm-gate) that makes the failure structurally impossible rather than discouraged. Rejected: relying on model compliance.

  2. Logic in code wherever judgment isn't required. Ranking, conflict authority, action classification, and time grounding are pure functions with unit tests. Deterministic code beats model judgment over long context, and the system stays correct even with extended thinking off. The model handles what only a model can: natural-language policy, tie-breaks, conversation. Rejected: letting the LLM rank the day and arbitrate stale-vs-fresh conflicts.

  3. Quarantine untrusted content at persistence time. Calendar invite notes and fetched web content are Unicode-stripped, wrapped in an explicit "data, not instructions" envelope, and provenance-tagged untrusted-derived before the planner ever sees them — a synchronous rewrite hook, not an async model pass — and the turn is tainted so same-turn writes escalate to gated approval. Nothing untrusted is ever durably written unguarded (anti-memory-poisoning; injection canaries live-verified quarantined). PDF attachments take a separate path: the runtime's bundled extractor inlines them inside OpenClaw's native untrusted-content framing (canary-verified live), not the owned pipeline. Rejected: dual-LLM token isolation — the runtime's persist hook is synchronous, so it's deferred, not dismissed.

  4. Model selection by measurement, not vibes. scripts/eval/ replays the assistant's own captured traffic offline against candidate models with deterministic scorers and a cross-family judge. The capture path is wire-exact (billed request body reproduced to ±0 tokens) after the naive approach — replaying reconstructed trajectories — was tested and refuted (−18.8% token fidelity). The harness's verdict set the current default (Haiku, −67% cost/turn at parity for routine traffic, with per-conversation escalation to Sonnet/Opus). It also halted a shipped decision: per-turn cross-provider routing was reverted when the live trial showed a stateful turn ships its whole session history to whichever provider serves it — the privacy carve-out unit is the session, not the turn. Rejected: benchmark-driven model choice, and the cross-provider router it initially justified.

  5. Time is an injected fact. A real incident (assistant asserted the wrong weekday, concluded the calendar UI was buggy, and moved real events) traced to a context where every ambient timestamp was UTC and no authoritative local "now" existed. The fix: a hook injects Now: <local datetime> — AUTHORITATIVE plus weekday annotations on every date, each turn, cache-neutrally. The incident class is closed structurally. Rejected: prompt rules telling the model to be careful about timezones.

Testing

294 tests across 22 files (vitest), verified passing from a clean clone. Coverage follows the "logic in code" rule — the deterministic pieces are the tested pieces:

  • ranking math, taxonomy classification, reconciliation authority, provenance parse/render, quarantine sanitization, egress-gate policy, eval-harness scorers/corpus logic

  • adversarial fixtures: prompt-injection and memory-poisoning payloads assert the gates fire and nothing durable is written from untrusted content

  • beyond unit tests, every PR in prs/ records its live binding test — the deployed behavior verified over Telegram against the real gateway before the PR closes

npm ci
npm test        # 294 tests
npm run typecheck
npm run build   # server bundle; build:plugin:* for each plugin

Running it

This is a personal, single-user system — the repo is published as a working reference, not a turnkey product. A full deployment needs an OpenClaw gateway (Docker), a Telegram bot, Google Calendar OAuth, a Todoist token, and an Anthropic API key; deploy/ contains the hardened gateway config, compose file, vendored patches, and a README with the wiring. One drift note: the committed gateway config predates the Haiku-default model switch (PR-033, applied on the live box) — it still pins Sonnet as primary. Expect to adapt paths and secrets handling to your own box.

Status — honest scope

  • In daily use since June 2026; v0.2.x. The commit history is the real changelog — each change is a research-gated PR with verification criteria.

  • The agent loop, channels, and cron are OpenClaw (third-party). This repo is the MCP server, plugins, eval harness, and deployment hardening on top.

  • Proactivity is deliberately minimal (one daily digest); the interrupt engine is designed but deferred.

  • Scanned/image-only PDFs don't reach the turn (documented gap); finance integration is designed for a sidecar-isolation pattern but not built.

  • Two vendored runtime patches (thinking-block handling, wire capture) are pinned to the current OpenClaw release and retire when upstream ships equivalents.

License

MIT

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • Person-owned, portable AI memory as a remote MCP server, readable and writable by any MCP client.

  • Personal assistant MCP server with search, execute, packages, jobs, secrets, and integrations.

  • User-owned memory for AI agents, Copilot, Claude, IDEs, CLIs, and chat apps over remote MCP.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rux-eth/iris'

If you have feedback or need assistance with the MCP directory API, please join our Discord server