Skip to content
Open
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
2 changes: 1 addition & 1 deletion docs/GLOSSARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ lower-level name left to list.
| **Definition** | workflow definition | A deployable unit of agent behavior, authored as code — not a template you mint into many conversations |
| **Workbench Definition** | — | The arktype description a named template instantiates: its default agents, routines, tools, required/optional plugins, and its ordered onboarding walkthrough — see `packages/workflow-catalog/src/templates.ts`'s `WorkbenchDefinitionSchema` |
| **Run** | workflow run | A definition executing in a bench; interactive runs carry conversations |
| **Routine** | — | The named parent entity over runs of one definition — a trigger (or none), a delivery workbench, and its run history; see [`@corbits/routines`](../packages/routines/README.md) |
| **Routine** | — | The named parent entity over runs of one definition — a trigger (or none), launch input, a delivery workbench, enabled state, and an explicit target: the definition's asset id, resolved at each launch to its latest approved deployment; see [`@corbits/routines`](../packages/routines/README.md) and [workflow-model.md](workflow-model.md) |
| **Approval** | approval | A human decision gating an external side effect |
| **Grant** | grant | Permission for a principal to act on a resource |
| **Hub** | hub | The API and coordination service a bench lives on |
Expand Down
113 changes: 113 additions & 0 deletions docs/workflow-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Workflows, agents, routines, and runs — the canonical model

Contract for CL-7349. Read against the vendored pin `a8bc06ae` and upstream
`faremeter/interchange` origin/main `d187e327` (2026-09-01). Every
implementer of the Routines & Workflow Alignment project builds to this
document; a surface that disagrees with it is wrong until this document
changes.

## Five nouns, one execution model

| Noun | What it is | Where it lives |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| **Workflow source** | A versioned code package: `package.json` declaring `interchange.workflow` plus the entry module it names, default-exporting a `WorkflowDefinition` | A tenant-scoped `kind: "workflow"` asset in the hub's git-backed asset store (`AssetService`) |
| **Deployed definition** | The frozen, approved projection of one source commit: `workflow_definition` row with non-null `approved_wire_hash`, `grant_snapshot`, `wire_projection`, `status: deployed` | Produced only by Interchange's source pipeline (`deployWorkflowFromSource`: bundle → sidecar probe → capability walk → gate → freeze) |
| **Agent** | A single-step conversational workflow. A product category, not an execution primitive | Same asset + same deployed definition as any workflow |
| **Routine** | Trigger, launch input, delivery configuration, enabled state, and an explicit reference to a definition | `@corbits/routines` (`routine` table) |
| **Run** | One execution of an approved deployed definition | `workflow_run`, launched by `launchAndCorrelate` |

`workflow.json` is retired. It is not an authoring format, not a
compatibility format, and no path may read or write it. The push validator
(`vendor/intx/hub-sessions/src/workflow-kind.ts`) refuses it;
`@corbits/workflow-source`'s `RetiredWorkflowEnvelopeError` is the only
remaining mention, and it exists to reject.

## Definition identity and the follow-latest rule

Interchange keys `workflow_definition` on `(asset_id, wire_hash)`
(`vendor/intx/hub-sessions/src/workflow-definition-ensure.ts`). Every
redeploy of a source asset whose probed wire hash differs mints a **new**
definition row. There is no native "definition id → newest approved
deployment" indirection; every native launch names an exact definition.

Product ruling (2026-09-01): a routine follows its target's latest approved
deployment and does not pin the version selected at creation. Under the
identity above that means:

- A routine stores the **definition asset id** (`routine.definition_asset_id`,
`NOT NULL`). The asset is the stable identity of a workflow across
redeploys.
- At launch, the routine's target resolves to the newest `workflow_definition`
row for that asset with `status = 'deployed'` and non-null
`approved_wire_hash`, `grant_snapshot`, and `wire_projection`. Resolution
is atomic with the launch and fails closed when no such row exists.
- Routine reads return both the asset id (identity) and the currently
resolved definition id (what would run now) so UI and Myra can show them.

Nothing in Workbench pins a wire hash or copies a projection into routine
storage.

## Authority boundaries

| Operation | Canonical operation | Authorized as | Human approval |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| Store source (create / republish) | `@corbits/agent-workflow-authoring` registry → `AssetService.createAsset` / `populateAsset` (hub-signed commit) | Initiating tenant + principal; `@intx/authz` `authorize` on `asset:*`/`create` or `asset:<id>`/`write` | None (writing source is not a side effect) |
| Deploy source | `POST /api/tenants/:tenantId/workflows/deployments` → vendored `SessionService.deployWorkflowFromSource` | Tenant session or run bearer; `workflow:*`/`create` | Agent-initiated deploys go through an `approval: "ask"` tool call carrying the probed capability surface (below) |
| Create / update a routine | `createRoutineRoutes` `POST /routines`, `PATCH /routines/:id`; the run-authenticated mirror `createWorkflowRoutineRoutes` delegates to the same store | Tenant + principal; target validated against the resolution rule above before persisting | None; a routine only references a definition asset — nothing executes at create/update time |
| Launch | `launchAndCorrelate` (`packages/routines/src/routes.ts`) → hub `RoutineLauncher` | Routine's tenant; grants materialized by the native launch path | Runtime tool calls with `approval: "ask"` park on the native `approval` resource |
| Approve | Native `POST /api/tenants/:tenantId/approvals/:id/approve` | A principal holding `approval:*`/`resolve` — a human; no agent holds it | This is the approval |

Credentials and resolved provider secrets never enter source trees, deploy
requests recorded by `@corbits/workflow-deploy-source`, or routine rows.
Inference sources are re-resolved from the tenant catalog at deploy and
redeploy (`resolveDefinitionSources`).

### Deploy approval for agent-authored workflows

Upstream's deploy route freezes with `approvals: { mode: "approve-probed" }`
(`vendor/intx/hub-sessions/src/session-service.ts`); the `ApprovalSet`
gate exists as a policy type but has no pending-approval record. The only
native pending-approval store is the runtime `approval` resource that an
`approval: "ask"` tool call parks on. Workbench composes those two seams and
adds no approval table:

1. Myra calls a preview operation that runs the native probe with an empty
`ApprovalSet` and returns the walked grant surface plus the wire hash. No
freeze.
2. Myra calls `workflow_deploy` (`approval: "ask"`) with the asset id,
commit sha, expected wire hash, and that grant list. The tool call parks;
the human sees exactly what will be approved.
3. On approval the tool posts to the native deployments route. The native
probe re-runs; a wire hash that differs from the approved one fails
closed. Rejection leaves the source intact and the definition
unlaunchable.

Myra cannot resolve approvals: `approval:*`/`resolve` is never minted for an
agent principal.

## Behavior to delete, not retain

- Routine target inference from chat membership: `resolveCreateTarget` in
`apps/web/src/shell/routine-panel.tsx` (`agents[0]?.definitionId`,
`ensureMyraWorkbench` fallback) and the "no agent invited" guards around
it.
- Hub-local self-freeze of agent-authored definitions:
`packages/agent-directory`'s use of `@corbits/workflow-freeze`
`DefinitionFreezer`, and the template-block inert freeze in
`apps/hub/src/index.ts`, once those callers deploy natively.
- Duplicate routine wire shapes in `packages/routines-tools/src/client.ts`
(use `@corbits/routines/client`).
- Any code path that reads or writes `workflow.json`.

No compatibility shim, feature flag, or dual-write period accompanies any of
these deletions.

## What is not native, and stays in Workbench

Checked against upstream origin/main `d187e327`: Interchange has no
routine or scheduler (its `ScheduleTrigger` type has zero consumers), no
per-principal "launchable definitions" query, and no agent-facing tool that
writes assets. Those three compositions are Workbench's, built over native
rows and `@intx/authz`. Before adding anything else, check upstream first;
if upstream has it past our pin, re-vendor at that commit rather than
reimplementing.
131 changes: 131 additions & 0 deletions docs/workflow-source-authoring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Authoring workflow source as a hub asset

Contract for CL-7352: how an agent (Myra) or a person writes a workflow code
package into the hub and gets it deployed through Interchange's native
pipeline. Companion to [workflow-model.md](workflow-model.md).

## The package shape

A `kind: "workflow"` asset carries an ordinary code package. The push
validator (`vendor/intx/hub-sessions/src/workflow-kind.ts`,
`workflowKindHandler.validatePush`) accepts nothing else.

```
package.json — name, version, "type": "module", "interchange": { "workflow": "./workflow.ts" }
workflow.ts — default-exports defineWorkflow({ ... }) from @intx/workflow
<other files> — anything the entry imports; no secrets, no .env
```

Rules the authoring boundary enforces before any write:

- Paths are repo-relative, normalized, no `..`, no leading `/`, no
`.git/`. Names matching secret-like patterns (`.env*`, `*.pem`, `*.key`,
`id_rsa*`) are rejected.
- `package.json` parses, declares a non-empty `interchange.workflow`, and
that entry exists in the same tree.
- Total tree size and per-file size are capped.
- The asset name is lowercase-kebab (`ASSET_NAME_PATTERN`).

Inert JSON definitions (`export default {...}` written by
`renderWorkflowSourceTree`) remain a valid package shape for hub-generated
single-step agents, because they are code the sidecar evaluates. They are
not what an agent authors by hand.

## The operations, in order

| Step | Operation | Authorized as | Returns |
| ---- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------- |
| 1 | `POST /api/workflow-workflow-authoring/author` (`@corbits/agent-workflow-authoring`) → `AssetService.createAsset` + `populateAsset` | Run bearer + run address → tenant/principal; `asset:*`/`create` | `{ assetId, name, commitSha }` |
| 1' | `.../republish` → `populateAsset` on `refs/heads/main` | `asset:<assetId>`/`write`, own-tenant row check first | `{ assetId, name, commitSha }` |
| 2 | Preview: native probe with empty `ApprovalSet`, no freeze | Same run scope | `{ wireHash, grants[] }` or an invalid-package error |
| 3 | `POST /api/tenants/:tenantId/workflows/deployments` | `workflow:*`/`create`; parked on `approval: "ask"` when agent-initiated | `WorkflowDeploymentResponse { id, definitionAssetId, status }` |
| 4 | Human resolves the parked approval (native `approvals` route) | `approval:*`/`resolve` | Deploy continues or is rejected |
| 5 | `workflow_definition` row frozen; appears in routine target discovery | — | Launchable |

The deploy body is the same one `packages/hub-client/src/seed.ts` sends:

```json
{
"source": {
"kind": "asset",
"assetId": "<assetId>",
"package": { "format": "source", "commitSha": "<sha>" }
},
"entry": "./workflow.ts",
"sources": [
{
"id": "...",
"provider": "...",
"baseURL": "...",
"apiKey": "...",
"model": "..."
}
],
"defaultSource": "..."
}
```

`sources` is resolved server-side from the tenant's inference catalog for
agent-initiated deploys; an agent never supplies or sees provider secrets.
`commitSha` is the pin: the same asset at a different commit is a different
deploy. `@corbits/workflow-deploy-source` records `{ assetId, commitSha,
entry }` per placement so redeploy re-resolves sources fresh from the
recorded initiating principal.

## Identity, conflicts, idempotency

- Asset identity is the asset id; the human-readable name is unique per
tenant (`duplicate_asset` → 409 conflict).
- A republish carries `expectedHeadSha`. If the ref moved, the write is
rejected with 409 and the current head; the caller re-reads and retries.
Nothing is silently overwritten.
- Writing an identical tree is a no-op commit (content-aware, like the CLI
pusher). Retrying an `author` after a network failure hits
`duplicate_asset`; the caller then republishes.
- An authored-but-never-deployed asset is a draft by state, not by table:
it has no `workflow_definition` row. It stays in the asset store until
deleted; it never appears in routine target discovery.
- Every operation is authorized as the run's own tenant and principal
(`WorkflowRunAuthenticator`); no tool argument names a tenant or asset it
cannot already reach.

## Sequence

```mermaid
sequenceDiagram
participant H as Human
participant M as Myra (run)
participant A as agent-workflow-authoring
participant S as AssetService (git)
participant D as /workflows/deployments
participant P as Sidecar probe
participant R as Routine targets

H->>M: "make a routine that does X"
M->>A: author { name, files }
A->>A: authorize asset:*/create, validate paths + package
A->>S: createAsset + populateAsset (hub-signed commit)
S-->>M: { assetId, commitSha }
M->>A: deploy preview { assetId, commitSha }
A->>P: probe (empty ApprovalSet, no freeze)
P-->>M: { wireHash, grants }
M->>M: workflow_deploy (approval: ask) parks
H->>H: inspects asset, commit, grants; approves
M->>D: POST { source: asset/source/commitSha, entry }
D->>P: bundle, probe, capability walk
P-->>D: wireHash must equal approved; else fail closed
D->>D: freeze workflow_definition (approved_wire_hash, grant_snapshot)
D-->>M: deployment { definitionAssetId }
R-->>H: definition selectable as routine target
```

## Seams that do not exist yet (and where they go)

- An `@intx/agent` tool bundle over the authoring routes
(`@corbits/workflow-authoring-tools`): CL-7360.
- A run-authenticated preview route and the `workflow_deploy` tool: CL-7361,
CL-7362.
- Path/package validation in `agent-workflow-authoring`'s registry:
CL-7360.

Nothing here adds a repository, compiler, probe, freezer, or approval store.
Loading