Shared ship door for AI agents
Rivalatch is one shared door that AI coding agents knock on before a ship moves. The door lodges the job, runs checks in a sandbox on a bundle or a public git commit, and hands back a pass/fail receipt with repro.
Public marketing name: Rivalatch. Code, env, packages, and webhook headers still say VibeGate (VIBEGATE_*, vibegate-*, X-VibeGate-Signature-256).
Product blurb. A shared acceptance gate for agent-callable smoke / DOM / screenshot checks.
Rivalatch is a shared acceptance gate for agent-callable smoke / DOM / screenshot checks. Calling agents knock with gate.run, poll gate.status, and read a terminal GateResult via gate.get_result — one door, not a sandbox product and not a CI marketplace.
Many rival agents, one frozen contract: Lodged → Recalled on the same key + payload; Occupied or Contested (HTTP 409) when the slot isn’t yours. Evidence composes to a clean pass/fail without inventing mid-floats.
HTTPS MCP or HTTP REST on the live Ship host. Authenticate with an API key (X-Api-Key / VIBEGATE_API_KEY). Intentional share-sheet / deep-link knocks (MOBILE_KNOCKER.md) are for ship claims; this page is the public directory product blurb. There is no billing product and no marketplace crowning.
Three frozen tools, over HTTP REST or MCP. Same body either way. Send your own key in X-Api-Key; never paste it into a page, ticket, or chat.
Knock. Lodged 202 for a new key, Recalled 200 for the same key and payload, 409 for the same key with a different payload.
{ job_id, state, updated_at } with state queued | running | succeeded | failed.
The receipt (GateResult) once terminal. Non-terminal returns 202 with the status body.
HOST=https://vibegate-production.up.railway.app # knock: a public github.com commit, smoke GET / curl -sS -X POST $HOST/v1/gate/run \ -H "Content-Type: application/json" \ -H "X-Api-Key: <your key>" \ -d '{ "app_ref": { "type": "git", "uri": "https://github.com/<owner>/<repo>", "rev": "<40-hex sha>" }, "checks": ["smoke"], "acceptance": { "criteria": [], "base_url_path": "/" }, "idempotency_key": "<uuid>" }' # → 202 Lodged { job_id, state } # then status and the receipt curl -sS -H "X-Api-Key: <your key>" \ $HOST/v1/gate/status/<job_id> curl -sS -H "X-Api-Key: <your key>" \ $HOST/v1/gate/results/<job_id>
app_ref.type is git (https, github.com, public repos; depth-1 clone at rev, static serve, no repo code executed) or bundle.
# HTTPS MCP: JSON-RPC, same dispatch as stdio curl -sS -X POST $HOST/mcp \ -H "Content-Type: application/json" \ -H "X-Api-Key: <your key>" \ -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "gate.run", "arguments": { …same body… } } }' # then, with { "job_id": "<job_id>" } gate.status # poll gate.get_result # the receipt # stdio MCP for local agents # (Cursor · Claude Code · Codex · Grok Build) vibegate-mcp # env: VIBEGATE_API_KEY=<your key>
No fourth tool. Install cards: MCP.md · agent card: AGENT.md.
The terminal GateResult from gate.get_result. Closed shape: no fields beyond these.
{
"job_id": "job-20261007T195500Z-3f9a1c2e",
"verdict": "pass",
"noul_acceptance_met": 1.0,
"thresholds": { "pass_min": 0.85 },
"checks": [
{ "id": "smoke-5b1d07e2", "kind": "smoke",
"status": "pass",
"detail": "GET http://…/ → 200 in 41.2ms" }
],
"repro": {
"sandbox_id": "mock-5e0c9a1b2d3f",
"commands": ["sandbox.create()",
"sandbox.provision({…})", "GET …/",
"sandbox.destroy(…)"],
"screenshot_uris": [],
"dom_snapshot_uri": null,
"logs_uri": "…/job.log",
"har_uri": null
},
"fail_class": null
}
pass or fail. Terminal only.criteria it composes to 0.0 or 1.0 only; never an invented mid-float.noul_acceptance_met for pass. Empty-criteria code compose always reports 0.85. Only field in thresholds.id, kind (smoke | e2e | visual | dom | log), status (pass | fail | error), detail.sandbox_id, commands, screenshot_uris, dom_snapshot_uri, logs_uri, har_uri. Smoke-only knocks leave screenshots empty and the DOM snapshot null.null on pass. Else one of build, runtime, assert, visual_reg, timeout, infra.Each door state has one glyph and one color. The shape alone is enough, so it reads in grayscale too. Wire codes are unchanged; the words are prose.
New idempotency key. The door takes the job and returns { job_id, state }.
Checks run in the sandbox. gate.status reports queued or running until terminal.
Receipt sealed with verdict: pass. fail_class is null.
Receipt with verdict: fail and a class saying why, with repro to replay it.
Same key, same payload. The existing job comes back. No second ship.
Same key, different payload. The 409 detail shows held (Occupied) vs incoming (Contested).
A rivalatch://knock link encodes the same gate.run body. A thin client (Shortcuts, agent chat paste, MCP host) parses it and calls the same three tools. Client-side only: no store app, no new route.
rivalatch://knock?idempotency_key=<uuid> &type=bundle&uri=fixtures &checks=smoke&base_url_path=/ # git tip knock (public repo) rivalatch://knock?idempotency_key=<uuid> &type=git &uri=https%3A%2F%2Fgithub.com%2FConWan30%2FQoresence &rev=<40-hex-sha>&checks=smoke &base_url_path=%2F
idempotency_key.app_ref. Defaults bundle, fixtures, null.checks defaults to smoke; criteria to empty.acceptance.base_url_path, default /.Full codec and proof: MOBILE_KNOCKER.md.
Every push to Qoresence’s main gets a receipt.
An opt-in workflow in ConWan30/Qoresence lodges the frozen gate.run body at the pushed commit. The door shallow-clones the public repo at that SHA, serves its Pages root, and smokes GET /.
The job_id, verdict, and fail_class land in the workflow’s job summary.
main.app_ref { type: git, uri: https://github.com/ConWan30/Qoresence, rev: <pushed sha> }, checks [smoke].GET /.verdict and fail_class in the job summary.