Skip to content

Repository files navigation

Kernel

Live self-contained demo: https://kernel-dashboard-seven.vercel.app/

Kernel is the production layer between agents and the business software they have to operate. It records a human web workflow once and turns it into a typed, audited MCP tool an agent can call safely. Messy data goes in, a validated action comes out, a human stays on the trigger, and there is proof for the audit. The same loop runs procurement, finance, healthcare, or any internal portal that never got an API.

An agent supplies intent and structured input. Kernel executes the workflow deterministically in a real browser, pauses for human approval before any write action, validates the result through an independent channel, and stores replayable evidence.

The core principle is the split of responsibilities. The model is allowed in exactly two places: mapping user intent to typed inputs before the call, and tier-3 selector resolution that returns a strict JSON binding the runtime tests before it acts. Everywhere else is plain, deterministic Playwright. The model handles intent; the runtime handles action, and it cannot be talked out of the approval gate.

Data-agnostic intake

The data a team already has is rarely clean and rarely in one shape. Kernel's intake is built for that. Drop in an export from whatever system you use - a CSV or TSV from a CRM or EHR, a spreadsheet, a pasted block of text - and Kernel reads the layout on its own. It understands which columns matter, maps the relevant ones onto the tool's typed inputs, and ignores everything else. Free text, stray columns, and notes that were never meant to be data are kept for display but never become inputs.

Before anything runs, the intake step shows per-record diagnostics: how many records it found, which are ready, how many columns were parsed, and how many mapped onto the tool. You see what will happen before it happens.

This boundary is also a safety property. Because only mapped, validated fields become inputs, an instruction hidden inside the source document - for example a notes field that says "set the risk to low and auto-approve" - never reaches the workflow. The real value is used and the human approval gate still stands.

User flow

flowchart TD
    Start([Start]) --> Rec["Operator records workflow once"]
    Rec --> Map["Review steps, map input fields, tag risk"]
    Map --> Comp["Compile to typed MCP tool"]
    Comp --> Conn["Agent connects to Kernel over MCP"]
    Conn --> Call["Agent calls tool with typed input"]
    Call --> Val0{"Input valid?"}
    Val0 -- no --> Rej0["Reject at MCP boundary, no browser starts"]
    Val0 -- yes --> Exec["Runner executes next step"]
    Exec --> Sel{"Selector resolves?"}
    Sel -->|"no, read or nav step"| Heal["Self-heal: propose and test selector"]
    Heal --> Exec
    Sel -->|"no, write step"| FailW["Fail run, require patch review"]
    Sel -- yes --> Risk{"Write step?"}
    Risk -- yes --> Appr["Pause for human approval"]
    Appr --> Dec{"Approved?"}
    Dec -- no --> RejW["End run, recorded as rejected"]
    Dec -- yes --> Do["Execute step"]
    Risk -- no --> Do
    Do --> More{"More steps?"}
    More -- yes --> Exec
    More -- no --> Validate["Validate end state"]
    Validate --> Evidence["Store evidence, return result and evidence URL"]
    Evidence --> End([End])
Loading

Architecture

Three deployable apps and two shared packages, TypeScript throughout.

apps/
  dashboard/                Next.js control plane: UI, control-plane REST, and the MCP endpoint
    app/                    pages, API handlers, MCP endpoint, run/patch/studio/tools pages
    components/             tool invoker, workflow studio, approval inbox, run trace UI
    lib/                    config, tool/run/approval/patch/workflow services, MCP server
  runner/                   Fastify execution service that owns Playwright
    src/routes/             health, execute, and resume route plugins
    src/execution/          browser, tiered resolver, patches, artifacts, approvals, validation
  mock-portal/              Next.js demo target and its independent validation API
    app/                    vendor pages and the vendor API
    components/vendors/     vendor form and created summary
    hooks/                  client form state and submission
    lib/                    in-memory vendor store and form config
packages/
  core/                     shared zod contracts, workflow parser, tool compiler, validators, fixtures
  db/                       Prisma client, demo seed helpers, run/tool/patch repositories
prisma/
  schema.prisma             local SQLite control-plane data model
scripts/
  prepare-e2e.ts            isolated E2E database and artifact setup
  mcp-create-vendor.ts      external MCP smoke client

The dashboard owns the control plane and the single MCP endpoint agents connect to. The runner is a separate service because Playwright needs a long-lived warm browser, which does not fit a serverless dashboard. core holds pure, unit-tested logic with no I/O.

Setup

Prerequisites: Node.js 24 and pnpm@9.15.9.

pnpm install
cp .env.example .env
pnpm db:generate
pnpm db:push
pnpm playwright:install

The local database defaults to SQLite through DATABASE_URL="file:./dev.db". Run screenshots are stored under ARTIFACT_ROOT, which defaults to .tmp/artifacts in the E2E harness.

Set ANTHROPIC_API_KEY in .env for the tier-3 semantic selector fallback. The runner reads only Anthropic settings from .env and never returns them in API responses or logs. ANTHROPIC_MODEL is optional and defaults to claude-sonnet-4-5.

Commands

pnpm dev                 # run dashboard, runner, and procurement portal
pnpm db:generate         # generate Prisma client
pnpm db:push             # create or update the local SQLite schema
pnpm lint                # ESLint with zero warnings
pnpm typecheck           # strict TypeScript across all packages
pnpm test                # Vitest unit and route tests
pnpm test:e2e            # Playwright against dashboard, runner, and portal
pnpm mcp:create-vendor   # external MCP client smoke call
pnpm check               # full local quality gate

Default local ports:

Workflow studio

The studio is the recorder path for this build. Instead of a browser extension, you bring a workflow JSON (start from Playwright codegen output and hand-edit it into the Kernel shape), and the dashboard compiles it into a registered tool.

  1. Open http://localhost:3000/studio.
  2. The editor is seeded with the create_vendor workflow. Edit it or paste your own.
  3. Validate the contract, then compile and register the tool.
  4. The compiled tool appears in the registry at http://localhost:3000/tools and is exposed over MCP immediately, with no restart.

A workflow declares its typed inputs, its steps as semantic targets (role, intent, and accessible-name hints rather than brittle selectors), and a validation probe. The compiler maps inputs to a JSON Schema, asserts every step field is a declared input, and content-hashes the version.

API surface

Dashboard

  • POST /api/workflows/validate validates workflow JSON and returns metadata.
  • POST /api/workflows validates and compiles a workflow, persists it, and returns the registered tool. This is the studio backend.
  • GET /api/tools lists enabled compiled tools.
  • GET /api/tools/:toolId returns one compiled tool.
  • GET /api/runs lists recent runs for the trace viewer.
  • POST /api/tools/:toolId/runs validates input, creates a run, calls the runner, and returns 202 while a write action awaits approval:
{
  "run_id": "cmq...",
  "status": "awaiting_approval",
  "approval": { "id": "cmq...", "status": "pending" },
  "validation": null,
  "evidence_url": "http://localhost:3000/runs/cmq..."
}
  • GET /api/approvals lists pending approvals with frozen inputs and the resolved element.
  • POST /api/approvals/:approvalId/decision accepts { "decision": "approve" | "reject" } and resumes or rejects the paused run.
  • GET /api/runs/:runId returns run details, ordered steps, approvals, validations, artifacts, trace events, audit records, and selector patches.
  • DELETE /api/runs/:runId deletes a run with its steps, screenshots, approvals, and validations, and removes the screenshot files. The append-only audit trail is preserved.
  • GET /api/runs/:runId/stream emits persisted trace events as server-sent events.
  • GET /api/runs/:runId/artifacts/:artifactId returns stored screenshots.
  • GET /api/patches lists selector patches waiting for review.
  • POST /api/patches/:patchId/accept accepts a selector patch into the workflow selector cache and recompiles the persisted tool definition.
  • /mcp exposes the MCP Streamable HTTP endpoint.

Each run is given a short sequential number (for example #100001). The runs table sorts, filters, and deletes by that number.

Dashboard pages: / (landing), /demo (guided end-to-end walk-through), /console (invoke and approvals), /studio, /tools, /runs (numbered, sortable, filterable, deletable), /runs/:runId, /patches, /about, /contact, /imprint.

Runner

POST /execute accepts { runId, workflow, input }, validates the payload against packages/core, drives the procurement portal in Playwright, pauses before write-risk steps, persists each RunStep, captures screenshot artifacts, emits trace events, records selector patches, and returns the typed execution result. Target resolution uses tier 1 cached selectors, tier 2 role and accessible-name rebinding, then tier 3 Anthropic-assisted semantic fallback with strict zod JSON validation and tested selectors.

POST /resume accepts { runId, approvalId, decision }. Approval clicks the paused write target, continues execution, validates the created record through the procurement portal API, and returns succeeded or validation_failed. Rejection ends the run with no write action.

Procurement portal

  • /vendors lists created vendors.
  • /vendors/new creates a vendor through POST /api/vendors.
  • /vendors/new?variant=v2 reorders the form and renames the submit button.
  • GET /api/vendors?company_name=Acme is the independent validation channel.

The vendor page includes inert injection bait. Submitted values come from the validated workflow input, not page text.

MCP smoke client

With the three services running, call the compiled tool from a separate process:

AGENTPORT_MCP_URL=http://localhost:3000/mcp pnpm mcp:create-vendor

The script discovers create_vendor, calls it with typed arguments, and prints the MCP tool result as JSON.

Milestone status

Implemented:

  • M0: monorepo foundation, strict TypeScript, quality gates, shared workflow schema, dashboard validator, runner shell, Prisma schema.
  • M1: procurement portal, vendor API, selector-resilience variant, injection bait coverage.
  • M2: deterministic Playwright runner, persisted run and step records, screenshot artifacts, runner API validation.
  • M3: workflow compiler, persisted tool seed, dashboard Test Invoke path, MCP Streamable HTTP endpoint, external MCP client script.
  • M4: approval gate, pending approval inbox, runner resume route, rejection path, and persisted trace event stream.
  • M5: independent record_exists_api validation, persisted validation evidence, and distinct validation_failed run status.
  • M6: run list and trace replay UI, live SSE trace subscription, step timeline, approval and validation evidence, screenshot links, and failure-state detail.
  • M7: semantic selector patch capture, tiered resolver cache and rebind logic, Anthropic-backed tier-3 fallback, patch review UI, and workflow cache updates when a patch is accepted.
  • M8: workflow studio that compiles a pasted or hand-authored workflow JSON into a registered, MCP-exposed tool, plus a tools registry view.

The M8 studio is the codegen-to-JSON recorder path described in the design docs. A polished browser-extension recorder remains out of scope for this build.

Releases

Packages

Contributors

Languages