engines
An engine is what powers the loop. One field in axon.config.ts, swappable without
touching agent code:
import { Axon } from "@arcforge/engines"
export default defineAgent({
engine: Axon(),
})
| Engine | Inference runs | You pay |
|---|---|---|
Axon() | Managed | Your Axon balance |
Codex() | OpenAI | Your ChatGPT subscription |
OpenRouter() | OpenRouter | OpenRouter directly, no markup |
Cerebras() | Cerebras | Cerebras directly |
Ollama() | Your machine | Nothing |
Mock() | Nowhere | Nothing — it's for tests |
Whatever the config says, * in the terminal overrides the model for the current
session. See Models.
Axon
The default. If you are signed in it works with no configuration, billed against your Axon balance.
import { Axon } from "@arcforge/engines"
engine: Axon()
engine: Axon({ model: "claude-sonnet-4-6" })
model is optional — omit it for the account default, or name any model in the Axon
catalogue, which includes everything reachable through OpenRouter.
No key management: authentication derives from your signed-in session. Top up from the account page at axon.arclabs.it.
Codex
OpenAI's Codex, over OAuth. Requires a ChatGPT Plus or Pro subscription — there is no API key, and Axon takes nothing.
import { Codex } from "@arcforge/engines"
engine: Codex()
engine: Codex({
model: "gpt-5.5", // default
effort: "medium", // low | medium | high | xhigh
})
Connect once from the terminal:
:provider codex connect
:provider codex disconnect
That opens a browser to approve. The connection refreshes itself, so it holds until you disconnect deliberately.
effort controls reasoning depth on models that support it — higher costs latency and
tokens. Omit it for non-reasoning models.
OpenRouter
Your own key, 100+ models across providers, charged at OpenRouter's rates with no Axon markup.
import { OpenRouter } from "@arcforge/engines"
engine: OpenRouter({ model: "openai/gpt-4o" })
Get a key at openrouter.ai/keys, then:
:provider openrouter connect
:provider openrouter disconnect
model is an OpenRouter ID in provider/model-name form, as listed on
openrouter.ai/models:
OpenRouter({ model: "anthropic/claude-sonnet-4-6" })
OpenRouter({ model: "google/gemini-2.5-pro" })
OpenRouter({ model: "meta-llama/llama-3.3-70b-instruct" })
The key is stored in your account vault, so it holds across sessions and a deployed agent uses the same connection.
Cerebras
Hosted inference built for speed. Reach for it when latency and throughput matter and the workload suits open-weight models.
import { Cerebras } from "@arcforge/engines"
engine: Cerebras({ model: "gpt-oss-120b" })
engine: Cerebras({ model: "gpt-oss-120b", temperature: 0.2 })
Needs a key in the agent's environment:
CEREBRAS_API_KEY=csk-...
For a deployed agent set it as a runtime secret rather than committing it. model is a
Cerebras model ID — see the Cerebras console for what is currently available.
Ollama
Local inference. No API cost, and nothing leaves the machine. Requires Ollama installed; Axon manages the server lifecycle.
import { Ollama } from "@arcforge/engines"
engine: Ollama({ model: "qwen2.5:7b" })
engine: Ollama({
model: "qwen2.5:7b",
host: "http://localhost:11434", // default
})
model is the name as it appears in ollama list — anything you have pulled.
The route appears in * as soon as the daemon answers. Models are listed with their
download size and whether they suit your hardware; selecting one you do not have pulls it
with progress in the palette. Or pull directly:
ollama pull qwen2.5:7b
Mock
A deterministic engine for tests. It replaces the inference step and nothing else — the full loop still runs, tools execute, the session log accumulates, stop conditions fire. Production behaviour without a model call.
import { Mock } from "@arcforge/engines/mock"
engine: Mock()
Bare Mock() echoes the last user message back. That is what a fresh axon init boots
with, before an engine is configured.
Response map. Patterns match case-insensitively as substrings against the last user message; no match falls back to echo.
engine: Mock({
"hello": "Hi there!",
"sprint status": "Two issues remain in review.",
})
A reply is a step: spoken text ends the wake, or run() executes code so the loop
continues and the model sees the result.
Sequences. An array plays one step per tick, in order; once exhausted the last step repeats.
import { Mock, run } from "@arcforge/engines/mock"
engine: Mock({
"review the file": [
run(`fs.read("src/index.ts")`), // tick 1: act
"The file looks correct.", // tick 2: see the result, speak
],
})
run() executes real code in the real capsule under the agent's real policy. The result
commits to the session log and re-enters the loop on the next tick — exactly the path a
real model's tool call takes. That is what makes multi-step flows, failure handling and
policy rejections deterministically testable.
Function form for full control:
engine: Mock(async (req) => {
const last = req.messages.at(-1)?.content ?? ""
return `You said: ${last}`
})
Return a string to speak, or run(code) to act.
Output grammar. You never choose one. A step describes intent, and Mock writes it in whichever dialect the agent's brain rendered, read from the contract block in the request:
| step | classic | sfc |
|---|---|---|
"some text" | <text> | <template> |
run(code) | <typescript> | <script> |
This is why one Mock({ ... }) works against any cognet — the steps are about behaviour,
not syntax. It also means Mock exercises structured output for real: a run() step becomes
the <script> a declared output type is checked against.
Mock streams spoken text by chunking words with small delays, so tests iterate the stream exactly as they would a real engine.