Script commands
Commands for creating C# script files, attaching MonoBehaviours to GameObjects, reading/writing serialized fields, and running compiled project entry points in memory. Writing a .cs file does not make its type available — Unity must import and compile it (a domain reload) first. The flow is: create_script → recompile → poll recompile_status (until completed/up_to_date) → attach_script. For bulk construction that does not need a new persistent project type, skip the recompile entirely: run_script compiles a file in memory and runs it with no asset import and no domain reload.
create_script
Create a new C# script (default base class MonoBehaviour) from a template under the authoring root. NOTE: the type does not exist until a recompile completes — to attach it, call recompile, poll recompile_status, then attach_script.
| Parameter | Required | Default | Description |
|---|---|---|---|
name |
yes | – | Class/file name without extension, e.g. PlayerController. Must be a valid C# identifier. |
path |
no | – |
Folder (relative to the authoring root; the Assets/ prefix is optional) to write the .cs into. Defaults to the authoring root. |
namespace |
no | – |
Optional namespace to wrap the class in. Omit for the global namespace. |
base_class |
no | MonoBehaviour |
Base class to derive from. Defaults to MonoBehaviour. |
overwrite |
no | false |
Overwrite the file if it already exists. Defaults to false (an existing file is an error). |
Returns: AuthoringResult
Notes: Does not trigger a recompile; the created type is not usable until a recompile completes (poll recompile_status).
attach_script
Add a MonoBehaviour to a GameObject by its (compiled) type name OR by its script asset path. Provide exactly one of 'type' or 'script'. If the type isn't compiled yet, returns a recoverable error: recompile, poll recompile_status, then retry.
| Parameter | Required | Default | Description |
|---|---|---|---|
target |
yes | – | Reference to the GameObject to add the component to (globalId/path/guid/instanceId/hierarchyPath). |
type |
no | – |
Component type name to add, e.g. PlayerController or Game.Player.PlayerController. Must already be compiled. Mutually exclusive with 'script'. |
script |
no | – |
Script asset path, e.g. 'Assets/Pool/Scripts/CueShooter.cs'. The backing class is resolved via MonoScript.GetClass(), so the class name may differ from the filename. Mutually exclusive with 'type'. |
Returns: AuthoringResult
Notes: Undo-able. Provide exactly one of type or script. A not-yet-compiled type returns a recoverable error (recompile, poll recompile_status, then retry).
set_serialized_field
Set a serialized field on a component/asset. Supports primitives, enums, Vector/Color/Rect/Bounds, object references (value = an ObjectRef: asset by guid/fileId/path or scene object by instanceId/hierarchyPath), and array elements via 'name.Array.data[i]' (or 'name.Array.size' to resize).
| Parameter | Required | Default | Description |
|---|---|---|---|
target |
yes | – | Reference to the component or asset to modify (globalId/path/guid/instanceId/hierarchyPath). May be a GameObject when 'component' is given. |
field |
yes | – | SerializedProperty path, e.g. 'speed', 'settings.speed', or 'waypoints.Array.data[0]'. |
value |
yes | – | JSON value to assign. For object references pass an ObjectRef object (or null to clear). For enums pass the value name. |
component |
no | – |
Component type name on the target GameObject (e.g. 'Rigidbody'). Use when 'target' is a GameObject; omit when 'target' is already a component handle. |
Returns: AuthoringResult
Notes: Undo-able.
get_serialized_fields
Read serialized fields of a component/asset. Returns each top-level field's name, type and value (object references are returned as re-usable handles). Pass 'field' to read a single SerializedProperty path.
| Parameter | Required | Default | Description |
|---|---|---|---|
target |
yes | – | Reference to the component or asset to read (globalId/path/guid/instanceId/hierarchyPath). May be a GameObject when 'component' is given. |
field |
no | – |
Optional single SerializedProperty path to read (e.g. 'speed' or 'items.Array.data[0]'). Omit to read all top-level fields. |
component |
no | – |
Component type name on the target GameObject (e.g. 'Rigidbody'). Use when 'target' is a GameObject; omit when 'target' is already a component handle. |
Returns: object
run_script
Compile a single project .cs file in memory (no domain reload, no code carried through the
protocol) and execute a named static entry point. This is the builder-pattern path for bulk
construction — see the note below. Editor command; still arbitrary code execution, so it is grouped
under the same scripts/eval tag as eval and is disabled wherever the eval family is disabled.
| Parameter | Required | Default | Description |
|---|---|---|---|
file |
yes | – |
Path to a .cs file. Relative paths resolve against the project root (the parent of Assets/), not the process working directory. May live outside Assets/ (e.g. AgentScripts/) so writing it never triggers an asset import or domain reload. |
entry |
no | – |
Entry point as Namespace.Type.Method (or Type.Method, or a bare Method). Nested types may be written with dots (Outer.Inner.Method). Default: the single public static method if unambiguous, else a method named Main. Static, non-generic methods only in v1. Ephemeral mode only — rejected with mode=hotpatch. |
args |
no | – |
JSON array of arguments, coerced to the entry's parameter types: primitives, enum names, string[], and a string/object handle (ObjectRef) for UnityEngine.Object parameters. JSON null is only valid for reference-type and nullable parameters (never a silent default(T)). Ephemeral mode only — rejected with mode=hotpatch. |
mode |
no | ephemeral |
ephemeral: compile to an in-memory assembly, run the entry, discard. hotpatch: apply in-place [HotReload] method replacements to already-loaded types (delegates to reload_file). |
references |
no | – |
Extra assembly name prefixes to reference. In the Editor all loaded assemblies are already referenced, so this is effectively a no-op there. Ephemeral mode only; not applied by hotpatch (the compile is reload_file's). |
defines |
no | – |
Extra scripting define symbols, appended to the project's active editor defines (EditorUserBuildSettings.activeScriptCompilationDefines) — so UNITY_EDITOR, version and platform symbols are already set and sources behave like project code; your defines extend that set rather than replace it. Ephemeral mode only; not applied by hotpatch. |
pdb |
no | false |
Emit a portable PDB mapped to the source file so breakpoints bind. Ephemeral runs always emit source-mapped symbols so exception stack traces carry file:line. Compiles unoptimized. |
timeout_ms |
no | 30000 |
Timeout in milliseconds (governs the main-thread wait budget end-to-end, like eval). |
dry_run |
no | false |
Compile only: return diagnostics; the emitted assembly is not loaded into the domain and nothing executes (the response carries no assemblyName). Rejected with mode=hotpatch — a hotpatch "dry run" would still apply live method replacements. |
Returns: RunScriptResponse — { result, diagnostics, compileMs, executeMs, assemblyName } on top
of the standard envelope. The result carries no echo of the source (path + diagnostics only). Compile
errors return full Roslyn diagnostics (with line/column) and execute nothing; runtime exceptions
surface as structured errors with file:line mapped to the source file.
Notes: MainThreadRequired = true. Editor command.
- Executing runs always compile unoptimized (Debug codegen) so the emitted symbols keep full
sequence points for
file:linemapping — expect the generated code to run somewhat slower than regular project assemblies. - Async entry points (
async Task/Task<T>) are awaited to completion;Task<T>.Resultbecomes the command result and exceptions in the async work surface as runtime errors. The await is asynchronous — the Editor main thread keeps pumping, so awaits that resume on Unity's main-thread context (Task.Yield(),Task.Delay(), Awaitable, …) work naturally; noConfigureAwait(false)contortions are required. The wait is bounded by what remains oftimeout_ms; on expiry the command returns aTimeouterror and the task keeps running detached (its effects may still land), like an abandonedeval. - hotpatch specifics:
entry,argsanddry_runare rejected (Bad Request);referencesanddefinesare not applied. Path resolution followsreload_file, which probes underAssets/for bare filenames (unlike ephemeral's project-root resolution). Timing:executeMscarries the whole reload duration (compile + apply, not split byreload_file);compileMsis0. Advisory reload diagnostics are surfaced as severity-warningentries on success. - Under Unity's Mono runtime the in-memory assembly emitted by an executing
ephemeralrun is not collectible/unloadable, so each run retains one small assembly (the same behavior aseval); prefer a single builder entry over many tiny runs.dry_runloads nothing.
The builder pattern (bulk construction)
For bulk construction — creating many objects, wiring private fields, generating content — put the
logic in a versioned project script and invoke it with run_script. Write the file with
write_text_file (ideally under a non-Assets/ folder such as AgentScripts/ so the write triggers
no asset import), then call run_script --file AgentScripts/Build.cs --entry Build.All. Iterating the
script and re-running it costs an in-memory compile (< ~2s), not a 15–20s domain reload.
The anti-patterns this replaces: carrying construction code as escaped C# strings through eval
(verbose, unreviewable, shell-escaping hazards), and editing a builder under Assets/ in a loop
(each edit pays a full domain reload). Hold the line: code goes in files on disk via run_script;
eval stays for genuinely ad-hoc one-liners.
See Creating commands and Connectivity.