Local models and shell
Local models and shell
Two ways to keep a step off the network entirely.
Ollama
The ollama backend posts to http://localhost:11434/v1/chat/completions with
the OpenAI-compatible chat schema. The host and port are fixed; point a custom
provider at any other address. Its default model is llama3.2. Set the model
you actually pulled.
{
"label": "classify",
"backend": "ollama",
"model": "llama3.2",
"prompt": "Classify this changelog entry as feature, fix, or chore. Answer with one word, then print CLASSIFY_COMPLETE.",
"completion_keyword": "CLASSIFY_COMPLETE",
"timeout": 120
}No credential is needed for a default local server. If yours requires one, set
OLLAMA_API_KEY in the environment or store it in the vault as
ollama-api-key. The bearer header is sent only when a key resolves.
ollama always reports available in the mentu-recipes adapters table. The
check asks for no credential and does not probe the server, so that row says
nothing about whether Ollama is running. A step against a stopped server fails
when it makes the request, not before it starts.
Any other local server
For a local server that is not Ollama, define a custom provider pointed at it:
{
"providers": {
"local-llm": {
"api": "chat_completions",
"base_url": "http://127.0.0.1:8080/v1",
"api_key_env": "LOCAL_LLM_API_KEY",
"model": "your-model"
}
}
}Anything speaking the OpenAI chat-completions or Responses schema works. Unlike
the built-in ollama backend, a custom provider always requires a credential to
resolve, so export LOCAL_LLM_API_KEY with any non-empty value even if the
server ignores it. See Cloud APIs for the provider
fields.
Shell
The shell backend runs the step's prompt as /bin/sh -c <prompt> in the
workspace. There is no model in the loop and no credential, and the runner
itself opens no connection. The command can still do anything your shell can,
including reaching the network.
{
"label": "test",
"backend": "shell",
"dir": "app",
"prompt": "swift test && echo TEST_COMPLETE",
"completion_keyword": "TEST_COMPLETE",
"timeout": 600
}A shell step completes on exit code zero. Adding completion_keyword layers a
text check on top: the keyword must appear in stdout or stderr as well.
A shell step receives your full process environment merged with the recipe and
step env blocks. It is not allow-listed the way the CLI agents are.
dir runs the command in a subdirectory. It must be relative, must not start
with ~, and must stay inside the selected workspace.
Shell is never selected automatically. A step gets one only by naming it, which
is why mentu-recipes adapters marks it explicit. That is a deliberate
boundary: an auto-detected shell would turn any prompt into a local command.
Treat recipes with shell steps as code. Review third-party recipes before you
run them. doctor reports destructive_shell for commands matching known
destructive patterns, but it is a lint, not a sandbox.