Scripting
architect-scripting: Luau over the Action List; a run is one undo step; saved scripts become actions in ⌘K, keys and MCP; errors carry their line.
What it is and when to use it#
A script is a Luau program over the app’s Action List. It can list,
describe and run actions, and branch or loop between them. A run is one
named undo step, and a failed run is taken back. Each run gets a fresh
sandboxed state with memory and step limits, and every action it runs is
authorized for whoever ran the script. A script saved under a name becomes
an action of its own (SCRIPT_MY_<NAME> for the person, SCRIPT_ORG_<NAME>
for the organization), so it shows in ⌘K, can be bound to a key and is
reachable over MCP. A live script defers functions that run once per frame;
other crates add globals (ImGui, Canvas) as modules.
Use it for a person’s macros and conditional actions, and for a model’s one-off scripts over MCP. Design: Extensions, the sections on actions, the model and scripts.
Example: examples/scripting, a task board scripted from Luau, saved scripts as actions and the board over MCP; walked through in Script an app, end to end.
A minimal example#
architect with action-list; architect-scripting with luau (the runtime
and the SCRIPT_* actions), crdt (runs over a History) or settings (a
settings file as the library). After features/scripting/tests/scripts.rs:
use architect::action::{ActionCaller, ActionList};
use architect_scripting::actions::Scripting;
use architect_scripting::history::HistoryTransactions;
use architect_scripting::{Library, MemoryStore, Script};
let list = ActionList::new(); // the app's actions registered on it
let scripting = Scripting::new(list.clone(), Library::new(MemoryStore::new()).with_org(MemoryStore::new()))
.with_transactions(HistoryTransactions::new(doc.clone(), Arc::clone(&history)));
scripting.install()?; // SCRIPT_RUN, SCRIPT_SAVE, .. and the saved ones
let id = scripting.save(Script::new("Arm selected", r#"
local selected = state.query("TRACKS_SELECTED")
for _, id in selected do
actions.run("TRACKS_ARM", { id = id, on = true })
end
return #selected
"#))?; // "SCRIPT_MY_ARM_SELECTED"
let report = scripting
.run(&ActionCaller::app(), "return #actions.list()", serde_json::Value::Null, "Count")
.await;
// report.result: Ok(value) or a ScriptError with its kind, line and message
A live script without the Scripting layer (from features/scripting/tests/live.rs):
let (report, live) = Runtime::new(list).with_module(Arc::new(my_module))
.start(&caller, src, args, "Tool").await;
let frame = live.unwrap().frame().await; // runs what the script deferred
Code tour#
features/scripting/src/lib.rsScript,action_id,Library,ScriptStore,MemoryStore: scripts and where saved ones are kept.features/scripting/src/actions.rsScripting(install,run,save,delete,describe): theSCRIPT_*actions and one action per saved script;MAX_RUNNINGstops a script running itself without end.features/scripting/src/luau.rsRuntime(with_module,with_gate,with_transactions,with_limits,run,start): one sandboxed state per run, with theactions,state,print,args,requireanddeferglobals.features/scripting/src/luau.rsLimits,Report: 64 MiB and 10 million steps by default; the result, log, actions run and undo step.features/scripting/src/luau.rsLive(frame,stop),Module,raise: live scripts and the globals other crates add.features/scripting/src/history.rsHistoryTransactions: a run as onecrdt::Historystep.
SCRIPT_RUN, or a saved script’s own action, reaches Scripting::run, which
counts running scripts and calls Runtime::run. The runtime builds a
sandboxed state and opens a transaction for the step. Each actions.run in
the script goes through ActionList::run_json_async_with with the original
caller, so permissions hold. On success the step commits; on failure it
aborts and the ScriptError carries the line that failed. The Report goes
back as JSON with the result, the log, the actions run and the undo step.
Tests to read#
features/scripting/tests/scripts.rs: a saved script runs from ⌘K and undoes as one step; a model describes, runs and saves scripts over MCP; a failure reports its line and is rolled back; step and memory limits and the sandbox hold; a script runs with its caller’s permissions; saving again replaces, deleting removes the action.features/scripting/tests/live.rs: thedeferloop one frame at a time; a custom module; a failing frame stops the script; each frame is its own undo step; a one-off run cannotdefer.
Status#
Done in architect; the sandbox uses it. Not yet: dry runs, the typed model services in the Luau library, streams, Luau on the web.
- Task and Studio do not use it yet; both have Action Lists to run scripts over.
Linked from
- FeaturesOne page per major architect feature: what it is, the least code to use it, a tour of its code, the tests to read, and its status.
- Extensionsarchitect-extensions: packages that add actions, settings, views and document data at run time; data, Luau and process kinds; git, folder and ReaPack sources; hot reload; a project's required extensions under an org's policy.