docs: add workflows API integration design spec

Design spec for integrating Mistral Python SDK v2.2.0 changes into the
Go SDK v1.2.0. Covers Workflows API (37 methods across 8 sub-resources),
DeleteBatchJob addition, sealed event interface with 17 variants, and
SSE streaming with StreamPayload envelope.
This commit is contained in:
2026-04-02 16:05:35 +02:00
parent aa5c53c407
commit b2a1f141e0

View File

@@ -0,0 +1,650 @@
# Workflows API Integration — Design Spec
**Date:** 2026-04-02
**Upstream:** Mistral Python SDK v2.2.0 (released 2026-03-31)
**SDK version:** v1.2.0
**Scope:** Full parity with Python SDK v2.2.0 changes since v2.1.3
## Summary
Add the Workflows API (37 new methods) and `DeleteBatchJob` (1 method) to the Go SDK.
This is purely additive — no breaking changes to existing API surface.
## New Package: `workflow/`
Types-only package following the two-layer architecture. 8 type files + `doc.go`.
### `workflow/doc.go`
Package documentation.
### `workflow/workflow.go` — Core CRUD types
```go
type Workflow struct {
ID string `json:"id"`
Name string `json:"name"`
DisplayName *string `json:"display_name,omitempty"`
Description *string `json:"description,omitempty"`
OwnerID string `json:"owner_id"`
WorkspaceID string `json:"workspace_id"`
AvailableInChatAssistant bool `json:"available_in_chat_assistant"`
Archived bool `json:"archived"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type WorkflowUpdateRequest struct {
DisplayName *string `json:"display_name,omitempty"`
Description *string `json:"description,omitempty"`
AvailableInChatAssistant *bool `json:"available_in_chat_assistant,omitempty"`
}
type WorkflowListResponse struct {
Workflows []Workflow `json:"workflows"`
NextCursor *string `json:"next_cursor,omitempty"`
}
type WorkflowListParams struct {
ActiveOnly *bool
IncludeShared *bool
AvailableInChatAssistant *bool
Archived *bool
Cursor *string
Limit *int
}
type WorkflowArchiveResponse struct {
ID string `json:"id"`
Archived bool `json:"archived"`
}
```
### `workflow/execution.go` — Execution types
```go
type ExecutionStatus string
const (
ExecutionRunning ExecutionStatus = "RUNNING"
ExecutionCompleted ExecutionStatus = "COMPLETED"
ExecutionFailed ExecutionStatus = "FAILED"
ExecutionCanceled ExecutionStatus = "CANCELED"
ExecutionTerminated ExecutionStatus = "TERMINATED"
ExecutionContinuedAsNew ExecutionStatus = "CONTINUED_AS_NEW"
ExecutionTimedOut ExecutionStatus = "TIMED_OUT"
ExecutionRetryingAfterErr ExecutionStatus = "RETRYING_AFTER_ERROR"
)
type ExecutionRequest struct {
ExecutionID *string `json:"execution_id,omitempty"`
Input map[string]any `json:"input,omitempty"`
EncodedInput *NetworkEncodedInput `json:"encoded_input,omitempty"`
WaitForResult bool `json:"wait_for_result,omitempty"`
TimeoutSeconds *float64 `json:"timeout_seconds,omitempty"`
CustomTracingAttributes map[string]string `json:"custom_tracing_attributes,omitempty"`
DeploymentName *string `json:"deployment_name,omitempty"`
}
type ExecutionResponse struct {
WorkflowName string `json:"workflow_name"`
ExecutionID string `json:"execution_id"`
RootExecutionID string `json:"root_execution_id"`
Status ExecutionStatus `json:"status"`
StartTime string `json:"start_time"`
EndTime *string `json:"end_time,omitempty"`
Result any `json:"result,omitempty"`
ParentExecutionID *string `json:"parent_execution_id,omitempty"`
TotalDurationMs *int `json:"total_duration_ms,omitempty"`
}
type NetworkEncodedInput struct {
B64Payload string `json:"b64payload"`
EncodingOptions []string `json:"encoding_options,omitempty"`
Empty bool `json:"empty,omitempty"`
}
type SignalInvocationBody struct {
Name string `json:"name"`
Input any `json:"input"`
}
type SignalResponse struct {
Message string `json:"message"` // default: "Signal accepted"
}
type QueryInvocationBody struct {
Name string `json:"name"`
Input any `json:"input,omitempty"`
}
type QueryResponse struct {
QueryName string `json:"query_name"`
Result any `json:"result"`
}
type UpdateInvocationBody struct {
Name string `json:"name"`
Input any `json:"input,omitempty"`
}
type UpdateResponse struct {
UpdateName string `json:"update_name"`
Result any `json:"result"`
}
// Trace response types
type TraceOTelResponse struct {
WorkflowName string `json:"workflow_name"`
ExecutionID string `json:"execution_id"`
RootExecutionID string `json:"root_execution_id"`
Status *ExecutionStatus `json:"status"`
StartTime string `json:"start_time"`
EndTime *string `json:"end_time,omitempty"`
Result any `json:"result"`
DataSource string `json:"data_source"`
ParentExecutionID *string `json:"parent_execution_id,omitempty"`
TotalDurationMs *int `json:"total_duration_ms,omitempty"`
OTelTraceID *string `json:"otel_trace_id,omitempty"`
OTelTraceData any `json:"otel_trace_data,omitempty"`
}
type TraceSummaryResponse struct {
WorkflowName string `json:"workflow_name"`
ExecutionID string `json:"execution_id"`
RootExecutionID string `json:"root_execution_id"`
Status *ExecutionStatus `json:"status"`
StartTime string `json:"start_time"`
EndTime *string `json:"end_time,omitempty"`
Result any `json:"result"`
ParentExecutionID *string `json:"parent_execution_id,omitempty"`
TotalDurationMs *int `json:"total_duration_ms,omitempty"`
SpanTree any `json:"span_tree,omitempty"`
}
type TraceEventsResponse struct {
WorkflowName string `json:"workflow_name"`
ExecutionID string `json:"execution_id"`
RootExecutionID string `json:"root_execution_id"`
Status *ExecutionStatus `json:"status"`
StartTime string `json:"start_time"`
EndTime *string `json:"end_time,omitempty"`
Result any `json:"result"`
ParentExecutionID *string `json:"parent_execution_id,omitempty"`
TotalDurationMs *int `json:"total_duration_ms,omitempty"`
Events []json.RawMessage `json:"events,omitempty"`
}
type TraceEventsParams struct {
MergeSameIDEvents *bool
IncludeInternalEvents *bool
}
type ResetInvocationBody struct {
EventID int `json:"event_id"`
Reason *string `json:"reason,omitempty"`
ExcludeSignals bool `json:"exclude_signals,omitempty"`
ExcludeUpdates bool `json:"exclude_updates,omitempty"`
}
type BatchExecutionBody struct {
ExecutionIDs []string `json:"execution_ids"`
}
type BatchExecutionResponse struct {
Results map[string]BatchExecutionResult `json:"results,omitempty"`
}
type BatchExecutionResult struct {
Status string `json:"status"`
Error *string `json:"error,omitempty"`
}
type StreamParams struct {
EventSource *EventSource
LastEventID *string
}
```
### `workflow/event.go` — Sealed event interface + 17 variants
Discriminator field: `event_type`
```go
type Event interface {
workflowEvent()
EventType() EventType
}
type EventType string
const (
EventWorkflowStarted EventType = "WORKFLOW_EXECUTION_STARTED"
EventWorkflowCompleted EventType = "WORKFLOW_EXECUTION_COMPLETED"
EventWorkflowFailed EventType = "WORKFLOW_EXECUTION_FAILED"
EventWorkflowCanceled EventType = "WORKFLOW_EXECUTION_CANCELED"
EventWorkflowContinuedAsNew EventType = "WORKFLOW_EXECUTION_CONTINUED_AS_NEW"
EventWorkflowTaskTimedOut EventType = "WORKFLOW_TASK_TIMED_OUT"
EventWorkflowTaskFailed EventType = "WORKFLOW_TASK_FAILED"
EventCustomTaskStarted EventType = "CUSTOM_TASK_STARTED"
EventCustomTaskInProgress EventType = "CUSTOM_TASK_IN_PROGRESS"
EventCustomTaskCompleted EventType = "CUSTOM_TASK_COMPLETED"
EventCustomTaskFailed EventType = "CUSTOM_TASK_FAILED"
EventCustomTaskTimedOut EventType = "CUSTOM_TASK_TIMED_OUT"
EventCustomTaskCanceled EventType = "CUSTOM_TASK_CANCELED"
EventActivityTaskStarted EventType = "ACTIVITY_TASK_STARTED"
EventActivityTaskCompleted EventType = "ACTIVITY_TASK_COMPLETED"
EventActivityTaskRetrying EventType = "ACTIVITY_TASK_RETRYING"
EventActivityTaskFailed EventType = "ACTIVITY_TASK_FAILED"
)
type EventSource string
const (
EventSourceDatabase EventSource = "DATABASE"
EventSourceLive EventSource = "LIVE"
)
type Scope string
const (
ScopeActivity Scope = "activity"
ScopeWorkflow Scope = "workflow"
ScopeAll Scope = "*"
)
```
Each concrete event type has common fields + type-specific attributes:
```go
// Common fields embedded in all event types
type eventBase struct {
ID string `json:"event_id"`
Timestamp int64 `json:"event_timestamp"`
RootWorkflowExecID string `json:"root_workflow_exec_id"`
ParentWorkflowExecID *string `json:"parent_workflow_exec_id"`
WorkflowExecID string `json:"workflow_exec_id"`
WorkflowRunID string `json:"workflow_run_id"`
WorkflowName string `json:"workflow_name"`
}
// Example concrete types:
type WorkflowExecutionStartedEvent struct {
eventBase
Attributes WorkflowStartedAttributes `json:"attributes"`
}
func (WorkflowExecutionStartedEvent) workflowEvent() {}
func (WorkflowExecutionStartedEvent) EventType() EventType { return EventWorkflowStarted }
type WorkflowExecutionCompletedEvent struct {
eventBase
Attributes WorkflowCompletedAttributes `json:"attributes"`
}
// ... pattern repeats for all 17 types
type UnknownEvent struct {
eventBase
RawType string
Raw json.RawMessage
}
```
SSE envelope types:
```go
type StreamPayload struct {
Stream string `json:"stream"`
Data json.RawMessage `json:"data"`
WorkflowContext StreamWorkflowContext `json:"workflow_context"`
BrokerSequence int `json:"broker_sequence"`
Timestamp *string `json:"timestamp,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
}
type StreamWorkflowContext struct {
Namespace string `json:"namespace"`
WorkflowName string `json:"workflow_name"`
WorkflowExecID string `json:"workflow_exec_id"`
ParentWorkflowExecID *string `json:"parent_workflow_exec_id,omitempty"`
RootWorkflowExecID *string `json:"root_workflow_exec_id,omitempty"`
}
func UnmarshalEvent(data json.RawMessage) (Event, error)
// Probes event_type discriminator, dispatches to concrete type.
// Unknown event_type returns UnknownEvent.
```
### `workflow/deployment.go`
```go
type Deployment struct {
ID string `json:"id"`
Name string `json:"name"`
IsActive bool `json:"is_active"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type DeploymentListResponse struct {
Deployments []Deployment `json:"deployments"`
}
type DeploymentListParams struct {
ActiveOnly *bool
WorkflowName *string
}
```
### `workflow/metrics.go`
```go
type Metrics struct {
ExecutionCount ScalarMetric `json:"execution_count"`
SuccessCount ScalarMetric `json:"success_count"`
ErrorCount ScalarMetric `json:"error_count"`
AverageLatencyMs ScalarMetric `json:"average_latency_ms"`
LatencyOverTime TimeSeriesMetric `json:"latency_over_time"`
RetryRate ScalarMetric `json:"retry_rate"`
}
type ScalarMetric struct {
Value float64 `json:"value"`
}
type TimeSeriesMetric struct {
Value [][]float64 `json:"value"`
}
type MetricsParams struct {
StartTime *string
EndTime *string
}
```
### `workflow/run.go`
```go
type Run struct {
ID string `json:"id"`
WorkflowName string `json:"workflow_name"`
ExecutionID string `json:"execution_id"`
Status ExecutionStatus `json:"status"`
StartTime string `json:"start_time"`
EndTime *string `json:"end_time,omitempty"`
}
type ListRunsResponse struct {
Runs []Run `json:"runs"`
NextPageToken *string `json:"next_page_token,omitempty"`
}
type RunListParams struct {
WorkflowIdentifier *string
Search *string
Status *string
PageSize *int
NextPageToken *string
}
```
### `workflow/schedule.go`
```go
type ScheduleRequest struct {
Schedule ScheduleDefinition `json:"schedule"`
WorkflowRegistrationID *string `json:"workflow_registration_id,omitempty"`
WorkflowIdentifier *string `json:"workflow_identifier,omitempty"`
ScheduleID *string `json:"schedule_id,omitempty"`
DeploymentName *string `json:"deployment_name,omitempty"`
}
type ScheduleDefinition struct {
Input any `json:"input"`
Calendars []ScheduleCalendar `json:"calendars,omitempty"`
Intervals []ScheduleInterval `json:"intervals,omitempty"`
CronExpressions []string `json:"cron_expressions,omitempty"`
Skip []ScheduleCalendar `json:"skip,omitempty"`
StartAt *string `json:"start_at,omitempty"`
EndAt *string `json:"end_at,omitempty"`
Jitter *string `json:"jitter,omitempty"`
TimeZoneName *string `json:"time_zone_name,omitempty"`
Policy *SchedulePolicy `json:"policy,omitempty"`
}
type ScheduleCalendar struct {
Second []ScheduleRange `json:"second,omitempty"`
Minute []ScheduleRange `json:"minute,omitempty"`
Hour []ScheduleRange `json:"hour,omitempty"`
DayOfMonth []ScheduleRange `json:"day_of_month,omitempty"`
Month []ScheduleRange `json:"month,omitempty"`
Year []ScheduleRange `json:"year,omitempty"`
DayOfWeek []ScheduleRange `json:"day_of_week,omitempty"`
Comment *string `json:"comment,omitempty"`
}
type ScheduleRange struct {
Start int `json:"start"`
End int `json:"end,omitempty"`
Step int `json:"step,omitempty"`
}
type ScheduleInterval struct {
Every string `json:"every"`
Offset *string `json:"offset,omitempty"`
}
type SchedulePolicy struct {
CatchupWindowSeconds int `json:"catchup_window_seconds,omitempty"`
Overlap *int `json:"overlap,omitempty"`
PauseOnFailure bool `json:"pause_on_failure,omitempty"`
}
type ScheduleResponse struct {
ScheduleID string `json:"schedule_id"`
}
type ScheduleListResponse struct {
Schedules []Schedule `json:"schedules"`
}
type Schedule struct {
ScheduleID string `json:"schedule_id"`
Definition ScheduleDefinition `json:"definition"`
WorkflowName string `json:"workflow_name"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
```
### `workflow/registration.go`
```go
type Registration struct {
ID string `json:"id"`
WorkflowID string `json:"workflow_id"`
TaskQueue string `json:"task_queue"`
Workflow *Workflow `json:"workflow,omitempty"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type RegistrationListResponse struct {
Registrations []Registration `json:"registrations"`
NextCursor *string `json:"next_cursor,omitempty"`
}
type RegistrationListParams struct {
WorkflowID *string
TaskQueue *string
ActiveOnly *bool
IncludeShared *bool
WorkflowSearch *string
Archived *bool
WithWorkflow *bool
AvailableInChatAssistant *bool
Limit *int
Cursor *string
}
type RegistrationGetParams struct {
WithWorkflow *bool
IncludeShared *bool
}
type WorkerInfo struct {
SchedulerURL string `json:"scheduler_url"`
Namespace string `json:"namespace"`
TLS bool `json:"tls"`
}
```
## Service Methods (root package)
### `workflows.go` — 10 methods
| Method | HTTP | Path |
|--------|------|------|
| `ListWorkflows` | GET | `/v1/workflows` |
| `GetWorkflow` | GET | `/v1/workflows/{id}` |
| `UpdateWorkflow` | PUT | `/v1/workflows/{id}` |
| `ArchiveWorkflow` | PUT | `/v1/workflows/{id}/archive` |
| `UnarchiveWorkflow` | PUT | `/v1/workflows/{id}/unarchive` |
| `ExecuteWorkflow` | POST | `/v1/workflows/{id}/execute` |
| `ListWorkflowRegistrations` | GET | `/v1/workflows/registrations` |
| `GetWorkflowRegistration` | GET | `/v1/workflows/registrations/{id}` |
| `ExecuteWorkflowRegistration` | POST | `/v1/workflows/registrations/{id}/execute` |
| `ExecuteWorkflowAndWait` | (composite) | execute + poll |
`ExecuteWorkflowRegistration` is deprecated (doc comment only).
`ExecuteWorkflowAndWait` calls `ExecuteWorkflow`, then polls `GetWorkflowExecution`
in a loop until status is terminal or context is canceled.
### `workflows_executions.go` — 14 methods
| Method | HTTP | Path |
|--------|------|------|
| `GetWorkflowExecution` | GET | `/v1/workflows/executions/{id}` |
| `GetWorkflowExecutionHistory` | GET | `/v1/workflows/executions/{id}/history` |
| `StreamWorkflowExecution` | GET (SSE) | `/v1/workflows/executions/{id}/stream` |
| `SignalWorkflowExecution` | POST | `/v1/workflows/executions/{id}/signals` |
| `QueryWorkflowExecution` | POST | `/v1/workflows/executions/{id}/queries` |
| `UpdateWorkflowExecution` | POST | `/v1/workflows/executions/{id}/updates` |
| `TerminateWorkflowExecution` | POST (204) | `/v1/workflows/executions/{id}/terminate` |
| `CancelWorkflowExecution` | POST (204) | `/v1/workflows/executions/{id}/cancel` |
| `ResetWorkflowExecution` | POST (204) | `/v1/workflows/executions/{id}/reset` |
| `BatchCancelWorkflowExecutions` | POST | `/v1/workflows/executions/cancel` |
| `BatchTerminateWorkflowExecutions` | POST | `/v1/workflows/executions/terminate` |
| `GetWorkflowExecutionTraceOTel` | GET | `/v1/workflows/executions/{id}/trace/otel` |
| `GetWorkflowExecutionTraceSummary` | GET | `/v1/workflows/executions/{id}/trace/summary` |
| `GetWorkflowExecutionTraceEvents` | GET | `/v1/workflows/executions/{id}/trace/events` |
Also contains `WorkflowEventStream` type (wraps `Stream[json.RawMessage]`,
dispatches via `workflow.UnmarshalEvent`).
### `workflows_events.go` — 2 methods
| Method | HTTP | Path |
|--------|------|------|
| `StreamWorkflowEvents` | GET (SSE) | `/v1/workflows/events/stream` |
| `ListWorkflowEvents` | GET | `/v1/workflows/events/list` |
### `workflows_deployments.go` — 2 methods
| Method | HTTP | Path |
|--------|------|------|
| `ListWorkflowDeployments` | GET | `/v1/workflows/deployments` |
| `GetWorkflowDeployment` | GET | `/v1/workflows/deployments/{id}` |
### `workflows_metrics.go` — 1 method
| Method | HTTP | Path |
|--------|------|------|
| `GetWorkflowMetrics` | GET | `/v1/workflows/{name}/metrics` |
### `workflows_runs.go` — 3 methods
| Method | HTTP | Path |
|--------|------|------|
| `ListWorkflowRuns` | GET | `/v1/workflows/runs` |
| `GetWorkflowRun` | GET | `/v1/workflows/runs/{id}` |
| `GetWorkflowRunHistory` | GET | `/v1/workflows/runs/{id}/history` |
### `workflows_schedules.go` — 3 methods
| Method | HTTP | Path |
|--------|------|------|
| `ListWorkflowSchedules` | GET | `/v1/workflows/schedules` |
| `ScheduleWorkflow` | POST | `/v1/workflows/schedules` |
| `UnscheduleWorkflow` | DELETE | `/v1/workflows/schedules/{id}` |
### `workflows_workers.go` — 1 method
| Method | HTTP | Path |
|--------|------|------|
| `GetWorkflowWorkerInfo` | GET | `/v1/workflows/workers/whoami` |
### `batch_api.go` — 1 new method
| Method | HTTP | Path |
|--------|------|------|
| `DeleteBatchJob` | DELETE | `/v1/batch/jobs/{id}` |
New type in `batch/`: `DeleteResponse { ID, Object, Deleted }`.
## Streaming Design
`WorkflowEventStream` wraps `Stream[json.RawMessage]` like `EventStream` does for conversations.
SSE data arrives as `StreamPayload` envelope:
```json
{
"stream": "...",
"data": { "event_type": "WORKFLOW_EXECUTION_COMPLETED", ... },
"workflow_context": { ... },
"broker_sequence": 42,
"timestamp": "...",
"metadata": {}
}
```
`WorkflowEventStream.Next()`:
1. Read next SSE `data:` line via inner `Stream[json.RawMessage]`
2. Unmarshal into `workflow.StreamPayload`
3. Dispatch `payload.Data` via `workflow.UnmarshalEvent` (probes `event_type`)
4. Expose both `Current() workflow.Event` and `CurrentPayload() *workflow.StreamPayload`
Both `StreamWorkflowExecution` and `StreamWorkflowEvents` use GET (not POST)
with SSE response. They use `doStream` without a request body — the stream method
needs to support GET + query params (verify `doStream` handles nil body for GET).
## Testing
One test file per service file. `httptest.NewServer` with inline handlers. Stdlib only.
Key scenarios:
- Query param encoding for list/filter endpoints
- PUT body marshaling for update/archive
- 204 no-body responses for terminate/cancel/reset
- 202 response for signal
- SSE streaming with StreamPayload envelope + event type dispatch
- UnknownEvent forward compatibility
- ExecuteWorkflowAndWait polling loop (mock multiple get-execution responses)
- Sealed interface UnmarshalEvent for all 17 event types + unknown
- Batch operations with map response
## Version & Docs
- Bump version constant in `mistral.go` to `1.2.0`
- Update `CLAUDE.md` sub-packages list to include `workflow/`
- Update `CHANGELOG.md` with v1.2.0 entry
- Upstream sync reference: Python SDK v2.2.0
## Non-Goals
- No pagination helpers (cursor chaining) — callers manage pagination manually, same as existing endpoints
- No traceparent injection hook — Go callers manage their own tracing headers
- No `execute_workflow_and_wait_async` — Go has context cancellation instead
- No WebSocket/realtime workflow support (not in Python SDK either)