Exec
One-off TypeScript execution against your workspace tools.
hrbr exec runs a TypeScript snippet inside a Harbor Cloud isolate, with
your workspace's plugin namespaces, orbit.*, and sand.* pre-bound as
globals. It's the most flexible way to use Harbor and the entry-point for
everything else.
hrbr exec 'return await linearMcp.listIssues({ limit: 5 })'What's available inside the isolate
| Binding | Source | Example |
|---|---|---|
<namespace> | Every installed plugin in the workspace | linearMcp.listIssues({...}), githubMcp.searchCode({...}) |
orbit.* | Workspace runtime primitives | orbit.storage.put('report', body), orbit.ai.run({...}) |
sand.* | Bridges to local CLIs on the caller's machine | sand.git.status({}), sand.gh.prsList({}) |
step.* | Durable execution atoms — auto-routes to Workflows | step.do('fetch', () => ...), step.sleep('10m') |
The isolate does not have:
- Arbitrary filesystem access
- Process env vars
fetch()to arbitrary URLs (only plugin invokers can talk to upstreams)- Other workspaces
Output format — TOON
Exec returns a TOON-formatted result on stdout (a structured plain-text format optimized for both LLM consumption and human reading). Shape the returned value in your code when you need a smaller or more structured output:
hrbr exec 'return { issues: await linearMcp.listIssues({}) }'
hrbr exec 'const issues = await linearMcp.listIssues({}); return { count: issues.length }'The 30-second cap
Synchronous exec runs are capped at 30 seconds of wall time. Anything longer must route through the Workflows lane (see below).
Durable execution — the Workflows lane
If your code references step.*, the API auto-routes the run to
Cloudflare Workflows:
hrbr exec `
const issue = await step.do("get-issue", () => sentryMcp.getIssue({...}));
await step.sleep("cool-down", "10m");
const commit = await step.do("get-commit", () => githubMcp.getCommit({...}));
return { issue, commit };
`You get:
- Per-step checkpoints — failure of one step doesn't replay earlier ones
- Real time-based backoff —
step.sleep('10m')doesn't burn isolate CPU - External-event resumption —
step.waitForEvent('approval')parks the run untilPOST /runs/eventsarrives - Multi-step pipelines that exceed the 30s cap
There's no flag, no separate command. Writing step.* is the entire opt-in.
See the meta-skill
harbor-workflows
for the full pattern.
When to use Exec (vs Function vs App)
| Want | Use |
|---|---|
| One-off run, returning a result | Exec |
| Smoke test the workspace | Exec |
| Chain a few plugin calls and see what comes back | Exec |
| Same code reusable across runs | Function (next) |
| Schedulable | Function |
| Reachable at an HTTP URL | App (next) |
The natural arc: prototype in Exec, publish a Function with
hrbr inspect -f ./thing.job.ts, then publish an App with
hrbr inspect -f ./thing.app.ts if you want a URL.
Tracing
Every Exec run is recorded as a run with a span tree (one span per
plugin tool call, one per step.*, one per orbit.* write).
See Runs & Traces.
hrbr inspect 'return await hrbr.runs.list({ limit: 5 })'
hrbr inspect 'return await hrbr.runs.get({ run_id: "<run-id>" })'
hrbr inspect 'return await hrbr.runs.graph({ run_id: "<run-id>" })'Limits
| Limit | Sync exec | Workflow lane |
|---|---|---|
| Max wall time | 30 s | up to plan limit (hours/days) |
| Max heap | 128 MB | 128 MB per step |
| Max plugin calls per run | none enforced, but billed | none enforced |
| Max output size | 1 MB | 1 MB |
Plan-specific caps live in Limits.