Spaces:
Running on Zero
Running on Zero
Download space/lab.lua from jeorgexyz/lua-agent-lab: direct link, hf CLI and curl.
- Browser
- Download file 5.88 kB
-
https://huggingface.co/spaces/jeorgexyz/lua-agent-lab/resolve/main/space/lab.lua
- Command line
-
hf download hf://spaces/jeorgexyz/lua-agent-lab/space/lab.lua
-
curl -L -o lab.lua https://huggingface.co/spaces/jeorgexyz/lua-agent-lab/resolve/main/space/lab.lua
5.88 kB
| -- The web adapter only supplies a backend and an isolated filesystem. | |
| -- Parsing, dispatch, approval, recovery, repetition and eviction remain Lua. | |
| local Agent = require('agent') | |
| local tools = require('tools') | |
| local trace = require('trace') | |
| local protocol = require('protocol') | |
| local tasks = require('eval.tasks.tasks').tasks | |
| local M = {} | |
| function M.tasks_json() | |
| local out = {} | |
| for _, t in ipairs(tasks) do | |
| out[#out + 1] = {id=t.id, prompt=t.prompt, tools=t.tools, | |
| budget=t.budget or 4096, approve=t.approve == true, script=t.script} | |
| end | |
| return trace.encode(out) | |
| end | |
| function M.registry(names, files) | |
| local reg = tools.registry() | |
| for _, name in ipairs(names) do | |
| local spec = {} | |
| for k, v in pairs(assert(tools[name], 'unknown tool')) do spec[k] = v end | |
| if name == 'read_file' then | |
| spec.run = function(a) | |
| local body = files[a.path] | |
| if body == nil then error('cannot open ' .. a.path .. ': No such file', 0) end | |
| if #body > tools.MAX_OBSERVATION then | |
| return body:sub(1, tools.MAX_OBSERVATION) .. '\n[truncated]' | |
| end | |
| return body | |
| end | |
| elseif name == 'write_file' then | |
| spec.run = function(a) | |
| -- These keys never become host filesystem paths. | |
| if #a.path > 200 or #a.text > 8192 then error('virtual file too large', 0) end | |
| local count = 0 | |
| for _ in pairs(files) do count = count + 1 end | |
| if count >= 32 and files[a.path] == nil then error('virtual file limit reached', 0) end | |
| files[a.path] = a.text | |
| return string.format('wrote %d bytes to %s', #a.text, a.path) | |
| end | |
| elseif name == 'list_dir' then | |
| spec.run = function(a) | |
| local prefix = a.path or '.' | |
| prefix = prefix:gsub('^%./', ''):gsub('/$', '') | |
| if prefix == '.' then prefix = '' end | |
| if prefix ~= '' then prefix = prefix .. '/' end | |
| local names, seen = {}, {} | |
| for path in pairs(files) do | |
| if path:sub(1, #prefix) == prefix then | |
| local name = path:sub(#prefix + 1):match('^[^/]+') | |
| if name and not seen[name] then names[#names+1] = name; seen[name] = true end | |
| end | |
| end | |
| table.sort(names) | |
| return table.concat(names, '\n') | |
| end | |
| elseif name == 'calc' then | |
| local calculate = spec.run | |
| spec.run = function(a) | |
| if #a.expr > 512 then error('expression exceeds 512 characters', 0) end | |
| return calculate(a) | |
| end | |
| end | |
| reg:add(spec) | |
| end | |
| return reg | |
| end | |
| function M.run(config_json, files_json, factory) | |
| local o = assert(trace.decode(config_json)) | |
| local files = assert(trace.decode(files_json)) | |
| local task | |
| for _, t in ipairs(tasks) do if t.id == o.task_id then task = t end end | |
| local names = o.tool_names or (task and task.tools) or {'calc','read_file','list_dir','write_file'} | |
| local reg = M.registry(names, files) | |
| local backend, tokenizer | |
| if o.model == 'scripted' then | |
| assert(task and o.prompt == task.prompt, 'Scripted replay requires an unchanged preset task.') | |
| local i = 0 | |
| backend = {complete=function() | |
| i = i + 1 | |
| return task.script[i] or 'CALL: {"tool":"answer","args":{"text":"(script exhausted)"}}', {} | |
| end} | |
| tokenizer = require('bpe').approximate() | |
| else | |
| backend, tokenizer = factory(reg, config_json) | |
| end | |
| local old_render = protocol.render_system | |
| if o.model == 'tiny' then protocol.render_system = protocol.render_compact end | |
| local fatal_on = {} | |
| if o.error_recovery == false or o.parse_recovery == false then fatal_on.parse_error = true end | |
| if o.error_recovery == false or o.tool_recovery == false then | |
| fatal_on.tool_error = true; fatal_on.timeout = true | |
| end | |
| local approvals = {} | |
| local agent = Agent.new({backend=backend, tools=reg, tokenizer=tokenizer, | |
| max_steps=o.max_steps or 10, max_tokens=o.context_limit or 4096, | |
| repeat_threshold=o.loop_detector == false and 10000 or 3, | |
| policy=o.policy or 'elide_observations', fatal_on=fatal_on, | |
| approve=function(call) | |
| local allowed = o.approval_gate == false or o.approve_write == true | |
| approvals[#approvals+1] = {call=call, allowed=allowed, gate_enabled=o.approval_gate ~= false} | |
| return allowed | |
| end}) | |
| local answer, reason, steps = agent:run(o.prompt) | |
| protocol.render_system = old_render | |
| local evicted = false | |
| for _, s in ipairs(steps) do if s.evicted then evicted = true end end | |
| local passed, why | |
| if task and o.prompt == task.prompt and not o.decoder_trial then | |
| -- Preserve the original judges, redirecting their out.txt checks to | |
| -- the same in-memory files used by the public tools. | |
| local old_open, old_remove = io.open, os.remove | |
| io.open = function(path) | |
| if files[path] == nil then return nil end | |
| return {read=function() return files[path] end, close=function() end} | |
| end | |
| os.remove = function(path) files[path] = nil; return true end | |
| local judge = o.model == 'scripted' and task.check or task.solved | |
| local ok, a, b = pcall(judge, answer, steps, {evicted=evicted, reason=reason}) | |
| io.open, os.remove = old_open, old_remove | |
| if not ok then error(a) end | |
| passed, why = a, b | |
| end | |
| return trace.encode({answer=answer, reason=reason, steps=steps, passed=passed, | |
| judge_note=why, evicted=evicted, approvals=approvals, files=files}) | |
| end | |
| return M | |