fused CLI reference
Part of the Fused skill set — don't work from it alone. This is the command reference. For the workflow that decides which commands to run, load
fused-projects(project lifecycle) orfused-widgets(rendering a UI). Seefused-guidefor the full set.
Check the installed version with fused --version (useful for confirming an install before configuring anything).
Environment selection
Every command targets a specific backend. Two ways to select it:
Named environment (recommended) — stored config in ~/.openfused/envs.json:
fused --env prod files list
fused --env staging secrets list
--env can be omitted when the environment is resolved by project manifest or sole-env auto-selection (see resolution rules below).
Legacy inline selection — reads config from environment variables:
fused --backend aws files list # reads OPENFUSED_* env vars
fused --backend local files list # host venvs (uv/pip)
fused --backend fused files list # Fused's managed fused
--env always wins over --backend. OPENFUSED_ENV is the env-var form of --env.
Environment resolution order (first match wins):
--envflag orOPENFUSED_ENV→ explicit override (beats everything)- Inside a project with
[project].default_envinopenfused.toml→ manifest pin - Exactly one named environment exists → sole-env auto-select
- Multiple environments, no pin → error naming both fixes: set
default_env(fused project set <project> --env <name>) or pass--env - No environments → error: run
fused env create
Logging
Host logs (openfused.* loggers) go to stderr with a timestamp + level + logger-name
format. Set verbosity with OPENFUSED_LOG_LEVEL (default INFO; accepts DEBUG,
INFO, WARNING, ERROR, CRITICAL). Use DEBUG to surface Lambda cache hits and
cold-start/digest-resolution details when troubleshooting.
OPENFUSED_LOG_LEVEL=DEBUG fused --env prod code run --file job.py
Environment management (env)
Named environments bundle all backend config into a named entry in ~/.openfused/envs.json.
Create
# AWS — provisions IAM role + Lambda automatically
fused env create prod --backend aws --prefix myapp- --region us-east-1
# AWS — skip provisioning (config only)
fused env create staging --backend aws --prefix myapp-staging- --no-provision
# Local — bare stdlib venv; scaffolds ~/.openfused/envs/dev/data automatically
fused env create dev --backend local
AWS env create runs infra apply automatically unless --no-provision is given. Pass --no-provision when the IAM role / Lambda already exist or when you want to review the plan first.
List and inspect
fused env list # all envs with their backend
fused env show prod # JSON dump of config
fused env show # config for the resolved environment
To pin an environment to a project (the recommended way to avoid per-command --env):
fused project set my-project --env prod # validates env exists, writes default_env
fused project set my-project --clear-env # remove the pin
Update fields
fused env update prod --region us-east-1
fused env update prod --prefix newprefix- --lambda-timeout 600
fused env update prod --audit-bucket my-audit-bucket # add/change audit bucket
fused env update prod --no-audit-bucket # remove audit bucket
fused env update prod --require-spec # block executions without a spec
fused env update prod --no-require-spec # remove spec requirement
fused env update prod -p pandas -p duckdb # set packages (AWS only: baked into the container image)
env update accepts all the same flags as env create (patch semantics — only specified fields change). Use --no-cache-bucket / --no-audit-bucket to clear a bucket field, and --no-require-spec to remove the spec requirement.
Delete
fused env delete staging --yes # removes config only; does NOT teardown AWS resources
Full option reference for env create
| Option | Default | Notes |
|---|---|---|
--backend |
aws |
aws (Lambda), local (host bare venv), or fused (Fused cloud). |
--region |
us-west-2 |
AWS region |
--prefix |
openfused- |
Lambda function name prefix |
--role-arn |
— | Use an existing IAM role instead of creating one |
--role-name |
derived | Override the managed IAM role name |
--lambda-timeout |
300 |
Execution timeout in seconds |
--lambda-memory-mb |
1024 |
Lambda memory (MB). AWS only. |
--lambda-tmp-storage-mb |
512 |
Lambda /tmp ephemeral storage (MB). AWS only. |
--lambda-architecture |
x86_64 |
Lambda CPU architecture (x86_64 or arm64). AWS only. |
--lambda-externally-managed |
off | Don't auto-manage the execution Lambda. AWS only. Skips the GetFunction existence check + CreateFunction at execute time (invokes it by name) and skips planning/applying it in infra plan/apply. Use when the Lambda lifecycle is managed separately (e.g. external IaC). Orthogonal to --role-arn, which only short-circuits the IAM role. On env update, toggle with --lambda-externally-managed / --no-lambda-externally-managed. |
--docker-image |
— | ECR image URI for the Lambda function. Normally set automatically by infra build-image; pass it only to register a pre-built image. |
--cache-bucket |
auto-derived | S3 bucket for input_files in execute_code. Auto-named <prefix>-cache by default |
--no-cache-bucket |
off | Disable the cache bucket for this env |
--audit-bucket |
— | S3 bucket for WORM audit logs. Must have Object Lock enabled; infra apply creates it. Also enables verify. |
--require-spec |
off | Block execute_code calls that omit a spec. Also enables verify. Works on all backends. |
-p / --package |
— | Pip package to pre-install (repeatable). AWS only — baked into the Lambda container image (image_build.packages). Errors if passed to a local env. |
--system-dep |
— | System package via dnf (repeatable). AWS only — errors if passed to a local env. |
--python-version |
3.12 |
Python version for the container image. AWS only — errors if passed to a local env. |
--image-platform |
linux/amd64 |
Docker build platform for the container image. AWS only — errors if passed to a local env. |
--image-repo |
derived from prefix | ECR repository name. AWS only — errors if passed to a local env. |
--image-tag |
latest |
Tag for the container image. AWS only — errors if passed to a local env. |
--builder |
codebuild |
Image builder. AWS only — codebuild (default; remote AWS CodeBuild, no local Docker; uses the cache bucket) or local (docker build on host). Errors if passed to a local env. |
--dockerfile |
— | Path to a user Dockerfile within --context-dir (requires --context-dir). AWS only. |
--context-dir |
— | User build-context directory to build instead of the generated Dockerfile. AWS only. |
--local-path |
~/.openfused/envs/<name>/data |
Local data directory (local only) |
--secrets-file |
~/.openfused/envs/<name>/secrets.json |
Keychain account key identifying the per-env secrets store (local only; no file is written) |
--no-provision |
off | Skip infra apply on AWS |
Fused (managed-openfused) backend — --backend fused
It runs code on Fused's hosted, managed fused environment over its data-plane MCP endpoint (an MCP client of the remote fused tool surface). The local side provisions nothing and runs no code itself. Note: serve and infra commands are not supported on Fused. Create an env with --backend fused:
| Option | Default | Purpose |
|---|---|---|
--tier |
prod |
Service tier selecting the base URL (prod/staging/unstable). |
--mcp-base-url |
— | Explicit data-plane base URL override (dev/self-host). |
--fused-org / --fused-env-id |
— | Org + environment (UUID or slug) for the scoped URL; set together. Omit both to use the bare endpoint with an env-bound key. |
--api-key-secret |
— | Name in fused's local secrets store holding the ofs_ API key. |
fused env create fused-prod --backend fused --tier prod \
--fused-org acme --fused-env-id default --api-key-secret fused/prod-key
The API key is resolved (first hit wins) from --api-key-secret (local secrets store) → FUSED_API_KEY → FUSED_JWT (scoped URL only). serve/infra are not applicable (Fused operates the runtime). Storage is read + presign only (list_files/get_file); writes and secrets are not exposed by the managed surface yet and raise a clear error.
Guided onboarding — the fused cloud group
Instead of hand-building the env above, the fused cloud group runs the control-plane flow (login → find org/env → wait ready → mint an API key → store it → create the env). Auth0 config defaults to Fused's tenant + the openfused-server-api audience; override with FUSED_CLOUD_AUTH0_DOMAIN / FUSED_CLOUD_AUTH0_CLIENT_ID / FUSED_CLOUD_AUTH0_AUDIENCE.
fused cloud login [--no-browser] # Auth0 PKCE; caches a control-plane JWT
fused cloud redeem [CODE] [--tier prod] # redeem a beta invite: admit + create your org + env
# omit CODE and it prompts (keeps it out of shell history)
fused cloud orgs [--tier prod] # list your orgs + envs and their provision_state
fused cloud setup [--tier prod] \ # the one-shot guided flow:
[--beta-code CODE] \ # (optional) redeem a beta invite first, then
[--org O --env E] [--env-name NAME] # pick org/env (auto if you have one), wait ready,
# mint a key, store it, create the `fused` env
fused cloud key create --org O --env E # mint + store a key for an existing managed env
fused cloud key revoke --org O --id K # revoke a data-plane key by id
fused cloud logout [--no-browser] # delete the cached control-plane JWT
fused cloud logout --env NAME # ALSO delete that env's stored data-plane key (full logout)
setup stores the minted key in the local secrets store (e.g. fused/<env-name>-key) and writes a FusedCloudEnvironmentConfig referencing it — never the raw key in envs.json. The fused env name defaults to fused for the canonical default managed env (else fused-<env>). The control-plane JWT is flow-scoped; the MCP server never holds it.
Token resolution at request time (first hit wins): FUSED_API_KEY env var (an explicit override) → the stored api_key_secret → FUSED_JWT (scoped-URL only). A configured-but-absent secret falls through rather than failing. Logging out: fused cloud logout clears the control-plane JWT; add --env NAME to also delete that environment's stored data-plane key (then fused key revoke to revoke it server-side).
Tune a managed env after creation with env update <name> — the managed-fused fields --tier, --mcp-base-url, --fused-org, --fused-env-id, and --api-key-secret are accepted (mirroring env create --backend fused). infra commands are not applicable (Fused operates the runtime) and report that posture.
If you have a beta invite code, redeem it during the beta gate either as a standalone step (fused cloud redeem, after login) or folded into setup (fused cloud setup --beta-code CODE). Redeeming admits your account and creates a personal org with a default environment, which setup then waits on and wires up. The code is single-use; an invalid or already-redeemed code raises a clear error. Prefer the standalone form with the code omitted — it prompts without echo, so the code stays out of ps and your shell history; the same applies to fused cloud accept [TOKEN], the sibling command for joining an org you were invited to.
Verify / security options (all backends)
The verify sub-object controls the pre/post-execution security pipeline for execute_code. Set fields via JSON patch with env update:
# Enable the verify pipeline with type-checking
fused env update prod --verify '{"enabled": true, "typecheck": true}'
# Also run ty inside Docker so user packages are installed (more accurate)
fused env update prod --verify '{"enabled": true, "typecheck": true, "typecheck_docker": true}'
| Field | Default | Notes |
|---|---|---|
enabled |
false |
Master toggle for the full verify pipeline |
typecheck |
false |
Run ty type-checking before execution |
typecheck_docker |
false |
When true and requirements are set, install packages in Docker first for full import resolution |
scan_deps |
true |
Query OSV for CVEs and typosquatting in requirements |
audit_bucket |
— | S3 bucket for WORM audit logs |
audit_key_prefix |
"audit/" |
Key prefix for audit objects |
audit_object_lock_days |
— | Retention days for S3 Object Lock (requires bucket with Object Lock enabled) |
rules |
— | List of {"rule_id": str, "severity": "BLOCK"|"WARN"|"INFO", "enabled": bool} overrides |
typecheck_docker builds a local mirror image (identified by a hash of base image + requirements, cached across runs) with the user's packages installed, using the env's docker_image (or python:3.12-slim) as the base. It requires the docker CLI on PATH — this is verify tooling only, not an execution backend.
Projects (project)
Projects are versioned, deployable collections of UDFs. The on-disk model is: workspace ⊃ project ⊃ UDF. All project commands implicitly target the default workspace at ~/.openfused/workspaces/default/ (override with OPENFUSED_WORKSPACES_DIR).
The first project new call auto-creates the default workspace (git init + installs the openfused-managed v5 pre-commit hook). The hook blocks manual commits that touch a UDF's spec.md without its entrypoint or vice versa. Use git commit --no-verify to bypass it when needed.
Create a project
fused project new taxi-pipeline
# Created project 'taxi-pipeline' at ~/.openfused/workspaces/default/taxi-pipeline
| Argument | Notes |
|---|---|
NAME |
Project name; must match ^[a-z][a-z0-9]*([-_][a-z0-9]+)*$, max 64 chars |
List projects
fused project list
Prints all project names in the default workspace, sorted. Prints a help message when none exist yet.
Add dependencies to a project
fused project add-dep taxi-pipeline duckdb pandas # runtime deps
fused project add-dep taxi-pipeline pytest coverage --dev # dev deps (for `code test`)
Runs uv add [--dev] <packages> then uv sync inside the project's scripts/
dir in one step, so the lockfile and the installed venv stay in step — avoiding
the stale-venv warning (and silent cache-disable) a bare uv add would leave
behind. Never hard-fails on tooling problems (missing uv, OPENFUSED_LOCAL_INSTALLER=pip,
non-zero exit) — it prints a guided Warning: instead. Errors only on an unknown project.
Show a project
fused project show taxi-pipeline
Re-syncs the openfused.toml manifest from disk first (structured merge: discovers UDF folders under scripts/, rewrites inventory, preserves user-set fields like description/auth/cache_max_age and TOML comments), then prints JSON with keys name, path, and udfs. Exits with an error when the project does not exist.
Delete a project
fused project delete taxi-pipeline
Removes a project from the default workspace: git rm -rf -- <name> followed by
a --no-verify commit, then cleans gitignored/untracked residue (scripts/.venv,
__pycache__). Rejects _core and any underscore-prefixed (reserved) name with a
ValueError. Prints JSON {name, deleted, root} on success.
Naming rules
Project and UDF names are lowercase slugs: ^[a-z][a-z0-9]*([-_][a-z0-9]+)*$, max 64 chars. Both - and _ are accepted as segment separators, so snake_case UDF names like list_comments are valid. Lowercase-only prevents case-collision bugs on case-insensitive filesystems and in S3 key segments.
Workspace layout
~/.openfused/workspaces/default/ # the default workspace (one git repo)
├── .git/ # openfused-managed pre-commit hook installed here
├── taxi-pipeline/ # a project = one folder
│ ├── openfused.toml # manifest (synced by the MCP project_show tool / at deploy)
│ ├── SKILL.md # project contract (agents read this for context)
│ ├── assets/ # static project assets
│ ├── references/ # dataset notes + findings (one file per topic)
│ ├── widgets/ # saved project dashboards
│ └── scripts/
│ ├── pyproject.toml # uv-managed Python deps (note: uv's [project] table, not fused's)
│ ├── tests/ # project-level pytest suites
│ ├── taxi-analysis/ # a UDF, kind: py
│ │ ├── main.py
│ │ ├── spec.md
│ │ └── test_main.py
│ └── trip-dashboard/ # a UDF, kind: json
│ ├── main.json
│ └── spec.md
└── sales-app/
UDF kind is inferred from the entrypoint: main.py → py, main.json → json. A folder with both is an error (ambiguous kind). Dot-prefixed and underscore-prefixed directories are skipped.
Authoring UDFs (agent-authored)
There is no udf generate or project regenerate command. UDFs are authored by the driving agent:
- Write
scripts/<name>/spec.mdand get it approved. - Write the entrypoint —
scripts/<name>/main.pyfor apyUDF, orscripts/<name>/main.jsonfor ajsonwidget UDF. - Validate with
fused code verify <file>(CLI) or MCPverify_codebefore committing. - Commit
spec.md+ entrypoint together — the pre-commit hook enforces that spec and entrypoint are always paired in the same commit.
See the fused-projects skill for the full spec-first, agent-authored flow (env → project → UDF → run → widget → deploy).
Deploy a project (project deploy)
Batch-deploys all UDFs in a project to a channel. The workspace must be clean (all changes committed) unless --force is used.
fused project deploy taxi-pipeline # deploy all UDFs to preview
fused project deploy taxi-pipeline --channel release # deploy all UDFs to release
fused project deploy taxi-pipeline --force # bypass dirty-tree check
| Option | Default | Notes |
|---|---|---|
NAME |
required | Project name |
--channel |
preview |
preview or release |
--force |
false | Deploy even with uncommitted changes |
--channel releasebypasses the preview gate and breaks the rollback invariant (rollback targets must be prior release events). Use it only for bootstrapping the very first release URL — never for a routine production release, which goesdeploy(preview) →promote. See thefused-projectsguardrails.
Requires AWS env + cache_bucket + provisioned serving plane (fused infra serve). Echoes the resolved env name; prints one URL per UDF. Exits 1 if any UDF fails to deploy.
Promote a project (project promote)
Batch-promotes all UDFs in a project from preview to release.
fused project promote taxi-pipeline
Show project deploy status (project status)
Shows the live cloud deploy snapshot for a project. Marks UDFs that are in the cloud snapshot but absent on disk as orphaned (prompt to restore or retire).
fused project status taxi-pipeline
Output columns: UDF, CHANNEL, COMMIT, ORPHANED, URL.
Deploy a single UDF (udf deploy)
fused udf deploy analysis --project taxi-pipeline
fused udf deploy analysis --project taxi-pipeline --channel release
fused udf deploy analysis --project taxi-pipeline --force
| Option | Default | Notes |
|---|---|---|
NAME |
required | UDF name |
--project WF |
required | Project that owns the UDF |
--channel |
preview |
preview or release |
--force |
false | Deploy even with uncommitted changes |
Requires a clean git tree (or --force), AWS env + cache_bucket, and a provisioned serving plane. Echoes the resolved env on stderr and prints the channel URL on stdout.
Promote a single UDF (udf promote)
Repoints the release channel to whatever commit preview is currently running.
fused udf promote analysis --project taxi-pipeline
| Option | Default | Notes |
|---|---|---|
NAME |
required | UDF name |
--project WF |
required | Project that owns the UDF |
Roll back a single UDF (udf rollback)
Rolls back the release channel to a prior commit. Defaults to the previous release commit when --to is omitted.
fused udf rollback analysis --project taxi-pipeline
fused udf rollback analysis --project taxi-pipeline --to abc123def
| Option | Default | Notes |
|---|---|---|
NAME |
required | UDF name |
--project WF |
required | Project that owns the UDF |
--to COMMIT |
previous release | Target commit SHA |
Retire a UDF (udf retire)
Revokes the UDF's preview + release mounts, appends a retire event, and drops the UDF from the deploy snapshot. This cannot be undone via this command. Prompts for confirmation unless --yes is passed.
Retire enforces the same workspace-id conflict gate as deploy/promote/rollback: if the live snapshot was written by a different workspace it refuses unless --force is passed (an intentional takeover).
fused udf retire analysis --project taxi-pipeline
fused udf retire analysis --project taxi-pipeline --yes
fused udf retire analysis --project taxi-pipeline --yes --force # take over a foreign-owned UDF
| Option | Default | Notes |
|---|---|---|
NAME |
required | UDF name |
--project WF |
required | Project that owns the UDF |
--yes |
false | Skip the confirmation prompt |
--force |
false | Take over a UDF deployed from a different workspace |
Pre-commit hook (v5)
The workspace .git/hooks/pre-commit is installed/upgraded by project new (and by any bootstrap_workspace call). Current version: v5.
The hook blocks manual commits that touch a UDF's spec.md without its entrypoint (main.py/main.json), or vice versa — including one-sided deletions. The pairing is enforced at depth 4: <project>/scripts/<udf>/<file>. Tests, resource files, and other files commit freely. fused's own auto-commits are always paired and pass through without --no-verify.
Use git commit --no-verify to bypass the hook when needed (e.g. fixing a typo in spec.md alone), but note this bypasses the spec↔entrypoint pairing check.
The hook body embeds # openfused-managed pre-commit hook v5. On re-install, older managed versions are upgraded; unmanaged hooks (no marker) are warned about and never overwritten.
File storage (files)
List
fused files list # list all buckets
fused files list --bucket my-bucket # list all keys in bucket
fused files list --bucket my-bucket --prefix data/2024/
Count
fused files count --bucket my-bucket
fused files count --bucket my-bucket --prefix logs/ --ext .parquet --ext .csv
Get presigned URL
fused files get --bucket my-bucket --key data/report.parquet
fused files get --bucket my-bucket --key data/report.parquet --expires-in 7200
Schema inspection
Prints column names, types, row count, and file metadata for Parquet, Arrow IPC, or CSV files.
fused files schema --bucket my-bucket --key data/report.parquet
Upload
fused files upload data.parquet --bucket my-bucket --key uploads/data.parquet
cat data.csv | fused files upload - --bucket my-bucket --key uploads/data.csv
SRC defaults to stdin when omitted; - also reads from stdin.
The web UI (flow) — separate tool, out of scope
The local web UI (project pages, task → agent runs with live transcripts, the
widget board) is flow — a separate client (fusedio/flow), started with
the flow CLI (or npx @fusedio/flow once published). It is not a fused
subcommand and is out of scope for this reference. flow talks to this backend
over fused dev serve and the _core UDFs. The fused CLI's own human-facing
widget surfaces are fused widget open / the parley on the standalone
widget-host (below) — no full UI needed.
Health check (doctor)
fused doctor surveys every workspace under ~/.openfused/workspaces/
plus the built-in _core workspace and reports per-project health findings. It
exists to turn latent layout/venv drift — the kind that makes a project's widgets
render empty with a confusing `.venv` not found error only at use time — into
one up-front, actionable report.
fused doctor # read-only survey (default)
fused doctor --fix # remediate the fixable findings, then re-diagnose
Read-only by default — diagnosis runs the existing resolvers as pure probes
(never uv sync, never rewrites a manifest, never re-materializes _core). It
prints one block per scope (_core first, then <workspace>/<project>); a scope
with no issues prints OK.
Findings carry a severity — BLOCK (broken / won't run) or WARN (degraded):
rule_id |
Severity | Meaning | Fixable by --fix? |
|---|---|---|---|
invalid-name |
BLOCK | Project dir name is not a valid slug | no (rename manually) |
legacy-layout |
BLOCK | Legacy v1 UDF folder (main.py/main.json) at project root, no scripts/ dir |
yes (migrate) |
venv-missing |
BLOCK | scripts/.venv absent/incomplete |
yes (uv sync) |
venv-stale |
WARN | venv older than pyproject.toml/uv.lock |
yes (rebuild) |
stray-root-venv |
WARN | Stale root .venv beside a valid scripts/.venv |
yes (remove) |
manifest-legacy |
WARN | openfused.toml uses [workflow] not [project] |
yes (migrate) |
manifest-unreadable |
BLOCK | openfused.toml missing/unparseable |
no (repair manually) |
env-unresolved |
BLOCK | Project env doesn't resolve | no (create/pin an env) |
core-uv-missing |
BLOCK | uv not on PATH |
no (install uv) |
core-cache-broken |
BLOCK | A _core project lacks its built venv |
yes (re-materialize) |
core-stale |
WARN | _core cache stamp ≠ installed wheel |
yes (re-materialize) |
--fix applies the fixable findings — migrate first (so scripts/ exists),
then build/refresh the venv, remove stray root venvs, and re-materialize _core —
then re-diagnoses and prints the residual. invalid-name, env-unresolved, and
manifest-unreadable always need a human and are never auto-fixed.
Exit code: doctor exits 1 when any BLOCK finding remains (after
remediation, under --fix); WARN-only or clean exits 0 — so it works as a CI
gate.
Workspace projects (project)
A project is a directory rooted at an openfused.toml manifest — the unit of
scope and memory. Projects are discovered by directory listing
under ~/.openfused/workspaces/default/ (no registry file). Resolution is
git-style: explicit name → OPENFUSED_PROJECT → manifest walk-up from cwd →
global scope (pre-project behavior unchanged).
fused project create taxi --description "NYC taxi analysis" # scaffold under workspaces/default/taxi
fused project create taxi --env prod-aws # scaffold and pin default_env (validates env exists)
fused project list # registered projects (name + path + exists flags)
fused project show [NAME] # the context packet (same as get_project_context)
fused project set NAME --description "new words" --env prod # update the manifest's [project] keys
fused project set NAME --clear-env # remove default_env from the manifest
fused project serve --mcp --project NAME # serve ONE project as a READ-ONLY stdio MCP (the product tier)
project usewas removed (ITEM-11816). Select a project with one of:
export OPENFUSED_PROJECT=NAME— persists across commands in the shellcdinto the project directory — cwd walk-up resolves automatically--project NAMEper individual tool/CLI call
set updates the [project] table of a registered project's openfused.toml
(at least one of --description / --env / --clear-env required; --env
and --clear-env are mutually exclusive). Edits are style-preserving —
comments, formatting, and unknown keys/tables in the manifest survive — and it
prints the updated {name, description, default_env} as JSON.
create scaffolds the standard layout: openfused.toml, a SKILL.md contract,
and the scripts/, widgets/, references/, assets/ convention directories
(each with a README stating its purpose; scripts/ also contains pyproject.toml
and tests/). Each UDF lives as a folder scripts/<name>/ with main.py (or
main.json) as the entrypoint and spec.md as its contract — UDFs anywhere else
are not listed or served. A project scopes:
- environment — the manifest's
default_envis used when no--env/OPENFUSED_ENVoverride is given (explicit override always wins); with a single named environment and no project pin, the sole env is auto-selected; - widget workspace —
widget open/push/watch/parleydefault to the project'swidgets/directory (explicit--dirwins); - audit — every event is stamped with the project name; filter with
audit log --project NAME.
Start agent work in a project with fused project show (or the
get_project_context MCP tool) — one call returns identity, the SKILL.md
contract, reference notes, widgets, UDF scripts, and the resolved environment.
Serve a project as a read-only MCP (project serve --mcp)
fused project serve --mcp [--project NAME] publishes one project as the
read-only external MCP product tier — a queryable data
source any MCP client (Claude Desktop, a BI tool, another agent stack) can connect
to. The closed surface is:
get_project_context— the orientation packet (a pure read);- one tool per published UDF in
scripts/(named by its stable nodeudfName— the file stem; params from the@fused.udfsignature; calling it runs the UDF as a cached query against the project'sdefault_env— never arbitrary code). When the project has a readable pipeline graph (canvas.toml, or the implicit floor derived from{{ref}}/fused.loadscans), each UDF tool's description is enriched read-only with upstream/downstream lineage (reads: …/feeds: …) and the{{ref}}argument names that drive it. A missing/corruptcanvas.tomlsimply omits the annotation — it never breaks tool publishing; widget:///reference://resources — each saved widget (config + resolved data) and reference note, readable point-in-time.
fused project serve --mcp --project taxi-analysis # read-only stdio MCP for one project
fused code serve ./taxi --mcp # equivalent: serve the project DIR read-only
--mcp is required (the only serve mode in Phase 2 — stdio-only). --project
is authoritative (wins the resolution chain); the process cwd is set to the
project root so the data plane resolves default_env and the convention
directories. code serve <project-dir> --mcp is the equivalent path (it serves the
project at that directory).
It never registers a write tool. This is the one hard difference from the bare
fused stdio server (run with no subcommand), which registers the full gated
RW surface (upload_file, put_secret, cache_clear, and the
--enable-infra/--enable-destructive tools). The read-only path is a separate
registry that never constructs those tools — they are unreachable, not merely
gated. It is the binary the app's "Serve as MCP" connect snippet bakes:
<openfused-bin> project serve --mcp --project <name>.
Pipeline graph (pipeline)
The project's UDFs wired into a persisted, versioned graph — nodes, {{ref}}
edges, and a canvas.toml home. Both commands resolve the
project the same way as project show (explicit --project → OPENFUSED_PROJECT
→ openfused.toml walk-up) and emit the Pipeline JSON
({name, path, version, nodes, edges, viewport}) to stdout. They are the seam the
flow UI reads/writes the canvas through; the graph is a design-time
lens and never enters the resolve loop.
fused pipeline graph # read the derived/persisted graph as JSON
fused pipeline graph --project taxi # explicit project
fused pipeline graph --canvas pipelines/reporting/canvas.toml # a named canvas
fused pipeline derive # "create canvas": write canvas.toml + emit reloaded graph
fused pipeline derive --project taxi # explicit project
graphreads the graph: it unions the implicit edges (widget→UDF from the shared{{ref}}SQL scanner, UDF→UDF from a staticfused.load(...)scan) with any explicit[canvas].edgesauthored incanvas.toml. A missingcanvas.tomlstill yields the derivable graph (the implicit-scan floor,versionnull); a corruptcanvas.tomlsurfaces as a CLI error (non-zero exit), never a crash.deriveis the derive-and-persist write path: it runs the implicit scan, lays nodes out left-to-right by stage depth, and writes acanvas.tomlat the project root (or--canvas PATH) capturing the derived nodes- edges as explicit lineage (whole-document atomic write). It then emits the
reloaded graph, so
versionis thesha256:<hex>content hash of the file just written. The Python core is the onlycanvas.tomlwriter (the mutation boundary).
- edges as explicit lineage (whole-document atomic write). It then emits the
reloaded graph, so
Audit log (audit)
fused audit log # last 50 events
fused audit log --limit 100
fused audit log --status blocked # blocked executions only
fused audit log --event-type execute_code --status warned
fused audit log --project taxi # events recorded under one project
Events are read from the local SQLite audit store (~/.openfused/audit.db) — the same database the get_audit_log MCP tool reads, so they persist across server restarts. For durable cross-session/cross-instance history, configure an audit_bucket in the environment; the get_audit_log MCP tool then merges S3-stored events when given a date range.
Secrets (secrets)
fused secrets put db-password # create or update (prompts for the value)
fused secrets get db-password # print value
fused secrets list # all secrets
fused secrets list --prefix db- # filter by prefix
fused secrets delete db-password # delete (prompts; --yes to skip)
secrets delete errors on a missing secret (Secret '<name>' not found). On
AWS the secret is scheduled for deletion with the default 30-day recovery
window, not force-deleted; on the local backend the name is removed from the
OS keychain map immediately. The MCP equivalent (delete_secret) requires the
server to run with --enable-destructive.
Keep the value off the command line. put takes the value from a no-echo
prompt when you omit it, so the secret never lands in argv — where any other
user on the host can read it (ps, /proc/<pid>/cmdline) — or in your shell
history. For scripts and CI, pipe it or point at a file:
printf %s "$DB_PASSWORD" | fused secrets put db-password # piped stdin
fused secrets put db-password --value-file ./db-password # from a file ('-' = stdin)
fused secrets put db-password "s3cr3t" # inline: works, but exposed
One trailing newline is stripped from piped/file input, and an empty read is an error rather than an empty secret.
Naming requirement for Lambda access: the Lambda execution role can only read secrets whose name starts with the environment's function prefix (e.g. openfused-). Always prefix secret names with the function prefix when they need to be read from execute_code:
fused secrets put openfused-db-password # readable from Lambda
fused secrets put db-password # NOT readable from Lambda
Never pass secret values through code run inline code strings — retrieve them inside the execution using openfused.get_secret("openfused-...") (works on AWS and the local backend).
Code execution (code run)
Runs Python code on the active backend (Lambda for AWS, a host-venv subprocess for local). Assign result to return a value.
# Inline code — requires -c/--code
fused code run -c "result = 1 + 1"
# From a file (auto-detected; --file flag is optional)
fused code run myanalysis.py
# From stdin
cat myanalysis.py | fused code run
# Pass local files into the execution context
fused code run myanalysis.py --input-file data.parquet --input-file config.json
pip requirements are configured per-environment via env update -p, not per-call. Set them once:
fused env update prod -p pandas -p duckdb
Output format:
stdoutis printed as-isstderrgoes to stderrresult: <value>is printed whenresultis set
Keep the package set stable across calls. On AWS, packages are baked into the container image — changing them means rerunning fused infra build-image (build + ECR push). On local, venvs are cached by a hash of the package set — changing it rebuilds the venv (seconds with uv).
Caching (cache_max_age / cache_refresh) is not available via code run — it is only exposed through the MCP execute_code tool. Use the MCP tool when you need result memoization.
--monitor-interval (CloudWatch poll seconds during a run) defaults to 10 on code run; the MCP execute_code tool defaults to 30.
Local backend — project venv (--project or --project-dir). On a local environment, pass one of:
--project <name>— workspace-registered project; venv must already exist (uv sync).--project-dir <path>— ad-hoc path to any directory containingopenfused.toml; venv is materialised in place on first run viauv syncin<dir>/scripts/. Use this for skill-folder bundles (e.g.~/.claude/skills/<project>) without registering them in the workspace.
The two flags are mutually exclusive. Both are local-only — rejected with a clear error on AWS and Fused backends. Without either flag, local execution uses a bare stdlib-only venv (third-party imports fail).
# Workspace-registered project (venv must already exist)
fused code run myanalysis.py --project taxi-pipeline
# Ad-hoc path (venv materialised on first run via uv sync)
fused code run myanalysis.py --project-dir ~/.claude/skills/taxi-pipeline
Security scan without execution (code verify)
Scans code and input files for security issues without running it. Packages configured in the resolved environment are scanned for CVEs. Exits 1 if any BLOCK-severity finding is produced.
# Scan a file
fused code verify myanalysis.py
# Inline scan
fused code verify -c "import subprocess; result = 1"
# Scan code + input files for PII and path traversal
fused code verify myanalysis.py --input-file data.csv
# Spec check — Claude reviews whether code matches description (requires an Anthropic
# API key: ANTHROPIC_API_KEY env var, or `fused secrets put anthropic-api-key`, which
# prompts for the key)
fused code verify myanalysis.py --spec "compute the mean of column A"
# Scan using a workspace project's deps
fused code verify myanalysis.py --project taxi-pipeline
# Scan using an ad-hoc project dir's deps (local-only; no backend execute)
fused code verify myanalysis.py
…(truncated)