How Ryva works under the hood

Mar 15, 2026

I want to be transparent about how Ryva actually works. Not just what it does, but how it does it. What data we store, what we do not store, how the agent thinks, and why it gets more accurate over time.

This is the post I would have wanted to read before trusting a tool with my GitHub and Slack.

The stack

Ryva is built on Shipr, my open source full stack SaaS template. You can see the full source at github.com/egeuysall/shipr.

The core stack:

  • Next.js with API routes for the frontend and server logic
  • Convex for the database and real-time agent infrastructure
  • Clerk for auth and billing
  • Vercel for deployment
  • Posthog and Vercel Analytics for product analytics
  • Sentry for error tracking
  • React Flow for the graph view
  • Tailark components for the landing page
  • Exa for web search

For the agent: Claude Sonnet 4.6 runs the main agent. Claude Haiku 4.5 handles summarization. Exa powers web search when external context is needed. Each full run costs around $0.20 to $0.30. The summarization subagent costs almost nothing.

The data model

Every piece of information in Ryva is a block. There are five block types and each one has a priority score, a domain, and a set of connections to other blocks.

Projects

A project maps to a GitHub repo and an organization.

{
  _id: "js7fx60sftsmjdheb4f1xz1h8x82qx95",
  name: "supabase",
  description: "The Postgres development platform...",
  githubRepo: "egeuysall/supabase",
  orgId: "org_39z3n7bJ2q7UAKnGioNnetEMTLz",
  slug: "supabase-RoQ_D_",
  createdAt: 1773195338800
}

Blocks

Everything the agent reads, generates, or stores is a block. The block schema carries priority scoring, archiving state, domain tagging, and a flag for whether it was agent-generated or manually added.

{
  _id: "jn7a4akx8kaq1s4nb9j4zk8xm582pg82",
  type: "note",
  content: "Missing decisions identified by agent: ...",
  projectId: "js7fx60sftsmjdheb4f1xz1h8x82qx95",
  orgId: "org_39z3n7bJ2q7UAKnGioNnetEMTLz",
  isAgentOutput: true,
  priorityScore: 84,
  priorityLevel: "high",
  prioritySource: "agent",
  priorityReason: "Decision/ship keywords; Meeting signal keywords; Agent-generated signal; Fresh (<6h)",
  priorityUpdatedAt: 1773195520653,
  archivedAt: null,
  archivedByClerkId: null,
  createdAt: 1773195520653
}

The five block types

TypeDescription
agent_runThe full output of a main agent run
decisionA decision made or missing, with status and domain
contextSignals pulled from GitHub or Slack
noteManual context added by the user
github_snapshotA point-in-time capture of repo state

Integrations

Integrations store connection state for GitHub and Slack per organization. We store OAuth tokens and scopes but we do not store raw Slack message content in the database.

{
  _id: "jd73n0y351eent6as2jk2cz049821h4s",
  provider: "slack",
  orgId: "org_3AIoHC6mtFMJPdZ6DRa2k76Qgj2",
  status: "connected",
  slackTeamName: "Huesly",
  slackTeamId: "T0A5WH6NH61",
  slackUserId: "U0AJ262KAUR",
  slackScope: "channels:history,groups:history,im:history,mpim:history,channels:read,groups:read,im:read,mpim:read,users:read,users:read.email,openid,email,profile",
  slackTokenType: "user",
  slackAccessToken: "xoxp-...",
  lastCheckedAt: 1772758181098,
  lastError: null,
  createdAt: 1772289416397
}

Priority scoring

Every block has a priority score from 0 to 100. The agent always reads higher priority blocks first. This is not arbitrary. The score is calculated from a combination of signals.

What raises priority:

  • Decision or ship keywords in content
  • Agent-generated output
  • Fresh signals created in the last 6 hours
  • Unresolved decisions
  • Critical risk keywords
  • Execution blockers

What lowers priority:

  • Archived blocks
  • Old signals with no recent activity
  • Low confidence lessons

The priority system means the agent never reads stale context before fresh context. When you archive a decision, it does not disappear. It just moves to the back of the queue.

How a run actually works

Before each run, you set an optional main direction. This is the highest priority signal of the entire run. The agent does not run blindly. It runs toward something specific.

The run sequence:

  1. Summarization subagent reads the GitHub snapshot first. It processes open PRs, recent commits, and open issues, then generates a structured summary using Haiku 4.5 before passing it to the main agent. This costs almost nothing.

  2. Main agent reads all blocks in priority order using Sonnet 4.6. It applies any stored lessons from previous runs. It generates decisions made, missing decisions, next recommendations, and risks.

  3. Output is written back as blocks: a new agent_run block, decision blocks for each missing or made decision, and context blocks for any new signals.

Decisions are categorized by domain. A missing decision in marketing does not pollute the product backlog. Each domain gets its own view so teams can filter to what matters to them.

Decisions have three states:

  • Open - identified but not acted on
  • In progress - someone is actively working on it, gets highest priority
  • Archived - resolved or deferred, gets lowest priority

The GitHub panel

The GitHub panel shows structured context from your repo without requiring you to leave Ryva.

Ryva GitHub panel

For each open PR the panel shows:

  • Review count
  • CI status (passing, failing, or unknown)
  • State (draft, stale, needs review, or ready to merge)
  • Whether it is mergeable

GitHub context is stored as snapshots in the database. This means the agent can reference historical repo state and compare it against the current snapshot to identify drift, stale work, and ownership gaps.

A snapshot looks like this:

{
  type: "github_snapshot",
  content: JSON.stringify({
    repo: "supabase/supabase",
    recentCommits: [...],
    openIssues: [...],
    openPRs: [...]
  }),
  priorityScore: 100,
  priorityLevel: "critical",
  prioritySource: "integration"
}

The Slack panel

Slack is handled completely differently from GitHub. We do not store Slack message content in the database at all.

Ryva Slack panel

Every time the Slack panel is used, it fetches directly from the Slack API using the stored OAuth token. Messages are categorized by channel and sorted by recency. DMs are supported too, which matters for teams routing Sentry alerts or Claude notifications through a shared admin account.

Users select up to 10 messages on the free plan and up to 40 on the Teams plan. A summarize button condenses the selected messages before they get passed to the main agent as context.

This architecture was a deliberate choice. Slack messages contain sensitive team conversations. Storing them creates risk we did not want to take on. Fetching on demand means the data never lives in our database.

The memory system

This is the part that makes Ryva different from a summarization tool.

After each run, the agent generates lessons from what it observed. A lesson looks like this:

{
  _id: "j579j8wqppqgcbv8yvatps7v5s82vcs0",
  lesson: "Track and mitigate risk early: PR #1 and PR #3 stale since August 2025 with unmergeable state indicate dependency hygiene is not enforced and security patches may be delayed",
  lessonType: "risk_watch",
  confidence: 0.58,
  impactScore: 68,
  appliesTo: ["stale", "unmergeable", "dependency", "security"],
  evidence: "Risk surfaced by agent: PR #1 and PR #3 stale since May and August 2025...",
  appliedCount: 0,
  priorityLevel: "high",
  projectId: "jh73m8nyd3jqmcv1424wjycdgd82vkwx",
  createdAt: 1773363658529
}

The next time the agent runs and encounters similar signals, it applies that lesson. It changes how it weights signals, what it looks for, and how it frames the output.

Lessons have a confidence score and an impact score. Low confidence lessons are applied more cautiously. High impact lessons get surfaced earlier in the run. As a lesson gets applied and proves useful its confidence increases. As it becomes stale it decays.

Over time the agent starts to think more like the team using it. It learns what your project treats as critical. It learns what you usually archive. It gets more accurate without you having to configure anything.

The graph view

Every block in Ryva is connected to the blocks that informed it or that it recommended. The graph view makes those connections visible.

Ryva graph view

You can filter by time range, block type, top priority only, or search by keyword. When you click a node you see the full block content, its priority score and reason, its domain, its status, and all of its connections.

A missing decision node might show connections like:

RelationshipSourcePriority
Informed byGitHub commit signal65
Informed bySlack message summary48
Recommended byagent_run output100

This means you can trace why a decision exists. Not just what it says but what signals the agent was reading when it surfaced it. If the agent flagged something that does not feel right, you can follow the chain back to the source and understand what it was responding to.

Trust metrics

The dashboard shows trust metrics calculated from your project history. These are not vanity numbers. They are calculated from real run data.

Metrics include estimated meeting hours recovered, decision resolution rate, percentage of runs that applied memory lessons, and contradiction reduction rate between runs. The contradiction reduction rate specifically tracks how often the agent reverses a previous recommendation, which is a proxy for how stable and accurate the memory system is becoming.

What this looks like in practice

The public demo at ryva.dev/demo runs on the real Supabase repo with no signup required. The agent found six open decisions with no owner on one of the most actively maintained open source repos in the world.

Not because the Supabase team is doing something wrong. Because this is what happens when project state lives across hundreds of contributors and no single layer assembles it.

That is the problem Ryva is built to solve.

If you want to see it running on a real repo, the demo is live. If you want to run it on your own repo, you can sign up at ryva.dev. GitHub and Slack connections are both optional.