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
58 changes: 29 additions & 29 deletions orchestrator-v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ A rebuild of the Bifrost orchestrator as a thin **get-work + dispatch** system.

## Packages

| Package | Purpose |
| -------------------------------- | -------------------------------------------------------------------- |
| `@bifrost-ai/interfaces-work` | Work item, handler, and execution contracts |
| `@bifrost-ai/protocol` | Signed WebSocket RPC between orchestrator and runners |
| `@bifrost-ai/orchestrator` | Thin orchestrator: stream work items, dispatch, record outcomes |
| `@bifrost-ai/runner` | Remote runner: config-driven dial, execute handlers, report outcomes |
| `@bifrost-ai/engine` | Engine interface, types, and `TestEngine` for development/testing |
| `@bifrost-ai/engine-claude-code` | Claude Code Agent SDK engine (`ClaudeCodeEngine`) |
| `@bifrost-ai/engine-cursor` | Cursor SDK engine (`CursorEngine`) |
| `@bifrost-ai/agent-3-task` | Task Agent — single-shot LLM execution (`kind: "task"`) |
| Package | Purpose |
| -------------------------------- | ----------------------------------------------------------------- |
| `@bifrost-ai/interfaces-work` | Work item, handler, and execution contracts |
| `@bifrost-ai/protocol` | Signed WebSocket RPC between orchestrator and runners |
| `@bifrost-ai/orchestrator` | Thin orchestrator: stream work items, dispatch, record outcomes |
| `@bifrost-ai/runner` | Remote runner: script stack execution, conventions, decorators |
| `@bifrost-ai/engine` | Engine interface, types, and `TestEngine` for development/testing |
| `@bifrost-ai/engine-claude-code` | Claude Code Agent SDK engine (`ClaudeCodeEngine`) |
| `@bifrost-ai/engine-cursor` | Cursor SDK engine (`CursorEngine`) |
| `@bifrost-ai/agent-3-task` | Task Agent — single-shot LLM execution (registered as scripts) |

For design background and how each piece fits together, see [docs/](docs/).

Expand All @@ -36,32 +36,32 @@ vp run -r build # build all packages

## Usage

### Define a work item handler
### Register a script on the runner

Handlers are registered on the runner and executed when a matching work item is dispatched. Higher-level agents (Task Agent, Workflow Agent) build on this interface.
Scripts and decorators are registered on the runner. Dispatch resolves `workItem.kind` to a script and `workItem.flow` to an ordered list of decorators. Runner-level **conventions** wrap every execution (including the built-in `failOnError` decorator).

```typescript
import type { WorkItemHandler } from "@bifrost-ai/interfaces-work";

const echo: WorkItemHandler = {
kind: "script",
name: "echo",
async run(workItem, ctx) {
const message = workItem.metadata.message as string;
await ctx.setState({ echoed: message });
return { outcome: "completed", message };
},
import type { ScriptFn } from "@bifrost-ai/interfaces-work";
import { Runner } from "@bifrost-ai/runner";

const echo: ScriptFn = async (workItem, ctx) => {
const message = workItem.metadata.message as string;
await ctx.setState({ echoed: message });
return { outcome: "completed", message };
};

const runner = new Runner();
runner.registerScript("echo", echo);
```

A handler receives:
A script receives:

- `workItem` — the dispatched instance (`workItemId`, `kind`, `name`, `state`, `metadata`)
- `ctx.data` — `get(type)` returns a typed `Registry<T>`, then `.get(name)` for the instance
- `ctx.handlers` — `get(kind, name)` for other registered handlers
- `workItem` — the dispatched instance (`workItemId`, `kind`, `flow`, `state`, `metadata`)
- `ctx.cwd` — working directory for local execution
- `ctx.data` — typed sub-registries (`ctx.data.get("engine").get("claude")`)
- `ctx.setState(state)` — persist state updates back to the work item source

It returns `{ outcome: "completed" | "failed" | "paused", message?, telemetry? }`. A thrown error is treated as `failed`.
It returns `{ outcome: "completed" | "failed" | "paused", message?, telemetry? }` or any other value (treated as completed). Thrown errors are caught by the `failOnError` convention.

### Implement a work item source

Expand All @@ -74,8 +74,8 @@ const workItemSource: WorkItemSource = {
async *watchWorkItems() {
yield {
workItemId: "work-item-1",
kind: "script",
name: "echo",
kind: "echo",
flow: [],
state: {},
metadata: { message: "hello" },
} satisfies WorkItem;
Expand Down
24 changes: 13 additions & 11 deletions orchestrator-v2/docs/runner.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,43 @@ The `@bifrost-ai/runner` package provides a config-first `Runner` class:

- Dials the orchestrator on `start()`, sends periodic heartbeats
- Validates every inbound orchestrator frame against the configured orchestrator public key
- Executes registered work item handlers locally with an RPC-backed `WorkItemExecutionContext`
- Executes registered scripts locally through a composable decorator stack with RPC-backed `ScriptContext`
- Reports terminal outcomes (`workItem.complete`, `workItem.fail`, `workItem.pause`) over signed RPC

### Developer workflow

Scripts and other runnable agents are enrolled incrementally. Create the data registry up front with type guards, then register instances into typed sub-registries.
Scripts, decorators, and task agents are enrolled incrementally. Create the data registry up front with type guards, then register scripts and optional decorators.

```typescript
import { Runner, createDataRegistry } from "@bifrost-ai/runner";
import "@bifrost-ai/agent-3-task/augment";
import { echo } from "./scripts/echo.js";
import { doSomething } from "./scripts/doSomething.js";
import { loadAgent, taskAgentDataGuards } from "@bifrost-ai/agent-3-task";

const data = createDataRegistry(taskAgentDataGuards);
const runner = new Runner({ data });

runner.registerEngine("claude", claudeEngine);
runner.registerTaskAgent("reviewer", await loadAgent("./agents/reviewer/AGENT.md"));
runner.registerScriptAgent("echo", echo);
runner.registerScriptAgent("doSomething", doSomething);

await runner.start();
```

`registerTaskAgent(name, agent)` registers the handler under the dispatch name and stores the definition in `data.get("agentDefinition")` for lookup by other agents.
`registerTaskAgent(name, agent)` registers a script under `kind: name` and stores the definition in `data.get("agentDefinition")`.

When `runner.yaml` (or `.bifrost-runner.yaml`) is present, keys and orchestrator URL are loaded automatically inside `start()` — no manual key handling required.

### Registry model
### Script stack model

| Kind | Setup | Dispatch | Handler access |
| ----------- | ------------------------------------------------------------------- | --------------------------------- | -------------------------------------- |
| **Data** | `createDataRegistry(guards)` then `.get(type).register(name, item)` | Not searched | `ctx.data.get(type).get(name)` — typed |
| **Handler** | `registerWorkItemHandler(handler)` | `workItem.kind` + `workItem.name` | `ctx.handlers.get(kind, name)` |
| Layer | Setup | Dispatch |
| -------------- | ------------------------------------------------------------------- | ------------------------------------------------------------ |
| **Data** | `createDataRegistry(guards)` then `.get(type).register(name, item)` | Available via `ctx.data` in scripts and decorators |
| **Script** | `registerScript(kind, fn)` | `workItem.kind` |
| **Decorator** | `registerDecorator(name, fn)` | Names in `workItem.flow` (outermost first) |
| **Convention** | `addConvention(name)` | Runner-level; wraps every work item (default: `failOnError`) |

Engines live in the data registry. Task agents (`kind: "task"`), scripts, and workflow agents are work item handlers. Dispatch looks up handlers by `(kind, name)`.
Engines live in the data registry. Task agents register as scripts. Dispatch composes `conventions → flow → script`. See [script-stack.md](temporal/script-stack.md).

### Config file (`runner.yaml`)

Expand Down
Loading