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
14 changes: 5 additions & 9 deletions apps/hub/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,7 @@ import {
import { createConnectGithubRoutes } from "@corbits/workflow-catalog/connect-github-routes";
import { createTemplateBlockRoutes } from "@corbits/workflow-catalog/template-block-routes";
import { renderWorkflowSourceTree } from "@corbits/workflow-source";
import {
createDefinitionFreezer,
freezeInertWorkflowDefinition,
} from "@corbits/workflow-freeze";
import { freezeInertWorkflowDefinition } from "@corbits/workflow-freeze";
import {
createDrizzleDraftStore,
createDrizzleRoutineStore,
Expand Down Expand Up @@ -2034,13 +2031,12 @@ export async function createHub(config: HubConfig) {
},
};

const definitionFreezer = createDefinitionFreezer(db);
app.route(
`${TENANT_PREFIX}/agent-definitions`,
createAgentDefinitionRoutes({
db,
assetService,
definitionFreezer,
deployer: workflowDeployer,
skillIndex: skills.skillIndex,
skillsStore: definitionSkillsStore,
history: createDefinitionAssetHistory({
Expand Down Expand Up @@ -2070,7 +2066,7 @@ export async function createHub(config: HubConfig) {
createWorkflowAgentCreateRoutes({
db,
assetService,
definitionFreezer,
deployer: workflowDeployer,
skillIndex: skills.skillIndex,
skillsStore: definitionSkillsStore,
capabilityInventory,
Expand All @@ -2092,7 +2088,7 @@ export async function createHub(config: HubConfig) {
createWorkflowCapabilityRoutes({
db,
assetService,
definitionFreezer,
deployer: workflowDeployer,
skillIndex: skills.skillIndex,
skillsStore: definitionSkillsStore,
capabilityInventory,
Expand All @@ -2109,7 +2105,7 @@ export async function createHub(config: HubConfig) {
createWorkflowSkillPinRoutes({
db,
assetService,
definitionFreezer,
deployer: workflowDeployer,
skillIndex: skills.skillIndex,
skillsStore: definitionSkillsStore,
authenticator: createWorkflowRunAuthenticator({ db }),
Expand Down
18 changes: 4 additions & 14 deletions bun.lock

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

30 changes: 22 additions & 8 deletions docs/AGENTS-PAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,32 @@ description) and definition (system prompt, model) and posts to
`POST /api/tenants/:tenantId/agent-definitions`, added by
`@corbits/agent-directory`. The route:

1. Builds a single-step, folded `workflow.json` from the submitted fields
(`buildAgentDefinitionWorkflow`) — the same shape
1. Builds a single-step, folded workflow definition from the submitted
fields (`buildAgentDefinitionWorkflow`) — the same shape
`@corbits/assistant-workflow` and `@corbits/chat`'s workbench host produce,
parametrized instead of fixed.
2. Creates a `workflow`-kind asset and writes that JSON into it in-process
(`AssetService.populateAsset` — no git subprocess).
3. Projects a first-class `workflow_definition` row over the asset
(`ensureWorkflowDefinitionForAsset`).
parametrized instead of fixed — and renders it as a source codebase
(`@corbits/workflow-source`'s `renderWorkflowSourceTree`), never a bare
`workflow.json` envelope.
2. Creates a `workflow`-kind asset and writes that source tree into it
in-process (`AssetService.populateAsset` — no git subprocess), which
produces a commit.
3. Deploys that commit through Interchange's native source pipeline
(install -> sidecar probe -> gate -> freeze) via the same
`WorkflowDeployer` `@corbits/agent-workflow-authoring`'s
agent-authored-workflow registry calls, which projects the first-class
`workflow_definition` row over the asset (CL-7363).

The definition lands with the schema's default status (`deployed`) and a
materialized asset, so it is immediately invitable and launchable — no
separate deploy step, and no page reload needed to see it appear.
separate deploy step, and no page reload needed to see it appear. Every
subsequent edit (instructions, model, tools, skills, restore) writes a new
commit and redeploys the same way; a definition frozen before CL-7363
stays launchable as-is and only redeploys through the native pipeline on
its next edit — no data migration.

A deploy that finds no connected sidecar fails the write outright (502,
`unavailable`) rather than falling back to the old bare-freeze path — no
fallback, per this repo's ground rules.

**Tools and a model provider are not exposed on the create form.** The
platform's wire contract for a workflow definition
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-directory/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
"test": "bun test"
},
"dependencies": {
"@corbits/agent-workflow-authoring": "workspace:*",
"@corbits/chat": "workspace:*",
"@corbits/error-sink": "workspace:*",
"@corbits/folded-run-one-shot": "workspace:*",
"@corbits/skills": "workspace:*",
"@corbits/workflow-catalog": "workspace:*",
"@corbits/workflow-freeze": "workspace:*",
"@corbits/workflow-source": "workspace:*",
"@intx/agent": "workspace:*",
"@intx/db": "workspace:*",
Expand Down
55 changes: 28 additions & 27 deletions packages/agent-directory/src/agent-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,21 @@ import { defineWorkflow, step } from "@intx/workflow";
import type { WorkflowDefinition } from "@intx/workflow";
import type { ToolPackagePin } from "@intx/types/tool-packages";
import type { CredentialBinding } from "@intx/types";
import { and, eq } from "drizzle-orm";
import { and, desc, eq } from "drizzle-orm";
import type { DB } from "@intx/db";
import { asset, workflowDefinition } from "@intx/db/schema";
import { AssetServiceError, DEFAULT_ASSET_REF } from "@intx/hub-sessions";
import { AssetServiceError } from "@intx/hub-sessions";
import type { AssetService } from "@intx/hub-sessions";
import type { DefinitionFreezer } from "@corbits/workflow-freeze";
import {
withAvailableSkills,
type PinnedSkillIndexEntry,
} from "@corbits/skills";
import { type } from "arktype";

import { agentDefinitionSourceTree } from "./definition-asset";
import {
writeAndDeployAgentDefinition,
type AgentDefinitionDeployer,
} from "./definition-asset";
import type { DefinitionSkillsStore } from "./skills-store";

export const AGENT_DEFINITION_STEP_ID = "agent";
Expand Down Expand Up @@ -386,10 +388,12 @@ export function serializeAgentDefinitionWorkflow(
export type CreateAgentDefinitionCoreDeps = {
readonly db: DB["db"];
readonly assetService: AssetService;
/** Freezes the definition's wire projection at create; the
* composition root binds `@corbits/workflow-freeze`'s
* `createDefinitionFreezer` to its own `db`. */
readonly definitionFreezer: Pick<DefinitionFreezer, "freeze">;
/** Deploys the definition's commit through the native source pipeline
* (install -> sidecar probe -> gate -> freeze) at create; the
* composition root injects the SAME `WorkflowDeployer`
* `@corbits/agent-workflow-authoring`'s registry calls, wrapping
* `sessionService.deployWorkflowFromSource`. */
readonly deployer: AgentDefinitionDeployer;
readonly skillIndex: {
resolve(
tenantId: string,
Expand Down Expand Up @@ -550,36 +554,33 @@ export async function createAgentDefinitionCore(
}
}

await deps.assetService.populateAsset({
assetId,
ref: DEFAULT_ASSET_REF,
principal: { kind: "hub" },
tree: {
files: agentDefinitionSourceTree({ handle: input.handle, workflowJson }),
message: `Define agent ${input.name}`,
},
});
await deps.skillsStore.setSkills(assetId, input.skills);

// Freeze, not a bare ensure: `ensureWorkflowDefinitionForAsset` alone
// leaves the version row's `wire_projection` NULL, and a definition
// without a frozen projection can never launch (CL-6447's 409
// `not_launchable`). The freeze projects, walks, and stamps in one
// transaction — the same machinery the sidecar probe deploy rides.
const { definitionId } = await deps.definitionFreezer.freeze({
await writeAndDeployAgentDefinition({
assetService: deps.assetService,
deployer: deps.deployer,
tenantId: input.tenantId,
principalId: input.principalId,
assetId,
handle: input.handle,
workflowJson,
message: `Define agent ${input.name}`,
});
await deps.skillsStore.setSkills(assetId, input.skills);

// The deploy above projects, walks, and stamps the definition row in
// one transaction — the same machinery the sidecar probe deploy
// rides. Read the row back by asset, newest first: a content-unchanged
// redeploy dedupes onto the existing `(assetId, wireHash)` row, so
// this still resolves to the one row a fresh create just produced.
const row = await deps.db.query.workflowDefinition.findFirst({
where: and(
eq(workflowDefinition.id, definitionId),
eq(workflowDefinition.assetId, assetId),
eq(workflowDefinition.tenantId, input.tenantId),
),
orderBy: desc(workflowDefinition.createdAt),
});
if (row === undefined) {
throw new Error(
`agent definition "${definitionId}" was created but is not readable back`,
`agent definition for asset "${assetId}" was created but is not readable back`,
);
}
return { row };
Expand Down
Loading
Loading