From aa2701481828e850fc1903da1d30eb562832da11 Mon Sep 17 00:00:00 2001 From: Wolfy-J Date: Sat, 8 Aug 2026 15:09:23 -0400 Subject: [PATCH] feat(agent): max_iterations joins the reserved agent-node inputs A workflow payload can now carry the iteration budget the way it carries context, model, and agent_id: a positive-integer max_iterations input overrides the arena config; absent keeps the configured or default cap. --- src/node/agent/node.lua | 28 ++++++++++++++++++++-------- src/node/agent/node_context_test.lua | 22 +++++++++++++++++++--- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/node/agent/node.lua b/src/node/agent/node.lua index bf67fda..902504f 100644 --- a/src/node/agent/node.lua +++ b/src/node/agent/node.lua @@ -500,7 +500,7 @@ local function process_multiple_inputs(inputs) if inputs.context and inputs.context.content ~= nil then local context_content = inputs.context.content if type(context_content) ~= "table" then - return nil, nil, nil, nil, "context must be a table/object" + return nil, nil, nil, nil, nil, "context must be a table/object" end input_context = context_content end @@ -509,7 +509,7 @@ local function process_multiple_inputs(inputs) if inputs.agent_id and inputs.agent_id.content ~= nil then local agent_id_content = inputs.agent_id.content if type(agent_id_content) ~= "string" or agent_id_content == "" then - return nil, nil, nil, nil, "agent_id must be a non-empty string" + return nil, nil, nil, nil, nil, "agent_id must be a non-empty string" end agent_id_override = agent_id_content end @@ -518,14 +518,25 @@ local function process_multiple_inputs(inputs) if inputs.model and inputs.model.content ~= nil then local model_content = inputs.model.content if type(model_content) ~= "string" or model_content == "" then - return nil, nil, nil, nil, "model must be a non-empty string" + return nil, nil, nil, nil, nil, "model must be a non-empty string" end model_override = model_content end + local max_iterations_override = nil + if inputs.max_iterations and inputs.max_iterations.content ~= nil then + local max_iterations_content = tonumber(inputs.max_iterations.content) + if not max_iterations_content or max_iterations_content < 1 + or max_iterations_content ~= math.floor(max_iterations_content) then + return nil, nil, nil, nil, nil, "max_iterations must be a positive integer" + end + max_iterations_override = max_iterations_content + end + local parts = {} for key, input in pairs(inputs) do - if key ~= "context" and key ~= "agent_id" and key ~= "model" and input.content ~= nil then + if key ~= "context" and key ~= "agent_id" and key ~= "model" and key ~= "max_iterations" + and input.content ~= nil then local content = input.content if type(content) == "table" then content = json.encode(content) @@ -537,10 +548,10 @@ local function process_multiple_inputs(inputs) end if #parts == 0 then - return input_context, agent_id_override, model_override, "", nil + return input_context, agent_id_override, model_override, max_iterations_override, "", nil end - return input_context, agent_id_override, model_override, table.concat(parts, "\n\n"), nil + return input_context, agent_id_override, model_override, max_iterations_override, table.concat(parts, "\n\n"), nil end local function validate_and_resolve_config(config) @@ -1805,7 +1816,8 @@ local function run(args) }, inputs_err) end - local input_context, agent_id_override, model_override, input_data, input_err = process_multiple_inputs(inputs) + local input_context, agent_id_override, model_override, max_iterations_override, input_data, input_err = + process_multiple_inputs(inputs) if input_err then return n:fail({ code = agent_consts.ERROR.INPUT_VALIDATION_FAILED, @@ -1917,7 +1929,7 @@ local function run(args) local saved_state = ((args.node or {}).metadata or {}).state or {} local iteration = saved_state.current_iteration or 0 - local max_iterations = config.arena.max_iterations or agent_consts.DEFAULTS.MAX_ITERATIONS + local max_iterations = max_iterations_override or config.arena.max_iterations or agent_consts.DEFAULTS.MAX_ITERATIONS local min_iterations = config.arena.min_iterations or agent_consts.DEFAULTS.MIN_ITERATIONS local tool_calling = config.arena.tool_calling local show_tool_calls = config.show_tool_calls ~= false diff --git a/src/node/agent/node_context_test.lua b/src/node/agent/node_context_test.lua index 392f4ca..2d0ab53 100644 --- a/src/node/agent/node_context_test.lua +++ b/src/node/agent/node_context_test.lua @@ -48,21 +48,23 @@ local function define_tests() -- An input_transform field whose expression resolves to nil produces -- an entry with nil content; the reserved carriers read it as "not -- provided", never as a malformed value. - local input_context, agent_id_override, model_override, input_data, err = process({ + local input_context, agent_id_override, model_override, max_iterations_override, input_data, err = process({ context = { content = nil, metadata = {} }, model = { content = nil, metadata = {} }, agent_id = { content = nil, metadata = {} }, + max_iterations = { content = nil, metadata = {} }, lead = { content = { name = "Jane" }, metadata = {} }, }) test.is_nil(err) test.is_nil(input_context) test.is_nil(agent_id_override) test.is_nil(model_override) + test.is_nil(max_iterations_override) test.is_true(input_data:find('', 1, true) ~= nil) end) it("renders no input tag for a nil-content input", function() - local _, _, _, input_data, err = process({ + local _, _, _, _, input_data, err = process({ empty = { content = nil, metadata = {} }, brief = { content = "text", metadata = {} }, }) @@ -72,10 +74,11 @@ local function define_tests() end) it("still merges a table context and applies string overrides", function() - local input_context, agent_id_override, model_override, input_data, err = process({ + local input_context, agent_id_override, model_override, max_iterations_override, input_data, err = process({ context = { content = { kb_ids = { "kb-1" } }, metadata = {} }, model = { content = "class:fast", metadata = {} }, agent_id = { content = "ns:researcher", metadata = {} }, + max_iterations = { content = 80, metadata = {} }, lead = { content = { name = "Jane" }, metadata = {} }, }) test.is_nil(err) @@ -83,7 +86,20 @@ local function define_tests() test.eq((input_context :: any).kb_ids[1], "kb-1") test.eq(model_override, "class:fast") test.eq(agent_id_override, "ns:researcher") + test.eq(max_iterations_override, 80) test.is_true(input_data:find('', 1, true) == nil) + test.is_true(input_data:find('', 1, true) == nil) + end) + + it("rejects a max_iterations input that is not a positive integer", function() + local _, _, _, _, _, err = process({ + max_iterations = { content = "eighty", metadata = {} }, + }) + test.not_nil(err) + local _, _, _, _, _, err2 = process({ + max_iterations = { content = -3, metadata = {} }, + }) + test.not_nil(err2) end) end) end