Independent Elixir Consumer¶
Use published Hex praxo_core 0.8.0 in your own application with Elixir 1.14
or newer. No PRAXO-CORE source checkout, API key, model service, or AxisVela
product is required. Installation needs Hex access or a cached package.
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.
Create the Application¶
Declare the exact dependency in mix.exs:
defp deps do
[
{:praxo_core, "~> 0.9.0"}
]
end
Then run mix deps.get. Do not use a path: dependency or a checkout of the
language source directory for acceptance.
Application Source¶
Use the following as starter.exs, rendered from the
tested source.
defmodule ConsumerStarter do
alias Praxo.{Agent, Models}
def run_scenario(target) do
counter = :counters.new(1, [:atomics])
model = fn request ->
last = List.last(request.messages)
if last.role == "tool" do
{:ok, Models.response(Models.message_assistant("recorded"))}
else
call = Models.tool_call("record-1", "record", %{"target" => target})
{:ok, Models.response(Models.message_assistant("", [call]))}
end
end
tool =
Agent.new_tool("record", "Record a target in an in-memory counter.", fn _arguments ->
:counters.add(counter, 1, 1)
"recorded"
end)
policy = fn action ->
case action.kind do
"model.generate" ->
Models.decision(true, "model generation")
"tool.invoke" ->
Models.decision(
action.tool_name == "record" and action.arguments["target"] == "public",
"only the public target is permitted"
)
_ ->
Models.decision(false, "unsupported action")
end
end
options = %{Agent.new_agent_options(model) |
tools: [tool],
allowed_tools: ["record"],
policy: policy}
case Agent.run_agent(options, Models.session("consumer-#{target}"), "Record the requested target.") do
{:ok, result, _} -> {result.output, :counters.get(counter, 1), nil}
{:error, code, _} -> {"", :counters.get(counter, 1), code}
end
end
def run do
{"recorded", 1, nil} = run_scenario("public")
IO.puts("allow: recorded; effects=1")
{"", 0, code} = run_scenario("private")
if code != "AGENT_POLICY_DENIED" do
raise "deny verification failed: code=#{inspect(code)}"
end
IO.puts("deny: policy rejected; effects=0")
end
end
Acceptance Tests¶
Use the following as test_starter.exs, rendered from the
tested source.
Code.require_file("starter.exs", __DIR__)
ConsumerStarter.run()
IO.puts("elixir consumer tests passed")
Verify¶
mix deps.get
mix run test_starter.exs
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 returns
AGENT_POLICY_DENIED and must not increment the in-memory counter. Keep these
tests when replacing the fixture with a real adapter.
For a source checkout without Hex, compile the beams and run with
elixir -pa language/elixir/build/ebin test_starter.exs; that path is not
independent consumer evidence.
Scope¶
This starter establishes only the stated Hex package 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 Elixir implementation guide and the security model.