Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
ac147cb
feat: sql, move some logic over from my old branch
mrkaye97 Jun 23, 2026
a043707
feat: call `ensureDAGOperator` on `PutWorkflow` so we know the operat…
mrkaye97 Jun 23, 2026
5b65eda
chore: gen
mrkaye97 Jun 23, 2026
7c6f92e
feat: first pass at operator implementation
mrkaye97 Jun 23, 2026
3a3f6ae
feat: task repo wiring
mrkaye97 Jun 23, 2026
85029fd
feat: more initial wiring, still very buggy
mrkaye97 Jun 23, 2026
58048bb
feat: it sort of works?
mrkaye97 Jun 23, 2026
a058731
fix: handle panic and shutdown / cancel
mrkaye97 Jun 23, 2026
cc05929
fix: get rid of the need to flatten workflow tasks into standalones
mrkaye97 Jun 24, 2026
61b2beb
feat: pass user message through to fe
mrkaye97 Jun 24, 2026
1059be1
feat: add `TriggerDagStep` method on the dispatcher
mrkaye97 Jun 24, 2026
9d5ac6b
feat: triggerDagStep wrapper
mrkaye97 Jun 24, 2026
de5c970
chore: lint, internals
mrkaye97 Jun 24, 2026
07ba3d7
feat: start wiring up the olap side
mrkaye97 Jun 24, 2026
50a344f
feat: map dag tasks to their parents so we get proper nesting
mrkaye97 Jun 24, 2026
abc0dc7
fix: handle expansion in side panel
mrkaye97 Jun 24, 2026
e812939
Revert "fix: handle expansion in side panel"
mrkaye97 Jun 24, 2026
be7655d
fix: render minimap correctly
mrkaye97 Jun 24, 2026
b558ee2
feat: task events on the dag view
mrkaye97 Jun 24, 2026
a441554
chore: rm unused proto change
mrkaye97 Jun 24, 2026
d326b82
chore: gen
mrkaye97 Jun 24, 2026
a89bb51
feat: pass `IsSkipped` and `IsCancelled` initial states through into …
mrkaye97 Jun 25, 2026
70612f0
feat: pass params through
mrkaye97 Jun 25, 2026
1a9c3dc
feat: wire conditions through to `triggerStep`
mrkaye97 Jun 25, 2026
a35266e
feat: more wiring
mrkaye97 Jun 25, 2026
b3b4be0
feat: conditions
mrkaye97 Jun 25, 2026
f22b45b
feat: add additional cancellation test
mrkaye97 Jun 25, 2026
40a4196
fix: naming
mrkaye97 Jun 25, 2026
596d360
fix: return output as expected in current impl
mrkaye97 Jun 25, 2026
61bea54
feat: env var to control operator use
mrkaye97 Jun 29, 2026
7eb6b00
fix: check for operator enabled on workflow registration
mrkaye97 Jun 29, 2026
ecbabcd
fix: backwards compat logic
mrkaye97 Jun 29, 2026
7138ed9
fix: some typing issues
mrkaye97 Jun 29, 2026
1a67af8
refactor: clean up some cel logic and other internals, naming, etc.
mrkaye97 Jun 29, 2026
01890f6
refactor: more naming fixes
mrkaye97 Jun 29, 2026
dc99e69
refactor: cel logic
mrkaye97 Jun 29, 2026
3fea468
refactor: clean up a bit more
mrkaye97 Jun 29, 2026
30d7b86
feat: add isUsingDagOperator to WorkflowVersion
mrkaye97 Jun 30, 2026
9117a3b
fix: checksum / version creation logic
mrkaye97 Jun 30, 2026
4643922
fix: trigger path fix for recursive explosion caused by env var toggle
mrkaye97 Jun 30, 2026
57ef4c1
fix: handle outputs properly on the dispatcher
mrkaye97 Jun 30, 2026
134c535
Merge branch 'belanger/operators' into mk/durable-tasks-as-operator
mrkaye97 Jul 1, 2026
3e8ab90
chore: gen
mrkaye97 Jul 1, 2026
428aee4
feat: filter orchestrator tasks out of workflow run id fetch
mrkaye97 Jul 1, 2026
a5904f5
fix: simplify / correct fetch query
mrkaye97 Jul 1, 2026
3b254d0
feat: lookup query for orchestrator tasks
mrkaye97 Jul 1, 2026
cfe0803
feat: finish wiring up runs list api
mrkaye97 Jul 1, 2026
2c8b24c
fix: ordering
mrkaye97 Jul 1, 2026
6425ded
fix: count query
mrkaye97 Jul 1, 2026
3a1659f
fix: workflow input bug
mrkaye97 Jul 1, 2026
5188807
Merge branch 'belanger/operators' into mk/durable-tasks-as-operator
mrkaye97 Jul 6, 2026
1d1e923
chore: migration version
mrkaye97 Jul 6, 2026
bf1ece5
feat: add a bunch of condition tests to cover additional cases
mrkaye97 Jul 6, 2026
e3ef9c3
fix: update dag logic to match new tests
mrkaye97 Jul 6, 2026
c6df508
fix: bad merge
mrkaye97 Jul 6, 2026
e4e805a
fix: output on skip
mrkaye97 Jul 6, 2026
da41109
fix: make getdetails endpoint work properly
mrkaye97 Jul 6, 2026
7412ee1
fix: additional meta propagation, failure handling
mrkaye97 Jul 6, 2026
f25ab01
fix: concurrency (workflow level), desired label propagation
mrkaye97 Jul 6, 2026
bad0cdc
fix: propagate labels
mrkaye97 Jul 6, 2026
191362c
fix: couple more issues
mrkaye97 Jul 6, 2026
28a2e3a
Merge branch 'belanger/operators' into mk/durable-tasks-as-operator
mrkaye97 Jul 8, 2026
21d0853
fix: migration version
mrkaye97 Jul 8, 2026
02a1e5e
fix: api for dag
mrkaye97 Jul 8, 2026
561b095
fix: ignore spans for orchestrator tasks
mrkaye97 Jul 8, 2026
8585332
feat: add orchestrator flag to task meta query
mrkaye97 Jul 8, 2026
b67770f
fix: allow or groups to contain parent conditions
mrkaye97 Jul 8, 2026
e880943
chore: remove annoying cron
mrkaye97 Jul 8, 2026
c2ddc89
feat: add col to v1_task to track if task belongs to a durable dag
mrkaye97 Jul 8, 2026
678df18
feat: use new col on olap side
mrkaye97 Jul 8, 2026
5601d24
fix: tie workflow run ids back to dag parent
mrkaye97 Jul 9, 2026
1e7469b
fix: otel spans
mrkaye97 Jul 9, 2026
5796ae5
fix: couple more bugs
mrkaye97 Jul 10, 2026
07c7fe3
fix: name
mrkaye97 Jul 10, 2026
c4fff5e
feat: parse dag shape to json graph so we can read it in other places
mrkaye97 Jul 10, 2026
5443611
feat: wire up dag shape logic
mrkaye97 Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api/v1/server/handlers/v1/tasks/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ func (t *TasksService) V1TaskGet(ctx echo.Context, request gen.V1TaskGetRequestO
return nil, echo.NewHTTPError(500, "Task type assertion failed")
}

if task.IsDagOrchestrator {
return gen.V1TaskGet404JSONResponse{
Errors: []gen.APIError{{Description: "task not found"}},
}, nil
}

attempt := request.Params.Attempt

var retryCount *int
Expand Down
1 change: 1 addition & 0 deletions api/v1/server/handlers/v1/tasks/list_by_dag_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func (t *TasksService) V1DagListTasks(ctx echo.Context, request gen.V1DagListTas
tenantId,
dagIds,
false,
nil,
)

if err != nil {
Expand Down
13 changes: 9 additions & 4 deletions api/v1/server/handlers/v1/workflow-runs/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,17 @@ func (t *V1WorkflowRunsService) getWorkflowRunDetails(
taskMetadata := rawWorkflowRun.TaskMetadata
workflowRunId := workflowRun.ExternalID

taskRunEvents, err := t.config.V1.OLAP().ListTaskRunEventsByWorkflowRunId(
ctx,
tenantId,
workflowRunId,
var (
taskRunEvents []*v1.TaskEventWithPayloads
err error
)

if workflowRun.Kind == sqlcv1.V1RunKindTASK {
taskRunEvents, err = t.config.V1.OLAP().ListTaskRunEventsByTaskIds(ctx, tenantId, taskMetadata)
} else {
taskRunEvents, err = t.config.V1.OLAP().ListTaskRunEventsByWorkflowRunId(ctx, tenantId, workflowRunId)
}

if err != nil {
return nil, err
}
Expand Down
32 changes: 30 additions & 2 deletions api/v1/server/handlers/v1/workflow-runs/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,18 +177,46 @@ func (t *V1WorkflowRunsService) WithDags(ctx context.Context, request gen.V1Work
}

dagExternalIds := make([]uuid.UUID, 0)
dagDurableTaskExternalIds := make(map[uuid.UUID]struct{}, 0)

for _, dag := range dags {
if dag.Kind == sqlcv1.V1RunKindDAG {
dagExternalIds = append(dagExternalIds, dag.ExternalID)
}

if dag.Kind == sqlcv1.V1RunKindDAG && dag.IsDagOrchestratorTask {
dagDurableTaskExternalIds[dag.ExternalID] = struct{}{}
}
}

orchestratorChildExternalIds := make(map[uuid.UUID]struct{})
idsInsertedAtsForDagChildren := make([]v1.IdInsertedAt, 0)
for _, dag := range dags {
if dag.IsDagOrchestratorTask || dag.ParentTaskExternalId == nil {
continue
}
if _, parentIsOrchestrator := dagDurableTaskExternalIds[*dag.ParentTaskExternalId]; parentIsOrchestrator {
orchestratorChildExternalIds[dag.ExternalID] = struct{}{}
idsInsertedAtsForDagChildren = append(idsInsertedAtsForDagChildren, v1.IdInsertedAt{
ID: dag.ID,
InsertedAt: dag.InsertedAt,
})
}
}

filteredDags := make([]*v1.WorkflowRunData, 0, len(dags))
for _, dag := range dags {
if _, isChild := orchestratorChildExternalIds[dag.ExternalID]; !isChild {
filteredDags = append(filteredDags, dag)
}
}

tasks, taskIdToDagExternalId, err := t.config.V1.OLAP().ListTasksByDAGId(
ctx,
tenantId,
dagExternalIds,
includePayloads,
idsInsertedAtsForDagChildren,
)

if err != nil {
Expand All @@ -197,7 +225,7 @@ func (t *V1WorkflowRunsService) WithDags(ctx context.Context, request gen.V1Work

pgWorkflowIds := make([]uuid.UUID, 0)

for _, wf := range dags {
for _, wf := range filteredDags {
pgWorkflowIds = append(pgWorkflowIds, wf.WorkflowID)
}

Expand Down Expand Up @@ -236,7 +264,7 @@ func (t *V1WorkflowRunsService) WithDags(ctx context.Context, request gen.V1Work
}
}

result := transformers.ToWorkflowRunMany(dags, dagChildren, taskIdToActionId, workflowNames, total, limit, offset)
result := transformers.ToWorkflowRunMany(filteredDags, dagChildren, taskIdToActionId, workflowNames, total, limit, offset)

// Search for api errors to see how we handle errors in other cases
return gen.V1WorkflowRunList200JSONResponse(
Expand Down
2 changes: 2 additions & 0 deletions cmd/hatchet-engine/engine/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ func runV0Config(ctx context.Context, sc *server.ServerConfig, cleanup *cleanup.
adminv1.WithOptimisticSchedulingEnabled(sc.Runtime.OptimisticSchedulingEnabled),
adminv1.WithGrpcTriggersEnabled(sc.Runtime.GRPCTriggerWritesEnabled),
adminv1.WithGrpcTriggerSlots(sc.Runtime.GRPCTriggerWriteSlots),
adminv1.WithDagOperatorEnabled(sc.Runtime.DagOperatorEnabled),
adminv1.WithPrometheusGate(sc.PrometheusGate),
)

Expand Down Expand Up @@ -850,6 +851,7 @@ func runV1Config(ctx context.Context, sc *server.ServerConfig, cleanup *cleanup.
adminv1.WithOptimisticSchedulingEnabled(sc.Runtime.OptimisticSchedulingEnabled),
adminv1.WithGrpcTriggersEnabled(sc.Runtime.GRPCTriggerWritesEnabled),
adminv1.WithGrpcTriggerSlots(sc.Runtime.GRPCTriggerWriteSlots),
adminv1.WithDagOperatorEnabled(sc.Runtime.DagOperatorEnabled),
adminv1.WithPrometheusGate(sc.PrometheusGate),
)

Expand Down
27 changes: 0 additions & 27 deletions cmd/hatchet-migrate/migrate/migrations/20260621133531_v1_0_119.sql

This file was deleted.

58 changes: 58 additions & 0 deletions cmd/hatchet-migrate/migrate/migrations/20260708133531_v1_0_126.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
-- +goose Up
-- +goose StatementBegin
ALTER TABLE "Worker" ADD COLUMN "operatorId" UUID;

ALTER TABLE "Step" ADD COLUMN "isDagOrchestrator" BOOLEAN NOT NULL DEFAULT false;

ALTER TABLE v1_task
ADD COLUMN is_dag_orchestrator BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN is_dag_subtask BOOLEAN NOT NULL DEFAULT false
;

ALTER TABLE v1_tasks_olap
ADD COLUMN is_dag_orchestrator BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN is_dag_subtask BOOLEAN NOT NULL DEFAULT false
;

ALTER TABLE "WorkflowVersion"
ADD COLUMN "isUsingDagOperator" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "dagShape" JSONB
;

CREATE TYPE v1_operator_kind AS ENUM ('HTTP_API', 'DAG');

CREATE TABLE v1_operator (
id UUID NOT NULL DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
name TEXT NOT NULL,
kind v1_operator_kind NOT NULL,
config JSONB NOT NULL,
worker_id UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT v1_operator_pkey PRIMARY KEY (id)
);
-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin
ALTER TABLE "Step" DROP COLUMN "isDagOrchestrator";
ALTER TABLE "WorkflowVersion"
DROP COLUMN "isUsingDagOperator",
DROP COLUMN "dagShape"
;
ALTER TABLE v1_tasks_olap
DROP COLUMN is_dag_orchestrator,
DROP COLUMN is_dag_subtask
;
ALTER TABLE v1_task
DROP COLUMN is_dag_orchestrator,
DROP COLUMN is_dag_subtask
;

DROP TABLE v1_operator;

DROP TYPE v1_operator_kind;

ALTER TABLE "Worker" DROP COLUMN "operatorId";
-- +goose StatementEnd
29 changes: 28 additions & 1 deletion examples/python/conditions/worker.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion examples/python/worker.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions frontend/app/src/pages/main/v1/workflow-runs-v1/$run/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ function ExpandedWorkflowRun({ id }: { id: string }) {
[open],
);

const { workflowRun, shape, taskRuns, isLoading, isError } =
const { workflowRun, shape, taskRuns, taskEvents, isLoading, isError } =
useWorkflowDetails();

const tasksForSynthesis = useMemo((): TaskSummaryForSynthesis[] => {
Expand Down Expand Up @@ -303,8 +303,11 @@ function ExpandedWorkflowRun({ id }: { id: string }) {
);

const durableTaskIds = useMemo(
() => taskRuns.filter((t) => t.isDurable).map((t) => t.taskExternalId),
[taskRuns],
() =>
taskRuns
.filter((t) => t.isDurable && t.taskExternalId !== id)
.map((t) => t.taskExternalId),
[taskRuns, id],
);

if (isLoading || isError || !workflowRun) {
Expand Down Expand Up @@ -381,6 +384,7 @@ function ExpandedWorkflowRun({ id }: { id: string }) {
fallbackTaskDisplayName={workflowRun.displayName}
onClick={handleTaskRunExpand}
durableTaskIds={durableTaskIds}
events={taskEvents.filter((e) => e.taskId !== id)}
/>
</TabsContent>
<TabsContent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ export function StepRunEvents({
workflowRunId,
isDurable,
durableTaskIds,
events: externalEvents,
}: {
taskRunId?: string;
workflowRunId?: string;
isDurable?: boolean;
durableTaskIds?: string[];
events?: V1TaskEvent[];
taskDisplayName?: string;
fallbackTaskDisplayName?: string;
onClick?: (stepRunId: string) => void;
Expand All @@ -61,7 +63,8 @@ export function StepRunEvents({
taskRunId,
workflowRunId,
),
refetchInterval,
enabled: !externalEvents,
refetchInterval: externalEvents ? false : refetchInterval,
});

// fixme: this is an n+1 query, would be better to have a bulk getter
Expand All @@ -76,15 +79,15 @@ export function StepRunEvents({

const logs = useMemo(() => {
const taskLines = toTaskEventLogLines(
eventsQuery.data?.rows ?? [],
externalEvents ?? eventsQuery.data?.rows ?? [],
isDag,
tenantId,
);
const durableEventLogLines = durableLogsQueries.flatMap((q) =>
toDurableEventLogLines(q.data ?? []),
);
return mergeByTimestamp(taskLines, durableEventLogLines);
}, [eventsQuery.data, isDag, tenantId, durableLogsQueries]);
}, [externalEvents, eventsQuery.data, isDag, tenantId, durableLogsQueries]);

const handleTaskRunExpand = useCallback(
(taskRunId: string) => {
Expand Down Expand Up @@ -112,7 +115,7 @@ export function StepRunEvents({
[open],
);

if (eventsQuery.isLoading) {
if (!externalEvents && eventsQuery.isLoading) {
return <Loading />;
}

Expand Down Expand Up @@ -327,6 +330,9 @@ function entryMessage(entry: V1DurableEventLogEntry): string {
const userMessage = entry.userMessage?.trim();

if (entry.kind === V1DurableEventLogKind.RUN) {
if (userMessage) {
return `spawned child ${userMessage}`;
}
const item = entry.waitData?.[0];
if (
entry.waitData?.length === 1 &&
Expand All @@ -337,7 +343,7 @@ function entryMessage(entry: V1DurableEventLogEntry): string {
? `spawned child ${item.workflowName}`
: 'spawned child';
}
return userMessage || 'spawned child';
return 'spawned child';
}
if (userMessage) {
return userMessage;
Expand All @@ -350,6 +356,10 @@ function entryMessage(entry: V1DurableEventLogEntry): string {

function entryCompletionMessage(entry: V1DurableEventLogEntry): string {
if (entry.kind === V1DurableEventLogKind.RUN) {
const userMessage = entry.userMessage?.trim();
if (userMessage) {
return `child ${userMessage} completed`;
}
const item = entry.waitData?.[0];
if (
entry.waitData?.length === 1 &&
Expand Down Expand Up @@ -412,6 +422,10 @@ function childNamesFromEntries(
entries: V1DurableEventLogEntry[],
): (string | null)[] {
return entries.map((e) => {
const userMessage = e.userMessage?.trim();
if (userMessage) {
return userMessage;
}
const item = e.waitData?.[0];
if (
e.waitData?.length === 1 &&
Expand Down
Loading
Loading