Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
51072ee
Identify flow runs by their entity id rather than their uuid, and fin…
CiaranMn Oct 15, 2026
0a96dc3
Give a browser inference run's entity the request's uuid, which is al…
CiaranMn Oct 15, 2026
63042a8
Load flow definitions from the graph in the frontend, and give runtim…
CiaranMn Oct 12, 2026
7e8540c
Show the status of steps inside a for-each, whose runs are named per …
CiaranMn Oct 13, 2026
30b4c0b
Load the edition of a run's flow definition current when the run star…
CiaranMn Oct 17, 2026
c8a22a9
Drop the old slugs from beside the flow definition ids, which the key…
CiaranMn Oct 17, 2026
fff4438
Build and read a for-each item's step run id with shared helpers, rat…
CiaranMn Oct 17, 2026
e0a223a
Identify flow definitions by entity id, recognising HASH's own flows …
CiaranMn Oct 18, 2026
4724f76
Drop the claim that a flow run's uuid is its workflow id, which isn't…
CiaranMn Oct 19, 2026
b340e12
Link an edition's flow run under the run's own web, which may not be …
CiaranMn Oct 19, 2026
a232ddf
Merge remote-tracking branch 'origin/cm/fe-1994-flow-run-entity-ids' …
CiaranMn Oct 19, 2026
8e099de
Merge branch 'cm/fe-1895-load-flow-definitions-from-graph' into cm/fe…
CiaranMn Oct 19, 2026
f02fc25
Only link an edition to a flow run when its origin is a flow, and its…
CiaranMn Oct 19, 2026
22fcdc7
Merge remote-tracking branch 'origin/cm/fe-1994-flow-run-entity-ids' …
CiaranMn Oct 19, 2026
5144c25
Merge branch 'cm/fe-1895-load-flow-definitions-from-graph' into cm/fe…
CiaranMn Oct 19, 2026
7852622
Merge remote-tracking branch 'origin/main' into cm/fe-1994-flow-run-e…
CiaranMn Oct 19, 2026
4e395fa
Merge branch 'cm/fe-1994-flow-run-entity-ids' into cm/fe-1895-load-fl…
CiaranMn Oct 19, 2026
09aff5f
Merge branch 'cm/fe-1895-load-flow-definitions-from-graph' into cm/fe…
CiaranMn Oct 19, 2026
6c6a914
Show a for-each step's open input requests on its node, which are rai…
CiaranMn Oct 19, 2026
0a31eaa
Merge branch 'cm/fe-1895-load-flow-definitions-from-graph' into cm/fe…
CiaranMn Oct 19, 2026
99fb15c
Merge commit '94eb1d66ac' into cm/fe-1895-load-flow-definitions-from-…
CiaranMn Oct 19, 2026
4cabb0a
Merge main, whose FE-1994 squash commit this branch already has
CiaranMn Oct 19, 2026
024dd59
Merge branch 'cm/fe-1895-load-flow-definitions-from-graph' into cm/fe…
CiaranMn Oct 19, 2026
430bcd3
Merge commit '0cdb349ae9^' into cm/fe-2006-flow-definition-entity-ids
CiaranMn Oct 19, 2026
a0f4322
Merge main, whose FE-1895 squash commit this branch already has
CiaranMn Oct 19, 2026
441fd8b
Pass flow definition entity ids in the flow scenario runner
CiaranMn Oct 20, 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
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ const getUserFlowDefinitionId = (
) => uuidv5(userId, scenarioFlowDefinitionId) as EntityUuid;

/**
* Runs load their definition from the `Flow Definition` entity whose uuid is the definition's id. Migrations only
* Runs load their definition from its `Flow Definition` entity, in the user's web here. Migrations only
* seed the flows HASH provides, so the scenario's definition is written to the user's web: created if it doesn't
* exist, and updated if it differs, like `seedFlowDefinitions` does for seeded flows.
*/
Expand All @@ -318,7 +318,10 @@ const ensureFlowDefinitionEntity = async ({
const properties = mapFlowDefinitionToEntityProperties(flowDefinition);

const existingEntity = await getFlowDefinitionEntityById({
flowDefinitionId,
flowDefinitionEntityId: entityIdFromComponents(
userId as WebId,
flowDefinitionId,
),
graphApiClient: graphApi,
userAuthentication: authentication,
});
Expand Down Expand Up @@ -446,6 +449,10 @@ const startRun = async ({
}) => {
const workflowId = generateUuid() as EntityUuid;
const webId = userId as WebId;
const flowDefinitionEntityId = entityIdFromComponents(
webId,
flow.flowDefinitionId,
);
const params: RunFlowWorkflowParams = {
...(taskQueue === "ai"
? {
Expand All @@ -460,7 +467,7 @@ const startRun = async ({
: {}),
flowRunEntityUuid: workflowId,
flowInputs: inputs,
flowDefinitionId: flow.flowDefinitionId,
flowDefinitionEntityId,
userAuthentication: { actorId: userId },
webId,
};
Expand All @@ -469,7 +476,7 @@ const startRun = async ({
taskQueue,
args: [params],
memo: {
flowDefinitionId: flow.flowDefinitionId,
flowDefinitionEntityId,
userAccountId: userId,
webId,
},
Expand Down Expand Up @@ -902,7 +909,10 @@ const main = async () => {
const workflowError = await workflowErrorWhenClosed;

const flowRun = await getFlowRunFromTemporalWorkflowId({
flowDefinitionId,
flowDefinitionEntityId: entityIdFromComponents(
userId as WebId,
flowDefinitionId,
),
flowRunEntityId: entityIdFromComponents(userId as WebId, workflowId),
name: flowDefinition.name,
temporalClient: client,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
flowDefinitionIds,
isGoalFlowDefinitionId,
isGoalFlowDefinitionEntityId,
isHashFlowDefinitionEntityId,
} from "@local/hash-isomorphic-utils/flows/flow-definition-ids";

import { getFlowContext } from "../shared/get-flow-context.js";
Expand All @@ -9,7 +10,7 @@ import { getTextContentFromLlmMessage } from "../shared/get-llm-response/llm-mes
import { graphApiClient } from "../shared/graph-api-client.js";

import type { UsageTrackingParams } from "../shared/get-llm-response.js";
import type { EntityUuid } from "@blockprotocol/type-system";
import type { EntityId, EntityUuid } from "@blockprotocol/type-system";
import type {
AutomaticInferenceInputName,
ManualInferenceInputName,
Expand All @@ -25,7 +26,7 @@ import type {

type GenerateFlowRunNameActivityParams = {
flowDefinition: FlowDefinition<FlowActionDefinitionId>;
flowDefinitionId: EntityUuid;
flowDefinitionEntityId: EntityId;
flowInputs: FlowInputValues;
};

Expand Down Expand Up @@ -95,13 +96,14 @@ const inputKindsToIgnore: PayloadKind[] = [
export const generateFlowRunName = async (
params: GenerateFlowRunNameActivityParams,
) => {
const { flowDefinition, flowDefinitionId, flowInputs } = params;
const { flowDefinition, flowDefinitionEntityId, flowInputs } = params;

const isHashFlow = (flowDefinitionId: EntityUuid) =>
isHashFlowDefinitionEntityId(flowDefinitionEntityId, flowDefinitionId);

if (
[
flowDefinitionIds.automaticBrowserInference,
flowDefinitionIds.manualBrowserInference,
].includes(flowDefinitionId)
isHashFlow(flowDefinitionIds.automaticBrowserInference) ||
isHashFlow(flowDefinitionIds.manualBrowserInference)
) {
const webPage = flowInputs[
"visitedWebPage" satisfies AutomaticInferenceInputName &
Expand All @@ -113,7 +115,7 @@ export const generateFlowRunName = async (
}

return `${
flowDefinitionId === flowDefinitionIds.automaticBrowserInference
isHashFlow(flowDefinitionIds.automaticBrowserInference)
? "Auto-analyze"
: "Analyze"
} webpage: ${webPage.url}`;
Expand All @@ -130,7 +132,7 @@ export const generateFlowRunName = async (
webId,
};

if (isGoalFlowDefinitionId(flowDefinitionId)) {
if (isGoalFlowDefinitionEntityId(flowDefinitionEntityId)) {
const researchBrief = flowInputs[
"researchGuidance" satisfies GoalFlowInputName
]?.value as PayloadKindValues["Text"] | undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import { vi } from "vitest";

import { entityIdFromComponents } from "@blockprotocol/type-system";
import { HashEntity } from "@local/hash-graph-sdk/entity";
import { mapFlowRunToEntityProperties } from "@local/hash-isomorphic-utils/flows/mappings";
import { generateUuid } from "@local/hash-isomorphic-utils/generate-uuid";
Expand Down Expand Up @@ -34,7 +35,10 @@ const createDummyFlow = async (params: { actorId: ActorEntityUuid }) => {
const dummyFlowRunProperties = mapFlowRunToEntityProperties({
name: "dummy-name",
flowInputs: {},
flowDefinitionId: generateUuid() as EntityUuid,
flowDefinitionEntityId: entityIdFromComponents(
actorId as WebId,
generateUuid() as EntityUuid,
),
steps: [],
temporalWorkflowId: generateUuid(),
});
Expand Down Expand Up @@ -132,7 +136,10 @@ vi.mock("@local/hash-backend-utils/temporal", async (importOriginal) => {
},
},
},
flowDefinitionId: generateUuid() as EntityUuid,
flowDefinitionEntityId: entityIdFromComponents(
aliceUserAccountId as WebId,
generateUuid() as EntityUuid,
),
flowInputs: {},
webId: aliceUserAccountId,
userAuthentication: {
Expand Down
4 changes: 2 additions & 2 deletions apps/hash-ai-worker-ts/src/workflows/run-flow-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { heartbeatTimeoutSeconds } from "../shared/heartbeats.js";
import { setQueryAndSignalHandlers } from "./run-flow-workflow/set-query-and-signal-handlers.js";

import type { createFlowActivities } from "../activities/flow-activities.js";
import type { EntityUuid } from "@blockprotocol/type-system";
import type { EntityId } from "@blockprotocol/type-system";
import type { ProxyFlowActivity } from "@local/hash-backend-utils/flows";
import type {
RunAiFlowWorkflowParams,
Expand Down Expand Up @@ -84,7 +84,7 @@ const proxyFlowActivity: ProxyFlowActivity<

const generateFlowRunName = (params: {
flowDefinition: FlowDefinition;
flowDefinitionId: EntityUuid;
flowDefinitionEntityId: EntityId;
flowInputs: FlowInputValues;
}) => {
const { generateFlowRunName: generateFlowRunNameActivity } = proxyActivities<
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { getFlowRuns } from "@local/hash-backend-utils/flows";
import { flowDefinitionIds } from "@local/hash-isomorphic-utils/flows/flow-definition-ids";
import { getHashWebId } from "@local/hash-backend-utils/flows/shared/get-hash-web-id";
import {
flowDefinitionIds,
generateHashFlowDefinitionEntityId,
} from "@local/hash-isomorphic-utils/flows/flow-definition-ids";

import { FlowRunStatus } from "../../graphql/api-types.gen";

Expand Down Expand Up @@ -41,20 +45,28 @@ export const handleInferEntitiesRequest = async ({
payload: { webId, ...flowInputs },
} = message;

const flowDefinitionId =
const hashWebId = await getHashWebId(graphApiClient, {
actorId: user.accountId,
});

const flowDefinitionEntityId = generateHashFlowDefinitionEntityId(
hashWebId,
message.type === "manual-inference-request"
? flowDefinitionIds.manualBrowserInference
: flowDefinitionIds.automaticBrowserInference;
: flowDefinitionIds.automaticBrowserInference,
);

if (message.type === "automatic-inference-request") {
const openFlowRuns = await getFlowRuns({
authentication: { actorId: user.accountId },
filters: {
executionStatus: FlowRunStatus.Running,
flowDefinitionIds: [
flowDefinitionEntityIds: [
flowDefinitionIds.automaticBrowserInference,
flowDefinitionIds.manualBrowserInference,
],
].map((flowDefinitionId) =>
generateHashFlowDefinitionEntityId(hashWebId, flowDefinitionId),
),
},
graphApiClient,
includeDetails: true,
Expand Down Expand Up @@ -91,7 +103,7 @@ export const handleInferEntitiesRequest = async ({
},
},
},
flowDefinitionId,
flowDefinitionEntityId,
flowInputs,
/* So that the run's entity id can be worked out from the request, as the browser plugin does. */
flowRunEntityUuid: requestUuid as EntityUuid,
Expand All @@ -100,7 +112,7 @@ export const handleInferEntitiesRequest = async ({
},
],
memo: {
flowDefinitionId,
flowDefinitionEntityId,
userAccountId: user.accountId,
webId,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@ const isEntityFlowScheduleEntity = (
);

export const createFlowSchedule: ImpureGraphFunction<
CreateFlowScheduleInput & {
/** The `Flow Definition` entity the schedule starts runs of. */
flowDefinitionEntityId: EntityId;
},
CreateFlowScheduleInput,
Promise<HashEntity<FlowSchedule>>
> = async (context, authentication, params) => {
const {
Expand Down
19 changes: 10 additions & 9 deletions apps/hash-api/src/graphql/resolvers/flows/flow-schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ export const createFlowScheduleResolver: ResolverFn<
const context = graphQLContextToImpureGraphContext(graphQLContext);
const { authentication } = graphQLContext;

const { flowDefinitionId } = input;
const { flowDefinitionEntityId } = input;

const { flowDefinitionEntity, flowType } = await getRunnableFlowDefinition(
const { flowType } = await getRunnableFlowDefinition(
graphQLContext,
flowDefinitionId,
flowDefinitionEntityId,
);

if (flowType === "ai" && !input.dataSources) {
Expand All @@ -56,10 +56,11 @@ export const createFlowScheduleResolver: ResolverFn<

assertClientFlowInputs(input.flowInputs);

const schedule = await createFlowScheduleEntity(context, authentication, {
...input,
flowDefinitionEntityId: flowDefinitionEntity.metadata.recordId.entityId,
});
const schedule = await createFlowScheduleEntity(
context,
authentication,
input,
);

const props = simplifyProperties(schedule.properties);
const scheduleId = extractEntityUuidFromEntityId(
Expand All @@ -72,7 +73,7 @@ export const createFlowScheduleResolver: ResolverFn<
...(flowType === "ai" && input.dataSources
? { dataSources: input.dataSources }
: {}),
flowDefinitionId,
flowDefinitionEntityId,
flowRunName: input.name,
flowInputs: input.flowInputs,
flowTriggerEntityId: schedule.metadata.recordId.entityId,
Expand All @@ -90,7 +91,7 @@ export const createFlowScheduleResolver: ResolverFn<
taskQueue,
args: [workflowParams],
memo: {
flowDefinitionId,
flowDefinitionEntityId,
userAccountId: user.accountId,
webId: input.webId,
},
Expand Down
4 changes: 2 additions & 2 deletions apps/hash-api/src/graphql/resolvers/flows/get-flow-runs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ export const getFlowRunsResolver: ResolverFn<

const { authentication, dataSources, temporal } = context;

const { flowDefinitionIds, executionStatus, cursor, limit } = args;
const { flowDefinitionEntityIds, executionStatus, cursor, limit } = args;

return await getFlowRuns({
authentication,
filters: {
flowDefinitionIds,
flowDefinitionEntityIds,
executionStatus,
cursor,
limit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getFlowDefinitionFromEntity } from "@local/hash-isomorphic-utils/flows/
import * as GraphQLError from "../../../error";

import type { LoggedInGraphQLContext } from "../../../context";
import type { EntityUuid } from "@blockprotocol/type-system";
import type { EntityId } from "@blockprotocol/type-system";

/**
* Loads a flow's definition from its `Flow Definition` entity, and checks that the user can run it.
Expand All @@ -15,19 +15,19 @@ import type { EntityUuid } from "@blockprotocol/type-system";
*/
export const getRunnableFlowDefinition = async (
graphQLContext: LoggedInGraphQLContext,
flowDefinitionId: EntityUuid,
flowDefinitionEntityId: EntityId,
) => {
const { dataSources, authentication, user } = graphQLContext;

const flowDefinitionEntity = await getFlowDefinitionEntityById({
flowDefinitionId,
flowDefinitionEntityId,
graphApiClient: dataSources.graphApi,
userAuthentication: authentication,
});

if (!flowDefinitionEntity) {
throw GraphQLError.notFound(
`No flow definition with id ${flowDefinitionId} exists, or you can't view it`,
`No flow definition with id ${flowDefinitionEntityId} exists, or you can't view it`,
);
}

Expand All @@ -38,7 +38,7 @@ export const getRunnableFlowDefinition = async (
} catch (error) {
/* The definition is stored data, so an invalid one is a fault here, not in the request. */
throw GraphQLError.internal(
`Flow definition ${flowDefinitionId} is invalid: ${(error as Error).message}`,
`Flow definition ${flowDefinitionEntityId} is invalid: ${(error as Error).message}`,
);
}

Expand Down
8 changes: 4 additions & 4 deletions apps/hash-api/src/graphql/resolvers/flows/start-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const startFlow: ResolverFn<
MutationStartFlowArgs
> = async (
_,
{ dataSources, flowInputs, flowDefinitionId, webId },
{ dataSources, flowInputs, flowDefinitionEntityId, webId },
graphQLContext,
) => {
const { temporal, user } = graphQLContext;
Expand All @@ -33,7 +33,7 @@ export const startFlow: ResolverFn<
*/
const { flowType } = await getRunnableFlowDefinition(
graphQLContext,
flowDefinitionId,
flowDefinitionEntityId,
);

assertClientFlowInputs(flowInputs);
Expand All @@ -48,7 +48,7 @@ export const startFlow: ResolverFn<
...(flowType === "ai" ? { dataSources } : {}),
flowRunEntityUuid: workflowId,
flowInputs,
flowDefinitionId,
flowDefinitionEntityId,
userAuthentication: { actorId: user.accountId },
webId,
};
Expand All @@ -59,7 +59,7 @@ export const startFlow: ResolverFn<
taskQueue: flowType,
args: [params],
memo: {
flowDefinitionId,
flowDefinitionEntityId,
userAccountId: user.accountId,
webId,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const createFileFromUrl: ResolverFn<
if (user.enabledFeatureFlags.includes("ai")) {
await triggerPdfAnalysisWorkflow({
entity,
graphApiClient: context.graphApi,
temporalClient: temporal,
userAccountId: user.accountId,
webId: extractWebIdFromEntityId(entity.entityId),
Expand Down
Loading
Loading