Workflow API
Table of Contents
upsquad/workflow/v1/workflow.proto
AnswerRunPromptRequest
AnswerRunPromptRequest records an answer to a mid-run user_input prompt. LBE-B2 (#2249).
| Field | Type | Label | Description |
|---|---|---|---|
| prompt_id | string | prompt_id is the governance_approvals row id to answer (required). | |
| choice | string | choice is the selected option value. Required unless the prompt is free-text-only (allow_text with no options); when set it must be one of the prompt's option values. | |
| text | string | text is the free-text answer. Permitted only when the prompt has allow_text=true; rejected otherwise. |
AnswerRunPromptResponse
AnswerRunPromptResponse reports the outcome of an answer attempt. LBE-B2 (#2249).
| Field | Type | Label | Description |
|---|---|---|---|
| recorded | bool | recorded is true when THIS call won the first-write and recorded the answer. False when the prompt was already answered before this call (already_answered is then true) — the answer the client should render is the recorded one. | |
| already_answered | bool | already_answered is true when the prompt was already resolved (this call was a benign no-op — first-write-wins). recorded is then false. | |
| prompt | GetRunPromptResponse | prompt is the prompt's post-answer projection (status flips to "answered" on a successful record), so the client re-renders the card from a single response. |
ApproveActionRequest
ApproveActionRequest approves a workflow action.
| Field | Type | Label | Description |
|---|---|---|---|
| action_id | string | action_id is the workflow action UUID (required). | |
| comment | string | comment is optional approval comment. |
ApproveActionResponse
ApproveActionResponse contains the approved action result.
| Field | Type | Label | Description |
|---|---|---|---|
| action | WorkflowAction | action is the updated workflow action. | |
| next_actions | WorkflowAction | repeated | next_actions are any new actions created as a result. |
CancelRunRequest
CancelRunRequest cancels a workflow run.
| Field | Type | Label | Description |
|---|---|---|---|
| run_id | string | run_id is the workflow run UUID to cancel (required). | |
| reason | string | reason is an optional human-readable note recorded in the audit log. |
CancelRunResponse
CancelRunResponse returns the post-cancel run state.
| Field | Type | Label | Description |
|---|---|---|---|
| run | WorkflowRun | run is the workflow run with status='cancelled' (or unchanged if the run was already terminal - idempotent). |
CreateWorkflowRequest
CreateWorkflowRequest creates a new workflow.
| Field | Type | Label | Description |
|---|---|---|---|
| name | string | name is the workflow name (required). | |
| description | string | description is an optional workflow description. | |
| definition | google.protobuf.Struct | definition is the workflow definition JSON (required, validated against schema). | |
| team_id | string | team_id is optional team scoping. | |
| visibility | string | visibility is the workflow's audience: "team" (default when empty — visible to its team, today's universal behaviour) |
CreateWorkflowResponse
CreateWorkflowResponse contains the created workflow.
| Field | Type | Label | Description |
|---|---|---|---|
| workflow | Workflow | workflow is the created workflow. |
DeleteWorkflowRequest
DeleteWorkflowRequest soft-deletes a workflow. #2002.
| Field | Type | Label | Description |
|---|---|---|---|
| workflow_id | string | workflow_id is the workflow UUID (required). |
DeleteWorkflowResponse
DeleteWorkflowResponse acknowledges a soft-delete. Empty by design: the workflow is gone from List/Get after this call, so there is nothing useful to echo back. A typed response (rather than google.protobuf.Empty) leaves room to add a deleted_at timestamp or a runs-hidden count later without a breaking change. #2002.
DeliverWorkflowEventRequest
DeliverWorkflowEventRequest delivers one external event to a run parked on a
signal-mode wait node. WR2-4 Slice 6 (#2266), LLD #2253 §3.4.
| Field | Type | Label | Description |
|---|---|---|---|
| run_id | string | run_id is the workflow run UUID to deliver into (required). The run is read RLS-scoped: a run owned by another org is NOT_FOUND. | |
| event_key | string | event_key is the correlation handle the waiting node declared (dsl.Step.event_key). It must match for the wait to resolve; a delivery on a key nothing is waiting on is buffered in the run's state and consumed if a wait on that key is reached later. Required. | |
| payload | bytes | payload is the OPAQUE event body as JSON bytes. bytes rather than google.protobuf.Struct on purpose: an external event body (CI metadata, a PR number, a vendor webhook) must ride through without a proto-struct constraint. The interpreter never inspects it — it binds it verbatim as the wait node's output envelope, and the SUCCESSOR's WR2-1 expects FieldSpec is what structurally validates the shape. |
Optional: an absent payload (and an explicit JSON null) both normalise to {} at the interpreter's drain, so a bare "it happened" event resolves the wait and still binds a valid empty object. |
| delivery_id | string | | delivery_id is the PRODUCER-supplied identity of THIS delivery attempt (required). It is what makes an at-least-once caller safe: the platform burns (org, run_id, event_key, delivery_id) durably before signalling, so re-sending the SAME delivery_id is a no-op even after the waiting node already consumed the first one. A genuinely NEW occurrence must carry a NEW delivery_id — a retry and a second occurrence are indistinguishable without it, which is exactly why the field is required rather than optional.
Opaque to the platform: whatever stable id the caller's retry loop re-sends (a webhook delivery id, an outbox row id, a broker message id). Empty is INVALID_ARGUMENT — never a silent fall-through to the non-idempotent path. |
DeliverWorkflowEventResponse
DeliverWorkflowEventResponse reports the outcome of a delivery. WR2-4 Slice 6 (#2266).
| Field | Type | Label | Description |
|---|---|---|---|
| run | WorkflowRun | run is the run's CURRENT projection. Delivery is asynchronous — the run's status transitions when the interpreter observes the signal and re-projects (the projection is the single writer of a Temporal run's status) — so this is the state at delivery time, not a post-resume state. | |
| deduplicated | bool | deduplicated is true when this delivery_id had ALREADY been accepted for (run_id, event_key): the call was a benign no-op and NO signal was sent. An at-least-once caller can treat true exactly like a success. |
EscalateActionRequest
EscalateActionRequest escalates a workflow action.
| Field | Type | Label | Description |
|---|---|---|---|
| action_id | string | action_id is the workflow action UUID (required). | |
| comment | string | comment is optional escalation comment. |
EscalateActionResponse
EscalateActionResponse contains the escalated action result.
| Field | Type | Label | Description |
|---|---|---|---|
| action | WorkflowAction | action is the updated workflow action. |
GetNodeIORequest
GetNodeIORequest retrieves the input/output payloads of one workflow_action.
| Field | Type | Label | Description |
|---|---|---|---|
| run_id | string | run_id is the parent run UUID (required, used for tenant scoping). | |
| node_id | string | node_id is the workflow_action UUID (required). |
GetNodeIOResponse
GetNodeIOResponse returns the redacted input + output of a node.
| Field | Type | Label | Description |
|---|---|---|---|
| input | google.protobuf.Struct | input is the action's input JSON, with sensitive top-level keys stripped per the caller's clearance. | |
| output | google.protobuf.Struct | output is the action's output JSON, with sensitive top-level keys stripped per the caller's clearance. Empty if the node has not produced output yet (queued / running / blocked). | |
| redacted_fields | string | repeated | redacted_fields lists every top-level key removed from input or output. Empty when no redaction occurred (caller has full clearance). |
GetNodeTranscriptRequest
GetNodeTranscriptRequest retrieves the agent turn transcript of one node.
| Field | Type | Label | Description |
|---|---|---|---|
| run_id | string | run_id is the parent run UUID (required, used for run + tenant scoping). | |
| node_id | string | node_id identifies the node whose transcript is requested (required). It accepts EITHER form (#2021): - the workflow-DAG node id, i.e. the step_name ("architect", "fetch_issue") — this is what the run-view client sends; OR - the workflow_action UUID (back-compat). Both map to the node's step_name, which keys the captured session_events. An unknown or foreign node_id yields an empty transcript (session_missing), never an error. |
GetNodeTranscriptResponse
GetNodeTranscriptResponse returns the ordered, redacted transcript events of a node across ALL of its attempts.
| Field | Type | Label | Description |
|---|---|---|---|
| events | TranscriptEvent | repeated | events are ordered by (session_id, seq, created_at). Multiple attempts interleave by session_id — each event carries its own session_id. |
| redacted_fields | string | repeated | redacted_fields lists every content field removed across all events (stable, deduplicated). Empty when no redaction occurred (caller has full clearance). |
| session_missing | bool | session_missing is true when the node has no captured transcript — a historical / non-agent node, or one whose capture was lost (best-effort). The client renders an empty state rather than an error. |
GetRunPromptRequest
GetRunPromptRequest fetches one mid-run user_input prompt's detail. LBE-B2 (#2249).
| Field | Type | Label | Description |
|---|---|---|---|
| prompt_id | string | prompt_id is the governance_approvals row id the run parked on — the node's blocked_by / approval_id uuid (required). RLS-scoped to the caller's org. |
GetRunPromptResponse
GetRunPromptResponse is the projected spec of a user_input prompt, plus the caller-specific can_answer verdict. LBE-B2 (#2249).
| Field | Type | Label | Description |
|---|---|---|---|
| prompt_id | string | prompt_id echoes the requested row id. | |
| question | string | question is the human-facing prompt headline (from prompt_spec.question). | |
| options | RunPromptOption | repeated | options are the structured choices the operator may pick from. Empty for a free-text-only prompt. |
| allow_text | bool | allow_text permits a free-text answer in addition to (or instead of) a choice. | |
| text_placeholder | string | text_placeholder is the display hint for the free-text field (display-only). | |
| default_choice | string | default_choice is the option value recorded on an on_timeout="default" expiry (surfaced so the UI can pre-select it). Empty when the prompt has no default. | |
| required_clearance | int32 | required_clearance is the clearance floor a caller must clear to answer (0 when the prompt carries no floor — anyone with view access may answer). | |
| expires_at | google.protobuf.Timestamp | expires_at is when the prompt window closes (the on_timeout policy then fires). | |
| status | string | status is the prompt lifecycle: "pending" (awaiting an answer), "answered" (resolved with an answer), or "closed" (denied / expired without an answer). | |
| can_answer | bool | can_answer reports whether THIS caller may answer the prompt right now, computed server-side from the required_clearance floor AND the answerable_by scope against the authenticated identity. False on an already-resolved prompt. | |
| answered_by | string | answered_by is the member id (or timeout sentinel) that recorded the answer, set only when status="answered". Empty otherwise. | |
| answered_choice | string | answered_choice is the recorded answer's choice value when status="answered" (so a re-opened card renders "answered: <choice>"). Empty on a pending prompt. | |
| answered_text | string | answered_text is the recorded answer's free text when status="answered". Empty on a pending prompt or a choice-only answer. | |
| asked_by_step | string | asked_by_step is the agent_action step whose agent posed this question (LBE-B3, #2292): either the DSL-declared asked_by or the last-completed agent step on the run's executed path. Empty on a pre-B3 prompt or a prompt reached before any agent turn. Recovered from the prompt row metadata (asked_by_step). | |
| asked_by_role | string | asked_by_role is the asking step's agent_role — the human-legible identity of the agent asking (e.g. "eng"). Recovered from row metadata (asked_by_role). | |
| asked_by_agent_id | string | asked_by_agent_id is the CONCRETE deployed-agent uuid the asking step resolved its role to (workflow_actions.resolved_agent_id), so the card can deep-link to the exact agent. Empty when the step never ran a turn or on a legacy row. |
GetToolCatalogRequest
GetToolCatalogRequest requests the tool_action whitelist. It carries no parameters: the whitelist is a platform-wide constant, not tenant-scoped. #2067.
GetToolCatalogResponse
GetToolCatalogResponse returns the whitelisted tools a tool_action step may invoke, each with its whitelisted actions and their argument names. #2067.
| Field | Type | Label | Description |
|---|---|---|---|
| tools | ToolCatalogEntry | repeated | tools is the set of whitelisted governed tools (today: github only). |
GetWorkflowRequest
GetWorkflowRequest retrieves a workflow by ID.
| Field | Type | Label | Description |
|---|---|---|---|
| workflow_id | string | workflow_id is the workflow UUID (required). |
GetWorkflowResponse
GetWorkflowResponse contains the requested workflow.
| Field | Type | Label | Description |
|---|---|---|---|
| workflow | Workflow | workflow is the requested workflow. |
GetWorkflowStatusRequest
GetWorkflowStatusRequest retrieves workflow run status.
| Field | Type | Label | Description |
|---|---|---|---|
| run_id | string | run_id is the workflow run UUID (required). |
GetWorkflowStatusResponse
GetWorkflowStatusResponse contains workflow run status and actions.
| Field | Type | Label | Description |
|---|---|---|---|
| run | WorkflowRun | run is the workflow run with status. | |
| actions | WorkflowAction | repeated | actions are the workflow actions for this run. |
ListPendingApprovalsRequest
ListPendingApprovalsRequest lists actions awaiting approval.
| Field | Type | Label | Description |
|---|---|---|---|
| agent_id | string | agent_id optionally filters by assigned agent. | |
| limit | int32 | limit controls pagination (default: 50, max: 100). | |
| offset | int32 | offset controls pagination. |
ListPendingApprovalsResponse
ListPendingApprovalsResponse contains pending approvals with context.
| Field | Type | Label | Description |
|---|---|---|---|
| approvals | PendingApproval | repeated | approvals are the pending approval entries. |
| total_count | int32 | total_count is the total number of pending approvals (for pagination). |
ListRunsRequest
ListRunsRequest pages through past runs of one workflow.
| Field | Type | Label | Description |
|---|---|---|---|
| workflow_id | string | workflow_id is the workflow whose runs to list (required). | |
| page_size | int32 | page_size caps the rows in the response. Default 20, max 100. Values outside (0, 100] are clamped silently. | |
| page_token | string | page_token is the opaque cursor returned by a previous ListRuns call's next_page_token. Empty starts from the newest run. | |
| status_filter | RunStatusFilter | status_filter narrows the result set; see RunStatusFilter. |
ListRunsResponse
ListRunsResponse returns one page of WorkflowRun rows.
| Field | Type | Label | Description |
|---|---|---|---|
| runs | WorkflowRun | repeated | runs is the page of WorkflowRun rows ordered (started_at DESC, id DESC). nodes_total / nodes_done are populated; cost_usd reflects the engine's rolled-up cost (zero until #1127 lands the cost-meter wiring). |
| next_page_token | string | next_page_token is the cursor to pass to the next ListRuns call. Empty means no more pages. |
ListVisibleRunsRequest
ListVisibleRunsRequest lists the caller's visible runs (QWP-3, #2359). Unlike ListRunsRequest there is NO workflow_id: the visibility predicate (team membership OR private ownership, both keyed off app.member_id) is the scoping.
| Field | Type | Label | Description |
|---|---|---|---|
| page_size | int32 | page_size caps the rows in the response. Default 20, max 100. Values outside (0, 100] are clamped silently. | |
| page_token | string | page_token is the opaque cursor returned by a previous ListVisibleRuns call's next_page_token. Empty starts from the newest run. Same keyset shape as ListRuns (started_at DESC, id DESC). | |
| status_filter | RunStatusFilter | status_filter narrows the result set; see RunStatusFilter. The panel defaults to RUN_STATUS_FILTER_ACTIVE (pending |
ListVisibleRunsResponse
ListVisibleRunsResponse is a page of VisibleRunRow ordered (started_at DESC, id DESC), plus the cursor for the next page.
| Field | Type | Label | Description |
|---|---|---|---|
| runs | VisibleRunRow | repeated | runs is the page of VisibleRunRow rows the caller may see. |
| next_page_token | string | next_page_token is the cursor to pass to the next ListVisibleRuns call. Empty means no more pages. |
ListWorkflowsRequest
ListWorkflowsRequest lists workflows with optional filtering.
| Field | Type | Label | Description |
|---|---|---|---|
| team_id | string | team_id optionally filters by team. | |
| status | string | status optionally filters by workflow status. | |
| limit | int32 | limit controls pagination (default: 50, max: 100). | |
| offset | int32 | offset controls pagination. |
ListWorkflowsResponse
ListWorkflowsResponse contains the workflow list.
| Field | Type | Label | Description |
|---|---|---|---|
| workflows | Workflow | repeated | workflows is the list of workflows. |
| total_count | int32 | total_count is the total number of workflows (for pagination). |
NodeIOFrame
NodeIOFrame is one live LLM-I/O frame for an agent node, mirroring the wfio wire contract the orchestrator publisher tees (LBE-A1). Exactly one event arm is set.
| Field | Type | Label | Description |
|---|---|---|---|
| node_id | string | node_id is the step name the frame belongs to. | |
| session_id | string | session_id is the attempt id (groups redos/laps; mirrors TranscriptEvent). | |
| seq | int64 | seq is the publisher's monotonic sequence within (run, node, session). A jump in seq means the publisher dropped frames — the client should expect a gap. | |
| iteration | int32 | iteration is the loop lap (mirrors NodeStatusUpdate.iteration); 0 outside a loop. | |
| token_text | string | token_text is a coalesced, security-filtered batch of LLM tokens. Suppressed (unset) for callers below clearance 4 (structural-frames-only floor). | |
| tool_call | NodeToolCall | tool_call is a tool invocation surfaced live. | |
| tool_result | NodeToolResult | tool_result is a completed tool result surfaced live. | |
| status | string | status is an agent status transition (thinking | |
| user_prompt | UserPromptFrame | user_prompt is a mid-run elicitation prompt (Capability B / LBE-B). Structural, delivered at every clearance so the "answer needed" card can render. |
NodeStatusUpdate
NodeStatusUpdate is a single transition or counter update for one node (workflow_action) inside the subscribed run. The frontend live-DAG keys updates by (run_id, node_id) and replaces the previous projection.
| Field | Type | Label | Description |
|---|---|---|---|
| run_id | string | run_id is the parent run UUID. | |
| node_id | string | node_id is the workflow_action UUID. | |
| status | string | status is one of: queued | |
| elapsed_ms | int64 | elapsed_ms is wall-clock time spent in running so far for this node. For terminal nodes this is the final duration; for blocked nodes this is the time since the gate was hit. | |
| cost_usd | double | cost_usd is the running cost rolled up to this node (sum of LLM / tool costs charged to this action). Convention matches analytics.proto: USD as double. | |
| blocked_by | string | blocked_by is the approval UUID this node is waiting on when status=blocked. Empty otherwise. The frontend uses this to deep-link to ApprovalService.GetApproval. | |
| at | google.protobuf.Timestamp | at is the timestamp of the underlying transition. | |
| iteration | int32 | iteration is the loop-lap ordinal for this node (WR2-2, migration 176): 0 for every non-loop step, and the 1-based lap number for a step executed inside a bounded loop's body. The live-DAG uses it to group a loop body step's repeated executions into ordered "round 1 / round 2 / ..." rows instead of collapsing them onto a single node. Additive and back-compatible: an existing consumer that ignores the field sees today's behaviour (a no-loop run streams iteration 0 everywhere). Client rendering of rounds is a separate upsquad-client task. |
NodeToolCall
NodeToolCall is the live tool-invocation payload of a NodeIOFrame.
| Field | Type | Label | Description |
|---|---|---|---|
| tool_name | string | tool_name is the invoked tool. | |
| tool_call_id | string | tool_call_id correlates the call with its later NodeToolResult. | |
| args_summary | string | args_summary is a compact, clamped, redaction-safe rendering of the arguments (structural chrome; the durable + un-clamped truth is the transcript). |
NodeToolResult
NodeToolResult is the live tool-result payload of a NodeIOFrame.
| Field | Type | Label | Description |
|---|---|---|---|
| tool_call_id | string | tool_call_id correlates the result with its NodeToolCall. | |
| tool_name | string | tool_name is the tool that produced the result. | |
| summary | string | summary is a compact, clamped rendering of the result. Suppressed (empty) for callers below clearance 4 — the same free-text floor as token_text. | |
| is_error | bool | is_error is true when the tool returned an error. | |
| duration_ms | int32 | duration_ms is the tool call's wall-clock duration. |
PauseRunRequest
PauseRunRequest pauses a workflow run at the next checkpoint.
| Field | Type | Label | Description |
|---|---|---|---|
| run_id | string | run_id is the workflow run UUID to pause (required). | |
| reason | string | reason is an optional human-readable note recorded in the audit log. |
PauseRunResponse
PauseRunResponse returns the post-pause run state.
| Field | Type | Label | Description |
|---|---|---|---|
| run | WorkflowRun | run is the workflow run with status='paused' (or unchanged if the run was already paused / terminal - idempotent). |
PendingApproval
PendingApproval represents an approval awaiting decision with context.
| Field | Type | Label | Description |
|---|---|---|---|
| action | WorkflowAction | action is the workflow action awaiting approval. | |
| workflow_name | string | workflow_name is the parent workflow name. | |
| agent_name | string | agent_name is the assigned agent name (if available). |
RejectActionRequest
RejectActionRequest rejects a workflow action.
| Field | Type | Label | Description |
|---|---|---|---|
| action_id | string | action_id is the workflow action UUID (required). | |
| comment | string | comment is optional rejection comment. |
RejectActionResponse
RejectActionResponse contains the rejected action result.
| Field | Type | Label | Description |
|---|---|---|---|
| action | WorkflowAction | action is the updated workflow action. |
ResumeRunRequest
ResumeRunRequest lifts a pause on a workflow run. WF-16 (#1819).
| Field | Type | Label | Description |
|---|---|---|---|
| run_id | string | run_id is the workflow run UUID to resume (required). | |
| reason | string | reason is an optional human-readable note recorded in the audit log. |
ResumeRunResponse
ResumeRunResponse returns the run state after the resume signal is sent. WF-16 (#1819).
| Field | Type | Label | Description |
|---|---|---|---|
| run | WorkflowRun | run is the workflow run. Resume is honored asynchronously by the interpreter at the next step boundary, so the returned status reflects the run's current projection (e.g. still 'paused' until the interpreter observes the signal and re-projects 'running'). |
RunLogLine
RunLogLine is a single log entry emitted during workflow execution.
| Field | Type | Label | Description |
|---|---|---|---|
| run_id | string | run_id is the parent run UUID. | |
| node_id | string | node_id is the workflow_action UUID the log line belongs to. Empty for run-level events (run started, run cancelled, run paused). | |
| level | string | level is one of INFO | |
| message | string | message is the human-readable log line. | |
| t | google.protobuf.Timestamp | t is the timestamp of the log emission. |
RunPromptOption
RunPromptOption is one structured choice in a user_input prompt. value is the
machine token that lands in the answer's choice; label/description are
display-only. Mirrors the DSL PromptOption (LBE-B1). LBE-B2 (#2249).
| Field | Type | Label | Description |
|---|---|---|---|
| value | string | value is the machine token recorded as the answer's choice (required). | |
| label | string | label is the display text for the option (optional). | |
| description | string | description is a longer display hint for the option (optional). |
SetWorkflowEnabledRequest
SetWorkflowEnabledRequest configures a workflow's trigger + the enabled gate. WF-24 (#1888), the W4 schedule pre-wiring.
| Field | Type | Label | Description |
|---|---|---|---|
| workflow_id | string | workflow_id is the workflow UUID (required). | |
| enabled | bool | enabled is the schedule-firing gate (W4). false leaves the workflow manual-only / dormant. | |
| trigger_type | string | trigger_type is "manual" (default when empty) or "schedule". | |
| trigger_config | google.protobuf.Struct | trigger_config is the schedule cron/interval spec (unset for manual). |
SetWorkflowEnabledResponse
SetWorkflowEnabledResponse returns the workflow with its new trigger config and enabled state.
| Field | Type | Label | Description |
|---|---|---|---|
| workflow | Workflow | workflow is the updated workflow. |
StreamGap
StreamGap signals that one or more NodeIOFrames were dropped between the last delivered seq and the next one, so the client can render a "stream resynced" marker and optionally refetch the snapshot. from_seq is the last seq the client saw before the gap; to_seq is the next seq it will see.
| Field | Type | Label | Description |
|---|---|---|---|
| node_id | string | node_id is the step whose frames were dropped (empty when not node-specific). | |
| from_seq | int64 | from_seq is the last seq delivered before the gap (0 when unknown). | |
| to_seq | int64 | to_seq is the first seq that will be delivered after the gap. |
StreamNodeIORequest
StreamNodeIORequest subscribes to a run's live agent-step LLM I/O.
| Field | Type | Label | Description |
|---|---|---|---|
| run_id | string | run_id is the workflow run UUID to stream (required). | |
| node_id | string | node_id optionally scopes the stream to a single agent node (step name). Empty means every agent node in the run. The snapshot honours this filter too. |
StreamNodeIOResponse
StreamNodeIOResponse is one frame of the node-I/O stream. Exactly one of the oneof arms is set on any given message; snapshot_done marks the transition from the replayed transcript snapshot to live frames.
| Field | Type | Label | Description |
|---|---|---|---|
| snapshot_event | TranscriptEvent | snapshot_event replays one already-captured transcript event (the verbatim GetNodeTranscript shape, clearance-redacted). Sent before snapshot_done. | |
| live | NodeIOFrame | live is a live frame coalesced from the running agent step (after snapshot_done). token_text is present only for clearance >= 4. | |
| gap | StreamGap | gap signals that frames were dropped (either upstream at the publisher — a jump in seq — or in this hub for a slow subscriber). The client may refetch the snapshot to resync. | |
| snapshot_done | bool | snapshot_done is true on the single marker frame that ends the snapshot replay and precedes the first live frame. All other fields are unset on the marker. |
StreamRunLogsRequest
StreamRunLogsRequest subscribes to live log lines for one run.
| Field | Type | Label | Description |
|---|---|---|---|
| run_id | string | run_id is the workflow run UUID to subscribe to (required). |
StreamRunLogsResponse
StreamRunLogsResponse wraps a single RunLogLine; same rationale as StreamRunStatusResponse - room for heartbeats / metadata without breaking the stream contract, and satisfies RPC_RESPONSE_STANDARD_NAME.
| Field | Type | Label | Description |
|---|---|---|---|
| line | RunLogLine | line is the log entry. |
StreamRunStatusRequest
StreamRunStatusRequest subscribes to live status transitions for one run.
| Field | Type | Label | Description |
|---|---|---|---|
| run_id | string | run_id is the workflow run UUID to subscribe to (required). |
StreamRunStatusResponse
StreamRunStatusResponse wraps a single NodeStatusUpdate so the stream envelope can grow new fields (e.g. heartbeat, run-level rollup) without a breaking proto change. The wrapper is also what buf v2 lint expects for streaming RPC response naming (RPC_RESPONSE_STANDARD_NAME).
| Field | Type | Label | Description |
|---|---|---|---|
| update | NodeStatusUpdate | update is the per-node status transition. |
StreamVisibleRunsRequest
StreamVisibleRunsRequest opens the caller-scoped live run stream (QWP-3,
#2359). It carries no run/workflow id: the caller's allow-set (team units +
owned private workflows) is materialized server-side from app.member_id at
subscribe. status_filter selects which runs the snapshot replays and which
live transitions surface as "in the active set" (a run leaving the filtered
set emits a leaving event so the panel drops it).
| Field | Type | Label | Description |
|---|---|---|---|
| status_filter | RunStatusFilter | status_filter narrows the snapshot + the live active set; see RunStatusFilter. The panel defaults to RUN_STATUS_FILTER_ACTIVE. |
ToolCatalogAction
ToolCatalogAction is one whitelisted operation on a tool, with the argument
names a tool_action step supplies under with. #2067.
| Field | Type | Label | Description |
|---|---|---|---|
| action | string | action is the operation name (e.g. "get_issue"). | |
| description | string | description is a short human-readable label for the action. | |
| args | ToolCatalogArg | repeated | args are the arguments the action accepts under the step's with block. |
ToolCatalogArg
ToolCatalogArg describes one argument a tool_action with block may set. #2067.
| Field | Type | Label | Description |
|---|---|---|---|
| name | string | name is the with key (e.g. "repo", "number", "body"). | |
| type | string | type is the JSON value type: one of string | |
| required | bool | required reports whether the action requires this argument. | |
| description | string | description is a short human-readable label for the argument. | |
| pinned_literal | bool | pinned_literal marks a SECURITY-PINNED argument whose with value MUST be an author-committed literal string, never a ${...} runtime template (the WF-30 repo pin). The create-time validator enforces this generically off the SAME catalog flag, so a future pinned arg needs only the flag set — and the client step composer drives its literal-only block from this field instead of inferring it by arg name (#2084). Default false = templatable (today's behaviour for every non-repo arg). #2084. |
ToolCatalogEntry
ToolCatalogEntry is one whitelisted governed tool and the actions it exposes to a tool_action step. #2067.
| Field | Type | Label | Description |
|---|---|---|---|
| tool | string | tool is the governed tool namespace (e.g. "github"). | |
| description | string | description is a short human-readable label for the tool. | |
| actions | ToolCatalogAction | repeated | actions are the whitelisted operations this tool exposes. |
TranscriptEvent
TranscriptEvent is one captured turn event (assistant message, tool call, or tool result), redacted per the caller's clearance.
| Field | Type | Label | Description |
|---|---|---|---|
| session_id | string | session_id is the agent session this event belongs to. A node re-run by an acceptance redo produces several sessions (attempts) for the same node; this field is preserved so the client can group events by attempt. | |
| seq | int32 | seq is the monotonic capture order WITHIN a session_id (stable render order for one attempt). It is NOT globally unique across attempts — group by session_id first, then order by seq. | |
| event_type | string | event_type is one of: assistant_message | |
| content | google.protobuf.Struct | content is the event payload, with sensitive fields redacted per the caller's clearance (same discipline as GetNodeIO): assistant_message: { text } tool_call: { tool_name, tool_call_id, arguments } tool_result: { tool_call_id, tool_name, result, is_error, duration_ms } | |
| created_at | google.protobuf.Timestamp | created_at is when the event was captured. |
TriggerWorkflowRequest
TriggerWorkflowRequest starts workflow execution.
| Field | Type | Label | Description |
|---|---|---|---|
| workflow_id | string | workflow_id is the workflow UUID to execute (required). | |
| context | google.protobuf.Struct | context is optional execution context data. |
TriggerWorkflowResponse
TriggerWorkflowResponse contains the started workflow run.
| Field | Type | Label | Description |
|---|---|---|---|
| run | WorkflowRun | run is the created workflow run. |
UpdateWorkflowDefinitionRequest
UpdateWorkflowDefinitionRequest edits a workflow's stored definition. #2002.
| Field | Type | Label | Description |
|---|---|---|---|
| workflow_id | string | workflow_id is the workflow UUID (required). | |
| definition | google.protobuf.Struct | definition is the new workflow definition JSON (required, validated against the same schema + semantic fences as CreateWorkflow). |
UpdateWorkflowDefinitionResponse
UpdateWorkflowDefinitionResponse returns the workflow with its edited definition and bumped updated_at. #2002.
| Field | Type | Label | Description |
|---|---|---|---|
| workflow | Workflow | workflow is the updated workflow. |
UpdateWorkflowStatusRequest
UpdateWorkflowStatusRequest transitions a workflow's lifecycle status. WF-24 (#1888).
| Field | Type | Label | Description |
|---|---|---|---|
| workflow_id | string | workflow_id is the workflow UUID (required). | |
| status | string | status is the target lifecycle status: draft |
UpdateWorkflowStatusResponse
UpdateWorkflowStatusResponse returns the workflow with its new status.
| Field | Type | Label | Description |
|---|---|---|---|
| workflow | Workflow | workflow is the updated workflow. |
UserPromptFrame
UserPromptFrame is the live mid-run elicitation payload of a NodeIOFrame. It is structural (no free text is redacted) so the "answer needed" prompt surfaces at every clearance; answering flows through the (LBE-B) AnswerRunPrompt RPC keyed on prompt_id. The full question/options detail is fetched via GetRunPrompt.
| Field | Type | Label | Description |
|---|---|---|---|
| prompt_id | string | prompt_id is the governance_approvals row id the answer is recorded against (== the node's blocked_by uuid). The client fetches detail via GetRunPrompt. | |
| question | string | question is the prompt headline (already model-authored, not free-text tool output — surfaced at every clearance so the card is legible). | |
| asked_by_step | string | asked_by_step is the agent_action step whose agent is posing the question (LBE-B3, #2292): either the DSL-declared asked_by or the last-completed agent step on the run's executed path. Empty on a pre-B3 run or a run with no agent turn yet. Structural — surfaced at every clearance so the card renders which agent is asking without a GetRunPrompt lookup. | |
| asked_by_role | string | asked_by_role is that step's agent_role — the human-legible identity of the asking agent (e.g. "eng", "reviewer"). Empty when unattributed. |
VisibleRunEvent
VisibleRunEvent is one caller-scoped run delta (QWP-3, #2359). It is STRUCTURAL ONLY — every field is chrome the panel renders a row from, at EVERY clearance; NO free text / token content rides this event (that stays gated on StreamNodeIO). A snapshot event and a live event share this shape; is_snapshot distinguishes replay from live, and snapshot_done marks the boundary.
| Field | Type | Label | Description |
|---|---|---|---|
| run_id | string | run_id is the workflow run this event describes. | |
| workflow_id | string | workflow_id is the run's parent workflow. | |
| workflow_name | string | workflow_name is the parent workflow's display name (structural label). | |
| status | string | status is the public run status: pending | |
| nodes_total | int32 | nodes_total is the run's total action count (populated on snapshot rows; a live status delta derived from the StatusHub tee leaves it 0 and the client keeps its last-known count — same zero-default convention as WorkflowRun.nodes_total). | |
| nodes_done | int32 | nodes_done is the count of actions in a terminal status. Same populate-on- snapshot / zero-on-live-delta convention as nodes_total. | |
| needs_input | bool | needs_input is true when the run is parked on a human user_input prompt (a governance_approvals row with action_type='user_prompt' open on the run). Derived from the StatusHub org tee, NOT from a per-run wfio subscription. | |
| needs_approval | bool | needs_approval is true when the run is parked on an approval/question gate (a governance_approvals row with action_type='approval_gate' | |
| asked_by_role | string | asked_by_role is the human-legible role of the agent posing the prompt/gate (e.g. "eng", "reviewer"), when known. Structural attribution — surfaces at every clearance. Empty when unattributed. | |
| prompt_id | string | prompt_id is the governance_approvals row id the panel deep-links to for the prompt/approval detail (GetRunPrompt / RecordDecision). Empty when the run is not currently parked on a prompt or gate. | |
| leaving | bool | leaving is true when this run has LEFT the caller's filtered active set (it reached a terminal status, or a status filter no longer matches). The panel drops the row. Mutually exclusive with a live status update on the same run. | |
| is_snapshot | bool | is_snapshot is true on the replay events emitted before snapshot_done; false on every live delta after it. | |
| snapshot_done | bool | snapshot_done is true on the single marker event that ends the snapshot replay and precedes the first live delta. All other fields are unset on the marker. |
VisibleRunRow
VisibleRunRow is the panel's list-row shape: the run plus the parent workflow's display name so the panel labels the row without a second fetch (QWP-3, #2359). The run body reuses the frozen WorkflowRun contract (nodes_total/nodes_done are populated, exactly as ListRuns); workflow_name is carried alongside because WorkflowRun itself does not hold the workflow's name.
| Field | Type | Label | Description |
|---|---|---|---|
| run | WorkflowRun | run is the WorkflowRun (id, workflow_id, status, node counts, timestamps). | |
| workflow_name | string | workflow_name is the parent workflow's display name (workflows.name). |
Workflow
Workflow represents a workflow definition.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | id is the workflow UUID. | |
| org_id | string | org_id is the organization UUID. | |
| team_id | string | team_id is the optional team UUID. | |
| name | string | name is the workflow name. | |
| description | string | description is the workflow description. | |
| definition | google.protobuf.Struct | definition is the workflow definition JSON. | |
| created_by | string | created_by is the creator UUID. | |
| status | string | status is the workflow status (draft, active, archived). | |
| created_at | google.protobuf.Timestamp | created_at is the creation timestamp. | |
| deleted_at | google.protobuf.Timestamp | deleted_at is the optional deletion timestamp. | |
| trigger_type | string | trigger_type is how the workflow is triggered: "manual" (default) or "schedule". Backed by workflows.trigger_type (migration 151, WF-23 #1887). | |
| enabled | bool | enabled gates schedule firing (W4). Always false for a manual workflow, and false for any workflow until an operator opts in via SetWorkflowEnabled. Backed by workflows.enabled (migration 151); a behavioral no-op until W4. | |
| last_run_status | string | last_run_status is the status of this workflow's most recent run ("" when it has never run). ROLLUP field — WF-25 computes it; this RPC (WF-24 #1888) lays the field and returns the zero value. | |
| last_run_at | google.protobuf.Timestamp | last_run_at is when this workflow's most recent run started. ROLLUP — WF-25 computes it; unset here. | |
| run_count | int32 | run_count is the total number of runs this workflow has had. ROLLUP — WF-25 computes it; 0 here. | |
| avg_cost_usd | double | avg_cost_usd is the mean rolled-up cost across this workflow's runs. ROLLUP — WF-25 computes it; 0 here. USD as double (analytics.proto convention). | |
| updated_at | google.protobuf.Timestamp | updated_at is the last-modification timestamp, maintained by a BEFORE UPDATE trigger (migration 166) that stamps now() on every UPDATE — definition edits (UpdateWorkflowDefinition, #2002), status changes, enable toggles, and the soft-delete write. A client compares it against a cached value to detect staleness. Backfilled to created_at for pre-migration rows so an untouched workflow never looks freshly modified. | |
| visibility | string | visibility is the workflow's audience, backed by workflows.visibility (migration 187): "team" (default — visible to its team) |
WorkflowAction
WorkflowAction represents an individual step within a workflow run.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | id is the action UUID. | |
| run_id | string | run_id is the parent run UUID. | |
| org_id | string | org_id is the organization UUID. | |
| step_name | string | step_name is the workflow step name. | |
| agent_id | string | agent_id is the assigned agent UUID. | |
| status | string | status is the action status (pending, in_progress, awaiting_approval, approved, rejected, escalated, completed, failed). | |
| input | google.protobuf.Struct | input is the step input data. | |
| output | google.protobuf.Struct | output is the step output data. | |
| decided_by | string | decided_by is the decision maker UUID. | |
| decided_at | google.protobuf.Timestamp | decided_at is the decision timestamp. | |
| created_at | google.protobuf.Timestamp | created_at is the creation timestamp. | |
| approval_id | string | approval_id is the governance_approvals row this step is waiting on, populated when the engine creates an approval as part of an approval_gate / escalated step. Empty otherwise. The frontend uses this to deep-link the live-DAG inspector to /approvals/<id> (#1109). | |
| cost_usd | double | cost_usd is the rolled-up cost charged to this action (LLM tokens + tool execution). Convention matches analytics.proto: USD as double (#1111). Populated by engine on transition; see #1127 (engine cost-meter wiring is the follow-up that actually writes non-zero values). Until #1127 lands this field is always 0. | |
| elapsed_ms | int64 | elapsed_ms is the wall-clock time spent in in_progress for this action. For terminal actions this is the final duration. Engine updates on every transition (#1111). |
WorkflowRun
WorkflowRun represents a workflow execution instance.
| Field | Type | Label | Description |
|---|---|---|---|
| id | string | id is the run UUID. | |
| workflow_id | string | workflow_id is the parent workflow UUID. | |
| org_id | string | org_id is the organization UUID. | |
| triggered_by | string | triggered_by is the trigger user UUID. | |
| status | string | status is the run status: pending | |
| context | google.protobuf.Struct | context is the execution context data. | |
| started_at | google.protobuf.Timestamp | started_at is the execution start timestamp. | |
| completed_at | google.protobuf.Timestamp | completed_at is the execution completion timestamp. | |
| created_at | google.protobuf.Timestamp | created_at is the creation timestamp. | |
| cost_usd | double | cost_usd is the rolled-up cost across every action in this run. Populated by engine on transition; see #1127 (engine cost-meter wiring is the follow-up that actually writes non-zero values). Until #1127 lands this field is always 0. | |
| paused_at | google.protobuf.Timestamp | paused_at is set when the run was last paused via PauseRun (#1110); cleared on resume. | |
| cancelled_at | google.protobuf.Timestamp | cancelled_at is set when the run was cancelled via CancelRun (#1110). | |
| nodes_total | int32 | nodes_total is the count of workflow_actions rows for this run. Populated by ListRuns (#1074); other RPCs leave it at 0 since they surface action lists directly. Convention: -1 is never used; an unpopulated total is 0 and the consumer falls back to the actions array length when present. | |
| nodes_done | int32 | nodes_done is the count of workflow_actions in a terminal status ('approved' |
RunStatusFilter
RunStatusFilter selects which subset of WorkflowRun rows ListRuns returns. Mirrors the four buckets the redesigned Workflows page renders in its segmented control. UNSPECIFIED maps to ALL (no filtering) so an unset filter on the wire returns the most-permissive response, matching proto3 default-zero ergonomics.
GAP-31 (#1074).
| Name | Number | Description |
|---|---|---|
| RUN_STATUS_FILTER_UNSPECIFIED | 0 | RUN_STATUS_FILTER_UNSPECIFIED returns runs in any status (== ALL). |
| RUN_STATUS_FILTER_ACTIVE | 1 | RUN_STATUS_FILTER_ACTIVE returns runs where status IN ('pending', 'running', 'paused'). |
| RUN_STATUS_FILTER_COMPLETED | 2 | RUN_STATUS_FILTER_COMPLETED returns runs where status = 'completed'. |
| RUN_STATUS_FILTER_FAILED | 3 | RUN_STATUS_FILTER_FAILED returns runs where status IN ('failed', 'cancelled'). Cancelled is grouped with failed because the dashboard surfaces both as "did not complete successfully". |
| RUN_STATUS_FILTER_ALL | 4 | RUN_STATUS_FILTER_ALL returns runs in any status. Semantically the same as UNSPECIFIED; provided so callers can be explicit. |
WorkflowService
WorkflowService provides lifecycle management for workflows, execution triggering, and approval chain operations.
2026-04-26 redesign extension (tracker #1031): adds streaming RPCs for live-DAG rendering (StreamRunStatus #1046, StreamRunLogs #1107), clearance-gated step I/O (GetNodeIO #1108), an approval_id deep-link on WorkflowAction (#1109), idempotent run-control RPCs (PauseRun + CancelRun #1110), and per-action / per-run cost tracking fields (#1111).
| Method Name | Request Type | Response Type | Description |
|---|---|---|---|
| CreateWorkflow | CreateWorkflowRequest | CreateWorkflowResponse | CreateWorkflow creates a new workflow with definition validation. |
| GetWorkflow | GetWorkflowRequest | GetWorkflowResponse | GetWorkflow retrieves a workflow by ID. |
| ListWorkflows | ListWorkflowsRequest | ListWorkflowsResponse | ListWorkflows lists workflows for an organization with filtering. |
| TriggerWorkflow | TriggerWorkflowRequest | TriggerWorkflowResponse | TriggerWorkflow starts execution of a workflow. |
| GetWorkflowStatus | GetWorkflowStatusRequest | GetWorkflowStatusResponse | GetWorkflowStatus retrieves the current status of a workflow run. |
| ApproveAction | ApproveActionRequest | ApproveActionResponse | ApproveAction approves a workflow action awaiting approval. |
| RejectAction | RejectActionRequest | RejectActionResponse | RejectAction rejects a workflow action awaiting approval. |
| EscalateAction | EscalateActionRequest | EscalateActionResponse | EscalateAction escalates a workflow action to higher clearance. |
| ListPendingApprovals | ListPendingApprovalsRequest | ListPendingApprovalsResponse | ListPendingApprovals lists workflow actions awaiting approval. |
| StreamRunStatus | StreamRunStatusRequest | StreamRunStatusResponse stream | StreamRunStatus opens a server-streaming RPC that emits a NodeStatusUpdate every time a workflow_action row in the given run transitions (queued -> running -> done |
Tenant isolation: the stream's org_id is taken from the JWT-derived ambient scope. Events for any other tenant are dropped before reaching the wire. The Hub also validates the requested run_id belongs to the caller's tenant on subscribe.
GAP-29 (#1046). |
| StreamRunLogs | StreamRunLogsRequest | StreamRunLogsResponse stream | StreamRunLogs opens a server-streaming RPC that emits RunLogLine messages as workflow execution writes log entries. Backed by the Postgres LISTEN/NOTIFY channel workflow_run_logs (migration 086).
GAP-30 (#1107). | | GetNodeIO | GetNodeIORequest | GetNodeIOResponse | GetNodeIO returns the input and output JSON of a single workflow_action, with sensitive fields redacted based on the caller's clearance level (compared against the workflow's data_classification - workflow_actions is registered as Operational+High in classregistry).
The redacted_fields list names every top-level key that was stripped from input or output so the UI can render a "redacted" placeholder rather than silently lying about completeness.
GAP-32 (#1108). | | GetNodeTranscript | GetNodeTranscriptRequest | GetNodeTranscriptResponse | GetNodeTranscript returns the agent's TURN TRANSCRIPT for one node — the intermediate reasoning/tool trail (assistant messages, tool calls, tool results) that FoldStream discards after keeping only the final output. Reads the session_events captured at fold time (migration 160, #1969 PR-1) by (run_id, step_name), org-scoped and run-scoped fail-closed exactly like GetNodeIO. Content is clearance-redacted on READ via the same RedactStruct discipline (raw at rest, redact at the boundary).
A node re-executed by an acceptance redo (#1966) has MULTIPLE agent sessions (attempts) for the same (run_id, step_name); this RPC returns ALL of their events with each event's session_id preserved, so the client can group the transcript by attempt without losing history.
#1969 PR-3. | | StreamNodeIO | StreamNodeIORequest | StreamNodeIOResponse stream | StreamNodeIO streams a run's agent-step LLM I/O: a snapshot of the already- captured transcript events (clearance-redacted, from session_events, the same read+redact path GetNodeTranscript uses), then a snapshot_done marker, then LIVE frames coalesced from the worker as the agent thinks/acts.
The live frames arrive over a new in-memory NodeIOHub in the context-engine, fed by the Redis pub/sub channel wfio:{org}:{run} the orchestrator publisher tees (LBE-A1). Subscribe-before-snapshot ordering (#1962): the hub subscription opens FIRST so no frame fired during the snapshot read is lost; the client dedups the snapshot↔live overlap by (session_id, seq).
Live redaction floor (byte-consistent with post-hoc GetNodeTranscript): a caller with clearance >= 4 receives full frames including token_text; a caller with clearance < 4 receives STRUCTURAL frames only (status, tool_call name/id, tool_result is_error/timing, user_prompt) — token_text is suppressed server-side in the CE adapter, exactly what GetNodeTranscript would redact for that clearance.
Backpressure: the hub delivers to a per-subscriber buffered channel; on a full buffer (slow client) it REPLACES the overflow with a synthesized StreamGap so the client renders "stream resynced" and may refetch the snapshot rather than silently missing frames. The Redis-subscribe reader never blocks.
Additive to the frozen MVP contract. LBE-A2 (#2240), HLD #2233 §1.2. | | PauseRun | PauseRunRequest | PauseRunResponse | PauseRun marks the run as PAUSED at the next checkpoint. Idempotent - calling PauseRun on an already-paused or terminal run returns the current state without erroring. Audit-logged.
GAP-34 (#1110). | | CancelRun | CancelRunRequest | CancelRunResponse | CancelRun halts the run and marks status=CANCELLED. Idempotent. Audit-logged.
GAP-34 (#1110). |
| ResumeRun | ResumeRunRequest | ResumeRunResponse | ResumeRun lifts a pause on a workflow run, letting execution continue from the parked step. This is the Temporal-era counterpart to PauseRun: it sends the resume signal (internal/temporalwf/api.SignalResume) to the run's Temporal execution, which clears the interpreter's pause flag so the walk advances at the next boundary. Idempotent — resuming a run that is not paused is a harmless no-op signal.
Requires Temporal execution (TEMPORAL_ENABLED). On a deployment where Temporal is disabled — or against a terminal run — ResumeRun returns FAILED_PRECONDITION: the legacy engine has no resumable pause state, so there is nothing to resume. Additive to the frozen 16-RPC MVP contract (LLD #1798 §4), WF-16 (#1819). | | ListRuns | ListRunsRequest | ListRunsResponse | ListRuns returns past run metadata for a single workflow, ordered newest-started first. Cursor-paginated on (started_at DESC, id DESC). RLS-scoped — only runs owned by the caller's org are returned.
Powers the Dashboard "Active workflows" panel and the redesigned Workflows page run-history view (workflows.jsx). Per-node detail is intentionally out of scope (use GetWorkflowStatus / StreamRunStatus — GAP-29).
GAP-31 (#1074). |
| UpdateWorkflowStatus | UpdateWorkflowStatusRequest | UpdateWorkflowStatusResponse | UpdateWorkflowStatus transitions a workflow's lifecycle status (draft | active | archived). Additive to the frozen MVP contract (LLD #1798 §4). WF-24 (#1888). |
| SetWorkflowEnabled | SetWorkflowEnabledRequest | SetWorkflowEnabledResponse | SetWorkflowEnabled configures a workflow's trigger (manual | schedule + schedule config) and the enabled gate that W4 schedule firing reads (LLD #1798 §2.2 disable-race). Behavioral no-op until W4 wires schedules: the RPC writes workflows.enabled / trigger_type / trigger_config, but nothing reads them yet, and manual runs are unaffected. Additive to the frozen MVP contract. WF-24 (#1888), backed by the WF-23 (#1887) store surface. |
| UpdateWorkflowDefinition | UpdateWorkflowDefinitionRequest | UpdateWorkflowDefinitionResponse | UpdateWorkflowDefinition edits a workflow's stored definition (the typed DAG). The new definition is validated through the SAME SchemaValidator.ValidateDefinition path as CreateWorkflow — an invalid definition (unknown step type, dangling next/on_approve reference, or a fenced case such as the WF-30 tool_action literal-repo rule) is rejected with INVALID_ARGUMENT and the stored definition is left untouched.
Run-immunity semantics (documented contract): editing the definition does NOT affect in-flight runs. A Temporal-managed run boots its definition from the run's own Temporal history (the interpreter walks the DAG captured at StartWorkflow), so a mid-flight edit cannot mutate a running walk. Runs started AFTER the edit commits pick up the new definition. Each edit bumps workflows.updated_at (migration 166) so a client can detect staleness; the workflow id is stable (this is an in-place edit, not a create-new + archive-old versioning scheme).
Guarded exactly like CreateWorkflow: RLS scopes the write to the caller's org (a foreign/missing workflow reads as NotFound), and the same icsBypassOnly auth surface applies. #2002. |
| DeleteWorkflow | DeleteWorkflowRequest | DeleteWorkflowResponse | DeleteWorkflow soft-deletes a workflow: it stamps workflows.deleted_at = now(). After delete the workflow disappears from ListWorkflows / GetWorkflow (both already filter deleted_at IS NULL). Idempotent — deleting an already-deleted (undiscoverable) workflow returns NotFound, matching Get.
Run-data semantics (documented contract): a soft-delete does NOT purge the workflow's runs, actions, or logs. The audit trail is preserved (CLAUDE.md: every action is auditable). The runs become UNDISCOVERABLE through the workflow — ListWorkflows no longer surfaces the parent, so a client cannot reach ListRuns for it without an out-of-band workflow_id — but any run row already known by id remains readable by GetWorkflowStatus until a DB-admin retention sweep hard-deletes it. Hard-delete of run data stays a DB-admin operation; this RPC adds no destructive cascade.
Governed exactly like archive (UpdateWorkflowStatus): RLS scopes the write, same icsBypassOnly auth surface. #2002. |
| GetToolCatalog | GetToolCatalogRequest | GetToolCatalogResponse | GetToolCatalog returns the create-time tool_action whitelist (WF-30) — the (tool, action, args) surface a tool_action step may name — from the single Go source of truth shared with the definition validator. The client step composer reads it so it never hard-codes a copy of the whitelist that could drift from what CreateWorkflow accepts. Read-only, tenant-independent (the whitelist is a platform constant), additive to the frozen MVP contract. #2067. |
| GetRunPrompt | GetRunPromptRequest | GetRunPromptResponse | GetRunPrompt returns the detail of a mid-run user_input prompt (Track B elicitation, LBE-B2 #2249). prompt_id is the node's blocked_by uuid — the governance_approvals row the run parked on (action_type='user_prompt', migration 181). It projects the typed question the step posed (question, options[], allow_text, text_placeholder, default_choice) plus the row's required_clearance, expires_at, and status, and computes can_answer SERVER-SIDE for THIS caller — the clearance floor AND the answerable_by scope ("" | "anyone" | "requester") are evaluated against the authenticated identity, so the client renders a live "you may answer" affordance without duplicating the authz. The row is RLS-scoped to the caller's org: a foreign prompt_id reads as NotFound.
Additive to the frozen MVP contract. LBE-B2 (#2249), HLD #2233 §2. | | AnswerRunPrompt | AnswerRunPromptRequest | AnswerRunPromptResponse | AnswerRunPrompt records an answer to a mid-run user_input prompt (LBE-B2 #2249). It writes the answer envelope {choice, text, answered_by} through B1's authoritative FIRST-WRITE path (approval.Service.RecordDecision → Store.TryResolveWithPayload — the same CAS that flips the row to answered and fans the DecisionPublisher signal that RESUMES the parked run); it does NOT reinvent the record. Authz is enforced SERVER-SIDE before the write: the caller must clear the row's required_clearance floor AND satisfy its answerable_by scope (answerable_by="requester" ⇒ only the run's triggerer may answer). A second answer to an already-answered prompt is a NO-OP (first-write-wins): the RPC returns already_answered=true carrying the recorded answer rather than erroring. A foreign prompt_id is NotFound; a wrong-clearance / wrong-answerer caller is PermissionDenied.
Additive to the frozen MVP contract. LBE-B2 (#2249), HLD #2233 §2. |
| DeliverWorkflowEvent | DeliverWorkflowEventRequest | DeliverWorkflowEventResponse | DeliverWorkflowEvent delivers an external event to a run parked on a signal-mode wait node (WR2-4 Slice 6, #2266; LLD #2253 §3.4). It signals the run's Temporal execution with external_event; the interpreter's drain moves the payload into the wait node's correlation slot and the parked wait resolves, binding the payload as the node's output envelope.
⚠ SCOPE — THIS IS THE CONSUME CONTRACT, NOT INGESTION (founder scope call on HLD #2223, LLD §0). The caller already KNOWS (run_id, event_key). Untrusted webhook INGESTION — a subscription registry that resolves an external correlation key to a waiting (run_id, event_key), HMAC verification, replay protection — is an explicit fast-follow and is OUT of scope. THIS RPC IS THE ENTIRE SEAM it attaches to: that adapter, when built, performs only the resolution and calls this RPC. No signal shape, no drain and no wait-node change is required for it.
IDEMPOTENCY IS BY delivery_id, NOT BY event_key. A wait CONSUMES its slot when it resolves, so a retried delivery that lands after the first was consumed could otherwise satisfy a LATER wait on the same event_key (the next lap of a loop body) — an advance on an event that never occurred twice. This RPC therefore burns (org, run_id, event_key, delivery_id) durably BEFORE signalling: a retry is a no-op that never reaches the run, and deduplicated is true on the response. delivery_id is REQUIRED (an empty one is INVALID_ARGUMENT, never a silent fall-through to the non-idempotent path).
AUTHORIZATION — state precisely what is enforced, because this is a frozen public contract and an overstated control here gets cited later as evidence the surface is covered. What gates this RPC is:
- an AUTHENTICATED principal, and 2. ORG-SCOPED ROW-LEVEL SECURITY: the run is read back through the request's RLS-bound scope transaction, so a run owned by another org reads zero rows and the caller gets NOT_FOUND — indistinguishable from "does not exist". Every org this handler then uses (the dedupe-ledger key, the audit row, the derived Temporal execution id) is that authoritative workflow_runs.org_id, NEVER ambient request input.
There is NO per-action RBAC on this RPC, and there is none on PauseRun / CancelRun / ResumeRun either: WorkflowService is mounted with the authenticate-only interceptor (no RBAC interceptor). So any authenticated caller in the owning org may deliver an event to that org's run, exactly as any such caller may pause, cancel or resume it. This RPC introduces no new authority — it is at parity with its siblings, no wider.
⚠ LLD #2253 O4 said this RPC "reuses the run-control entitlement". That premise was false — no such per-action entitlement exists — and the LLD is being corrected. Whether these run-control RPCs SHOULD carry one is tracked separately (#2362); a narrower event-delivery capability remains a future refinement, not something this contract already provides.
unknown / foreign run -> NOT_FOUND terminal run -> FAILED_PRECONDITION empty event_key -> INVALID_ARGUMENT empty delivery_id -> INVALID_ARGUMENT Temporal disabled -> FAILED_PRECONDITION
Additive to the frozen MVP contract. WR2-4 (#2266), LLD #2253 §3.4. | | ListVisibleRuns | ListVisibleRunsRequest | ListVisibleRunsResponse | ListVisibleRuns returns the runs whose parent workflow the CALLER may see — the Quad Workflows Panel's caller-scoped run list (QWP-3, #2359, HLD #2347). Visibility is two arms (HLD §A): a workflow whose team_id is one of the caller's team units, OR a private workflow the caller owns. A caller in NONE of a workflow's teams and NOT its owner sees NOTHING for it. Unlike ListRuns (single workflow) this lists ACROSS workflows; it is RLS-scoped to the caller's org (no cross-org leak) AND filtered by app.member_id (SET LOCAL per request). Cursor-paginated on (started_at DESC, id DESC) — same keyset as ListRuns. The panel defaults status_filter to ACTIVE. | | StreamVisibleRuns | StreamVisibleRunsRequest | VisibleRunEvent stream | StreamVisibleRuns is the live analogue of ListVisibleRuns: a snapshot of the caller's currently-visible runs, a snapshot_done marker, then live VisibleRunEvent deltas (status transition / a run entering or leaving the caller's active set / needs_input / needs_approval). It REUSES the existing StatusHub org tee (Subscribe(org,""), the single process-wide LISTEN on workflow_run_status) filtered per-subscriber against the caller's allow-set — it does NOT open a per-run wfio subscription (that is StreamNodeIO's job for the single selected run). VisibleRunEvent is STRUCTURAL only — name, status, node counts, needs_input/needs_approval, asked_by_role, prompt_id — surfacing at EVERY clearance; no free text / tokens ride this event. QWP-3 (#2359). |
Scalar Value Types
| .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby |
|---|---|---|---|---|---|---|---|---|
| double | double | double | float | float64 | double | float | Float | |
| float | float | float | float | float32 | float | float | Float | |
| int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| uint32 | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) |
| uint64 | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) |
| sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) |
| fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum |
| sfixed32 | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) |
| sfixed64 | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum |
| bool | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass | |
| string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) |
| bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | []byte | ByteString | string | String (ASCII-8BIT) |