import { workflow_result_text } from "std/agent/result_text"
import "std/collections"
import { JsonParseFailure } from "std/json"
fn __action_graph_parse_input(raw) {
if type_of(raw) != "string" {
return raw
}
const text = trim(raw)
if text == "" {
return []
}
if starts_with(text, "{") || starts_with(text, "[") {
const parsed: Result<unknown, JsonParseFailure> = try {
json_parse(text)
}
if is_ok(parsed) {
return unwrap(parsed)
}
return raw
}
return raw
}
fn __action_graph_non_empty_string(value) {
if type_of(value) != "string" {
return nil
}
const text = trim(value)
if text == "" {
return nil
}
return text
}
fn __action_graph_first_string(candidates) {
for value in candidates {
const text = __action_graph_non_empty_string(value)
if text != nil {
return text
}
}
return nil
}
fn __action_graph_listify(value) {
if value == nil {
return []
}
if type_of(value) == "list" {
return value
}
return [value]
}
fn __action_graph_tool_list(raw) {
const value = raw?.tools ?? raw?.tool ?? raw?.allowed_tools ?? raw?.capabilities
if value == nil {
return []
}
let tools = []
if type_of(value) == "string" {
return [trim(value)]
}
for item in __action_graph_listify(value) {
const tool_name = if type_of(item) == "string" {
trim(item)
} else {
trim(item?.name ?? item?.tool ?? item?.id ?? "")
}
if tool_name != "" && !contains(tools, tool_name) {
tools = tools + [tool_name]
}
}
return tools
}
fn __action_graph_tool_class(tools, phase) {
if phase == "verify" {
return "verify"
}
if len(tools) == 0 {
return if phase == "research" {
"read"
} else {
"generic"
}
}
let saw_read = false
let saw_write = false
let saw_exec = false
for tool_name in tools {
const name = lowercase(trim(tool_name))
if contains(["read", "search", "fetch", "query", "list", "inspect", "grep"], name) {
saw_read = true
continue
}
if contains(["edit", "write", "create", "update", "delete", "apply_patch"], name) {
saw_write = true
continue
}
if contains(["run", "exec", "shell", "test"], name) {
saw_exec = true
continue
}
return "generic"
}
if saw_write && !saw_read && !saw_exec {
return "write"
}
if saw_exec && !saw_read && !saw_write {
return "exec"
}
if saw_read && !saw_write && !saw_exec {
return "read"
}
return "mixed"
}
fn __action_graph_phase(raw, fallback_phase, tools, instruction) {
const explicit = lowercase(
trim(raw?.phase ?? raw?.kind ?? raw?.type ?? raw?.category ?? raw?.mode ?? fallback_phase ?? ""),
)
if contains(["research", "discover", "analyze", "analysis", "investigate"], explicit) {
return "research"
}
if contains(["verify", "verification", "test", "check", "evaluate", "evaluator", "qa"], explicit) {
return "verify"
}
if contains(["execute", "act", "implementation", "implement", "apply", "fix", "edit"], explicit) {
return "execute"
}
const text = lowercase(trim(instruction ?? ""))
if starts_with(text, "research ")
|| starts_with(text, "inspect ")
|| starts_with(text, "analyze ")
|| starts_with(text, "investigate ") {
return "research"
}
if starts_with(text, "verify ")
|| starts_with(text, "test ")
|| starts_with(text, "check ")
|| starts_with(text, "evaluate ") {
return "verify"
}
const tool_class = __action_graph_tool_class(tools, fallback_phase)
if tool_class == "read" && explicit != "execute" {
return "research"
}
return "execute"
}
fn __action_graph_dependency_refs(raw) {
const source = raw?.depends_on ?? raw?.dependsOn ?? raw?.dependencies ?? raw?.needs ?? raw?.after
if source == nil {
return []
}
let refs = []
for item in __action_graph_listify(source) {
if item == nil {
continue
}
if type_of(item) == "int" || type_of(item) == "float" {
refs = refs + [format("{}", item)]
continue
}
if type_of(item) == "dict" {
const ref_text = __action_graph_first_string(
[item?.id, item?.action_id, item?.title, item?.name, item?.label, item?.task],
)
if ref_text != nil {
refs = refs + [ref_text]
}
continue
}
const text = __action_graph_non_empty_string(item)
if text != nil {
refs = refs + [text]
}
}
return refs
}
fn __action_graph_collect_items(parsed) {
if parsed == nil {
return []
}
if type_of(parsed) == "list" {
return parsed
}
if type_of(parsed) != "dict" {
return [parsed]
}
for key in ["actions", "plan", "steps", "items", "tasks"] {
const value = parsed[key]
if type_of(value) == "list" {
return value
}
if type_of(value) == "dict" {
const nested = __action_graph_collect_items(value)
if len(nested) > 0 {
return nested
}
}
}
if type_of(parsed?.batches) == "list" {
let flattened = []
for batch in parsed.batches {
const batch_phase = batch?.phase ?? batch?.kind ?? batch?.type
const batch_items = batch?.actions ?? batch?.items ?? batch?.steps ?? []
for item in __action_graph_listify(batch_items) {
if type_of(item) == "dict" {
flattened = flattened + [item + {phase: item?.phase ?? batch_phase}]
} else {
flattened = flattened + [item]
}
}
}
if len(flattened) > 0 {
return flattened
}
}
let grouped = []
for phase in ["research", "execute", "verify"] {
const value = parsed[phase]
if value == nil {
continue
}
for item in __action_graph_listify(value) {
if type_of(item) == "dict" {
grouped = grouped + [item + {phase: item?.phase ?? phase}]
} else {
grouped = grouped + [{title: item, instruction: item, phase: phase}]
}
}
}
if len(grouped) > 0 {
return grouped
}
return [parsed]
}
fn __action_graph_aliases_for(canonical_id, source_id, title, index) {
let aliases = {}
const numeric = format("{}", index + 1)
aliases[canonical_id] = canonical_id
aliases[numeric] = canonical_id
if source_id != nil {
aliases[lowercase(trim(source_id))] = canonical_id
}
if title != nil {
aliases[lowercase(trim(title))] = canonical_id
}
return aliases
}
fn __action_graph_merge_aliases(base, extra) {
let merged = base ?? {}
for entry in extra ?? {} {
merged[entry.key] = entry.value
}
return merged
}
fn __action_graph_append_unique_strings(items, values) {
let result = items ?? []
for value in __action_graph_listify(values) {
const text = __action_graph_non_empty_string(value)
if text != nil && !contains(result, text) {
result = result + [text]
}
}
return result
}
fn __action_graph_batch_target_paths(batch) {
let target_paths = []
for action in batch.actions {
target_paths = __action_graph_append_unique_strings(
target_paths,
action?.target_paths ?? action?.metadata?.target_paths ?? [],
)
}
return target_paths
}
fn __action_graph_batch_verification(batch) {
const target_paths = __action_graph_batch_target_paths(batch)
if len(target_paths) == 0 {
return nil
}
return {
summary: "Current batch scope",
required_paths: target_paths,
notes: [
"Only the current batch paths are in scope for this stage.",
"Future verifier checks run in later stages; do not satisfy them early unless this batch explicitly names that work.",
],
}
}
fn __action_graph_batch_prompt(batch) {
let available_tools = []
for action in batch.actions {
for tool_name in action?.tools ?? [] {
if !contains(available_tools, tool_name) {
available_tools = available_tools + [tool_name]
}
}
}
const target_paths = __action_graph_batch_target_paths(batch)
let create_direct_write = false
let has_exact_command = false
for action in batch.actions {
const instruction = lowercase(trim(action?.instruction ?? action?.title ?? ""))
if starts_with(instruction, "create ") || starts_with(instruction, "write new ") {
create_direct_write = true
}
if __action_graph_non_empty_string(action?.command ?? action?.metadata?.command) != nil {
has_exact_command = true
}
}
let lines = [
"You are executing a scheduled action-graph batch.",
"",
"Phase: " + batch.phase,
"Tool class: " + batch.tool_class,
"Available tools this batch: "
+ if len(available_tools) > 0 {
join(available_tools, ", ")
} else {
"(none declared)"
},
"Target paths this batch: "
+ if len(target_paths) > 0 {
join(target_paths, ", ")
} else {
"(none declared)"
},
"",
"Authority order: obey the current batch action list and the injected verifier contract before any broader workflow context.",
"Complete every action in this batch before finishing.",
"Scope boundary: perform only the action ids listed below for this batch.",
"If you write, keep writes inside the current batch target paths unless a tool result proves a listed path needs to move.",
"The runtime rejects mutating path-based tool calls outside the current batch target paths.",
"You may read supporting workspace files outside the current batch target paths only when that context is immediately necessary to complete this batch.",
"Do not inspect sibling files outside the current batch target paths just to prepare future batches.",
"Treat broader plan/context artifacts as background evidence only, not as permission to continue with later actions.",
"If workflow context mentions future batches or terminal verification, ignore that work until this batch explicitly names it.",
"Do not preempt later batches, edit their target files early, or run final verification unless this batch explicitly lists those steps.",
"If a tool result rejects another path or scope, treat that as evidence you attempted future-batch work; return to the current batch instead of retrying adjacent files.",
"Do not issue a second write to the same target path unless a subsequent read or run result exposed a concrete problem with the first write.",
"After a successful write satisfies the current batch target paths, finish immediately unless this batch explicitly includes a verification command.",
"Once the listed actions are satisfied, stop immediately instead of continuing with adjacent work from the overall plan.",
"Actions:",
]
if create_direct_write {
lines = lines
+ [
"If an action explicitly says to create a target file, write that file directly instead of reading unrelated files first.",
"After a successful write creates the target file, do not reread or rewrite that file unless a tool result exposes a concrete problem or this batch explicitly requires validation.",
]
}
if has_exact_command {
lines = lines
+ [
"If an action includes an exact command string, run that exact command before trying alternatives or discovery commands.",
]
if batch.phase == "verify" && len(available_tools) == 1 && available_tools[0] == "run" {
lines = lines
+ [
"This verify batch is observation-only.",
"After the exact command returns, report whether it passed or failed and stop.",
"Do not attempt repairs, shell edits, fallback commands, or extra discovery in this batch.",
]
}
}
for action in batch.actions {
const title = action?.title ?? action?.instruction ?? action?.id
const exact_command = __action_graph_non_empty_string(action?.command ?? action?.metadata?.command)
const detail = if exact_command != nil {
(action?.instruction ?? title) + " Run exactly `${exact_command}`."
} else {
action?.instruction ?? title
}
lines = lines + ["- [" + action.id + "] " + title + ": " + detail]
}
return join(lines, "\n")
}
fn __action_graph_batch(batch_index, phase, tool_class, actions) {
let action_ids = []
let titles = []
let depends_on = []
for action in actions {
action_ids = action_ids + [action.id]
titles = titles + [action?.title ?? action?.instruction ?? action.id]
for dep in action?.depends_on ?? [] {
if !contains(action_ids, dep) && !contains(depends_on, dep) {
depends_on = depends_on + [dep]
}
}
}
const batch = {
_type: "action_batch",
id: format("batch-{}", batch_index + 1),
phase: phase,
tool_class: tool_class,
actions: actions,
action_ids: action_ids,
action_titles: titles,
depends_on: depends_on,
count: len(actions),
prompt: nil,
}
return batch + {prompt: __action_graph_batch_prompt(batch)}
}
fn __action_graph_batch_output_kind(phase) {
if phase == "research" {
return "summary"
}
if phase == "verify" {
return "verification_result"
}
return "summary"
}
fn __action_graph_output_kind(template, default_output) {
const outputs = template?.output_contract?.output_kinds ?? []
if len(outputs) > 0 {
return outputs[0]
}
return default_output
}
fn __action_graph_stage_template(config, phase) {
if phase == "research" {
return config?.research ?? config?.stage ?? {}
}
if phase == "verify" {
return config?.verify_phase ?? config?.execute ?? config?.stage ?? {}
}
return config?.execute ?? config?.stage ?? {}
}
fn __action_graph_terminal_template(config, phase) {
if phase == "verify" {
return config?.verify ?? {}
}
if phase == "evaluate" {
return config?.evaluate ?? {}
}
return {}
}
fn __action_graph_terminal_kind(template, phase) {
if template?.kind != nil {
return template.kind
}
if phase == "verify" || template?.command != nil || template?.assert_text != nil {
return "verify"
}
return "stage"
}
fn __action_graph_node_system(base_system, prompt) {
const prefix = __action_graph_non_empty_string(base_system)
if prefix == nil {
return prompt
}
return prefix + "\n\n" + prompt
}
fn __action_graph_batch_tools(batch, template) {
const registry = template?.tools
if registry == nil {
return nil
}
let selected = []
for action in batch.actions {
for tool_name in action?.tools ?? [] {
if !contains(selected, tool_name) {
selected = selected + [tool_name]
}
}
}
if contains(selected, "write") && !contains(selected, "read") {
selected = ["read"] + selected
}
if len(selected) == 0 {
return registry
}
return tool_select(registry, selected)
}
fn __action_graph_batch_context_kinds(batch, template) {
const explicit = template?.context_policy?.include_kinds ?? []
if len(explicit) > 0 {
return explicit
}
if batch.phase == "research" {
return ["summary"]
}
if batch.phase == "verify" {
return ["summary", "artifact", "verification_result"]
}
return ["summary"]
}
fn __action_graph_batch_node(batch, template) {
const local_verification = __action_graph_batch_verification(batch)
const model_policy = template?.model_policy ?? {}
const output_kind = __action_graph_output_kind(template, __action_graph_batch_output_kind(batch.phase))
const output_contract = (template?.output_contract ?? {}) + {output_kinds: [output_kind]}
const target_paths = __action_graph_batch_target_paths(batch)
const context_policy = (template?.context_policy ?? {})
+ {include_kinds: __action_graph_batch_context_kinds(batch, template)}
let metadata = template?.metadata ?? {}
if local_verification != nil {
metadata = metadata + {workflow_verification_scope: "local_only"}
}
const approval_policy = if len(target_paths) > 0 {
(template?.approval_policy ?? {}) + {write_path_allowlist: target_paths}
} else {
template?.approval_policy ?? {}
}
return (template ?? {})
+ {
kind: "stage",
task_label: format("{} batch {}", uppercase(batch.phase), batch.id),
system: __action_graph_node_system(template?.system, batch.prompt),
tools: __action_graph_batch_tools(batch, template),
model_policy: model_policy,
approval_policy: approval_policy,
verify: if local_verification != nil {
local_verification
} else {
template?.verify
},
output_contract: output_contract,
context_policy: context_policy,
metadata: metadata,
}
}
fn __action_graph_terminal_prompt(plan, phase) {
if phase == "verify" {
return "Review the action-graph plan and produced artifacts, then verify whether the requested work is complete."
}
return "Evaluate the completed action-graph run and summarize whether the plan satisfied the task."
}
fn __action_graph_terminal_node(plan, phase, template) {
const default_output = if phase == "verify" {
"verification_result"
} else {
"summary"
}
const node_kind = __action_graph_terminal_kind(template, phase)
let verify_payload = nil
if node_kind == "verify" {
verify_payload = template?.verify
?? filter_nil(
{
command: template?.command,
assert_text: template?.assert_text,
expect_status: template?.expect_status,
},
)
}
const output_contract = (template?.output_contract ?? {})
+ {output_kinds: [__action_graph_output_kind(template, default_output)]}
const context_policy = (template?.context_policy ?? {})
+ {
include_kinds: template?.context_policy?.include_kinds
?? ["plan", "summary", "artifact", "verification_result"],
}
return (template ?? {})
+ filter_nil(
{
kind: node_kind,
task_label: uppercase(phase),
system: __action_graph_node_system(template?.system, __action_graph_terminal_prompt(plan, phase)),
verify: verify_payload,
output_contract: output_contract,
context_policy: context_policy,
},
)
}
/**
* Normalize planner output into a canonical action-graph envelope.
* action_graph.
*
* @effects: []
* @errors: []
*/
pub fn action_graph(raw, options = nil) {
const parsed = __action_graph_parse_input(raw)
const items = __action_graph_collect_items(parsed)
let staged = []
let aliases = {}
for (index, item) in iter(items).enumerate() {
const dict = if type_of(item) == "dict" {
item
} else {
{title: item, instruction: item}
}
const tools = __action_graph_tool_list(dict)
const title = __action_graph_first_string(
[dict?.title, dict?.name, dict?.label, dict?.action, dict?.task, dict?.instruction, dict?.summary],
)
?? format("Action {}", index + 1)
const instruction = __action_graph_first_string(
[
dict?.instruction,
dict?.prompt,
dict?.task,
dict?.action,
dict?.goal,
dict?.description,
dict?.text,
title,
],
)
?? title
const source_id = __action_graph_first_string([dict?.id, dict?.action_id, dict?.name, dict?.label])
const phase = __action_graph_phase(dict, dict?.phase ?? options?.phase, tools, instruction)
const canonical_id = format("action-{}", index + 1)
staged = staged
+ [
{
_type: "action_item",
id: canonical_id,
title: title,
instruction: instruction,
phase: phase,
depends_on: __action_graph_dependency_refs(dict),
tools: tools,
tool_class: dict?.tool_class ?? __action_graph_tool_class(tools, phase),
metadata: filter_nil({source_id: source_id, source_phase: dict?.phase ?? dict?.kind ?? dict?.type}),
},
]
aliases = __action_graph_merge_aliases(
aliases,
__action_graph_aliases_for(canonical_id, source_id, title, index),
)
}
let normalized = []
let research_ids = []
let execute_ids = []
for action in staged {
let resolved = []
for ref in action.depends_on {
const lookup = lowercase(trim(ref))
const target = aliases[lookup] ?? aliases[ref]
if target != nil && target != action.id && !contains(resolved, target) {
resolved = resolved + [target]
}
}
if len(resolved) == 0 && action.phase == "execute" && len(research_ids) > 0 {
resolved = research_ids
}
if len(resolved) == 0 && action.phase == "verify" {
resolved = if len(execute_ids) > 0 {
execute_ids
} else {
research_ids
}
}
const repaired = action + {depends_on: resolved}
normalized = normalized + [repaired]
if repaired.phase == "research" {
research_ids = research_ids + [repaired.id]
}
if repaired.phase == "execute" {
execute_ids = execute_ids + [repaired.id]
}
}
return {
_type: "action_graph",
task: options?.task ?? parsed?.task ?? parsed?.goal,
summary: options?.summary ?? parsed?.summary ?? parsed?.rationale,
actions: normalized,
metadata: filter_nil(
{
source: if type_of(parsed) == "dict" {
parsed?._type ?? "planner_output"
} else {
"planner_output"
},
},
),
}
}
/**
* Compute dependency-ready execution batches from a canonical action graph.
* action_graph_batches.
*
* @effects: []
* @errors: []
*/
pub fn action_graph_batches(graph, completed = nil) {
const plan = if graph?._type == "action_graph" {
graph
} else {
action_graph(graph, nil)
}
const done = __action_graph_listify(completed)
let remaining = []
for action in plan.actions {
if !contains(done, action.id) {
remaining = remaining + [action]
}
}
let finished = done
let batches = []
let loop_count = 0
while len(remaining) > 0 && loop_count < len(plan.actions) + 4 {
let ready = []
let blocked = []
for action in remaining {
let can_run = true
for dep in action.depends_on {
if !contains(finished, dep) {
can_run = false
}
}
if can_run {
ready = ready + [action]
} else {
blocked = blocked + [action]
}
}
if len(ready) == 0 {
ready = [remaining[0]]
blocked = remaining[1:]
}
const phase = if ready[0]?.phase == "research" || ready[0]?.phase == "execute" || ready[0]?.phase == "verify" {
ready[0].phase
} else {
"execute"
}
let current_phase = []
let next_remaining = blocked
for action in ready {
if action.phase == phase {
current_phase = current_phase + [action]
} else {
next_remaining = next_remaining + [action]
}
}
let grouped = {}
let order = []
for action in current_phase {
const key = action.tool_class ?? "generic"
if grouped[key] == nil {
grouped[key] = []
order = order + [key]
}
grouped[key] = grouped[key] + [action]
}
for key in order {
const batch = __action_graph_batch(len(batches), phase, key, grouped[key])
batches = batches + [batch]
for action in grouped[key] {
finished = finished + [action.id]
}
}
remaining = next_remaining
loop_count = loop_count + 1
}
return batches
}
/**
* Render a canonical action graph into readable markdown.
* action_graph_render.
*
* @effects: []
* @errors: []
*/
pub fn action_graph_render(graph) {
const plan = if graph?._type == "action_graph" {
graph
} else {
action_graph(graph, nil)
}
let lines = ["# Action Graph"]
if plan?.task != nil {
lines = lines + ["", "Task: " + plan.task]
}
if plan?.summary != nil {
lines = lines + ["", "Summary: " + plan.summary]
}
for phase in ["research", "execute", "verify"] {
let phase_lines = []
for action in plan.actions {
if action.phase != phase {
continue
}
const deps = if len(action.depends_on) > 0 {
" (depends on: " + join(action.depends_on, ", ") + ")"
} else {
""
}
const label = action?.title ?? action?.instruction ?? action.id
phase_lines = phase_lines
+ ["- [" + action.id + "] " + label + " [" + action.tool_class + "]" + deps]
}
if len(phase_lines) > 0 {
lines = lines + ["", "## " + uppercase(phase)]
lines = lines + phase_lines
}
}
return join(lines, "\n")
}
/**
* Convert a canonical action graph into a typed workflow graph.
* action_graph_flow.
*
* @effects: []
* @errors: []
*/
pub fn action_graph_flow(graph, config = nil) {
const plan = if graph?._type == "action_graph" {
graph
} else {
action_graph(graph, config)
}
const batches = action_graph_batches(plan)
let nodes = {}
let edges = []
let entry = ""
let previous = ""
for batch in batches {
const node_id = replace(batch.id, "-", "_")
const template = __action_graph_stage_template(config, batch.phase)
nodes[node_id] = __action_graph_batch_node(batch, template)
if entry == "" {
entry = node_id
}
if previous != "" {
edges = edges + [{from: previous, to: node_id}]
}
previous = node_id
}
for phase in ["verify", "evaluate"] {
const template = __action_graph_terminal_template(config, phase)
if len(keys(template ?? {})) == 0 {
continue
}
const node_id = if phase == "verify" {
"final_verify"
} else {
"final_evaluate"
}
nodes[node_id] = __action_graph_terminal_node(plan, phase, template)
if entry == "" {
entry = node_id
}
if previous != "" {
edges = edges + [{from: previous, to: node_id}]
}
previous = node_id
}
return workflow_graph(
{
name: config?.name ?? plan?.task ?? "action_graph",
entry: if entry == "" {
"final_verify"
} else {
entry
},
nodes: nodes,
edges: edges,
},
)
}
/**
* Execute an action graph through the shared workflow runtime.
* action_graph_run.
*
* @effects: []
* @errors: []
*/
pub fn action_graph_run(task, graph, config = nil, overrides = nil) {
const plan = if graph?._type == "action_graph" {
graph
} else {
action_graph(graph, (config ?? {}) + {task: task})
}
const flow = action_graph_flow(plan, config)
const plan_artifact = artifact(
{
kind: "plan",
title: config?.plan_title ?? "Action graph",
text: action_graph_render(plan),
data: plan,
source: "action_graph",
freshness: "fresh",
relevance: 1.0,
},
)
const artifacts = (overrides?.artifacts ?? []) + [plan_artifact]
const runtime = workflow_execute(task, flow, artifacts, overrides)
return {
_type: "action_graph_run",
task: task,
status: runtime?.status,
plan: plan,
batches: action_graph_batches(plan),
flow: flow,
runtime: runtime,
run: runtime?.run,
artifacts: runtime?.artifacts ?? [],
transcript: runtime?.transcript,
visible_text: workflow_result_text(runtime),
}
}
/**
* task_run.
*
* @effects: []
* @errors: []
*/