Skip to Content
We are live but in Staging πŸŽ‰
Scriptum LanguageStepsAgents & Human Input

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 -> research

Inputs

InputRequiredDescription
taskyesthe goal the agent works toward
toolsyeslist of tool names the agent may call (from the catalog)
systemnosystem prompt
max_iterationsnocap on think-act-observe cycles (default 10)
temperature, max_tokens, modelnoLLM 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.text

Agents 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 -> market

ask β€” 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.value

Inputs

InputRequiredDescription
promptyesthe question or instruction to show
optionsnovalid choices (selection mode)
typenoexpected type for free-form input: "text", "number", "boolean" (default "text")
timeoutnoTTL such as "24h", "30m"
max_retriesnovalidation retry attempts
defaultnovalue 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_notes

The 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 = false

plan β€” 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 script

plan 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_result
run "scripts/weekly_report.scriptum" input week = current_week -> report

Worked 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.text

Next: incremental output in Streaming, recovery in Error Handling, and escaping to Python in Inline Python.