Skip to content

feat(agents): point an agent at an OpenAI-compatible endpoint you alr… - #92

Merged
antiv merged 1 commit into
mainfrom
claude/external-openai-agents
Sep 2, 2026
Merged

feat(agents): point an agent at an OpenAI-compatible endpoint you alr…#92
antiv merged 1 commit into
mainfrom
claude/external-openai-agents

Conversation

@antiv

@antiv antiv commented Sep 2, 2026

Copy link
Copy Markdown
Owner

…eady run

MATE's control layer applied only to agents built inside MATE, so trying it meant rebuilding your agents in it. That is the wrong price for a control layer, and it is the likeliest reason the project collects stars without collecting users.

An agent reached over /v1/chat/completions is, on the wire, the same shape as a model, and create_model already routes openai/* through LiteLLM with a custom base_url — it is how LM Studio and llama.cpp are already supported. The only thing missing was somewhere to put the endpoint: create_model was called with the model name alone, so base_url and api_key came from provider env vars and every agent shared one endpoint per provider.

So this adds two columns rather than a proxy agent class. An external agent is an ordinary agent, which means RBAC, guardrails, evals, versioning, audit, the widget and cost tracking apply to it with no new code paths to keep in sync. Both runtimes resolve the endpoint through one function so a config means the same thing under either.

Two refusals are deliberate, and each is covered by a test that fails when the behaviour is mutated away:

A ${VAR} that is not set refuses to build the agent instead of falling back. Falling back would have LiteLLM read the provider's own key and send it to whatever third-party host base_url names.

An endpoint configured with no key is given a placeholder rather than left unset, for the same reason — keyless endpoints are normal on local servers and private networks, and OPENAI_API_KEY should not follow them out.

Keys are meant to be written as ${VAR}. A literal one is stored in the row and captured in version history, so the dashboard never sends it to the browser: responses carry a sentinel, and saving the form with the sentinel unchanged leaves the stored value alone. The copy form clears it rather than showing the sentinel, which would have created a keyless agent silently.

A template carries the endpoint but never the credential, since a template is a shared artifact. A config export round-trips both.

The ${VAR} resolver moved from mcp_tools to shared utils now that it has a second consumer.

814 tests, OK. Migration V030 verified against a real database: applied, columns present, and idempotent on a second run.

Claude-Session: https://claude.ai/code/session_01ToCUNy2SqwvfTq4a6xfSk1

…eady run

MATE's control layer applied only to agents built inside MATE, so trying it
meant rebuilding your agents in it. That is the wrong price for a control
layer, and it is the likeliest reason the project collects stars without
collecting users.

An agent reached over /v1/chat/completions is, on the wire, the same shape
as a model, and create_model already routes openai/* through LiteLLM with a
custom base_url — it is how LM Studio and llama.cpp are already supported.
The only thing missing was somewhere to put the endpoint: create_model was
called with the model name alone, so base_url and api_key came from provider
env vars and every agent shared one endpoint per provider.

So this adds two columns rather than a proxy agent class. An external agent
is an ordinary agent, which means RBAC, guardrails, evals, versioning, audit,
the widget and cost tracking apply to it with no new code paths to keep in
sync. Both runtimes resolve the endpoint through one function so a config
means the same thing under either.

Two refusals are deliberate, and each is covered by a test that fails when
the behaviour is mutated away:

A ${VAR} that is not set refuses to build the agent instead of falling back.
Falling back would have LiteLLM read the provider's own key and send it to
whatever third-party host base_url names.

An endpoint configured with no key is given a placeholder rather than left
unset, for the same reason — keyless endpoints are normal on local servers
and private networks, and OPENAI_API_KEY should not follow them out.

Keys are meant to be written as ${VAR}. A literal one is stored in the row
and captured in version history, so the dashboard never sends it to the
browser: responses carry a sentinel, and saving the form with the sentinel
unchanged leaves the stored value alone. The copy form clears it rather than
showing the sentinel, which would have created a keyless agent silently.

A template carries the endpoint but never the credential, since a template
is a shared artifact. A config export round-trips both.

The ${VAR} resolver moved from mcp_tools to shared utils now that it has a
second consumer.

814 tests, OK. Migration V030 verified against a real database: applied,
columns present, and idempotent on a second run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ToCUNy2SqwvfTq4a6xfSk1
@antiv
antiv merged commit 78c3cfd into main Sep 2, 2026
1 check passed
@antiv
antiv deleted the claude/external-openai-agents branch September 3, 2026 07:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants