CodeExecutor and Workspace
codeexecutor provides a controlled execution environment for an Agent.
What It Is Used For
Once codeexecutor is enabled, an Agent can run programs inside a workspace
and read or write files in that workspace.
Common use cases include:
- running shell commands or code
- processing files in a fixed working directory
- making uploaded user files available to the execution environment
- generating output files for later steps
- switching between local, container, and Jupyter backends
If your Agent only generates text and does not need program execution or local file handling, this layer is usually unnecessary.
Quick Start
Configure an executor on LLMAgent:
More complete examples:
- examples/codeexecution/main.go (local backend)
- examples/codeexecution/container/README.md (Docker container backend)
- examples/codeexecution/jupyter/README.md (Jupyter kernel backend)
WithCodeExecutor vs fenced-code auto-execution
llmagent.WithCodeExecutor(...) and the response-side fenced-code
auto-execution processor are two independent switches. It is worth
internalising this distinction up front, because a lot of confusion
comes from treating them as a single knob.
WithCodeExecutor(...)supplies a runtime that execution-backed tools — most notablyworkspace_exec— use to run commands. It does not, by itself, cause anything to be executed from the assistant's reply.EnableCodeExecutionResponseProcessor(default:true, toggled viaWithEnableCodeExecutionResponseProcessor(enable bool)) controls whether the framework scans the assistant reply and, if it is exactly one runnable fenced code block, runs that block automatically.
Auto-execution of fenced code actually fires only when both are true: an executor is available and the response processor is enabled.
If you only want the executor to power workspace_exec or other
tool-backed execution paths, and do not want assistant replies to be
auto-executed, opt out of the response-side processor explicitly:
Common cases for disabling fenced-code auto-execution:
- using
workspace_execonly - providing a runtime for other tools
- requiring code execution to happen only through explicit tool calls
Interaction with WithSkills(repo) auto-fallback: when the skills
layer implicitly injects a local CodeExecutor on your behalf (see the
Agent Skills guide), that implicit executor is treated as "only here to
power workspace_exec". In that case the framework automatically sets
EnableCodeExecutionResponseProcessor=false unless you explicitly
called WithEnableCodeExecutionResponseProcessor(...) yourself. Using
WithCodeExecutor(...) explicitly, by contrast, leaves the switch at
its framework default so your existing behavior is preserved.
Choosing a Backend
Common backends:
local.New()Runs directly on the host machine. Easiest to wire up and debug.container.New()Runs inside a container. Better isolation and closer to production.jupyter.New()Best for notebook or kernel-style execution, especially Python analysis.
Typical recommendations:
- local development:
local - isolated or production-like execution:
container - notebook workflows:
jupyter
Workspace Layout
Programs run inside a workspace. Common directories are:
work/inputs/Input files prepared before execution. Uploaded user files usually appear here.work/Temporary working directory for intermediate files.out/Output directory for final results or files that later steps may read.runs/Per-run auxiliary files such as logs.
Common paths:
- read user input files from
work/inputs/ - write intermediate files to
work/ - write result files to
out/
Where Uploaded Files Appear
On execution paths that support conversation-file auto-staging, the framework materializes these files before execution into:
- the
work/inputs/directory
The actual filename may be sanitized or de-duplicated, so the original basename is not guaranteed verbatim.
There are two common ways to provide such files.
Option 1: Put File Content in the Message
You can also provide raw bytes directly:
Option 2: Upload to Artifact First, Then Attach a Reference
If the file is already stored in the artifact service, attach an
artifact://... reference as file_id:
Before execution, the framework resolves that reference and writes the file
under work/inputs/.
Example: Upload to Artifact First
This example shows the full flow for uploading a file first and letting the executor stage it automatically later:
Requirement:
- the
AppName / UserID / SessionIDused inSaveArtifact - must match the values used later in
Runner.Run(...)
Otherwise the framework will not find the artifact when resolving the
artifact://... reference.
How Tools Usually Use These Files
When your Agent exposes workspace tools such as workspace_exec, the common
flow is:
- read files from
work/inputs/... - process them in
work/orout/ - read
out/...and return the final answer
This keeps the contract simple: tools and models rely on stable paths instead of dealing with staging internals.
Workspace Bootstrap: Preparing the Workspace Before User Commands
Some workspaces need predictable setup before workspace_exec runs any
user-authored command: a preloaded config file, a pinned Python virtualenv, a
one-shot pip install, etc. Rather than teaching the model to perform this
setup itself (which is error-prone and burns prompt tokens), the framework
lets you declare it once on the agent.
Use codeexecutor.WorkspaceBootstrapSpec to list the required files and
commands, and wire it in via llmagent.WithWorkspaceBootstrap(...). The first
workspace_exec call in each workspace will converge the workspace to that
spec; later calls find everything already in place and skip the work.
Field reference
WorkspaceFile:
Target: workspace-relative destination path (required). Parent directories are created automatically.Content: inline bytes to write.Input: alternatively, acodeexecutor.InputSpecthat resolvesartifact://,host://,workspace://, orskill://URIs. Exactly one ofContentandInputmust be set.Mode: optional file mode (octal); defaults to0o644.Key: optional stable identifier used for idempotency; if omitted it is derived fromTarget.Optional: if true, provider errors are logged as warnings instead of aborting workspace preparation.
WorkspaceCommand:
Cmd/Args/Env/Cwd: standard exec wiring.Cwdis workspace-relative and defaults to the workspace root.Timeout: bounds a single invocation.MarkerPath: workspace-relative file whose existence signals the command has already run; this is the cheapest way to make a command self-healing after the marker gets deleted. When absent, the reconciler falls back to fingerprint-only skip.ObservedPaths: alternative toMarkerPathwhen success is defined by a set of files rather than a single marker.FingerprintInputs/FingerprintSalt: fold external inputs into the command fingerprint so the command reruns when they change. The fingerprint does not automatically hash files referenced on the command line, so a command likepip install -r work/requirements.txtmust listwork/requirements.txthere explicitly — otherwise the marker will short-circuit the install after the first successful run even ifrequirements.txtis later edited.Key: stable identifier used for idempotency; derived fromCmd/Argswhen omitted.Optional: same semantics as for files.
Ordering and idempotency
- Files are staged first, then commands run, both in declaration order.
- Each entry is fingerprinted (content for files, command line + optional
inputs for commands). On subsequent invocations, the reconciler checks
the fingerprint and the on-disk sentinel before skipping, so a user
rm -rfinsideworkspace_execdoes not silently leave the workspace half-broken: the next call re-applies the missing piece. - Reconciliation is serialized per workspace (in-process), so two
parallel
workspace_execcalls on the same session cannot step on each other during preparation. - Entries without
Optionalset (the default required behavior) abort theworkspace_execcall before the user command runs on failure;Optional: trueentries downgrade to a logged warning.
Opting out
If you want to keep the legacy "stage conversation files only" behavior
— for example, because an existing regression test depends on it —
pass llmagent.WithWorkspacePreparersDisabled(true) on the agent. In
normal use this switch should not be necessary.
Interaction with skill_load
Skills loaded via skill_load are materialized into
skills/<name> through the same reconcile path. You do not need to
add anything to WorkspaceBootstrapSpec for skills — they are picked
up automatically from session state whenever workspace_exec runs.
The bootstrap spec is for static, session-independent setup.
What Persists Across Turns
There are two important cases:
- the request lands on the same physical workspace
- the request lands on a fresh workspace
In the same physical workspace:
- files in
work/andout/are usually still available - result files can often be read again directly
In a fresh workspace:
- conversation file inputs can usually be re-staged from visible message history
- old
out/**files are not restored automatically - old
work/**files should not be assumed to exist
If a file must survive beyond a fresh workspace, store it as an artifact or persist it explicitly in your application layer.
Provider File IDs vs Artifact References
Some providers support native file_id values. Whether those IDs can be
downloaded back by the executor depends on the model integration.
If executor-side access must be reliable, prefer:
artifact://...
This path is managed by the framework's artifact service and does not depend on provider-specific file download support.
Environment Variables
When the executor runs in a container, on a remote worker, or in another isolated environment, environment variables usually need to be injected explicitly.
Typical use cases:
- passing user-scoped tokens
- injecting tenant or region configuration
- avoiding exposure of sensitive values to the model
Relevant wrappers:
NewEnvInjectingCodeExecutorNewEnvInjectingEngine
FAQ
Why can the model not find an uploaded file?
Check:
- whether the file was actually attached to the message
- whether the filename matches what the prompt refers to
- whether the Runner is configured with an
artifact.Service - whether the session information for
artifact://...matches
Why did an old file under out/ disappear on the next turn?
If the next request lands on a different physical workspace, old out/** files
may be missing. Conversation file inputs can be re-staged, but out/** is not
restored automatically by default.
When should I use work/ instead of out/?
- temporary intermediate files:
work/ - result files that later steps may read:
out/
Is codeexecutor tied to one specific tool?
No. It is a lower-level execution and workspace capability. Which concrete tool exposes it depends on the higher-level Agent and tool wiring.
References
- Examples:
- examples/codeexecution/main.go (local backend)
- examples/codeexecution/container/README.md (Docker container backend)
- examples/codeexecution/jupyter/README.md (Jupyter kernel backend)
- Related docs: