Skip to content

Independent Lua Consumer

Use the published LuaRocks praxo-core 0.6.0-1 package in your own application with Lua 5.4. No PRAXO-CORE source checkout, API key, model service, or AxisVela product is required. Installation needs LuaRocks access or a cached rock.

This is an extended-language starter. It demonstrates the allow/deny path but is not one of the four pilot languages that carry the governed negative matrix.

Choose the Boundary

This is a fit for an embedded model/tool loop with explicit tool scope and policy. Your application still owns model access, authentic identity, persistent approvals, external effects, and isolation. It is not a hosted agent platform, durable graph engine, or sandbox service. See when not to use Core.

Install in a New Application

luarocks install praxo-core 0.7.0-1

Do not add the repository language/lua directory to package.path, and do not use a local rockspec or checkout for acceptance. The starter adds a source fallback only so this repository can run the same file without LuaRocks; a real consumer should install the rock.

Application Source

Use the following as starter.lua, rendered from the tested source.

local script_dir = (arg and arg[0] and arg[0]:match("^(.*)[/\\][^/\\]*$")) or "."

package.path = table.concat({
  script_dir .. "/../../language/lua/?.lua",
  script_dir .. "/../../language/lua/?/init.lua",
  "language/lua/?.lua",
  "language/lua/?/init.lua",
  "./?.lua",
  package.path,
}, ";")

local agent = require("praxo.agent")
local errors = require("praxo.errors")
local models = require("praxo.models")

local M = {}

local function fixture_model(target)
  return {
    generate = function(request)
      local last = request.messages[#request.messages]
      if last.role == models.TOOL then
        return models.response({ message = models.message_assistant("recorded") })
      end
      local call = models.tool_call({
        id = "record-1",
        name = "record",
        arguments = { target = target },
      })
      return models.response({
        message = models.message_assistant("", { call }),
      })
    end,
  }
end

function M.run_scenario(target)
  local calls = 0
  local registry = agent.tool_registry()
  agent.register_tool(registry, agent.func_tool("record", "Record a target.", function(_arguments)
    calls = calls + 1
    return "recorded"
  end))

  local options = agent.agent_options(fixture_model(target))
  options.tools = registry
  options.allowed_tools = { "record" }
  options.policy = function(action)
    if action.kind == "model.generate" then
      return { allowed = true, reason = "model generation" }
    elseif action.kind == "tool.invoke" then
      return {
        allowed = action.tool_name == "record" and action.arguments.target == "public",
        reason = "only the public target is permitted",
      }
    end
    return { allowed = false, reason = "unsupported action" }
  end

  local ok, result = pcall(agent.run_agent, options,
    models.session({ id = "consumer-" .. target }), "Record the requested target.")
  if not ok then
    return "", calls, errors.code(result)
  end
  return result.output, calls, nil
end

function M.run()
  local output, calls, code = M.run_scenario("public")
  assert(code == nil and output == "recorded" and calls == 1, "allow verification failed")
  print("allow: recorded; effects=1")

  local _, deny_calls, deny_code = M.run_scenario("private")
  assert(deny_code == "AGENT_POLICY_DENIED" and deny_calls == 0, "deny verification failed")
  print("deny: policy rejected; effects=0")
end

return M

Acceptance Tests

Use the following as test_starter.lua, rendered from the tested source.

local script_dir = (arg and arg[0] and arg[0]:match("^(.*)[/\\][^/\\]*$")) or "."
local starter = dofile(script_dir .. "/starter.lua")

local output, calls, code = starter.run_scenario("public")
assert(code == nil and output == "recorded" and calls == 1, "allow verification failed")

local _, deny_calls, deny_code = starter.run_scenario("private")
assert(deny_code == "AGENT_POLICY_DENIED" and deny_calls == 0, "deny verification failed")

print("lua consumer tests passed")

Verify

luarocks install praxo-core 0.7.0-1
lua test_starter.lua

Expected program output:

allow: recorded; effects=1
deny: policy rejected; effects=0

The model is a deterministic fixture, not an LLM. allowed_tools limits the tool name; Policy also checks the target argument. A denied private target raises AGENT_POLICY_DENIED and must not increment the in-memory counter. Keep these tests when replacing the fixture with a real adapter.

Scope

This starter establishes only the stated rock and fixture behavior. It does not add the governed negative matrix, prove authentic identity, provide durable approvals, reconcile effects, or establish isolation. Continue with the Lua implementation guide and the security model.