Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 3 additions & 3 deletions PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,9 @@ person names what should happen again, on what schedule, and where the
result should land. See `@corbits/workflows` for the schedule/cron
helpers and the hub's `workflow-scheduler.ts` for the poller. Owner
ruling (2026-09-17, LESS IS MORE): the shipped workflow catalog is
deleted — `WORKFLOW_CATALOG` now carries only Myra and `echo` (the
e2e smoke fixture), and every other workflow a workbench runs is one
Myra creates dynamically for that workbench's own needs.
deleted — `WORKFLOW_CATALOG` now carries only Myra, and every other
workflow a workbench runs is one Myra creates dynamically for that
workbench's own needs.

## Inbox and approvals

Expand Down
1 change: 0 additions & 1 deletion apps/hub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
"@corbits/cron": "workspace:*",
"@corbits/docker-provisioner": "workspace:*",
"@corbits/e2b-sandbox-sidecar": "workspace:*",
"@corbits/echo-workflow": "workspace:*",
"@corbits/error-sink": "workspace:*",
"@corbits/hub-api-client": "workspace:*",
"@corbits/inbox": "workspace:*",
Expand Down
15 changes: 0 additions & 15 deletions bun.lock

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

22 changes: 2 additions & 20 deletions packages/workflows/src/catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import {
} from "./catalog";

describe("workflow catalog", () => {
test("marks neither echo nor assistant automatable", () => {
expect(isAutomatableWorkflowName("echo")).toBe(false);
test("marks the seeded assistant non-automatable", () => {
expect(isAutomatableWorkflowName("assistant")).toBe(false);
});

Expand All @@ -25,7 +24,6 @@ describe("workflow catalog", () => {
});

test("prefers catalog display names over raw asset names", () => {
expect(workflowDisplayName("echo")).toBe("Echo");
expect(workflowDisplayName("assistant")).toBe("Myra");
});

Expand All @@ -49,25 +47,11 @@ describe("workflow catalog", () => {
expect(isConversationalWorkflowName("assistant")).toBe(true);
});

test("marks echo, the mail-triggered wiring check, non-conversational", () => {
expect(isConversationalWorkflowName("echo")).toBe(false);
});

test("treats a name absent from the catalog as conversational — a runtime agent-directory definition", () => {
expect(isConversationalWorkflowName("my-researcher")).toBe(true);
expect(isConversationalWorkflowName("wfd_deadbeef")).toBe(true);
});

// CL-6649: a non-automatable utility (echo) is still non-conversational
// — the exact combination that let a picker gated on
// `!isAutomatableWorkflowName` alone (rather than
// `isConversationalWorkflowName`) mistake a routine's delivery
// workflow for an invitable chat agent.
test("a non-automatable utility is still non-conversational (the CL-6649 trap)", () => {
expect(isAutomatableWorkflowName("echo")).toBe(false);
expect(isConversationalWorkflowName("echo")).toBe(false);
});

test("every catalog entry declares a conversational flag", () => {
for (const entry of WORKFLOW_CATALOG) {
expect(typeof entry.conversational).toBe("boolean");
Expand Down Expand Up @@ -125,7 +109,6 @@ describe("workflow catalog", () => {

test("workflows with no external connector requirement declare an empty list", () => {
const byAssetName = new Map(WORKFLOW_CATALOG.map((entry) => [entry.assetName, entry]));
expect(byAssetName.get("echo")?.requiredConnections).toEqual([]);
expect(byAssetName.get("assistant")?.requiredConnections).toEqual([]);
});

Expand All @@ -149,8 +132,7 @@ describe("workflow catalog", () => {
}
});

test("echo and assistant take no human-supplied trigger content", () => {
expect(workflowCatalogEntry("echo")?.triggerFields).toBeUndefined();
test("assistant takes no human-supplied trigger content", () => {
expect(workflowCatalogEntry("assistant")?.triggerFields).toBeUndefined();
});
});
Expand Down
19 changes: 3 additions & 16 deletions packages/workflows/src/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
// only here, so there is no second copy for them to drift from.
import { type } from "arktype";

import echoPkg from "../../../workflows/echo/package.json";
import assistantPkg from "../../../agents/assistant/package.json";

const CorbitsWorkflowBlock = type({
Expand Down Expand Up @@ -69,10 +68,8 @@ export type WorkflowCatalogEntry = {
/**
* A real chat partner a person can open a DM with and converse
* freely — as opposed to a mail-triggered utility whose only sane
* input is its declared trigger contract. `automatable` alone can't
* tell these apart: `echo` is a non-automatable on-demand run, not a
* conversational agent. Only the seeded `assistant`/Myra definition
* is `true`.
* input is its declared trigger contract. Only the seeded
* `assistant`/Myra definition is `true`.
*/
readonly conversational: boolean;
/**
Expand Down Expand Up @@ -117,16 +114,6 @@ export type WorkflowCatalogEntry = {
* here, so they cannot pass the automatable filter by accident.
*/
export const WORKFLOW_CATALOG: readonly WorkflowCatalogEntry[] = [
{
...workflowBlock(echoPkg),
conversational: false,
deliveryMode: "workbench",
whatItDoes:
"Replies with the exact text it received — a wiring check for the mail-triggered contract, not a real assistant.",
requiredConnections: [],
exampleOutput: "Echoed back: Testing 1 2 3",
typicalDuration: "a few seconds",
},
{
...workflowBlock(assistantPkg),
conversational: true,
Expand Down Expand Up @@ -163,7 +150,7 @@ export function isAutomatableWorkflowName(name: string): boolean {
* agent-directory-created definition — "Agent definitions created at
* runtime are never listed here" (see `WORKFLOW_CATALOG`'s own comment) —
* and is always conversational; a name present here is conversational
* only when its entry says so (`assistant`/Myra today).
* only when its entry says so (`assistant`/Myra, the only entry today).
*/
export function isConversationalWorkflowName(name: string): boolean {
const entry = byAssetName.get(name);
Expand Down
21 changes: 12 additions & 9 deletions scripts/e2e/smoke-webhook.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ import {
import { describe, expect, test } from "bun:test";

import { resetSchema, setupDatabase } from "../db-setup.ts";
import { buildEchoWorkflow, serializeEchoWorkflow } from "../../workflows/echo/src/index.ts";
import {
buildAssistantWorkflow,
serializeAssistantWorkflow,
} from "../../agents/assistant/src/index.ts";
import { ensureNoopCatalogOffering, startNoopInferenceServer } from "./noop-inference-server.ts";
import { publishCorbitsToolsRegistry } from "../../packages/tool-registry-publish/src/publish.ts";
import { createHubAPI } from "../../packages/hub-api-client/src/index.ts";
Expand Down Expand Up @@ -129,7 +132,7 @@ describe.skipIf(databaseUrl === undefined)("smoke: webhook trigger", () => {
),
);

const assetName = "echo";
const assetName = "assistant";
const { assetId, commitSha } = await hop("workflow asset publication", async () => {
const created = await api(
hub.baseUrl,
Expand All @@ -156,8 +159,8 @@ describe.skipIf(databaseUrl === undefined)("smoke: webhook trigger", () => {
);
expectStatus("mint git token", minted, 201);

const definition = buildEchoWorkflow({
triggerAddress: `echo@${slug}.localhost`,
const definition = buildAssistantWorkflow({
triggerAddress: `assistant@${slug}.localhost`,
inferencePreferences: [{ provider: "anthropic", model: "noop" }],
turnTimeoutMs: 30_000,
});
Expand All @@ -166,7 +169,7 @@ describe.skipIf(databaseUrl === undefined)("smoke: webhook trigger", () => {
tenantId,
assetName,
tokenSecret: stringField(minted.data, "secret", "mint git token"),
workflowJson: serializeEchoWorkflow(definition),
workflowJson: serializeAssistantWorkflow(definition),
});
return { assetId: id, commitSha: pushed.commitSha };
});
Expand Down Expand Up @@ -223,7 +226,7 @@ describe.skipIf(databaseUrl === undefined)("smoke: webhook trigger", () => {
await Bun.sleep(200);
continue;
}
expectStatus("deploy echo workflow", res, 201);
expectStatus("deploy assistant workflow", res, 201);
break;
}

Expand All @@ -239,15 +242,15 @@ describe.skipIf(databaseUrl === undefined)("smoke: webhook trigger", () => {
typeof listed.data === "object" && listed.data !== null && "data" in listed.data
? (listed.data as { data: unknown[] }).data
: (listed.data as unknown[]);
const echo = (rows as { id: string; name?: string }[]).find(
const deployed = (rows as { id: string; name?: string }[]).find(
(row) => row.name === assetName,
);
if (echo === undefined) {
if (deployed === undefined) {
throw new Error(
`no workflow definition named "${assetName}": ${JSON.stringify(listed.data)}`,
);
}
return echo.id;
return deployed.id;
});

const { triggerId, secret } = await hop("webhook trigger creation", async () => {
Expand Down
46 changes: 36 additions & 10 deletions scripts/e2e/walking-skeleton.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@
import { describe, expect, test } from "bun:test";

import { resetSchema, setupDatabase } from "../db-setup.ts";
import { buildEchoWorkflow, serializeEchoWorkflow } from "../../workflows/echo/src/index.ts";
import {
buildAssistantWorkflow,
serializeAssistantWorkflow,
} from "../../agents/assistant/src/index.ts";
import { publishCorbitsToolsRegistry } from "../../packages/tool-registry-publish/src/publish.ts";
import { createHubAPI } from "../../packages/hub-api-client/src/index.ts";
import {
api,
assertNeverRealProvider,
Expand Down Expand Up @@ -143,7 +148,7 @@ async function seedPlaceholderCatalogOffering(options: {
const { tempDir, track } = createCleanupHarness();

describe.skipIf(databaseUrl === undefined)("walking skeleton", () => {
test("fresh schema to addressable echo-workflow deployment", async () => {
test("fresh schema to addressable assistant-workflow deployment", async () => {
const url = databaseUrl;
if (url === undefined) throw new Error("unreachable: suite is skipped");

Expand Down Expand Up @@ -205,11 +210,11 @@ describe.skipIf(databaseUrl === undefined)("walking skeleton", () => {
return stringField(res.data, "id", "create tenant");
});

// Hop: workflow asset. The echo workflow definition, built by its
// own package, published as a workflow asset whose source tree
// arrives over the platform's git smart-HTTP surface — the only
// surface that writes asset tree content.
const assetName = "echo";
// Hop: workflow asset. The assistant (Myra) workflow definition,
// built by its own package, published as a workflow asset whose
// source tree arrives over the platform's git smart-HTTP surface —
// the only surface that writes asset tree content.
const assetName = "assistant";
const { assetId, commitSha } = await hop("workflow asset publication", async () => {
const created = await api(
hub.baseUrl,
Expand All @@ -236,8 +241,8 @@ describe.skipIf(databaseUrl === undefined)("walking skeleton", () => {
);
expectStatus("mint git token", minted, 201);

const definition = buildEchoWorkflow({
triggerAddress: `echo@${slug}.localhost`,
const definition = buildAssistantWorkflow({
triggerAddress: `assistant@${slug}.localhost`,
inferencePreferences: [{ provider: "anthropic", model: PLACEHOLDER_MODEL }],
turnTimeoutMs: 60_000,
});
Expand All @@ -246,7 +251,7 @@ describe.skipIf(databaseUrl === undefined)("walking skeleton", () => {
tenantId,
assetName,
tokenSecret: stringField(minted.data, "secret", "mint git token"),
workflowJson: serializeEchoWorkflow(definition),
workflowJson: serializeAssistantWorkflow(definition),
});
return { assetId: id, commitSha: pushed.commitSha };
});
Expand Down Expand Up @@ -282,6 +287,27 @@ describe.skipIf(databaseUrl === undefined)("walking skeleton", () => {
if (hub.exited()) {
throw new Error(`hub exited before deploy; output:\n${hub.output()}`);
}
// CL-7071 cutover: the launched run's sidecar initialization
// resolves its `@corbits` scope pins against the tenant's own
// `corbits-tools` package registry — Myra's assistant definition
// carries several such pins, so this registry must exist before
// the deploy's sidecar allocation can succeed.
const published = await publishCorbitsToolsRegistry({
api: createHubAPI(hub.baseUrl),
cookies: user.cookies,
hubUrl: hub.baseUrl,
tenantId,
log: () => undefined,
});
if (!published.success) {
if (Date.now() > deadline) {
throw new Error(
`corbits-tools registry publish failed: ${JSON.stringify(published.summaries)}`,
);
}
await Bun.sleep(200);
continue;
}
res = await api(
hub.baseUrl,
"POST",
Expand Down
3 changes: 0 additions & 3 deletions tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,6 @@
},
{
"path": "./vendor/intx/workflow-host/tsconfig.src.json"
},
{
"path": "./workflows/echo/tsconfig.src.json"
}
]
}
5 changes: 3 additions & 2 deletions workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
Owner ruling (2026-09-17, LESS IS MORE): the shipped workflow catalog is
deleted. Myra (`agents/assistant`) creates workflows dynamically for a
tenant's own needs; there is no seeded catalog of ready-made workflows
any more. `workflows/echo` is kept only as the e2e smoke fixture proving
the mail-triggered contract works — it is not a product offering.
any more. The `echo` fixture workflow is gone too — the e2e smoke suites
now deploy Myra's own `agents/assistant` definition to prove the
mail-triggered contract works.

Each subdirectory is a deployable workflow package: an Interchange
`defineWorkflow` plus a `corbits.workflow` block in its `package.json`
Expand Down
Loading
Loading