Shared Project
The durable collaboration boundary. Specs, tickets, sessions, receipts, subscriptions, and journals hang off this scope.
Rut Documentation
Rut now centers around a shared-project model. Specs and tickets live in durable project state, sessions execute locally on the active macOS host, collaborators coordinate through shared runtime state, and external systems connect through signed inbound and outbound webhooks.
Specs, tickets, sessions, endpoints, and subscriptions all belong to the same durable project scope.
RuntimeThe app hosts execution locally while exposing canonical ticket and spec tools to sessions through MCP.
AutomationStart with an inbound endpoint, send ticket.create, then subscribe to durable outbound events.
The redesign is easiest to reason about if you keep these concepts separate.
The durable collaboration boundary. Specs, tickets, sessions, receipts, subscriptions, and journals hang off this scope.
A project-shaping artifact. Specs define intent, validation, and the context that tickets can link back to.
The canonical unit of durable work. Tickets move through workflow states, claims, approvals, validation, and completion.
A local runtime session associated with the shared project. Sessions read and mutate project state through canonical tools.
A project-scoped signed webhook entrypoint for ticket.create and optional narrow service controls.
A project-scoped signed delivery target for durable ticket.* and agent.run.* events.
Rut deliberately uses different surfaces for different trust boundaries.
| Surface | Audience | What It Owns |
|---|---|---|
| Shared Project Store | Hosts and collaborators | Canonical specs, tickets, approvals, receipts, subscriptions, presence, and durable run lifecycle milestones. |
| Local Runtime | Active host device | Owning execution, hosting sessions, local process state, and bridging results back into shared project records. |
| MCP Workflow Tools | Local agent sessions only | Project reads and writes through canonical tools such as noded_ticket_list, noded_ticket_transition, and noded_spec_get. |
| Inbound Webhooks | External systems | Durable work ingress via ticket.create plus narrow feature-gated service controls. |
| Outbound Webhooks | External systems | Durable notifications emitted from the backend outbox using signed ticket.* and agent.run.* envelopes. |
| Control Intents | Hosts and collaborators | Host-bound actions such as handoff or interrupt. External callers only request these through narrow service controls. |
| Relay / Live Observation | Collaborators | Live collaboration fanout. It is not the public automation subscription interface. |
The app is now the runtime host. Shared project state stays durable and collaborative, while execution stays on the active macOS host that owns the session.
noded_ticket_* and noded_spec_*
surfaces for local session work. These tools express durable project mutations through the app-owned runtime instead of a public CLI contract.
The webhook manager lives directly inside the macOS app and is scoped to the shared projects you currently have open.
Use an account that can open shared projects from Settings > Account.
Automation is only available for shared projects that are open in the current window.
Go to Settings > Automation, choose the project, then manage endpoints and subscriptions.
Endpoint and subscription signing secrets are shown when created or rotated. Store them in the receiving system immediately.
| Area | Available Actions |
|---|---|
| Inbound Endpoints | Create, edit, revoke, rotate secrets, constrain commands, constrain initial states, and inspect ingress receipts. |
| Outbound Subscriptions | Create, edit, pause, resume, revoke, filter event families, and inspect recent delivery attempts. |
| Quickstart | Copy sample ticket.create and service-control curl payloads directly from the selected endpoint. |
This is the fastest path from an external system to a durable Rut ticket lifecycle.
Start with ticket.create only. Enable service controls later if you explicitly need them.
Keep ingress narrow by constraining create states to automation-safe workflow states.
Sign the raw body with your endpoint secret and include the timestamp and signature headers.
Subscribe to the event families you need and verify outbound signatures with the subscription secret.
curl -X POST 'https://noded-35e17.web.app/api/automation/inbound/<endpoint-id>' \
-H 'Content-Type: application/json' \
-H 'X-Noded-Timestamp: <unix-seconds>' \
-H 'X-Noded-Signature: <hmac-sha256>' \
-d '{
"version": 1,
"command": "ticket.create",
"idempotencyKey": "crm-case-10491",
"payload": {
"title": "Investigate failed billing webhook",
"description": "Raised by Stripe alerting automation.",
"initialStateId": "intake"
}
}'
ticket.createUse ticket.create when an external system needs to create durable work inside a shared project.
| Field | Required | Notes |
|---|---|---|
version |
Yes | Current protocol version is 1. |
command |
Yes | Must be ticket.create. |
idempotencyKey or sourceEventId |
Yes | Rut deduplicates by endpoint plus this key. Replays with the same body return the original result. |
payload.title |
Yes | Ticket title. |
payload.description |
No | Stored as the ticket description. |
payload.initialStateId |
No | Optional create state, validated against endpoint policy and workflow safety rules. |
payload.linkedSpecId |
No | Links the new ticket to an existing spec when present. |
payload.parentTicketId |
No | Creates a child ticket under an existing parent ticket. |
| State | Allowed | Behavior |
|---|---|---|
intake |
Yes | Default safe ingress state. |
backlog |
Yes, if endpoint policy allows it | Useful when upstream triage already happened. |
ready |
Yes, if endpoint policy allows it | Useful for already-approved automation-ready work. |
review, done, cancelled |
No | Rejected as unsafe create targets. |
{
"ok": true,
"ticketId": "ticket_01HZY1M8VQ9W",
"revision": 1,
"ingressReceiptId": "endpoint_01HZ..._64f3434f..."
}
Rut supports a very small service-originated live-control surface. These commands are feature-gated and disabled by default.
| Command | Payload | What Rut Does |
|---|---|---|
control.interrupt_turn |
{ "targetSessionId": "..." } |
Creates a host-targeted control intent that asks the active host to interrupt the current turn. |
control.request_handoff |
{ "targetSessionId": "..." } |
Creates a host-targeted control intent requesting a handoff for the active shared session. |
targetSessionId. The backend resolves the canonical
flow, current lease epoch, host user, host device, and expiry. If there is no active shared host or lease, the command fails.
curl -X POST 'https://noded-35e17.web.app/api/automation/inbound/<endpoint-id>' \
-H 'Content-Type: application/json' \
-H 'X-Noded-Timestamp: <unix-seconds>' \
-H 'X-Noded-Signature: <hmac-sha256>' \
-d '{
"version": 1,
"command": "control.interrupt_turn",
"idempotencyKey": "interrupt-481",
"payload": {
"targetSessionId": "7C0A7EE6-4E3C-4E19-B9B8-2B0CC804F761"
}
}'
Outbound automation is delivered from a durable backend outbox fed by canonical ticket journals and durable run lifecycle milestones.
| Family | Meaning |
|---|---|
ticket.created |
Canonical ticket creation. |
ticket.state.changed |
Workflow movement such as ready → in_progress or review → done. |
ticket.claim.changed |
Ticket claim acquired or released. |
ticket.progress.appended |
Progress journal append. |
ticket.validation.changed |
Validation report or snapshot change. |
ticket.approval.requested, ticket.approval.resolved |
Approval lifecycle changes. |
agent.run.started, agent.run.awaiting_approval, agent.run.awaiting_input, agent.run.capacity_paused, agent.run.turn.completed, agent.run.failed, agent.run.done |
Durable run lifecycle milestones for shared-project execution. |
| Header | Meaning |
|---|---|
X-Noded-Event-ID |
Stable event id. Deduplicate on this value. |
X-Noded-Delivery-ID |
Unique id for a specific attempt. |
X-Noded-Timestamp |
Unix seconds used in signature verification. |
X-Noded-Signature |
HMAC-SHA256 over timestamp + "." + rawBody. |
Delivery is at least once. Rut retries with exponential backoff and records each attempt in the Automation settings pane.
Compute the request signature over the raw request body and the timestamp header.
import { createHmac } from "crypto";
function signRutWebhook(secret, timestamp, rawBody) {
return createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`, "utf8")
.digest("hex");
}
sourceEventId when your upstream system already has a stable event identifier.idempotencyKey.Outbound deliveries use the same HMAC format: timestamp + "." + rawBody. Verify the signature with the subscription secret, then deduplicate by X-Noded-Event-ID.
{
"id": "ticket-event:event-1",
"type": "ticket.created",
"version": 1,
"occurredAt": "2026-03-25T00:00:00.000Z",
"projectRef": {
"workspaceId": "workspace-1",
"projectId": "project-1"
},
"entityRef": {
"ticketId": "ticket-1"
},
"actor": {
"type": "system",
"displayName": "Automation Webhook",
"sessionId": "session-1"
},
"causation": {
"ticketEventId": "event-1",
"ingressReceiptId": "receipt-1",
"sourceEventId": "source-1",
"rootRunId": "root-run-1",
"controlIntentId": null
},
"data": {
"ticketEventKind": "created",
"summary": "Created remotely",
"details": "Ticket created via canonical journal",
"ticketRevision": 3,
"artifactCount": 1
}
}
{
"id": "agent-run:run-event-1",
"type": "agent.run.awaiting_input",
"version": 1,
"occurredAt": "2026-03-25T12:00:00.000Z",
"projectRef": {
"workspaceId": "workspace-1",
"projectId": "project-1"
},
"entityRef": {
"ticketId": "ticket-1",
"sessionId": "session-1",
"flowId": "flow-1",
"rootRunId": "root-run-1",
"turnId": "turn-1"
},
"actor": {
"type": "session",
"displayName": "Shared Runtime",
"sessionId": "session-1"
},
"causation": {
"ticketEventId": null,
"ingressReceiptId": null,
"sourceEventId": null,
"rootRunId": "root-run-1",
"controlIntentId": "intent-1"
},
"data": {
"detailMessage": "Waiting for approval answer",
"errorMessage": null,
"resumeAt": null,
"hostUserId": "host-user",
"hostDeviceId": "host-device"
}
}
The app exposes recent operational state directly in Automation settings so you can debug integrations from the source of truth.
Each inbound request records an endpoint-scoped receipt with command, dedupe key, created ticket or control intent, and status.
Each outbound subscription delivery attempt records success or failure, the event id, and the latest error message.
Outbound deliveries are at least once and retry with exponential backoff. Deduplicate on X-Noded-Event-ID.
Endpoint and subscription secrets can be rotated in the app. Treat the new secret as authoritative immediately after rotation.
| Problem | Likely Cause | What To Check |
|---|---|---|
| 401 on inbound webhook | Bad signature or timestamp skew | Verify the raw-body HMAC, the X-Noded-Timestamp value, and that your clock is reasonably accurate. |
| Duplicate request rejected | Same dedupe key, different body | Keep the body stable when retrying an event, or issue a new dedupe key for a distinct event. |
initialStateId rejected |
State is outside the endpoint policy or not automation-safe | Restrict create states to intake, backlog, or ready. |
| Service control rejected | No active host or lease, or feature flag disabled | Confirm the target session belongs to an active shared flow and that service controls are enabled for the deployment. |
| No outbound deliveries | Subscription paused, revoked, or filtered out | Review the subscription status and event-family filter in Settings > Automation. |
| Need operational visibility | Looking only at the receiver side | Use the in-app Automation pane to inspect ingress receipts, delivery attempts, and the latest failure message. |