Agents & Human Input
These primitives bring autonomy and people into a workflow. agent runs an
LLM-driven ReAct loop that calls tools on its own; ask pauses the thread to
collect input from a human; plan has an LLM generate a sub-script that the
compiler validates; and run executes that generated (or stored) script as a
sub-thread.
agent β autonomous ReAct loop
agent "..." with llm starts a think β act β observe loop. The LLM receives
a task and a set of tools, then reasons about what to do, calls a tool, reads the
result, and repeats β until it produces a final answer or hits max_iterations.
Unlike plan, there is no intermediate script: the agent calls tools directly.
agent "Research and summarize" with llm
system = "You are a research assistant."
task = "Find the top 3 competitors of {company} and summarize their products."
tools = ["search_web", "fetch_url", "file_write"]
max_iterations = 15
temperature = 0.2
-> researchInputs
| Input | Required | Description |
|---|---|---|
task | yes | the goal the agent works toward |
tools | yes | list of tool names the agent may call (from the catalog) |
system | no | system prompt |
max_iterations | no | cap on think-act-observe cycles (default 10) |
temperature, max_tokens, model | no | LLM tuning / override |
How tools are exposed
The tools list names tools the agent is allowed to call. They are resolved
from the same ToolCatalog as do, and their schemas are handed to the LLM so it
knows each toolβs name, purpose, and arguments. The agent then decides, per
cycle, which tool to invoke and with what arguments.
Output
The binding holds the agentβs result: .text is the final answer,
.iterations is the cycle count, .tool_calls is the full log of every call
with arguments and results, and .exhausted is true if it ran out of
iterations without concluding.
agent "Deep research agent" with llm
system = "You are a thorough research analyst. Use web_search and fetch_url to gather data. Organize findings into sections with citations."
task = "Research the following topic in depth: {topic}. Depth level: {depth}."
tools = ["web_search", "fetch_url", "file_write"]
max_iterations = 8
temperature = 0.4
-> research
emit "Report"
final_report = research.textAgents compose with the other primitives β for example, fan out two agents in a
together block, then have an LLM merge them (from
examples/parallel_agents_decide.scriptum):
together "Research from multiple angles"
agent "Technical feasibility agent" with llm
system = "You are a senior systems architect."
task = "Analyze technical feasibility for '{project_name}'.\nRequirements:\n{requirements}"
tools = ["web_search", "fetch_url"]
max_iterations = 6
-> technical
agent "Market and risk agent" with llm
system = "You are a business analyst specializing in risk assessment."
task = "Analyze market context and risks for '{project_name}'.\nRequirements:\n{requirements}"
tools = ["web_search", "fetch_url"]
max_iterations = 6
-> marketask β human-in-the-loop input
ask pauses the thread and requests structured input from an external actor (a
person, a UI, an API). The thread durably waits β possibly for hours β and
resumes when the input arrives.
ask "Approve deployment"
prompt = "Deploy {deploy_version} to production?"
options = ["approve", "reject", "defer"]
timeout = "24h"
max_retries = 3
-> approval
emit "Done"
approval = approval.valueInputs
| Input | Required | Description |
|---|---|---|
prompt | yes | the question or instruction to show |
options | no | valid choices (selection mode) |
type | no | expected type for free-form input: "text", "number", "boolean" (default "text") |
timeout | no | TTL such as "24h", "30m" |
max_retries | no | validation retry attempts |
default | no | value to use on timeout instead of failing |
Two modes: selection (give options) and free-form (give a type):
ask "Request revision guidance"
prompt = "What changes would you like to the research on '{topic}'?"
type = "text"
timeout = "24h"
-> revision_notesThe result object exposes .value (the answer), plus .provided_by,
.provided_at, and .attempt. Route on .value with a decide:
ask "Review research findings"
prompt = "Please review the research on '{topic}'.\n\n{research.text}\n\nDo you approve?"
options = ["approve", "request_changes", "reject"]
timeout = "48h"
-> review
decide "Handle review decision" with rules
when review.value == "approve" -> "Approved"
when review.value == "request_changes" -> "Revise"
otherwise -> "Rejected"
"Approved":
emit "Approved Report"
final_report = research.text
approved = true
"Revise":
agent "Revise research" with llm
system = "You are a research analyst. Revise based on feedback."
task = "Original:\n{research.text}\n\nRevision request:\n{revision_notes.value}"
tools = ["web_search", "fetch_url"]
max_iterations = 5
-> revised
emit "Revised Report"
final_report = revised.text
approved = true
"Rejected":
emit "Rejected"
final_report = "Research rejected by reviewer."
approved = falseplan β LLM generates a sub-script
plan "..." with llm is the meta-primitive. The LLM receives the task, the
available tools, and context, and outputs a .scriptum script body. The
compiler then validates that generated script β tools exist, types match, fields
are valid β before it runs. This is what makes Scriptum an agent framework: the
steps need not be known in advance.
plan "Generate execution plan" with llm
task = task -- what needs to be done
tools = ["file_read", "grep", "shell_exec", "file_write"]
context = codebase_summary.text -- any additional context
model = "gpt-4o"
max_steps = 10 -- guard: limit generated script size
validate = true -- compile-check before executing (default)
-> mission -- binding holds the generated scriptplan versus agent: plan produces an inspectable, compiler-validated script
(best for multi-phase workflows); agent calls tools directly in a loop (best
for exploratory tasks). If a generated script fails validation, the runtime
returns the compiler errors to the LLM and retries, up to a retry limit.
run β execute a sub-script
run executes a script β the one produced by plan, or a stored script by
path. It runs as a sub-thread with its own state and step records, but its
result flows back into the parent.
run mission
-> execution_resultrun "scripts/weekly_report.scriptum"
input
week = current_week
-> reportWorked example: plan then run
A complete flow β summarize a codebase, plan the work, run the plan, then report.
(Adapted from examples/plan_run_flow.scriptum.)
script "Plan and Run Flow"
LLM plans a sub-script, then executes it via run.
version 0.1.0
input
task : text
output
result : text
plan_summary : text
import tools
* from native
env
LLM_API_KEY from "LLM_API_KEY"
LLM_MODEL = "gpt-4o"
### Phase 1: Gather context ###
do "Read project structure" with shell_exec
command = "find . -maxdepth 2 -type f -name '*.rs' | head -20"
-> file_listing
do "Summarize codebase" with llm
prompt = "Given this file listing, summarize the project structure:\n{file_listing.stdout}"
system = "You are a senior engineer. Be concise."
-> codebase_summary
### Phase 2: Plan ###
plan "Generate execution plan" with llm
task = task
tools = ["file_read", "grep", "shell_exec", "file_write"]
context = codebase_summary.text
model = "gpt-4o"
max_steps = 10
validate = true
-> mission
### Phase 3: Execute ###
run mission
-> execution_result
### Phase 4: Report ###
do "Summarize results" with llm
prompt = "Summarize what was accomplished:\nTask: {task}\nResult: {execution_result}"
-> summary
emit "Final Report"
result = execution_result
plan_summary = summary.textNext: incremental output in Streaming, recovery in Error Handling, and escaping to Python in Inline Python.