HarborHarbor
DocumentationGuidesPlugins
Concepts

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

BindingSourceExample
<namespace>Every installed plugin in the workspacelinearMcp.listIssues({...}), githubMcp.searchCode({...})
orbit.*Workspace runtime primitivesorbit.storage.put('report', body), orbit.ai.run({...})
sand.*Bridges to local CLIs on the caller's machinesand.git.status({}), sand.gh.prsList({})
step.*Durable execution atoms — auto-routes to Workflowsstep.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 backoffstep.sleep('10m') doesn't burn isolate CPU
  • External-event resumptionstep.waitForEvent('approval') parks the run until POST /runs/events arrives
  • 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)

WantUse
One-off run, returning a resultExec
Smoke test the workspaceExec
Chain a few plugin calls and see what comes backExec
Same code reusable across runsFunction (next)
SchedulableFunction
Reachable at an HTTP URLApp (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

LimitSync execWorkflow lane
Max wall time30 sup to plan limit (hours/days)
Max heap128 MB128 MB per step
Max plugin calls per runnone enforced, but billednone enforced
Max output size1 MB1 MB

Plan-specific caps live in Limits.