Custom agents¶
A custom agent is a program of your own that gmlx launch runs in a
container against your gmlx server. You define it in your config, and
gmlx launch <name> starts it with the server's address, a key and a model
in its environment. Agent examples has a chat page, a
notebook server and coding agents that run in the background.
Your first agent¶
These steps make a LangChain program that asks your server one question and prints the answer:
- Make a Python project and add the library:
- Write the agent in
src/research_bot/__init__.py:
import os
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
def main():
llm = ChatOpenAI(model=os.environ["GMLX_MODEL"], max_tokens=2048)
reply = llm.invoke([HumanMessage("Name three uses of a local model.")])
print(reply.content)
- Add the agent to your gmlx config file:
- Launch it from the project folder:
The first launch builds the agent image and installs the project, which
takes a few minutes. Later launches start at once. @instruct turns off
thinking for a Qwen model, so the answer comes in a few seconds.
The program leaves sampling to the server's
family defaults, because a fixed temperature=0 makes
a thinking model repeat itself. It caps the answer with max_tokens, which
a thinking model spends on its reasoning first.
runtime: python tells launch to install the project's dependencies with
uv, and command is the script that [project.scripts] defines.
launch.agents lists every key an agent takes,
and gmlx launch --help lists your agents.
What the agent gets¶
launch sets these variables in the container:
| Variable | Value |
|---|---|
GMLX_BASE_URL |
The server's /v1 address, such as http://127.0.0.1:8080/v1. |
GMLX_API_KEY |
The key the agent sends, the placeholder gmlx-container-session. |
GMLX_MODEL |
The model, from --model, then the agent's model, then the server's default. |
OPENAI_BASE_URL, OPENAI_API_BASE |
The same as GMLX_BASE_URL, with api: openai, the default. |
OPENAI_API_KEY |
The same as GMLX_API_KEY, with api: openai. |
ANTHROPIC_BASE_URL |
The server's root address, without /v1, with api: anthropic. |
ANTHROPIC_API_KEY |
The same as GMLX_API_KEY, with api: anthropic. |
ANTHROPIC_MODEL, ANTHROPIC_SMALL_FAST_MODEL |
The same as GMLX_MODEL, with api: anthropic. |
CLAUDE_CODE_MAX_CONTEXT_TOKENS |
The model's context window, with api: anthropic. |
HOST, PORT |
127.0.0.1 and the web_port, for an agent with a browser interface. |
Most frameworks, including the OpenAI SDK and LangChain, read the OPENAI_
variables with no code of your own. Set api: anthropic
for the Anthropic SDKs and the Claude Agent SDK.
The agent reaches only the server's inference routes, as What the client reaches on the server describes. Keep a framework's concurrency at 16 requests or fewer.
Dependencies at run time¶
With runtime: python, the agent runs in an
image with Python 3.13 and uv. Each session starts with uv sync, then runs
the command in the project's environment. A command can be a script from
[project.scripts], [python, -m, mypackage], or a .py file. To skip the
sync, add UV_NO_SYNC=1 to the agent's env.
The first runtime agent on a Mac builds that image once, in about three minutes. The first launch in each project folder then installs the dependencies, about a minute for a LangChain project.
uv downloads another Python on the first launch when the project's
requires-python excludes 3.13, or its .python-version names another
version. uv init writes your Mac's Python version into both files, so
create projects with --python 3.13, as the steps above do.
The source folder¶
By default the project is the folder you launch from, shared read-write so
uv can update uv.lock. An agent that lives elsewhere names its folder with
source:
launch shares that folder read-only, so the agent can work in another
project without changing its own code. Three things follow:
- The
uv.lockmust be up to date. To refresh it, launch the agent once from the source folder. - The project must be a package, as
uv init --packagemakes it, so its module imports from any folder. - Its build backend must not write into the source. hatchling and uv_build are fine, while setuptools is not.
Troubleshooting covers each error:
the lockfile,
No module named
and
Read-only file system.
A relative command, such as agent.py or bin/start, is looked up in the
folder you launch from and then in the source. A script in the source
therefore runs from any folder.
A single script¶
A script with an inline # /// script dependency block runs with the script
as the command, as [agent.py]. uv installs what the block names. The form
[python, agent.py] ignores the block. Write the lockfile beside the script
with uv lock --script agent.py.
The dependency volume¶
uv keeps the environment on a volume, one per
project. An agent with a source therefore installs again in each folder
you launch it from.
To share one environment from every folder, launch with --no-mount-cwd
from a folder that no --mount or mounts entry shares. The agent then
starts in its source when you launch from inside the source, and in the
private home otherwise.
To set the volume's size, add a volumes entry for /opt/agent to the
agent. The environment stays from one launch to the next, including anything
the agent changes in it, until --remove-home deletes the volume.
In a --shell session, uv run python uses the agent's environment from
any folder.
Offline launches¶
With network: none, uv cannot
download anything. Launch the agent once with the network in each project
folder before you turn it off, as
troubleshooting
explains.
Your own image¶
image and
build work for an agent as they do
for a client, as Custom container images shows. An
agent's Containerfile can start from any client's image, such as
gmlx.invalid/launch-claude-code:base.
To add system packages to a runtime agent, start from the runtime base and
keep runtime: python:
FROM gmlx.invalid/launch-runtime-python:base
RUN apt-get update \
&& apt-get install -y --no-install-recommends ffmpeg \
&& rm -rf /var/lib/apt/lists/*
Without runtime, the command runs in the image as written.
A browser interface¶
An agent with web_port is a
browser app. launch opens it in your
Mac browser at http://[::1]:<port>/ once it answers.
The command must listen on 127.0.0.1 at the web_port inside the
container. A command list runs without a shell, so write the port number in
the list rather than $PORT.
A chat app in the browser
and A notebook server show complete
setups.
Sessions and data¶
Agents follow the same session rules as clients, as
Container sessions describes. Each project folder
gets its own session and private home, a second launch joins the running
session, and --shell opens a shell in it. An agent's session log is
~/.cache/gmlx/launch/last-agent-<name>-<project>.log, as
Logs describes.
--detach runs an agent in the background, and --stop ends it. An agent
in the background gets no input, so give it a browser interface or a task in
its arguments.
gmlx launch <name> --remove-home in the project folder removes the
project's private home and the agent's volumes, after one question.