Skip to main content

Kintsua

Self-maintaining integrations for AI agents. Kintsua finds every external API and MCP tool an agent repo depends on, watches those upstreams for breaking changes, opens a GitHub issue within an hour of one landing, and opens a tested fix PR for the common cases.

It is a GitHub App plus a hosted watcher, with an open-source CLI (kintsua watch) and a runtime SDK (import kintsua) that reports failed tool calls and schema drift from production agents.

Watcher snapshot → diff → change_event → classifier severity → matcher joins call_sites
→ impact → GitHub issue → fixer PR, with runtime events entering at the matcher as a
second source of change events.
  • Install in under five minutes from GitHub; the install issue is your dependency map.
  • Daily watch of OpenAPI specs, MCP servers, PyPI/npm packages and changelog pages.
  • Rules first, model second: every severity decision is reproducible; the model only sees what rules cannot decide, as data, never as instructions.
  • Fix PRs grounded in your call sites, validated in a sandbox against your own tests before they are opened. A failing validation is an honest comment, not a PR.
  • Privacy defaults: no payloads, no PII, SDK keys hashed at rest, a per-repo data deletion endpoint from day one.

Pricing: Free for public repos and up to 5 watched integrations · Pro $29 per watched integration per month.


Contents

  1. Step-by-step guide
  2. Quick start (the CLI, no account)
  3. The GitHub App
  4. Runtime SDK
  5. .kintsua.yml
  6. Trust and data boundaries
  7. What the fixer refuses
  8. Known gaps
  9. Architecture
  10. Running it yourself
  11. HTTP API
  12. Testing
  13. Layout

1. Step-by-step guide

Pick the path that matches what you want to do. Each one is a numbered checklist you can follow top to bottom; the sections after this one are the reference.

Path You want to Time You need
A. Watch from CI fail a build when an MCP server or API you call changes 10 minutes Python 3.12
B. Run Kintsua operate the service: watcher, GitHub App, issues, fix PRs about an hour Postgres, a GitHub account, an Anthropic or OpenAI key, Docker for fix PRs
C. Use it on your repo get issues and fix PRs on your agent repo from a running Kintsua 5 minutes a running Kintsua (path B)

Before you start. The package is not on PyPI yet, so every install below comes straight from GitHub. There is no public hosted instance yet either: paths B and C use the instance you run yourself, and KINTSUA_URL points at it.

Path A: watch an MCP server or API from CI

  1. Install the CLI (base dependencies only, no server pieces):
    pip install "kintsua @ git+https://github.com/hr7657316/Kintusa-Ai"
    kintsua --version
    
  2. Take a snapshot of the server or spec your agent calls. This writes .kintsua/<name>.lock.json with every tool, its input schema and a fingerprint.
    kintsua watch snapshot --server https://mcp.vendor.com/mcp --name vendor
    # stdio server:      --server "npx -y @vendor/mcp-server"
    # needs a token:     --auth-env VENDOR_TOKEN        (sent as Authorization: Bearer)
    # an OpenAPI spec:   --spec https://api.vendor.com/openapi.json
    
  3. Commit the lockfile. From now on a schema change shows up as a diff in a pull request.
    git add .kintsua && git commit -m "kintsua: snapshot vendor MCP tools"
    
  4. Check for changes locally whenever you like. Exit code 1 means a breaking change, 0 means none.
    kintsua watch diff --server https://mcp.vendor.com/mcp --name vendor
    
  5. Gate your CI. Add a step that runs the same diff on every push. It prints annotations on the run and fails the job on a breaking change.
    # .github/workflows/kintsua.yml
    name: kintsua
    on: [push, pull_request]
    jobs:
      watch:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: hr7657316/Kintusa-Ai@main
            with:
              server: https://mcp.vendor.com/mcp
              fail-on: breaking            # or degraded
              snapshot-if-missing: "true"
              version: " @ git+https://github.com/hr7657316/Kintusa-Ai"   # until it is on PyPI
    
  6. When it fails, read the table it prints (tool, change, severity, the rule that fired), update your code, then accept the new upstream state. The --update run still reports the change (exit 1); the runs after it pass:
    kintsua watch diff --server https://mcp.vendor.com/mcp --name vendor --update
    
  7. See what drifted lately: kintsua watch report --since 7d prints a Markdown summary from .kintsua/history.jsonl.

Path B: run Kintsua yourself

Prerequisites: Python 3.12, uv, Postgres 16 (local or Docker), Node 20 (for stdio MCP servers), Docker (only for fix PRs), and a public HTTPS URL for GitHub webhooks (a deployment, or a tunnel such as cloudflared tunnel --url http://localhost:8000 or ngrok while you try it).

  1. Get the code and dependencies.
    git clone https://github.com/hr7657316/Kintusa-Ai && cd Kintusa-Ai
    uv sync --all-extras
    cp .env.example .env
    
  2. Fill in the basics in .env: DATABASE_URL, BASE_URL (your public URL, the tunnel URL while testing), ANTHROPIC_API_KEY (or LLM_PROVIDER=openai and OPENAI_API_KEY), and a random SESSION_SECRET (openssl rand -hex 32). Leave the GitHub values empty for now.
  3. Create the database and load the 90 seeded sources (Stripe, GitHub, Slack, Jira, Google discovery docs, public MCP servers, popular SDK packages):
    make db        # docker run postgres:16, or skip if you have a local server
    make migrate   # alembic upgrade head
    make seed      # python -m kintsua.seed
    
  4. Run the watcher once and read the drift report. This is the week-one deliverable on its own: it works before any GitHub setup.
    make watch     # polls every seeded source, then prints the Markdown report
    
    Sources that need a token (several MCP servers) show unconfigured: environment variable X is not set until you set that variable.
  5. Start the service in two terminals (or scripts/dev.sh, or docker compose up --build):
    make api       # http://localhost:8000: landing page, /report/weekly, /healthz
    make worker    # every background job; re-polls sources every 15 minutes
    
  6. Create your GitHub App in one click. Open BASE_URL/setup/github and press the button. GitHub creates the App with the right permissions and events and sends you back to a page that shows GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY, GITHUB_WEBHOOK_SECRET, GITHUB_OAUTH_CLIENT_ID and GITHUB_OAUTH_CLIENT_SECRET. Copy them into .env, set GITHUB_APP_SLUG to the App's name, and restart the API and worker.
  7. Install the App on a repo from the App's page on GitHub. Within a few minutes the worker clones it and opens the install issue: "Kintsua is watching N of your M external dependencies". If nothing appears, see the troubleshooting table below.
  8. Sign in at BASE_URL/login (GitHub OAuth), then open BASE_URL/settings. For local experiments without OAuth: set ENV=dev and DEV_LOGIN_INSTALLATION_ID=<id> (the number at the end of the App installation's URL on GitHub) and visit BASE_URL/auth/dev.
  9. Decide how issues open. Every org starts in review mode: matched changes become draft issues listed on the settings page under "Changes affecting this repo", and you press "Open all drafts" to post them. To post issues automatically, set AUTO_ISSUE=true for every org, or for one org:
    psql "$DATABASE_URL" -c "UPDATE org SET settings = settings || '{\"auto_issue\": true}' WHERE name = 'acme'"
    
    (Use a plain postgresql:// URL with psql.)
  10. Turn on fix PRs (optional). Build the sandbox image, use the Docker backend, and switch the repo to PR mode, either with "Fix mode" on the settings page or in the repo's .kintsua.yml (fix: {mode: pr}):
    make sandbox-image                  # docker build -t kintsua-sandbox:latest
    # .env: SANDBOX_BACKEND=docker      (the worker needs access to the Docker socket)
    
    The fixer only opens a PR for renames, new required parameters and SDK major bumps, and only when the repo's own tests pass after the patch. Otherwise it comments on the issue with the reason (what the fixer refuses).
  11. Deploy for real. fly.toml runs one web and two worker processes from one image and runs migrations on release. Set the secrets listed at the top of fly.toml. In production the service refuses to start with a weak SESSION_SECRET or without S3 storage for large snapshots. See running it yourself for every setting.

Path C: use Kintsua on your repo

  1. Install the App on the repos that contain your agent (an operator from path B gives you the install link). Nothing else is required.
  2. Read the install issue. It lists every external dependency Kintsua found: MCP servers from .mcp.json and similar configs, SDK packages, and raw HTTP hosts, each with its call-site count and whether it is watched.
  3. Watch what it missed. For a grey (unwatched) row, add the vendor's spec, changelog or remote MCP URL, either under "Dependency map" on BASE_URL/settings/<owner>/<repo> or in a .kintsua.yml at the repo root:
    sources:
      - kind: openapi
        url: https://api.vendor.com/openapi.json
        hosts: [api.vendor.com]
      - kind: mcp
        url: https://mcp.vendor.com/mcp
    
    Stdio MCP servers in your configs are never run by Kintsua; watch them through their npm/PyPI package or with path A's --push (step 8).
  4. When an issue arrives, it tells you what changed upstream (before and after), when, which files and lines in your repo call it, and how to fix it. Fix it yourself, or let the fixer do it (step 5). Closing the issue marks the change as handled.
  5. Let Kintsua open fix PRs: set Fix mode to pr on the settings page, or add fix: {mode: pr, base: main} to .kintsua.yml. Review the PR like any other: its body says what changed, which call sites it touched, and how it was verified (test command, baseline run, static checks). Kintsua never pushes to a protected branch and never force-pushes.
  6. Silence noise: add a pattern under "Mute rules" or in .kintsua.yml (mute: [stripe:/v1/legacy/*, tool:debug_*]). Matching open issues are closed with a comment.
  7. Report live failures from production (optional). Create an SDK key under "Runtime SDK key" (it is shown once), then:
    pip install "kintsua @ git+https://github.com/hr7657316/Kintusa-Ai"
    export KINTSUA_KEY=kts_... KINTSUA_REPO=acme/support-agent KINTSUA_URL=https://your-kintsua-host
    
    import kintsua
    session = kintsua.wrap_mcp(session)              # an mcp ClientSession
    client = kintsua.wrap_http(httpx.AsyncClient())  # or a requests.Session
    kintsua.hook_langchain()                         # or hook_openai_agents() / hook_google_adk()
    
    Failed calls then show up on open issues ("12 failed calls in the last hour"), and a tool whose schema keeps changing without notice opens its own issue. No arguments or results are sent.
  8. Push snapshots from CI (optional), for servers Kintsua cannot reach itself, such as stdio or internal ones: add --push to path A's diff step:
    kintsua watch diff --server "npx -y @vendor/mcp" --push --repo acme/support-agent
    # uses KINTSUA_KEY and KINTSUA_URL from the environment
    
  9. Remove your data at any time with "Delete data" on the settings page. It deletes the repo's runtime events, dependencies, call sites and impacts; your GitHub issues and PRs stay.

Troubleshooting

Symptom Check
No install issue after installing the App The worker is running; the App's "Advanced" tab on GitHub shows webhook deliveries with status 200 (otherwise BASE_URL is not reachable or GITHUB_WEBHOOK_SECRET differs); then look at failed jobs: psql "$DATABASE_URL" -c "select kind, status, last_error from job order by created_at desc limit 20". Re-run an index by hand with uv run python -m kintsua.indexer.run owner/repo.
A source shows as failing Its last_error on the settings page or in the source table says why: an unset token (unconfigured: ...), a refused private address, a vanished spec. Failing sources retry with backoff up to 48 hours.
A change was detected but no issue opened It is a draft: open it from the settings page, or turn on AUTO_ISSUE. Only breaking and degraded changes that touch one of your call sites become issues; the rest are in the weekly report.
No fix PR The issue has a comment with the reason: not a fixable category, fix mode is issue_only, no test command, or validation failed.
SDK events never arrive KINTSUA_KEY, KINTSUA_REPO and KINTSUA_URL are set in the agent's environment, and the repo is installed in the same org as the key.
kintsua watch exits 3 It could not connect: wrong URL, the server is down, or it needs --auth-env. Exit 4 means there is no lockfile yet: run snapshot first.
The API or worker will not start in production The log names the setting: a placeholder SESSION_SECRET, or no S3_BUCKET (set DATA_DIR_SHARED=true only when every process shares one disk).

2. Quick start (the CLI, no account)

kintsua watch is the MCP/OpenAPI watcher and the rules classifier packaged as a CLI (Apache 2.0). It snapshots a server's tools/list into a lockfile you commit, so a schema change shows up in a PR diff, and it exits non-zero on breaking changes so it can gate a deploy.

pip install "kintsua @ git+https://github.com/hr7657316/Kintusa-Ai"   # until it is published to PyPI

# snapshot: writes .kintsua/<server>.lock.json (tools, inputSchemas, fingerprints)
kintsua watch snapshot --server https://mcp.vendor.com/mcp
kintsua watch snapshot --server "npx -y @vendor/mcp-server"        # stdio servers work too
kintsua watch snapshot --spec https://api.vendor.com/openapi.json  # OpenAPI 3.x / Swagger 2 / Google discovery

# diff: exit 1 on breaking changes, 0 otherwise; appends to .kintsua/history.jsonl
kintsua watch diff --server https://mcp.vendor.com/mcp
kintsua watch diff --server ... --fail-on degraded --github-action   # CI annotations + step summary
kintsua watch diff --server ... --json                               # the ChangeEvent + classification

# report: Markdown drift report from history.jsonl
kintsua watch report --since 7d

Useful flags: --header K=V (values may use ${VAR}), --auth-env NAME (bearer token from an env var), --env K=V (for stdio servers), --name (lockfile base name), --update (rewrite the lockfile after diffing), --dir (default .kintsua).

The hosted product is one flag away: kintsua watch diff --push --repo owner/name --key $KINTSUA_KEY sends the snapshot to your account so the repo gets issues and PRs without CI running.

As a GitHub Action:

- uses: hr7657316/Kintusa-Ai@main
  with:
    server: https://mcp.vendor.com/mcp
    fail-on: breaking

Exit codes: 0 no gate hit, 1 severity at or above --fail-on, 2 usage error or an unreadable lockfile (merge-conflict markers, or a lockfile another server wrote under the same --name), 3 could not connect (auth required, bad URL, server down), 4 no lockfile yet (run snapshot; the GitHub Action does this itself with snapshot-if-missing: true). --auth-env applies to --spec too, sent as Authorization: Bearer.

3. The GitHub App

GitHub is the whole v0 user interface: install is the signup, the install issue is the dependency map, issues are alerts, PRs are fixes.

  1. Install the App on the repos you want watched (https://github.com/apps/kintsua/installations/new). Permissions: Contents (read/write), Pull requests (read/write), Issues (read/write), Metadata (read), Checks (read). No org-member or email scopes.
  2. Within an hour the indexer clones the default branch, finds your MCP configs, SDK imports and raw HTTP calls, resolves each to a watched source, and opens the install issue: "Kintsua is watching 11 of your 14 external dependencies" with a table of dependency, kind, watch status and call-site count, and how to add a spec or changelog URL for the unwatched ones.
  3. When something breaks upstream, the matcher joins the classified change against your call sites and opens one issue per change event and repo (never duplicates), labelled kintsua and severity:breaking or severity:degraded. The body says what changed (before/after), when, which files and lines in your repo call it, the migration hint, and whether a PR is coming.
  4. If fix mode is pr and the change is a rename, a new required parameter or an SDK major bump, the fixer clones your repo into a sandbox, runs a bounded tool-using agent (12 steps, five tools), validates the patch against your own test command and static checks, and opens kintsua/<vendor>-<hash> with a PR body that states exactly how it was verified. The customer's CI result is captured back onto the impact.
  5. Every org starts in issue_only with draft issues so a noisy first week cannot destroy trust: drafts are visible on /settings/{owner}/{repo} and in the weekly report, and a human flips them to real issues (or sets AUTO_ISSUE=true / the org setting once the golden set passes).

Push to the default branch re-indexes with a 5-minute debounce; an impact whose call site disappeared is closed with a comment saying why.

The weekly drift report (/report/weekly, also RSS and Markdown) lists every change event by severity, the ones that need a human look, sources that have been failing for 7+ days, and the fixer's merged-as-is rate.

4. Runtime SDK

The SDK sees what tools an agent actually calls and whether they still answer the way they did yesterday. Python only in v0, under 500 lines, one wrapper call, no payloads.

import kintsua

# MCP: wrap any ClientSession from the official mcp package (1.x or 2.x)
session = kintsua.wrap_mcp(session, repo="acme/support-agent", key=KINTSUA_KEY)

# HTTP: wrap an httpx Client/AsyncClient or a requests Session
client = kintsua.wrap_http(httpx.AsyncClient(), repo="acme/support-agent", key=KINTSUA_KEY)

# Frameworks: one-line hooks (no-ops when the framework is not installed)
kintsua.hook_langchain(); kintsua.hook_openai_agents(); kintsua.hook_google_adk()

What it records: per MCP tool a schema_fingerprint (SHA-256 of the canonical inputSchema) compared with the last one stored locally, so a changed schema becomes a schema_mismatch event with the diff; per tools/call the tool name, latency, outcome and error class; per HTTP call host, method, normalised path, status, latency and, on 4xx/5xx, the first 512 bytes of the error body with secrets redacted. Arguments and results are never sent unless you pass sample=True, and then keys only.

Transport: batched every 5 seconds or 100 events to POST /v1/ingest, one retry then drop, never blocks or raises (every public function is wrapped; failures log once at debug level). Configure with kintsua.configure(key=..., repo=..., url=...) or KINTSUA_KEY / KINTSUA_REPO / KINTSUA_URL.

Server side, match_runtime creates dependency and call_site rows for tools and hosts the indexer never saw (discovered_at_runtime), promotes three or more schema_mismatch events for a tool within 24 hours with no documented change into a synthetic breaking change event (the vendor changed something they never documented), and attaches error spikes to open issues ("12 failed calls in the last hour").

5. .kintsua.yml

Optional, at the repo root. Add sources the indexer missed, mute noisy ones, choose whether PRs open automatically, set the base branch and the test command.

sources:
  - kind: openapi
    url: https://api.vendor.com/openapi.json
    hosts: [api.vendor.com]            # raw HTTP calls to these hosts resolve to this spec
  - kind: changelog
    url: https://vendor.com/changelog
  - kind: mcp
    url: https://mcp.vendor.com/mcp    # or: command: "npx -y @vendor/mcp"
mute:
  - stripe:/v1/legacy/*
  - tool:debug_*
fix:
  mode: pr          # pr | issue_only (default issue_only)
  base: main
  radius: 1         # import hops the validator lets a patch reach beyond the call-site files
test_command: "uv run pytest -q"   # overrides detection (pyproject / package.json / Makefile)

6. Trust and data boundaries

Every watched source records where it came from (source.origin), and that decides what the hosted worker does with it:

  • Operator-seeded sources (kintsua/seed/sources.yml and the curated SDK map) are the only ones the worker will run as stdio MCP commands, and the only ones on the public weekly report.
  • Sources from your repo or the settings page (.kintsua.yml, MCP configs, raw HTTP hosts) are fetched over http(s) only. Kintsua never executes a command line it found in your repo; a stdio MCP server in your config is watched through its package on PyPI/npm, or through kintsua watch --push from your CI.
  • Every customer-supplied URL is network-guarded: localhost, private, link-local and cloud-metadata addresses are refused, each redirect hop is re-checked, response bodies are size-capped and every poll has a time limit.
  • Pushed snapshots are scoped to your org. A push can only change what your own repos see; it never touches a source another customer depends on.
  • The public weekly report lists only operator-seeded sources, never a customer's repo, source, issue or PR, and redacts secrets from error text.
  • Runtime data stays thin: the SDK sends tool names, latencies, outcomes and schema fingerprints, never arguments or results (keys only with sample=True); secrets in error bodies, URLs and paths are redacted on both the client and the server.

7. What the fixer refuses

The validator is the product: Kintsua never ships a PR whose tests did not run. The fixer leaves an issue (with a comment saying why) and opens no PR when:

  • the change is not a rename, required_param or deprecation (SDK major bump), or is not breaking/degraded;
  • the change touches authentication or secrets handling (category auth), or the patch would touch files matching *.env, .env*, *secret*, *credential*, auth*.py|ts|js, *token*, *.pem, *.key, id_rsa* or a secrets/ / credentials/ directory, or adds lines containing api_key=, token=, secret=, password=, client_secret=, private_key=, Authorization or a live key shape;
  • the change would need new credentials or scopes;
  • the repo has no test command and no static checks (adding either unlocks PRs);
  • the test suite did not actually run, on the baseline or after the patch (pytest collection or usage errors, no tests collected): matching the baseline's failures only counts when tests really ran;
  • the agent's patch exceeds 300 changed lines, touches files outside the affected call sites plus one import hop, fails the test command (or fails tests the baseline run on the unpatched tree passed), fails ruff / tsc --noEmit where present, or (for MCP tools) produces a call that does not validate against the new inputSchema;
  • the agent stops with done(needs_human=true) because the fix needs a decision a human should make (for example a new required parameter with no inferable value);
  • more than 3 fix jobs are already running: the job is deferred, not refused, and fails with a reason only after about 6 hours of deferrals. A fix job lost with its worker (a deploy or an out-of-memory kill) is re-queued once, then failed.

Kintsua never pushes to a protected branch and never force-pushes. Everything it writes is prefixed [Kintsua], labelled kintsua, and lives on kintsua/* branches so it can be found and reverted.

8. Known gaps

  • Static indexing is partial by design. Dynamic tool names built at runtime, URLs assembled from config or other modules, and monorepos with several services are indexed partially (constants are tracked per file; the first literal assignment wins). The runtime SDK closes these gaps by observing real calls.
  • SDK symbol to HTTP operation equivalence is table-driven and covers Stripe, Slack, GitHub (PyGithub/Octokit), OpenAI, Anthropic, Notion, HubSpot, Twilio and SendGrid; other SDK call sites match package-level events (major bumps) but not individual OpenAPI operations.
  • OpenAPI $ref dereferencing is bounded (one level inside paths by default; components/schemas are diffed per component) so Stripe/GitHub-sized specs stay fast; deep shared-schema changes report under /components/schemas/... and may not join to an HTTP call site. Raise meta.deref_depth per source if needed.
  • Renames are detected between siblings under the same parent (identical shape or Levenshtein < 3); an operation moved between paths is reported as removal plus addition.
  • Changelog pages must be server-rendered; JavaScript-rendered changelogs collapse to coarse text events.
  • Promotion from runtime names a server-less MCP tool dependency after the tool unless the SDK knows the server locator.
  • Model agreement on the residue (90%) is measured with real model calls outside the unit suite; the rules' 100% on the golden set is enforced in CI.
  • Sandbox egress is a name-level allowlist unless SANDBOX_HTTPS_PROXY is set; agent, test and validation steps run with no network at all.
  • No Slack or email alerts, no languages beyond Python and TypeScript, no self-hosting support, no rollback of agent actions (v1).

9. Architecture

One Python monorepo, one Postgres, one FastAPI service, one worker pool. Five components talk through the database and a job queue, never directly to each other, so each can be built and tested alone. The diagrams below are Mermaid; GitHub renders them inline.

Components

Change events flow left to right from the watchers to a GitHub PR. The indexer and the runtime SDK feed the matcher from above and below, and GitHub webhooks trigger re-indexing.

flowchart LR
    I["Indexer<br/>maps call sites"]
    W["Watchers<br/>poll and diff"] -->|change event| C["Classifier<br/>rules, then LLM"]
    C -->|severity| M["Matcher<br/>joins call sites"]
    I -->|call sites| M
    R["Runtime SDK<br/>failed calls, schema drift"] -->|runtime events| M
    M -->|impact| F["Fixer<br/>sandbox + PR"]
    F -->|PR + issue| G["GitHub App<br/>issues and PRs"]
    G -->|install and push webhooks| I
    M ~~~ DB[("Postgres: snapshots, change events, call sites, impacts, job queue<br/>every arrow above is a job row in this queue")]
Component Reads Writes Job kinds
Watchers (kintsua/watchers) sources snapshots, change events tick, poll_source
Classifier (kintsua/classify) change events, call-site operations classifications classify_event
Indexer (kintsua/indexer) repo clone, .kintsua.yml dependencies, call sites, install issue index_repo
Matcher (kintsua/matcher) classifications, call sites, runtime events impacts, issues match_event, open_issue, match_runtime
Fixer (kintsua/fixer) impacts, snapshots PRs, impact status/outcomes fix_impact, pr_outcome
API (kintsua/api) GitHub webhooks, SDK ingest, sessions orgs, repos, runtime events, jobs billing_tick

Deployment

One image runs as a web process and as worker processes. Postgres holds all state and is also the job queue.

flowchart LR
    subgraph outside["Outside world"]
        direction TB
        GH["GitHub<br/>webhooks, issues, PRs, checks"]
        AGENT["Customer agents<br/>runtime SDK"]
        CI["Customer CI<br/>kintsua watch"]
        STRIPE["Stripe<br/>Checkout + webhooks"]
        VENDORS["Upstreams<br/>OpenAPI specs, MCP servers,<br/>PyPI, npm, changelog pages"]
    end

    subgraph deploy["Kintsua deployment: one image, Fly.io or docker compose"]
        direction TB
        WEB["web process<br/>python -m kintsua.api<br/>FastAPI, HTMX settings page,<br/>public weekly report"]
        PG[("Postgres 16<br/>all state + job queue")]
        WORKER["worker processes x2<br/>python -m kintsua.worker<br/>every job handler"]
        S3[("S3-compatible storage<br/>snapshots over 256 KB")]
        SBX["Docker sandbox per fix job<br/>15 min, 4 GB, 2 CPUs,<br/>no network for agent steps"]
        WEB <--> PG
        PG <-->|SKIP LOCKED queue| WORKER
        WORKER --> S3
        WORKER --> SBX
    end

    LLMAPI["Anthropic or OpenAI API<br/>via core/llm.py"]

    GH -->|signed webhooks| WEB
    AGENT -->|POST /v1/ingest| WEB
    CI -->|POST /v1/watch/push| WEB
    WORKER -->|poll, network-guarded| VENDORS
    WORKER -->|issues, PRs, comments| GH
    WORKER -->|classifier residue, fixer agent| LLMAPI
    WEB <--> STRIPE

A breaking change, end to end

What happens when a vendor's MCP server makes a tool input required. This is the path tests/e2e/test_mcp_to_issue.py runs against a real local MCP server.

sequenceDiagram
    autonumber
    participant V as Vendor MCP server
    participant W as Watcher
    participant Q as Postgres queue
    participant C as Classifier
    participant M as Matcher
    participant G as GitHub
    participant F as Fixer + sandbox

    Note over W,Q: tick every 15 min enqueues poll_source for due sources
    Q->>W: poll_source
    W->>V: initialize, tools/list, resources/list, prompts/list
    V-->>W: tools with inputSchema (v1.1.0)
    W->>W: canonicalise, hash, diff against the last snapshot
    W->>Q: snapshot + change_event, enqueue classify_event
    Q->>C: classify_event
    C->>C: rule required_added: breaking / required_param
    C->>Q: classification, enqueue match_event
    Q->>M: match_event
    M->>M: join affected paths with this repo's call sites, apply mutes
    M->>Q: impact (detected), enqueue open_issue
    Q->>M: open_issue
    M->>G: issue [Kintsua] ... labels kintsua, severity:breaking
    M->>Q: fix mode pr and category fixable: enqueue fix_impact
    Q->>F: fix_impact
    F->>F: clone, baseline tests, agent loop, validate patch
    F->>G: branch kintsua/vendor-hash, PR that closes the issue, comment
    G-->>F: webhook pull_request closed, enqueue pr_outcome
    F->>Q: impact merged, outcome merged_as_is

Job graph

Every arrow between components is a row in the job table, claimed with SELECT ... FOR UPDATE SKIP LOCKED. Jobs that must not pile up carry a dedupe key, for example one queued index_repo per repo, which is how the push debounce works.

flowchart TD
    tick(["tick"]) -->|"every 15 min"| tick
    tick -->|"sources due for a poll"| poll_source(["poll_source"])
    tick -->|"Mondays"| weekly_report(["weekly_report"])
    tick -->|"1st of the month"| billing_tick(["billing_tick"])
    poll_source -->|"content hash changed"| classify_event(["classify_event"])
    push["POST /v1/watch/push"] -->|"pushed snapshot changed"| classify_event
    classify_event --> match_event(["match_event"])
    match_event -->|"auto issues on"| open_issue(["open_issue"])
    review["Settings page: open a draft issue"] -->|"human review"| open_issue
    open_issue -->|"fix mode pr and rename,<br/>required_param or deprecation"| fix_impact(["fix_impact"])
    install["webhook: installation"] --> index_repo(["index_repo"])
    gitpush["webhook: push to default branch"] -->|"5 min debounce"| index_repo
    index_repo -->|"new sources"| poll_source
    ingest["POST /v1/ingest"] --> match_runtime(["match_runtime"])
    match_runtime -->|"3+ schema mismatches in 24 h,<br/>no documented change"| open_issue
    closed["webhook: pull_request closed"] --> pr_outcome(["pr_outcome"])

Impact lifecycle

State lives on impact and job; every event row is immutable. Only the CI conclusions failure and timed_out move a PR to failed; cancelled, action_required, neutral and skipped are recorded without a status change.

stateDiagram-v2
    [*] --> detected: matcher joined a change to a call site
    detected --> issued: issue opened, automatically or after review
    detected --> dismissed: muted, or the call site was removed
    issued --> fixing: fix_impact started
    issued --> failed: fixer refused, reason on the issue
    issued --> dismissed: closed by a human, muted, or call site removed
    fixing --> pr_open: validated PR opened
    fixing --> failed: validation failed or the agent needs a human
    pr_open --> merged: customer merged the PR
    pr_open --> failed: customer CI failed on the PR
    pr_open --> dismissed: PR closed without merging
    fixing --> issued: worker died mid-job, re-queued once
    failed --> pr_open: a CI re-run passes every check that had failed
    dismissed --> pr_open: the PR was reopened
    merged --> [*]
    failed --> [*]
    dismissed --> [*]

Data model

Twelve tables: the eleven from the spec plus subscriber for the weekly report, which is not drawn here. docs/SPEC.md is the product spec and docs/CONTRACTS.md holds the component contracts: job kinds, payloads, path dialects, cross-module seams and the source trust rules.

erDiagram
    ORG ||--o{ REPO : installs
    ORG |o--o{ SOURCE : "owns pushed"
    REPO ||--o{ DEPENDENCY : "depends on"
    SOURCE |o--o{ DEPENDENCY : watches
    DEPENDENCY ||--o{ CALL_SITE : "called from"
    SOURCE ||--o{ SNAPSHOT : captures
    SOURCE ||--o{ CHANGE_EVENT : emits
    SNAPSHOT |o--o{ CHANGE_EVENT : "from / to"
    CHANGE_EVENT ||--o| CLASSIFICATION : "classified as"
    REPO ||--o{ IMPACT : "affected by"
    CHANGE_EVENT ||--o{ IMPACT : causes
    CLASSIFICATION |o--o{ IMPACT : explains
    REPO ||--o{ RUNTIME_EVENT : reports

    ORG {
        uuid id PK
        bigint github_installation_id UK
        string plan "free or pro"
        string sdk_key_hash "sha256, shown once"
    }
    REPO {
        uuid id PK
        uuid org_id FK
        string full_name
        string default_branch
        string last_indexed_sha
        jsonb config "parsed .kintsua.yml"
    }
    SOURCE {
        uuid id PK
        string kind "openapi, mcp, pypi, npm, changelog"
        text locator UK "with kind"
        string origin "seed, customer, push, runtime, observed"
        int poll_interval_s
        timestamptz last_polled_at
    }
    SNAPSHOT {
        uuid id PK
        uuid source_id FK
        string content_hash
        jsonb storage_ref "inline or S3"
    }
    CHANGE_EVENT {
        uuid id PK
        uuid source_id FK
        jsonb diff "ChangeEvent with ChangeOps"
        timestamptz detected_at
    }
    CLASSIFICATION {
        uuid id PK
        uuid change_event_id FK
        string severity "breaking, degraded, safe, unknown"
        string category
        jsonb affected_paths
        jsonb rule_hits
        float llm_confidence
    }
    DEPENDENCY {
        uuid id PK
        uuid repo_id FK
        uuid source_id FK
        string kind
        string name
        bool discovered_at_runtime
    }
    CALL_SITE {
        uuid id PK
        uuid dependency_id FK
        text file_path
        int line
        text operation "METHOD /path, tool name or SDK method"
        jsonb args_shape
    }
    IMPACT {
        uuid id PK
        uuid repo_id FK
        uuid change_event_id FK
        string status
        int github_issue_number
        int github_pr_number
        string pr_outcome
    }
    RUNTIME_EVENT {
        uuid id PK
        uuid repo_id FK
        text tool_name
        string outcome "ok, error, schema_mismatch"
        string schema_fingerprint
        timestamptz observed_at
    }
    JOB {
        uuid id PK
        string kind
        jsonb payload
        string status "queued, running, done, failed"
        int attempts
        timestamptz run_after
        string dedupe_key
    }

Stack and scheduling

Stack: Python 3.12, FastAPI, SQLAlchemy 2 + Alembic, Postgres 16 (also the queue), httpx, the official mcp client (1.x and 2.x), tree-sitter (Python, TypeScript, TSX), PyGithub, Docker sandboxes, the Anthropic or OpenAI SDK behind one core/llm.py adapter (structured output for the classifier, a strict-schema tool loop for the fixer), Pydantic for every event schema.

Scheduling: a self-perpetuating tick job every 15 minutes enqueues poll_source for due sources (MCP 6h, OpenAPI 24h, packages 6h, changelog 24h; 1h for sources a paying customer depends on), with exponential backoff per failing source up to 48 hours. The weekly report runs on Mondays and the billing sync on the 1st.

10. Running it yourself

Requirements: Python 3.12, uv, Postgres 16 (local or Docker), Node 20 for stdio MCP servers and TypeScript checks, Docker for the fixer sandbox in production.

git clone https://github.com/hr7657316/Kintusa-Ai && cd Kintusa-Ai
cp .env.example .env                       # fill in what you have; everything is optional for a local run
uv sync --all-extras
make db                                    # docker run ... postgres:16  (or use a local server)
make migrate                               # alembic upgrade head
make seed                                  # 90 seeded sources: Stripe, GitHub, Slack, Twilio, Jira, Google discovery docs, MCP servers, SDK packages ...
make api                                   # python -m kintsua.api      -> http://localhost:8000
make worker                                # python -m kintsua.worker   -> tick + every job kind
make watch                                 # python -m kintsua.watchers.run --all --report   (the daily run + Markdown report)

scripts/dev.sh does all of that in one go. docker compose up --build runs Postgres, migrations, seeds, the API and two workers.

Create the GitHub App in one click: open http://localhost:8000/setup/github (or your deployed URL). It posts a GitHub App manifest with the right permissions and events; the callback shows the app id, private key, webhook secret and OAuth client id/secret as env lines to copy into .env. Restart, install the App on a repo, and watch the install issue appear.

LLM: LLM_PROVIDER=anthropic (default, ANTHROPIC_API_KEY) or openai; models default to claude-opus-5-5 for both the classifier and the fixer (LLM_CLASSIFIER_MODEL, LLM_FIXER_MODEL, effort via LLM_*_EFFORT). LLM_PROVIDER=fake is what the tests use.

Fixer sandbox: SANDBOX_BACKEND=docker (production; build the image with make sandbox-image, mount the Docker socket into the worker) or local (development; no isolation). Limits: 15 minutes wall clock, 4 GB, 2 CPUs, 3 concurrent fix jobs.

Snapshots over 256 KB go to S3-compatible storage (S3_*) or, unset, to DATA_DIR.

Deploy: fly.toml runs one web process and worker processes from the same image with alembic upgrade head as the release command; docker-compose.yml is the equivalent for a VM.

Production guards: with ENV=prod the service refuses to start unless SESSION_SECRET is a random value of 32+ characters, and the API and workers refuse to start without S3 unless every process shares DATA_DIR (DATA_DIR_SHARED=true, as in the single-host compose file). Jobs whose worker died are handed out again after JOB_LEASE_S (15 minutes; running jobs heartbeat). On SIGTERM a worker lets the running job finish for WORKER_SHUTDOWN_GRACE_S, then returns it to the queue; keep that below the platform's kill timeout. After an outage that outlasted the retries, re-drive failed jobs with python -m kintsua.worker --requeue-failed [--kinds classify_event,open_issue] [--since 24h].

Key environment variables (all in .env.example): DATABASE_URL, BASE_URL, GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY, GITHUB_WEBHOOK_SECRET, GITHUB_OAUTH_CLIENT_ID/SECRET, SESSION_SECRET, DATA_DIR_SHARED, JOB_LEASE_S, WORKER_SHUTDOWN_GRACE_S, NET_GUARD, POLL_TIMEOUT_S, POLL_HTTP_TIMEOUT_S, SOURCE_BACKOFF_BASE_S, ANTHROPIC_API_KEY, AUTO_ISSUE, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_ID.

11. HTTP API

Eleven endpoints from the spec plus the few the CLI and the setup flow need. Everything else happens in GitHub.

Method and path Auth Purpose
POST /webhooks/github GitHub signature installation, installation_repositories, repository (renames, default-branch changes), push, pull_request, issues, check_suite/check_run, workflow_run
POST /v1/ingest SDK key runtime event batches
POST /v1/watch/push SDK key a snapshot pushed by kintsua watch --push
GET /v1/repos/{id}/dependencies session dependency map JSON
GET /v1/repos/{id}/changes session change events affecting this repo, newest first
POST /v1/repos/{id}/sources session add a spec, changelog or MCP source
POST/DELETE /v1/repos/{id}/mute session mute / unmute a path or tool pattern
POST /v1/repos/{id}/fix-mode session switch pr / issue_only
POST /v1/repos/{id}/issues/open session turn a draft impact into a GitHub issue
DELETE /v1/repos/{id}/data session delete all runtime and index data for a repo
POST /v1/keys, GET /v1/keys session create or rotate the org's SDK key (shown once)
GET /report/weekly public the weekly drift report (`?format=md
POST /report/subscribe public subscribe an email to the report
POST /v1/billing/checkout, POST /v1/billing/webhook session / Stripe Stripe Checkout and plan sync
GET /login, GET /auth/callback, POST /logout sign in with GitHub (scoped to the installations you can see)
GET /settings/{owner}/{repo} session the HTMX settings page
GET /setup/github GitHub App manifest flow
GET /healthz, GET / public health, landing page

12. Testing

make test        # uv run pytest -q -m "not network"   (~750 tests, under 2 minutes, needs Postgres)
make smoke       # live end-to-end: real API + worker processes, a signed webhook, the CLI and the SDK (needs Postgres)
make lint        # ruff check + ruff format --check

What the suite covers: every watcher's diff against fixture pairs (MCP, OpenAPI 3, Swagger 2, Google discovery, PyPI/npm, changelog HTML) and a live stdio MCP server; a golden set of 50+ labelled change events with a CI check that the rules stay at 100%; the indexer against six fixture agent repos (LangChain, Google ADK, OpenAI Agents SDK, CrewAI, raw httpx, TypeScript) with exact dependency and call-site counts; matcher dialects, mutes, dedupe, runtime discovery and promotion; the fixer's sandbox, test-command detection, validator (including recorded breaks replayed against a fixture repo), agent loop, PR body and outcome capture; the SDK's wrappers, reporter and the 500-line boundary; the CLI against the fixture MCP server; the API's webhooks, ingest, settings, auth, report and billing; and tests/e2e, which runs the whole pipeline on the real job handlers: a local MCP server changes a tool schema and Kintsua produces the issue, the sandboxed PR, the merge outcome and the report numbers.

Tests marked network (real vendor specs, registries, public MCP servers) run on demand: uv run pytest -m network.

13. Layout

kintsua/
  api/          FastAPI: webhooks, ingest, repos, keys, report, billing, auth, settings pages, manifest setup
  core/         config, models, db, queue (SKIP LOCKED), storage, llm adapter, github app client, events
  watchers/     base, mcp, openapi, packages, changelog, schemadiff, run (tick + poll_source)
  classify/     rules, llm_classify, run, schema
  indexer/      clone, python_extract, ts_extract, mcp_config, sdk_map, resolve, depmap, config, run
  matcher/      match, issues, mute, operations, runtime
  fixer/        sandbox (docker | local), agent, validate, pr, testcmd, run
  sdk/          wrap, report, hooks        (import kintsua)
  cli/          kintsua watch
  report/       weekly report model, Markdown/RSS renderers, builder, job
  seed/         sources.yml + loader
alembic/        migrations
docker/         sandbox image
tests/          unit, integration, fixtures (changes golden set, agent repos, recorded breaks), e2e
docs/           SPEC.md, CONTRACTS.md

Licence: Apache 2.0.

Metadata

Release files for kintsua 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for kintsua 0.1.0
File Size Uploaded Provenance
kintsua-0.1.0.tar.gz 369.3 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for kintsua 0.1.0
File Interpreter ABI Platform
kintsua-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 775.8 kB

Release files / kintsua-0.1.0.tar.gz

Download URL kintsua-0.1.0.tar.gz
Size 369.3 kB
Tags Source
SHA-256 checksum
How to use checksums
fb5f475cd0a60d4e6553d766cd24e0b5e76752671877b765eca8407475858a04
BLAKE2b-256 checksum
How to use checksums
9cbaf2f5930acac7f11b15151888bbedcdb3f70734f4f5c1d3ef4a43746b8986
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / kintsua-0.1.0-py3-none-any.whl

Download URL kintsua-0.1.0-py3-none-any.whl
Size 406.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
65d1681cd279558a63c76101641971712d1a8df5d909670d35a87bd5570a0cec
BLAKE2b-256 checksum
How to use checksums
fdb85f04d69191029d19b2c52efadf4836458a0a82f14822a8f9853f9b7d1f82
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Security for kintsua 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for kintsua-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page