Rithmo

ai.rithmoPublished by rithmo.aiOfficial publisherv0.1.0Updated Oct 24, 2026

AI agent governance with resolved business context, current decisions, provenance, and supersession.

VerifiedStreamable HTTPWeb executableProductivity & WorkflowKnowledge & Memory

Overview

AI-generated overview

Read-only Rithmo MCP server that lets an assistant look up an organization's recorded decisions and poll for decision changes before acting.

What you can do
  • Query the decision record with a natural-language question, optionally filtered by type, owner, area or external surface, and get a current answer with status, owner, source, rationale, conditions and supersession history
  • Receive explicit no-record outcomes, including typed refusals that explain why a premise is held and whether it is safe to proceed
  • Poll a cursor-based feed of decisions whose state changed, including dropped and superseded ones, for use as a trigger
  • Check the token and connection with a whoami call returning organization and scopes
When to use it
Useful when an agent or automation should check what an organization actually decided about something before acting, and should hold or route to a human when there is no confident answer. Also fits workflows that need to react when a decision is reversed or superseded.
Requirements
A remote Rithmo MCP endpoint (Rithmo Cloud or a self-hosted instance) and a per-organization Rithmo MCP service token sent as an Authorization Bearer header, carrying the mcp:read scope. The README examples are Node.js scripts configured through RITHMO_MCP_URL and RITHMO_MCP_TOKEN.
Before you install
The Authorization token is a secret scoped to one organization; keep it in a credential store rather than in code. The server is read-only and does not act in your systems, but its answers are meant to gate actions, so treat no-record and held results as a stop rather than a guess. A record_unavailable reason signals a transient read failure and should be retried.

Installation

In SourceWeft

  1. Open Rithmo in the dashboard and add it to a workspace.
  2. Connect Authorization token credentials. They are stored privately and never shown on public pages.
  3. Enable the server for the chats that should use its tools.

Web executable via Streamable HTTP. Remote servers run from the web runtime once configured in a workspace.

Use with an agent

Paste this prompt into Claude Code, Codex, Cursor or another agent — or into SourceWeft chat. The agent reads this server's install guide, shows you its publisher, endpoint and sign-in, and adds it to your client once you agree. Keys never go into a command.

Read https://sourceweft.com/mcp/ai.rithmo%2Frithmo/install.md and connect the MCP server it describes to my client. Before changing any config, show me its publisher, endpoint and sign-in method and wait for my OK. Never put an API key in a command or in a file I would commit; ask me where it should come from.

Read the install guide the agent follows

Other MCP clients

Add this to your client's mcpServers config.

{
  "mcpServers": {
    "rithmo": {
      "type": "http",
      "url": "https://app.rithmo.ai/api/mcp",
      "headers": {
        "Authorization": "Bearer <YOUR_TOKEN>"
      }
    }
  }
}

README

Rithmo MCP — examples

Runnable examples for connecting to Rithmo's read-only MCP server.

Rithmo is a system of record for decisions. This server lets your tools query the decision record before they act — "what did we actually decide about X?" — and react when decisions change. Rithmo emits the record; your tools act on it. Rithmo never takes actions in your systems.

It speaks the Model Context Protocol, so it works with n8n, AI agents, and anything else that speaks MCP.

New here? Start with the Integration Guide — what it is, how to get access, how to connect, and the full tool reference.

Run it in a few minutes

bash
git clone https://github.com/Rithmo-Inc/rithmo-mcp-examplescd rithmo-mcp-examplesnpm installcp .env.example .env      # then edit .env

Set two values in .env:

  • RITHMO_MCP_URL — the MCP endpoint of a Rithmo instance. Rithmo Cloud is https://app.rithmo.ai/api/mcp; a self-hosted deployment exposes the same server at your own address.
  • RITHMO_MCP_TOKEN — a per-organization Rithmo MCP service token (read-only, scoped to one org, carrying the mcp:read scope).

Then:

bash
node examples/query_decision.mjs "What did we decide about GitHub integration?"node examples/no_record.mjsnode examples/list_decision_changes.mjs

What you'll see

query_decision — the current decision, or no_record:

Q: What did we decide about GitHub integration?✓ found (confidence 0.66)  answer: Look into GitHub integration details, including permissions and OAuth requirements.  status: open  owner:  participant_50332672  source: meeting · <source ref>  decided_at: 2026-07-09T14:39:48.296Z

no_record — Rithmo will not fabricate a decision (the differentiator):

Q: Did we decide anything about office snacks?∅ no_record — Rithmo will not fabricate a decision.   Branch here: pause the automation, route to a human, or use a safe default.

A no_record may carry a typed refusal explaining why it is held:

Q: Are we still migrating to the new billing provider?∅ no_record — held (closed · superseded)   subject: Billing provider migration   held_because: That decision was superseded; acting on the old premise is unsafe.   safe_to_proceed: false

list_decision_changes — a cursor-drained feed of decisions changing state:

changes: 20   has_more: truenext cursor: 1783606904319_f901849e-...   ← pass this as the argument on the next poll  [open] Push the local build after the meeting ends.  (changed 2026-07-09T14:21:44.319Z)  ...

The tools

query_decision

Look up the current authoritative decision for a natural-language question.

Input

fieldtyperequirednotes
questionstringyese.g. "Are we still shipping the new onboarding flow?"
typeaction_item | follow_up | decisionnorestrict to one type
ownerstringnorestrict to a named owner (substring match, case-insensitive)
areastringnotopical hint to bias retrieval
surfacestringnothe external system this task concerns (e.g. "jira", "salesforce"). When Rithmo does not observe that surface, a miss returns a typed not_observed instead of a bare no_record — absence on an unobserved surface is unknown, not "no".

Output — a discriminated union on result with three variants: "found", "found_record", and "no_record".

⚠️ Do not write result === "found" ? act : treat-as-no-record. found_record is a real answer, and two-state branching silently routes valid answers into your no-record path.

But do not flip to ["found", "found_record"].includes(result) either — that is also wrong. A found_record can name a subject that is genuinely on record while carrying no answer text (subject.currentAnswer === null). The correct rule gates on a usable answer:

js
const r = res.structuredContent;const answer =  r.result === 'found'        ? r.answer :  r.result === 'found_record' ? r.subject.currentAnswer :  null;if (answer != null && String(answer).trim() !== '') { /* act */ }else { /* hold — no answer to act on */ }
result: "found" — a live answer from the commitment ledger
jsonc
{  "result": "found",  "answer": "<verbatim decision text>",  "status": "open | in_progress | blocked | fulfilled",  "lifecycle_state": "confirmed",  "owner":  { "state": "owned", "name": "...", "user_id": "..." },   // or { "state": "unowned" }  "source": { "state": "resolved", "system": "meeting|slack|github|...", "ref": "...", "url": "..." }, // or { "state": "unresolved" }  "decided_at": "2026-07-19T15:04:00Z",  "due_at": "2026-07-25T00:00:00Z",   // or null  "rationale":  "<the WHY behind the decision>",   // or null  "conditions": "<what it depends on>",            // or null  "director":   "<who assigned/directed it>",      // or null  "context":    { /* arbitrary payload */ },       // or null  "supersedes": {                                  // or null    "answer": "<the decision this one replaced>",    "decided_at": "2026-06-02T10:00:00Z",    "supersedes": null                             // recurses — walk it for the full chain  },  "spine": {                                       // or null; present when spine context resolves    "decision_id": "...",    "lifecycle_state": "<canonical live state>",    "verified": true,    "board_status": "..." ,                        // or null    "presence": [{ "provider": "github", "external_status": "...", "external_url": "..." }],    "related_count": 3  },  "confidence": 0.71}

supersedes is the supersession history. It is a linked list, newest-first: each node is a prior decision this answer replaced, with its own supersedes pointer. Walk it to reconstruct how the answer changed over time (e.g. cloud → private env → customer servers). null means this decision superseded nothing. The chain is cycle-guarded and capped at 12 hops.

result: "found_record" — a live answer from the subject record

Returned when the subject record holds a current answer that the commitment ledger does not. This is an answer, not a refusal.

jsonc
{  "result": "found_record",  "subject": {    "subjectId": "...",    "label": "<human-readable subject>",    "state": "<subject state>",    "currentAnswer": "<the current answer>"   // may be null  },  "observation": {                            // optional    "sinceMs": 1783606904319,    "claim": "<nothing observed since … bears on this subject>",    "surfaces": [      { "surface": "slack", "observedFromMs": 0, "observedThroughMs": 0, "basis": "..." }    ]  }}

The answer lives at subject.currentAnswer, not answer.

currentAnswer may be null, and that case is not actionable. The subject is genuinely on record — Rithmo knows what you are asking about — but there is no answer text to act on. That is a meaningful signal (the subject exists, its answer does not), and it is distinct from no_record, which says Rithmo has no confident match at all. Either way you must hold: route a null-answer found_record down the same path as no_record.

result: "no_record" — no confident answer
jsonc
// bare form — no confident match, no refusal detail{ "result": "no_record" }
// typed-refusal form — `record` explains WHY{  "result": "no_record",  "record": { /* one of the four shapes below */ }}

When present, record.status is one of:

record.statusmeaningextra fields
closedthe subject is settled and the premise is deaddisposition: reversed | abandoned | superseded; subject
no_recordthe record was consulted and genuinely has nothingreason (string)
not_observedRithmo does not observe the surface you asked about, so absence proves nothingunobserved_surfaces: string[]

All three carry premise_check, and closed/no_record/not_observed may also carry an observation.surfaces array describing what was read and over what window.

jsonc
"premise_check": {  "safe_to_proceed": false,  "held_because": "<why acting on this premise is unsafe>"}

premise_check.safe_to_proceed is always false when the field is present. It is not a boolean to evaluate — it is a marker that the answer is held, and held_because tells you why. Never proceed on a no_record, with or without a premise_check.

One reason value is worth special handling: a record.status: "no_record" whose reason starts with record_unavailable: means the record could not be read (a transient failure). That is not "no record" and not "we decided no" — hold and retry.

Notes:

  • Deterministic answer. For the same resolved decision, every found field except confidence is byte-identical every call (it's the record verbatim — no LLM in the response path). confidence is a ranked-retrieval signal and may vary slightly; don't gate on its exact value.
  • owner and source are discriminated states, never bare nulls — unowned and unresolved are real, meaningful answers your code can branch on.
  • no_record is expected, not an error. Treat it as a branch: Rithmo has no confident answer, so don't act on a guess.

list_decision_changes

Drain decisions whose state changed, since a cursor. Built for a polling trigger.

Input

fieldtyperequirednotes
sincestringnoopaque cursor from a prior call. Omit on the first call to start "from now" (no backfill); pass "0_" to backfill from the start.
limitintegerno1–200, default 50. Out-of-range or non-integer values are rejected as a validation error, not silently clamped.

Output

jsonc
{  "changes": [    {      "decision_id": "...",      "answer": "...",      "status": "open | in_progress | blocked | fulfilled | dropped | superseded",      "lifecycle_state": "confirmed",      "owner": { ... }, "source": { ... },      "decided_at": "ISO", "due_at": "ISO|null",      "rationale": "...|null", "conditions": "...|null",      "director": "...|null",  "context": {...}|null,      "changed_at": "ISO"    }  ],  "cursor": "<opaque — pass as `since` next time>",  "has_more": true   // page was full; call again to drain the rest}

Each change carries the same record core as query_decision's found — including rationale, conditions, director, and context. It does not carry supersedes, spine, or confidence; those are query_decision-only.

status is wider here than in found. Reversals are emitted as changes, so status can be dropped or superseded — values query_decision's found never returns (it only resolves to live rows). If you mirror this feed into your own store, handle those two values explicitly.

Cursor semantics:

  • First call (no since) returns changes: [] and the current head cursor. You start from now and do not replay history. has_more is false.
  • has_more is rows.length === limit — a full page. It can be true on an exactly-full final page, so the next call legitimately returns zero changes. That is not an error; it is how the drain terminates.
  • When a page returns no rows, cursor is echoed back unchanged, so re-polling with it is safe and idempotent.
  • Ordering is by (changed_at, decision_id).

whoami

No arguments. Returns { org_id, scopes } — a quick way to verify your token and connection.

Auth

Every request carries a per-organization service token as a Bearer header:

Authorization: Bearer rmcp_...

The token resolves to exactly one organization and must carry the mcp:read scope; every result is scoped to that org. Keep it in a secret/credential store, never in code.

A request with no token, or an unknown token, is rejected with 401. There is no anonymous access.

Registry

Rithmo's MCP server is described by server.json in this repo, under the registry identity ai.rithmo/rithmo (version 0.1.0). Full docs live at https://rithmo.ai/mcp.

Deployment model

The RITHMO_MCP_URL above points at Rithmo Cloud. Self-hosted Rithmo deployments expose the same MCP server at the customer's own address — same tools, same contract, different host.

Using it from n8n

See n8n/ for an importable workflow (query a decision before an execution step) and setup notes.

Source: README.md at commit c141954

Tools

0
This server asks for a credential such as an API key, so SourceWeft could not read its tools without one and has not indexed them.

Version history

1
  1. v0.1.0LatestSep 22, 2026

Score

75% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 daysRequired25 pts
  • Auth declaredRequired10 pts
  • Tools listed15 pts

    Answer tools/list without a credential, or ship a package that starts in a sandbox without one.

  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts

    Declare a license in the repository or the registry entry.