Independent TypeScript Consumer¶
Use the published @axisvela/praxo-core@0.7.1 package in your own Node.js
application. Node.js 20 or newer is required. No PRAXO-CORE source checkout, API
key, model service, or AxisVela product is needed. Installation needs npm
registry access or a populated cache.
Choose the Boundary¶
This fits 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¶
In a new application directory, create package.json with an exact dependency,
then install from the lockfile:
npm install --save-exact @axisvela/praxo-core@0.7.1
Commit the generated package-lock.json, and use npm ci in repeatable checks.
Do not use file:, link:, a repository URL, or a local path to substitute the
package. The tests verify the pinned version and that the module resolves inside
the application.
Application Source¶
Use the following as starter.js, rendered directly from the
tested source.
const {
Agent,
FuncHook,
Registry,
ROLE_ASSISTANT,
ROLE_TOOL,
PolicyDeniedError,
} = require("@axisvela/praxo-core");
class FixtureModel {
constructor(target) {
this.target = target;
}
async generate(request) {
const last = request.messages[request.messages.length - 1];
if (last.role === ROLE_TOOL) {
return { message: { role: ROLE_ASSISTANT, content: "recorded" } };
}
return {
message: {
role: ROLE_ASSISTANT,
toolCalls: [
{
id: "record-1",
name: "record",
arguments: { target: this.target },
},
],
},
};
}
}
class RecordTool {
constructor() {
this.name = "record";
this.description = "Record a target in an in-memory counter.";
this.risk = "write";
this.parameters = {
type: "object",
required: ["target"],
properties: { target: { type: "string" } },
};
this.calls = 0;
}
async invoke() {
this.calls += 1;
return "recorded";
}
}
function buildAgent(target) {
const tool = new RecordTool();
const tools = new Registry();
tools.register(tool);
const policy = new FuncHook((action) => {
if (action.kind === "model.generate") {
return { allowed: true };
}
if (action.kind === "tool.invoke") {
return {
allowed:
action.toolName === "record" &&
action.arguments?.target === "public",
reason: "only the public target is permitted",
};
}
return { allowed: false, reason: "unsupported action" };
});
const agent = new Agent({
model: new FixtureModel(target),
tools,
maxSteps: 3,
allowedTools: ["record"],
policy,
});
return { agent, tool };
}
async function run(write = (line) => console.log(line)) {
const allowed = buildAgent("public");
const result = await allowed.agent.run(
{ id: "starter-public" },
"Record the requested target."
);
if (result.output !== "recorded" || allowed.tool.calls !== 1) {
throw new Error(
`allow verification failed: output=${JSON.stringify(result.output)} effects=${allowed.tool.calls}`
);
}
write("allow: recorded; effects=1");
const denied = buildAgent("private");
try {
await denied.agent.run(
{ id: "starter-private" },
"Record the requested target."
);
} catch (error) {
if (!(error instanceof PolicyDeniedError)) {
throw error;
}
if (denied.tool.calls !== 0) {
throw new Error(`denied call produced effects=${denied.tool.calls}`);
}
write("deny: policy rejected; effects=0");
return;
}
throw new Error("private target was not denied");
}
if (require.main === module) {
run().catch((error) => {
console.error(error);
process.exitCode = 1;
});
}
module.exports = { buildAgent, run };
Acceptance Tests¶
Use the following as test_starter.test.js, rendered from the
tested source.
const assert = require("node:assert/strict");
const path = require("node:path");
const test = require("node:test");
const praxo = require("@axisvela/praxo-core");
const pkg = require("@axisvela/praxo-core/package.json");
const { buildAgent, run } = require("./starter");
const expectedVersion = process.env.PRAXO_EXPECTED_VERSION || "0.7.1";
test("allowed tool executes once", async () => {
const { agent, tool } = buildAgent("public");
const result = await agent.run(
{ id: "test-public" },
"Record the requested target."
);
assert.equal(result.output, "recorded");
assert.equal(tool.calls, 1);
});
test("denied tool has no effect", async () => {
const { agent, tool } = buildAgent("private");
await assert.rejects(
agent.run({ id: "test-private" }, "Record the requested target."),
praxo.PolicyDeniedError
);
assert.equal(tool.calls, 0);
});
test("starter output is deterministic", async () => {
const lines = [];
await run((line) => lines.push(line));
assert.deepEqual(lines, [
"allow: recorded; effects=1",
"deny: policy rejected; effects=0",
]);
});
test("dependency comes from the published package", () => {
assert.equal(pkg.version, expectedVersion);
assert.equal(praxo.SDK_VERSION, pkg.version);
const resolved = require.resolve("@axisvela/praxo-core");
const root = path.resolve(__dirname);
assert.ok(
resolved.startsWith(root + path.sep),
`dependency resolved outside the application: ${resolved}`
);
const dependency = require("./package.json").dependencies["@axisvela/praxo-core"];
assert.equal(dependency, expectedVersion);
assert.ok(!/^(file:|link:|git|https?:)/.test(dependency));
});
Verify¶
npm ci --no-audit --no-fund
npm test
node starter.js
Expected program output:
allow: recorded; effects=1
deny: policy rejected; effects=0
The model is a deterministic fixture, not an LLM. allowedTools limits the tool
name; Policy also checks the target argument. A denied call throws
PolicyDeniedError and must not increment the in-memory counter. An empty
allowlist is not deny-all in the current API. Keep these tests when replacing
the fixture with a real model adapter.
Governed Acceptance¶
governed.test.js adds five negative checks:
node --test governed.test.js
| Check | Required outcome |
|---|---|
| Missing mandatory policy | application guard rejects before a run; zero tool effects |
| Empty tool scope | application guard rejects; zero tool effects |
| Middleware changes final arguments | final operation is reauthorized; denial prevents the effect |
| Approval edit changes arguments | edited operation is reauthorized; denial prevents the effect |
| Policy service error | run fails closed; zero tool effects |
Use the tested source. The guard is application-level: Core keeps its ordinary nil-policy behavior. These checks do not prove authentic identity, durable approvals, effect reconciliation, or a general security profile.
These checks establish only the stated package and fixture behavior, not live model quality, authentic identity, isolation, durable effects, or production safety. Continue with the TypeScript implementation guide and the security model.