Skip to content

Command deployments

A deployment normally runs a handle function (the handler preset). The command preset instead runs an exact process you choose: the platform starts your entrypoint argv, delivers the run input on stdin, and returns what the process writes to stdout. This lets you deploy an existing CLI or an agent-framework program without wrapping it in a Magmell handler.

Pass --preset command and give the exact argument vector with a repeatable --entrypoint. The first --entrypoint is the executable; the rest are its arguments, in order.

Terminal window
# Python 3.12
magmell deploy ./agent \
--name my-agent --version 1 \
--preset command --entrypoint python --entrypoint main.py \
--wait
# Node.js 22
magmell deploy ./agent \
--name my-agent --version 1 \
--runtime node-22 \
--preset command --entrypoint node --entrypoint dist/agent.js \
--wait

The entrypoint is an argv array, not a shell string: there is no shell expansion, word splitting, or interpolation. It carries 1 to 64 items; each item is at most 4 KiB and the whole vector is at most 16 KiB. --entrypoint values may look like flags (--entrypoint --verbose is a literal argument).

The command preset is available for python-3.12 and node-22. A command deployment needs no handler.py or handler.ts; requirements.txt, explicit --requirements, package-lock.json, and setup steps work exactly as they do for handlers. Node command deployments run your precompiled JavaScript — Magmell does not compile TypeScript for a command, so commit the build output (for example dist/agent.js) and point the entrypoint at it.

  • Input arrives as a single line on stdin: the run’s input as normalized UTF-8 JSON bytes followed by one \n, after which stdin is closed. A program that ignores stdin (an echo-shaped command) is fine — the platform tolerates a broken pipe and does not fail the run for it.
  • Output is whatever the process writes to stdout, up to 1 MiB. On a successful exit it becomes the run’s result as a JSON string, returned verbatim — not parsed, not trimmed. Empty stdout produces the empty string "", which is a real result, distinct from null (“no result yet”).
  • Working directory is /app. Secrets are provided as environment variables (see below).
  • Logs: everything the process writes to stderr goes to the run logs, with secret values redacted.

The process starts in a new process group under a single deadline that covers stdin delivery, execution, and stdout collection.

A run succeeds only when the process exits 0 and its stdout is valid UTF-8 with no NUL byte. Otherwise it fails with an explicit category. When more than one condition holds at once, the category is chosen by this precedence, highest first:

  1. deadline — the timeout expired;
  2. output too large — stdout exceeded 1 MiB;
  3. terminated by signal — the process died on a signal (for example an out-of-memory kill), reported with the signal number;
  4. non-zero exit — reported with the exit code;
  5. invalid stdout — not UTF-8, or contains a NUL byte.

On any failure the captured stdout is discarded and never appears in the result or the error. The bounded error names the category (and the exit code for a normal non-zero exit); it never contains your stdout or stderr.

Deployment secrets are passed to the process as environment variables under their exact names, so conventional provider settings such as OPENAI_API_KEY and OPENAI_BASE_URL work without a wrapper. The child environment is a fresh copy of a curated safe base plus the runtime’s default PATH, then your secrets applied last (last-write-wins — a secret named PATH deliberately replaces the default, after which bare names like node may no longer resolve). On Python the pip console-scripts directory is on the default PATH, so an entrypoint like ["uvicorn", "app:app"] resolves; on Node node_modules/.bin is on the default PATH.

An argv[0] with no / is resolved through PATH; an argv[0] containing / is resolved relative to /app and is not PATH-searched.

The effective timeout is an integer from 1 to 3600 seconds and is the run’s single deadline. An explicit out-of-range or non-integer timeout_s is rejected. On expiry the platform terminates the whole process group, waits a short grace, then force-kills it.

Command deployments require max_retries = 0. An omitted value resolves to 0 for a command even when the server’s handler default is non-zero; an explicit non-zero value is rejected at admission and at run time. This is intentional and asymmetric with the timeout: a one-shot customer command may not be idempotent, so a silent retry could run side effects twice — retries are forced off, while a timeout is benign and merely enforced.

The platform delivers the input bytes to stdin at full precision: JSON integers larger than JavaScript’s safe range (2⁵³) are preserved as sent. Parsing is entirely your program’s concern, and the common parsers lose precision by default:

  • Node: plain JSON.parse truncates large integers to doubles. Use a bigint-aware parser (for example json-bigint) when you need full fidelity.
  • Python: json.loads keeps arbitrary-precision integers, but decimal values become floats; pass parse_float=Decimal when exact decimals matter.

Because the result is an unparsed string, there is no corresponding concern on the output path.

The command preset is a one-shot run — one process, one final result. It does not provide streaming run responses, persistent sessions, resume tokens, cross-run memory, human-in-the-loop pause/resume, scheduled or background execution, or automatic retries after the process starts. Workloads that need those require a protocol beyond one-shot runs.

Magmell classifies command-preset workloads with four labels, each with its own evidence bar:

  • Certified — covered by the regression suite; a regression blocks release.
  • Certified example — a shipped example, smoked in CI against a loopback stub and run through the executors.
  • Generic — works through the generic command contract, but is not validated per framework and may break without notice.
  • Unsupported — needs a protocol or state model beyond one-shot runs.
Workload Label
Existing Magmell Python/Node handler Certified
Plain Python/Node one-shot CLI Certified
Other one-shot executable (shell script, static binary) Generic
OpenAI Agents SDK (Python/TypeScript), one logical run Certified example
Client-executed stdio MCP tool inside that run Certified example
Client-executed Streamable HTTP MCP Generic
Other one-shot frameworks or CLIs Generic
Claude Agent SDK Generic
Streaming, persistent sessions, human-in-the-loop resume Unsupported

The Claude Agent SDK runs as an ordinary customer command; Magmell makes no provider-independence claim for it. Mistral-specific integration is out of scope.