WebSocket protocol
The Kredence pipeline runs over a persistent WebSocket connection.
Endpoint: wss://credenceserver-production.up.railway.app/ws
One pipeline run per connection. The server hard-caps maxProjects at 10.
Connection lifecycle
Client Server
│ │
│─── connect /ws ──────────────>│
│ │
│<── { type: "ready" } ─────────│ Server ready
│ │
│─── { type: "run", ... } ─────>│ Start pipeline
│ │
│<── { type: "pipeline_start" } │
│<── { type: "stage_start" } │ scout
│<── { type: "log" } ... │
│<── { type: "tool_call" } ... │
│<── { type: "stage_done" } │
│<── { type: "stage_start" } │ evidence
│ ... │
│<── { type: "project_complete"}│ first project done
│ ... │
│<── { type: "pipeline_done" } │ all done
│ │
└─── close ────────────────────>│Client → Server messages
run
Start a pipeline run. Send this immediately after receiving ready.
{
"type": "run",
"payload": {
"kind": "manual",
"urls": ["https://github.com/owner/repo"]
},
"maxProjects": 3
}| Field | Type | Description |
|---|---|---|
payload | EcosystemInput | The ecosystem to evaluate |
maxProjects | number | Optional, default 3, max 10 |
Only one run per connection. Subsequent run messages on the same connection are ignored.
ping
Keep-alive ping.
{ "type": "ping" }The server responds with { "type": "pong" }.
Server → Client messages
ready
Sent immediately on connection. Wait for this before sending run.
{ "type": "ready", "serverVersion": "0.1.0" }pipeline_start
{
"type": "pipeline_start",
"runId": "v8Kj3mNp",
"ecosystem": "manual"
}stage_start / stage_done
{ "type": "stage_start", "runId": "v8Kj3mNp", "stage": "scout" }
{ "type": "stage_done", "runId": "v8Kj3mNp", "stage": "scout" }Stage values: scout · evidence · adversarial · synthesis
log
Structured log entry from an agent.
{
"type": "log",
"runId": "v8Kj3mNp",
"agent": "evidence",
"entry": {
"timestamp": "2026-03-29T14:24:28.378Z",
"level": "info",
"phase": "execute",
"action": "fetch_github",
"details": { "repo": "owner/repo", "commits90d": 42 }
}
}tool_call / tool_done / tool_error
{
"type": "tool_call",
"runId": "v8Kj3mNp",
"agent": "evidence",
"phase": "execute",
"tool": "fetch_github_repo",
"input": { "owner": "owner", "repo": "repo" }
}
{
"type": "tool_done",
"runId": "v8Kj3mNp",
"agent": "evidence",
"phase": "execute",
"tool": "fetch_github_repo",
"output": { "stars": 12, "commitCount90d": 42 },
"durationMs": 834
}project_complete
Emitted once per project when the full hypercert payload is ready.
{
"type": "project_complete",
"runId": "v8Kj3mNp",
"payload": { ...HypercertPayload }
}pipeline_done
Final message. Emitted after all projects are evaluated.
{
"type": "pipeline_done",
"runId": "v8Kj3mNp",
"summary": {
"projectsEvaluated": 3,
"hypercertsStored": 3,
"atprotoPublished": 2,
"totalVerified": 11,
"totalFlagged": 4,
"totalUnresolved": 2,
"durationMs": 94321
}
}pipeline_error
Emitted if the pipeline fails unrecoverably.
{
"type": "pipeline_error",
"runId": "v8Kj3mNp",
"stage": "evidence",
"message": "GitHub API rate limit exceeded"
}Raw WebSocket example (no SDK)
const ws = new WebSocket('wss://credenceserver-production.up.railway.app/ws');
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'ready') {
ws.send(JSON.stringify({
type: 'run',
payload: { kind: 'manual', urls: ['https://github.com/owner/repo'] },
maxProjects: 2,
}));
}
if (msg.type === 'project_complete') {
console.log(msg.payload.title, msg.payload.confidenceScore);
}
if (msg.type === 'pipeline_done') {
console.log('Done:', msg.summary);
ws.close();
}
};TIP
Use the SDK instead of raw WebSocket — it handles reconnection edge cases, typed events, async iteration, and the ready → run handshake automatically.