| name | fused-execute |
| description | Best practices for running code through fused's execute_code tool. Use when writing or reviewing any mcp__openfused__execute_code call — covers how to structure user code, choose a data library, handle results, and write outputs to the file store. For security scanning, spec checks, and testing see fused-verify. If this is part of building or running a project, load `fused-projects` first for the end-to-end model (and `fused-widgets` if the result is a rendered UI). |
Running code via fused
Part of the Fused skill set — don't work from it alone. Fused is workspace ⊃ project ⊃ UDF. If the task is part of building or running a project (not a
one-off snippet), load fused-projects for the end-to-end model, and
fused-widgets if the result is a rendered UI. See fused-guide for the
full set.
Core principle: code should do one thing
The code string passed to execute_code should be as focused as possible:
- One clear task per execution
- No boilerplate, no CLI argument parsing, no
if __name__ == "__main__" guards
- Return the result from a
@fused.udf-decorated function (preferred), or assign it to a top-level result variable
Choosing how to return: @fused.udf vs result
Prefer a @fused.udf entrypoint over a top-level result = assignment. Both
return the value identically, but a UDF is the more capable, portable form:
- It takes parameters, so the same code runs standalone and as a fan-out worker
(
.map() / fused.load()) or a served route — kwargs arrive via _openfused_args.json.
- It matches the real
fused SDK, so code moves between fused and Fused unchanged.
- It keeps the return value in an explicit
return, not a magic module global.
import fused
@fused.udf
def main(threshold: int = 0):
...
return {"count": n}
Use a bare result = <scalar> only for a quick one-off where there are no
parameters and you won't reuse the code (e.g. result = df.shape[0]).
The two are mutually exclusive — a script returns via a @fused.udf or a
result variable, never both; using both (including result = None alongside a
UDF) is an error.
Returning results
Prefer writing to S3 over returning data inline.
The return value (whether from a @fused.udf or a result variable) is serialized as a string in the response. That works fine for scalars (a URL, a count, a status message), but large DataFrames or binary blobs will be truncated or unusable.
The right pattern:
- Compute the output inside
execute_code
- Write it to S3 using
boto3 inside the same code block
- Return the S3 key or a presigned URL — not the data itself
- After execution, call
mcp__openfused__get_file with that key if a download URL is needed
Only return inline when the value is a simple scalar: a number, a short string, a boolean.
Writing DataFrames to S3 inside execute_code
Always write DataFrames to the environment's cache bucket rather than returning them inline. Get the bucket name first with mcp__openfused__env_show, then embed it in the code string (it is not a secret).
code = """
import boto3, io
# ... compute df ...
buf = io.BytesIO()
df.to_parquet(buf, index=False)
buf.seek(0)
s3 = boto3.client("s3")
bucket = "openfused-abc123" # from env_show cache_bucket
key = "outputs/my_result.parquet"
s3.put_object(Bucket=bucket, Key=key, Body=buf.read())
result = f"s3://{bucket}/{key}"
"""
Choosing what to return as result:
| Use case | Return |
|---|
| Internal / follow-up tool calls | f"s3://{bucket}/{key}" (path only) |
| User download / public sharing | Presigned URL — either from mcp__openfused__get_file after execution, or generated inside execute_code with boto3 |
For a presigned URL via a follow-up tool call: mcp__openfused__get_file(bucket="openfused-abc123", key="outputs/my_result.parquet").
To generate the URL inside execute_code (avoids an extra round-trip when the user explicitly asks for a URL):
import boto3, io
s3 = boto3.client("s3", region_name="us-west-2")
bucket, key = "openfused-abc123", "outputs/my_result.parquet"
s3.put_object(Bucket=bucket, Key=key, Body=buf.read())
url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": bucket, "Key": key},
ExpiresIn=3600,
)
result = url
When writing output via DuckDB COPY … TO 's3://…' is not possible due to region/credential mismatch, write to /tmp/ first then upload with boto3:
conn.execute("COPY (SELECT …) TO '/tmp/output.parquet'")
with open("/tmp/output.parquet", "rb") as f:
s3.put_object(Bucket=bucket, Key=key, Body=f.read())
Choosing a data library
Prefer libraries in this order:
- DuckDB — best default for SQL-style analysis, reading Parquet/CSV directly from S3, and aggregations over large files. Zero-copy, no materialisation until needed.
- Polars — preferred for Python-native DataFrame transformations. Lazy API, fast, low memory footprint.
- Pandas — use only when an existing snippet, library, or output format requires it.
DuckDB S3 setup
AWS Lambda: Lambda has no writable home directory by default. Always create one in /tmp and pass it via the config dict — using SET home_directory=... after connection does not work reliably in Lambda.
import duckdb, os
os.makedirs("/tmp/duckdb_home", exist_ok=True)
conn = duckdb.connect(config={"home_directory": "/tmp/duckdb_home"})
conn.execute("SET s3_region='us-east-1'")
conn.execute("INSTALL httpfs; LOAD httpfs")
S3 region: set s3_region to the region of the bucket you are reading from, which may differ from the Lambda's own region. Public buckets (e.g. fused-asset) are typically in us-east-1; the fused cache bucket (openfused-cache) is in us-west-2. If you read from one and write to the other, update s3_region between the two operations.
requirements = ["duckdb"]
code = """
import duckdb
con = duckdb.connect()
result_df = con.execute(
"SELECT col, COUNT(*) FROM read_parquet('s3://my-bucket/data/*.parquet') GROUP BY col"
).df()
# write result_df to S3 cache bucket ...
"""
requirements = ["polars", "pyarrow"]
code = """
import polars as pl
df = pl.read_parquet("/tmp/context/input.parquet")
out = df.filter(pl.col("value") > 0).group_by("category").agg(pl.col("value").sum())
# write out to S3 cache bucket ...
"""
Passing file paths to DuckDB — Python variables vs SQL
DuckDB's conn.execute(sql) runs SQL; it cannot reference Python variables by name. A common mistake when building file lists dynamically:
paths = ["s3://bucket/a.parquet", "s3://bucket/b.parquet"]
conn.execute("SELECT * FROM read_parquet(paths)")
conn.execute("SELECT * FROM read_parquet(?)", [paths])
Geospatial data
For any spatial analysis involving latitude/longitude points, use H3 (Uber's hierarchical hexagonal grid). It is faster to join, aggregate, and visualize than raw coordinates.
Common patterns:
requirements = ["h3", "polars", "pyarrow"]
code = """
import h3
import polars as pl
df = pl.read_parquet("/tmp/context/points.parquet") # has lat, lng columns
# Index points into H3 hexagons at resolution 8 (~460 m)
df = df.with_columns(
pl.struct(["lat", "lng"])
.map_elements(lambda r: h3.latlng_to_cell(r["lat"], r["lng"], 8), return_dtype=pl.Utf8)
.alias("h3_cell")
)
# Aggregate per cell
agg = df.group_by("h3_cell").agg(pl.len().alias("count"))
buf = __import__("io").BytesIO()
agg.write_parquet(buf)
buf.seek(0)
__import__("boto3").client("s3").put_object(Bucket="openfused-abc123", Key="outputs/h3_agg.parquet", Body=buf.read())
result = "s3://openfused-abc123/outputs/h3_agg.parquet"
"""
Choose the H3 resolution based on desired granularity:
| Resolution | Avg cell area | Typical use |
|---|
| 5 | ~252 km² | Country/region |
| 7 | ~5.2 km² | City district |
| 8 | ~0.7 km² | Neighborhood |
| 10 | ~15 000 m² | Block |
| 12 | ~320 m² | Parcel / fine-grained point |
Requirements
Requirements are configured per-environment via fused env update -p <pkg> and applied automatically to every execute_code call — no requirements field on the call itself.
fused env update prod -p duckdb -p polars
AWS backend: The -p/--package list drives the Docker image build (image_build.packages). Packages are pre-baked into the env's container image — nothing is pip-installed at invocation time. After changing packages, run fused infra build-image (builds + pushes the image and records its digest URI as docker_image), then fused infra apply to point the env's single <prefix>container Lambda function at the new image.
Local backend: The -p/--package list is AWS-only (drives the Docker image build). For the local backend, third-party dependencies belong to a project's pyproject.toml (managed with uv add inside the project directory). Without a project, execution runs in a bare stdlib-only venv — add the dependency to a project's pyproject.toml and run uv sync there.
Per-call requirements is backend-split. Only the local backend honors a requirements list passed on the call (it materializes them into the run venv). On Lambda/AWS the container image is prebuilt, so per-call requirements are ignored at execution time — packages must already be baked into the env image (see AWS backend above). On the realtime/Fused runtime, passing requirements raises a clear error. The cache key still incorporates requirements, but on Lambda it does not change what is installed.
Project venvs on the local backend
When executing against a local environment, pass project=<name> (MCP) or one of the two CLI selectors to run inside a project's .venv.
MCP: project parameter
mcp__openfused__execute_code(
code="import pandas as pd; result = pd.__version__",
project="taxi-pipeline",
)
The project parameter is workspace-registered only and is not available as a path on the MCP tools. For path-based access use the CLI --project-dir option (see below).
CLI: --project NAME (workspace mode)
fused code run myanalysis.py --project taxi-pipeline
- Resolves
<workspace>/taxi-pipeline/scripts/.venv/bin/python.
- Venv must already exist (
uv sync in <project>/scripts/).
CLI: --project-dir PATH (ad-hoc / path mode)
fused code run myanalysis.py --project-dir ~/.claude/skills/taxi-pipeline
fused code test mymodule.py --test-file test_mymodule.py \
--project-dir ~/.claude/skills/taxi-pipeline
fused code verify myanalysis.py --project-dir ~/.claude/skills/taxi-pipeline
- Reads
<dir>/openfused.toml for the manifest (name defaults to directory basename).
- Materialises
<dir>/scripts/.venv via uv sync on first run; subsequent runs reuse it.
- Local-only: if the resolved env is not
local, the CLI raises a clear error.
- Mutually exclusive with
--project on all three commands.
- Available on
code run, code test, and code verify — not on the MCP execute_code/test_code tools.
What both project selectors do:
- Select the project's
.venv/bin/python as the interpreter (all installed packages are available).
- Use a project-aware cache identity:
openfused-workflow-venv:<interpreter_id>:<lock_hash>. Two calls with the same code but different lock files get separate cache entries.
- If the venv is stale (pyproject.toml or uv.lock is newer than pyvenv.cfg), the response includes a
warnings list explaining which file changed — and caching is skipped for that call. Run uv sync inside <project>/scripts/ to resolve.
- Local fan-out children (
_openfused.invoke / fused.map) inherit the parent call's project interpreter, so workers see the same project dependencies as the coordinator — not the bare stdlib fallback.
Sharp edges:
project / --project / --project-dir are local-only. Passing them to an AWS or Fused backend raises a clear error.
test_code / code test requires project or --project-dir on the local backend. Without one of these, the call raises a guided error. pytest and coverage must be declared as dev dependencies in the project's pyproject.toml; add them in one step with fused project add-dep <project> pytest coverage --dev (runs uv add --dev + uv sync). They are not auto-installed in the project venv (unlike the AWS backend where they are pip-installed on first use).
- Stale project venv → auto-reconciled. When the project's
uv.lock is newer than its .venv (e.g. after adding deps out-of-band), the local execute/test path runs uv sync once before executing, then proceeds with caching on. If the sync fails it falls back to running on the stale venv, skipping the cache and returning a warnings list. Set OPENFUSED_NO_VENV_SYNC=1 to disable auto-reconcile (CI / locked-down). fused doctor reports staleness without syncing.
- Without a project selector, local execution runs in a bare stdlib-only venv. Third-party imports will fail.
- If the workspace project does not exist (
--project), the backend raises a ValueError pointing at fused project new <name>. If the directory has no openfused.toml (--project-dir), the CLI raises a clear manifest-not-found error.
Only list packages not available in the base image / host interpreter. Common stdlib is always present; boto3 is always present on AWS Lambda but may not be in a local venv (add it explicitly if needed).
Reading secrets inside UDF code
Use openfused.get_secret(name) — a uniform accessor that works unchanged on
both AWS and the local backend with no extra dependency declaration. Never
interpolate secret values into the code string; that exposes them in logs and
tool call history.
Store the secret first (name must be prefixed with the environment's function
prefix, e.g. openfused-, so the Lambda execution role can read it):
fused secrets put openfused-my-password "s3cr3t"
Then read it inside the code:
code = """
import openfused
secret = openfused.get_secret("openfused-my-password")
import psycopg2
conn = psycopg2.connect(password=secret, ...)
...
result = "done"
"""
get_secret raises KeyError if the secret does not exist — never returns
None. No requirements entry is needed; fused is injected into every
call directory automatically.
AWS — the shim reads Secrets Manager directly via boto3 inside the Lambda
(execution-role scoped to openfused-*). Local — the shim dispatches
through the host invoke broker; the project venv needs neither fused nor
cryptography installed.
Tool call sequence for a typical analysis
mcp__openfused__list_files — find the input file
mcp__openfused__get_file_schema — confirm columns / row count before running heavy code
mcp__openfused__execute_code — run the analysis; write output to S3; set result to the output key
mcp__openfused__get_file — return a presigned URL for the output
Parallel fan-out across partitions
When a task spans many partitions or files, don't loop sequentially. The right strategy depends on per-partition weight:
- Light per-partition work (counts, small aggregations) → a
ThreadPoolExecutor inside a single execute_code call.
- Heavy per-partition work, or a partition that would OOM or exceed
lambda_timeout → fan out to child Lambdas via the _openfused SDK or a coordinator that dispatches workers.
Both patterns use thread fan-out, _openfused.invoke, the coordinator/worker pattern, monitor_concurrency_limit, validating a worker before dispatch, batching oversized partitions, and avoiding recursion storms.
Fused realtime backend constraints
The Fused realtime backend (compute_mode="realtime", the default) has specific limitations when running code:
- No arbitrary
requirements — the realtime runtime does not install pip packages at invocation time. Only packages pre-baked into the runtime image are available (plus stdlib and code injected via Udf.headers). Passing requirements raises a clear error pointing you to the AWS or local backend for arbitrary packages.
- No
test_code — pytest harness is not available on the Fused runtime. Use the AWS or local backend to run tests.
- Fused-native caching only — result memoization uses Fused's own run cache, not fused's content-addressed cache. Pass
cache_max_age (e.g. "15m") to memoize a run on Fused's server; use cache_refresh=True to force a fresh execution. The cache_clear tool and cache_object_key parameter are not supported (raise a clear error); cache_refresh is the per-call escape hatch for stale results.
input_files staging — files are uploaded via the files API to fd://tmp (the staging_path default) and passed as a mapping of original filename → staged fd:// path to the UDF. For a decorated UDF (@fused.udf def fn(...)), the function must declare an input_files parameter. For a synthesized UDF (plain result = Python), input_files is an in-scope variable. Filenames with path separators, .., or leading slashes are rejected as unsafe.
- Large and cached results work — Fused delivers the result body via a presigned redirect (
x-fused-redirect, used for both cached and large responses). With large_result_delivery="presign", fused returns the redirect URL without downloading. With large_result_delivery="inline" (default), fused probes the size; results ≥ 5 MiB fall back to the redirect URL. Otherwise the data is downloaded and deserialized inline. UDF errors with a redirect present return None (error propagates). The data is returned as-is (no re-caching — Fused owns its run cache).
- Batch mode (
compute_mode="batch) not yet implemented — selecting batch compute raises NotImplementedError with guidance.
Monitoring
After each execute_code call, the response may include a monitoring key with runtime metrics. The shape differs by backend.
AWS backend — CloudWatch metrics for the Lambda function over the last 5 minutes:
{
"monitoring": {
"poll_interval_s": 30,
"snapshots": [
{
"function_name": "openfused-container",
"window_minutes": 5,
"note": "CloudWatch metrics lag ~1 min; this invocation may not yet be reflected.",
"concurrent_executions_max": 1,
"invocations": 3,
"errors": 0,
"throttles": 0,
"duration_avg_ms": 450,
"duration_max_ms": 800,
"warnings": []
}
]
}
}
Always check warnings before proceeding. Warnings are emitted for:
concurrent_executions_max > baseline + monitor_concurrency_limit — possible recursion runaway; stop and investigate before running more code
throttles > 0 — invocations were rejected due to concurrency limits
errors > 0 — function errors in the recent window (distinct from the current call's own error)
monitor_concurrency_limit (default 1) raises the threshold for the concurrency warning above. Fanning out intentionally trips it, so set it to the fan-out count + 1.
Local backend — monitoring is accepted but returns no snapshots (there is no metrics source to poll on the host).
To skip monitoring (e.g. in tight loops where latency matters), pass monitoring=false.
While a long call runs, execute_code streams each monitoring snapshot to the MCP client as a live progress notification (in addition to the final monitoring key in the response). This is purely informational — the underlying execution is never affected if the client cannot receive updates.
@fused.udf compatibility
Code that uses the @fused.udf decorator runs without modification inside execute_code. The handler injects a fused mock module into every execution directory, so no real fused package is required.
How it works
The mock provides:
@fused.udf — transparent decorator; the function is directly callable normally.
@fused.udf(filename="worker.py") — same, but pins a default worker file for .map().
fused.load("worker") — returns a worker reference bound to a file (the .py is implied: fused.load("worker") loads worker.py). Call it (worker(state="ak")) to dispatch a single child Lambda, or use .map() to fan out. Not registered for auto-call. The file is not read until the worker is called or .map()'d.
udf.map(items, filename=None, max_workers=16) — fans out to child Lambdas (see below).
fused.run(udf_fn, **kwargs) — equivalent to udf_fn(**kwargs); prefer calling directly.
fused.Response(body, *, media_type, status_code=200, headers=None) and helpers fused.HTMLResponse / fused.PlainTextResponse / fused.JSONResponse — return one (as result or from a UDF) to set the HTTP content type/status/headers when the code is served via code serve. Any non-Response value is sent as JSON. Outside a serve context the body is just the return value.
import fused
@fused.udf
def resolution_frame(resolution=10):
import pandas as pd
return pd.DataFrame({"res": [resolution]})
result = resolution_frame(resolution=8)
Fan-out with .map()
udf.map(items, filename, max_workers=16) dispatches one child Lambda per item using
_openfused.invoke() under the hood, collecting results in submission order.
import fused
worker = fused.load("count_state")
@fused.udf
def coordinator(bucket=BUCKET, prefix=PREFIX, max_workers=51):
states = list_states(bucket, prefix)
results = worker.map(
[{"state": s} for s in states],
max_workers=max_workers,
)
return {"total": sum(r["rows"] for r in results)}
items is an iterable of dicts; each dict becomes the kwargs for one child invocation.
The child's @fused.udf receives those kwargs via the auto-call mechanism.
filename resolution order:
filename argument to .map()
filename argument to @fused.udf(filename=...)
"user_code.py" (the current execution's file inside Lambda)
Per-child caching. _openfused.invoke(filename, …, cache_max_age="0s") takes
the same cache_max_age parameter as execute_code. Its default is "0s":
a child caches only when its own invoke(...) opts in, independently of the
parent's cache_max_age — fanning out under a cached coordinator does not
implicitly cache the children.
Auto-call behaviour
If the code defines a @fused.udf function but never assigns to result, the last decorated function is called automatically after the code block finishes. Arguments are loaded from _openfused_args.json in the working directory (populated by _openfused.invoke() kwargs) if that file exists; otherwise the UDF is called with no arguments.
import fused
@fused.udf
def compute():
return 42
import fused
@fused.udf
def double_value(x=0):
return x * 2
Rules
- Direct call —
udf(x=5) works; no need for fused.run(udf, x=5).
result and @fused.udf cannot coexist — a script returns via a result variable or a registered @fused.udf, never both. Defining a @fused.udf and also assigning result (including result = None) is an error; the runner does not guess which was intended. Pick one return mechanism per script.
- Only the last decorated function is auto-called when multiple
@fused.udf functions are defined.
- Errors in the UDF (wrong argument count, runtime exception) appear in
stderr and error just like any other execution error.
fused.py is a framework file and is not packed into child invocation zips; child Lambda invocations receive a fresh copy from the handler.
What NOT to do
- Do not set
result to a large DataFrame, dict, or list — serialize to Parquet/JSON and write to S3 instead
- Do not use
print as a return mechanism — stdout is captured but not a structured return channel
- Do not import packages in
requirements that are already in stdlib
- Do not run multiple unrelated analyses in one
execute_code call — split them
- Do not fan out more than one level deep — workers must never invoke child Lambdas
Verification, expectations, and testing
When verify is enabled on the resolved environment, execute_code accepts two quality parameters and may return a verify key in its response. The full security model — verify_code, the findings table, spec conformance, the audit log, test_code, and verify configuration — lives in the fused-verify skill; only the two execute_code parameters are covered here.
spec — a natural-language description of intent. Claude checks the code against it before execution; a mismatch blocks the call. Required when the environment sets require_spec.
expectations — a data-quality contract validated against the return value after execution. Violations are WARN-level and never block.
execute_code(
code="...",
spec="Compute total revenue grouped by region from the input CSV.",
expectations={
"expected_columns": [{"name": "region", "dtype": "object"},
{"name": "revenue", "dtype": "float64"}],
"max_null_rate": 0.01,
"row_count": {"min": 1, "max": 200},
"bounds": {"revenue": {"min": 0}},
},
)
Response shapes when verify is active:
- Blocked:
{"blocked": true, "error": "Execution blocked by security policy", "verify": {"findings": [...], "blocked": true}}
- Proceeded with warnings:
{"return_value": "...", "verify": {"findings": [...], "blocked": false, "summary": {"warn": 2, "block": 0}}}
To confirm behaviour rather than just safety, run a pytest suite inside the real Lambda with test_code — see fused-verify.
Execution isolation & tenancy
Each execute_code call runs in a fresh subprocess under a unique working dir, so module state, monkeypatches, and files don't leak between calls. On the AWS backend, every compute function is additionally created with Lambda tenant isolation mode, and each call carries a tenant id so it runs in an execution environment dedicated to that tenant:
- The tenant id is the caller identity from
OPENFUSED_CALLER_NAME (free-form names are sanitized to Lambda's allowed character set). When it's unset, all calls share a single placeholder tenant (openfused-shared).
- Set distinct
OPENFUSED_CALLER_NAME values per caller to keep mutually-distrusting workloads on separate warm-container pools. This isolates compute only — it does not scope the IAM role, so data isolation still needs a bucket-scoped role.
Resetting the compute backend
Call mcp__openfused__infra_lambda_reset (or fused infra lambda-reset from the CLI) to reset the resolved compute backend. Use this when:
- The backend is stuck in a broken state
- You need to force a fresh environment (e.g. after updating packages or handler logic)
- The local image cache or Lambda ARN cache has diverged from actual state
AWS backend: Deletes all Lambda functions for the resolved environment, clears the local ARN cache, and immediately recreates a fresh function. After reset, a fresh function is created immediately so the next execute_code call is warm. The IAM role, ECR repository, and S3 cache bucket are not affected.
Local backend: Clears the in-process venv ready-cache. No venvs are deleted from disk; the next execute_code call re-checks the on-disk markers (and rebuilds anything missing).
The MCP tool deletes every prefix-matched Lambda before recreating one, so it is registered only when the server is started with --enable-infra (and can be suppressed even then with --disable-reset). The fused infra lambda-reset CLI command is unaffected.
Large results and result caching
execute_code writes large return values to S3 instead of stuffing them into
the 6 MB synchronous invoke payload (which would fail). When this happens the
response gains a result_ref ({bucket, key, content_type, length}):
large_result_delivery="inline" (default) — the backend transparently
downloads the spilled value, so return_value is exactly what the code
returned.
large_result_delivery="presign" — the backend skips the download and
returns return_value = '{"result_url": "<presigned URL>"}' (~1 h TTL) plus
the result_ref. Use this when the result is large enough that pulling it
through the MCP boundary is itself the problem; fetch it on demand.
Caching (cache_max_age). Pass a duration ("90s", "15m", "24h",
"7d") to memoize the result. A later call with the same code, requirements,
and inputs returns the stored value without re-executing — a seconds-long
Lambda call collapses to a single S3 HEAD. execute_code defaults to "1h"
(ITEM-11856), so repeated calls memoize for an hour unless you override it; pass
"0s" to disable caching for live/non-deterministic code. (test_code stays
opt-in at "0s".)
When to cache. Match cache_max_age to how often the underlying data changes:
| Query type | Suggested cache_max_age | Rationale |
|---|
| Historical/immutable S3 data (e.g. AIS, satellite imagery, archived logs) | "7d" or "30d" | Source data never changes; safe to cache indefinitely |
| Partition/schema discovery (which chunk covers a bounding box, column names) | "24h" | Layout is stable; re-check daily is sufficient |
| Iterative analysis over the same dataset in one session | "1h" | Avoids re-downloading the same large file for each follow-up query |
| Live/streaming data | "0s" (off) | Results change with each read |
Code that calls datetime.now(), random(), or any non-deterministic API | "0s" (off) | Cache key does not capture runtime state |
Practical pattern for large historical datasets — cache the expensive load step, not the cheap aggregation:
execute_code(
code=LOAD_AND_FILTER,
cache_max_age="7d",
spec="...",
)
execute_code(
code=AGGREGATE,
cache_max_age="1h",
spec="...",
)
If you ran the same query without cache_max_age earlier in a session, add cache_refresh=True on the first cached call to populate the entry from a fresh run.
- The key is a content hash of
(runner version, execution environment, code, input file bytes), scoped per environment — two environments never share
entries even on a shared cache bucket. On AWS the environment component
is the resolved container image digest, so re-pushing the same tag (e.g.
:latest) invalidates prior entries.
- Both backends: caching works the same on AWS and the local backend
(local stores the entry on its own filesystem and replays hits inline —
no S3/presign). The local env component of the key is the venv identity
(interpreter path + version + package set).
cache_clear is wired for both.
- The response carries
cache: {hit, hash, object_key, age_s}. On a hit,
stdout/stderr/duration_ms are the original run's values (replayed);
no monitoring snapshot is returned (nothing executed).
- Volatility caveat: the hash captures the inputs shipped with the call,
never what the code reads at run time (S3 objects, secrets,
now(),
network). Pick a cache_max_age no longer than the staleness you can
tolerate, and leave it at "0s" for code that reads live data. Pin
requirement versions (pandas==2.2.1) for a reproducible cache identity.
cache_refresh=True (requires cache_max_age > "0s") bypasses the read,
re-executes, and rewrites the entry — use it when you know the underlying
data changed. cache_refresh=True with cache_max_age="0s" is rejected.
test_code accepts the same cache_max_age/cache_refresh; outcomes +
coverage are memoized and the test file is folded into the key.
- Result formats. A cached result is stored by kind: a
DataFrame/
GeoDataFrame/Arrow table as Parquet (directly queryable via
get_file_schema/pd.read_parquet on the result_ref), a binary
fused.Response as its raw bytes (a presigned GET returns the image/PDF
verbatim), and any other value as JSON. The inline return_value is
identical whether the call executed or hit the cache.
Reclaim cached/spilled bytes with cache_clear(route?, object_key?, all?). The
default clears the current environment's scope; object_key=K (from a prior
cache.object_key) clears one object; all=True clears every environment's
cache objects (results/) plus un-scoped spill objects (spill/). It is not
destructive-gated but is always audited.
Expiry vs. freshness. The two object classes live under different prefixes
and have different lifetimes:
spill/ — one-shot artifacts written only when caching is off; read once via
the returned result_ref, then garbage. A bucket lifecycle rule expires them
after 1 day.
results/ — content-addressed cache entries. Freshness is decided at read
time by cache_max_age, not by S3 expiry; a lifecycle rule expires them
after 30 days purely as a storage backstop, so keep cache_max_age well
under 30d. (Spilled large cache bodies live here too, under the entry's
hash key — they are part of the cache, not separate spills.)
These lifecycle rules are provisioned on the cache bucket by infra_apply; see
the fused-infra skill.