From a9693ea9ad35acbd4dc5406baae9f19fa5b80125 Mon Sep 17 00:00:00 2001 From: Alfonso Sastre Date: Fri, 25 Sep 2026 11:03:02 +0200 Subject: [PATCH 1/2] providers: --lex-gpu, wired for when its tools land lex-gpu serves OpenAI chat completions from its own compiled Metal and CUDA kernels. lex-llm drives that shape unchanged, so this is the same nine-module wiring every other backend gets: a lex_gpu_agent() beside vllm_agent() in each agent, an arm in session.lex, a flag in main.lex. It is chat-only today and the README says so plainly. lex-gpu accepts a `tools` list and drops it, so the model is never told the tools exist and its replies carry no tool_calls -- the loop never dispatches, which for a coding agent means it describes work rather than doing it. The wiring lands now so that it starts working the moment lex-gpu renders `tools` into its prompt and splits out of content; neither needs a change on this side. Depends on alpibrusl/lex-llm#64 for providers.lex_gpu_model and lex_gpu_local, so CI fails here until that merges. Checked against that branch locally: all nine agents, session.lex and main.lex type-check with no errors, `lex fmt --check` is clean across the fourteen touched files, and `lex doc-sync --check` reports all targets current. Co-Authored-By: Claude Opus 5 --- README.md | 28 ++++++++++++++++++++++++---- src/agents/bar.lex | 5 +++++ src/agents/build.lex | 5 +++++ src/agents/explore.lex | 5 +++++ src/agents/plan.lex | 5 +++++ src/agents/refactor.lex | 5 +++++ src/agents/review.lex | 5 +++++ src/agents/spec_agent.lex | 5 +++++ src/agents/test_agent.lex | 5 +++++ src/agents/verify.lex | 5 +++++ src/server/session.lex | 11 +++++++++++ src/tui/main.lex | 10 +++++++--- 12 files changed, 87 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1436952..42659f3 100644 --- a/README.md +++ b/README.md @@ -253,16 +253,36 @@ read-only toolset yet (#88). ## Providers -lex-code can talk to eight provider backends (see `--help` for the full -flag list — Anthropic, OpenAI, Google, Mistral, LiteLLM, vLLM, and Vertex -are implemented in code alongside the two below), but only these two have -actually been run end-to-end against this repo: +lex-code can talk to nine provider backends (see `--help` for the full +flag list — Anthropic, OpenAI, Google, Mistral, LiteLLM, vLLM, lex-gpu and +Vertex are implemented in code alongside the two below), but only these two +have actually been run end-to-end against this repo: | Flag | Provider | Model | Key required | |------|----------|-------|--------------| | `--ollama` | Ollama (local, native API) | `$OLLAMA_MODEL` (default `qwen3.8:27b-mlx`) | none | | `--opencode` | OpenCode Go plan (cloud, direct) | `$OPENCODE_MODEL` | `OPENCODE_API_KEY` | +### lex-gpu + +`--lex-gpu` points at [lex-gpu](https://github.com/alpibrusl/lex-gpu)'s +server, which answers OpenAI chat completions from its own compiled Metal +and CUDA kernels rather than llama.cpp or MLX. No key; `$LEX_GPU_BASE_URL` +overrides the default `http://127.0.0.1:8080`. + +```sh +cargo run --release -p lex-rt --example serve -- --model qwen3.8:27b-mlx +lex-code --lex-gpu --explore "what does src/agents/build.lex do?" +``` + +**Chat only, for now.** lex-gpu accepts a `tools` list and drops it, so the +model is never told the tools exist and its replies carry no `tool_calls` — +the agent loop gets an answer and never dispatches a tool, which for a +coding agent means it will describe work rather than do it. The wiring is +here so that it starts working the moment lex-gpu renders `tools` into its +prompt and splits `` out of `content`; neither needs a change on +this side. + ### Ollama _Runnable: `examples/providers/ollama.sh`_ diff --git a/src/agents/bar.lex b/src/agents/bar.lex index b4247e9..e50d707 100644 --- a/src/agents/bar.lex +++ b/src/agents/bar.lex @@ -59,6 +59,11 @@ fn vllm_agent() -> [env] ag.AgentLoop { ag.with_permission_gate(base, rules.bar_permission()) } +fn lex_gpu_agent() -> [env] ag.AgentLoop { + let base := { name: "bar", goal: barp.system(), model: prov.make_model_ref("lex-gpu", providers.lex_gpu_model()), provider: providers.lex_gpu_local(), tools: tools.bar_tools(), options: { temperature: None, top_p: None, max_steps: Some(25), max_tokens: None }, permission_spec: None } + ag.with_permission_gate(base, rules.bar_permission()) +} + fn opencode_agent() -> [env] ag.AgentLoop { let base := { name: "bar", goal: barp.system(), model: prov.make_model_ref("opencode", tools.opencode_model()), provider: providers.opencode_go(), tools: tools.bar_tools(), options: { temperature: None, top_p: None, max_steps: Some(25), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.bar_permission()) diff --git a/src/agents/build.lex b/src/agents/build.lex index 351ea13..99ccd40 100644 --- a/src/agents/build.lex +++ b/src/agents/build.lex @@ -61,6 +61,11 @@ fn vllm_agent() -> [env] ag.AgentLoop { ag.with_permission_gate(base, rules.build_permission()) } +fn lex_gpu_agent() -> [env] ag.AgentLoop { + let base := { name: "build", goal: bp.system(), model: prov.make_model_ref("lex-gpu", providers.lex_gpu_model()), provider: providers.lex_gpu_local(), tools: tools.all_tools(), options: { temperature: None, top_p: None, max_steps: Some(50), max_tokens: None }, permission_spec: None } + ag.with_permission_gate(base, rules.build_permission()) +} + fn google_agent() -> [env] ag.AgentLoop { let base := { name: "build", goal: bp.system(), model: prov.gemini_pro(), provider: providers.google(), tools: tools.all_tools(), options: { temperature: None, top_p: None, max_steps: Some(50), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.build_permission()) diff --git a/src/agents/explore.lex b/src/agents/explore.lex index 1aaf179..76a7600 100644 --- a/src/agents/explore.lex +++ b/src/agents/explore.lex @@ -35,6 +35,11 @@ fn vllm_agent() -> [env] ag.AgentLoop { ag.with_permission_gate(base, rules.explore_permission()) } +fn lex_gpu_agent() -> [env] ag.AgentLoop { + let base := { name: "explore", goal: ep.system(), model: prov.make_model_ref("lex-gpu", providers.lex_gpu_model()), provider: providers.lex_gpu_local(), tools: tools.tools_for_spec(rules.explore_permission()), options: { temperature: None, top_p: None, max_steps: Some(20), max_tokens: None }, permission_spec: None } + ag.with_permission_gate(base, rules.explore_permission()) +} + fn openai_agent() -> [env] ag.AgentLoop { let base := { name: "explore", goal: ep.system(), model: prov.gpt4o_mini(), provider: providers.openai(), tools: tools.tools_for_spec(rules.explore_permission()), options: { temperature: None, top_p: None, max_steps: Some(20), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.explore_permission()) diff --git a/src/agents/plan.lex b/src/agents/plan.lex index 31b090d..ede6823 100644 --- a/src/agents/plan.lex +++ b/src/agents/plan.lex @@ -35,6 +35,11 @@ fn vllm_agent() -> [env] ag.AgentLoop { ag.with_permission_gate(base, rules.plan_permission()) } +fn lex_gpu_agent() -> [env] ag.AgentLoop { + let base := { name: "plan", goal: pp.system(), model: prov.make_model_ref("lex-gpu", providers.lex_gpu_model()), provider: providers.lex_gpu_local(), tools: tools.tools_for_spec(rules.plan_permission()), options: { temperature: None, top_p: None, max_steps: Some(30), max_tokens: None }, permission_spec: None } + ag.with_permission_gate(base, rules.plan_permission()) +} + fn openai_agent() -> [env] ag.AgentLoop { let base := { name: "plan", goal: pp.system(), model: prov.gpt4o(), provider: providers.openai(), tools: tools.tools_for_spec(rules.plan_permission()), options: { temperature: None, top_p: None, max_steps: Some(30), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.plan_permission()) diff --git a/src/agents/refactor.lex b/src/agents/refactor.lex index 904cc3d..06a85d8 100644 --- a/src/agents/refactor.lex +++ b/src/agents/refactor.lex @@ -35,6 +35,11 @@ fn vllm_agent() -> [env] ag.AgentLoop { ag.with_permission_gate(base, rules.refactor_permission()) } +fn lex_gpu_agent() -> [env] ag.AgentLoop { + let base := { name: "refactor", goal: rp.system(), model: prov.make_model_ref("lex-gpu", providers.lex_gpu_model()), provider: providers.lex_gpu_local(), tools: tools.tools_for_spec(rules.refactor_permission()), options: { temperature: None, top_p: None, max_steps: Some(40), max_tokens: None }, permission_spec: None } + ag.with_permission_gate(base, rules.refactor_permission()) +} + fn openai_agent() -> [env] ag.AgentLoop { let base := { name: "refactor", goal: rp.system(), model: prov.gpt4o(), provider: providers.openai(), tools: tools.tools_for_spec(rules.refactor_permission()), options: { temperature: None, top_p: None, max_steps: Some(40), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.refactor_permission()) diff --git a/src/agents/review.lex b/src/agents/review.lex index b221c16..64ec7aa 100644 --- a/src/agents/review.lex +++ b/src/agents/review.lex @@ -35,6 +35,11 @@ fn vllm_agent() -> [env] ag.AgentLoop { ag.with_permission_gate(base, rules.review_permission()) } +fn lex_gpu_agent() -> [env] ag.AgentLoop { + let base := { name: "review", goal: rvp.system(), model: prov.make_model_ref("lex-gpu", providers.lex_gpu_model()), provider: providers.lex_gpu_local(), tools: tools.tools_for_spec(rules.review_permission()), options: { temperature: None, top_p: None, max_steps: Some(25), max_tokens: None }, permission_spec: None } + ag.with_permission_gate(base, rules.review_permission()) +} + fn openai_agent() -> [env] ag.AgentLoop { let base := { name: "review", goal: rvp.system(), model: prov.gpt4o(), provider: providers.openai(), tools: tools.tools_for_spec(rules.review_permission()), options: { temperature: None, top_p: None, max_steps: Some(25), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.review_permission()) diff --git a/src/agents/spec_agent.lex b/src/agents/spec_agent.lex index c50037f..dbd27a2 100644 --- a/src/agents/spec_agent.lex +++ b/src/agents/spec_agent.lex @@ -35,6 +35,11 @@ fn vllm_agent() -> [env] ag.AgentLoop { ag.with_permission_gate(base, rules.spec_permission()) } +fn lex_gpu_agent() -> [env] ag.AgentLoop { + let base := { name: "spec", goal: sp.system(), model: prov.make_model_ref("lex-gpu", providers.lex_gpu_model()), provider: providers.lex_gpu_local(), tools: tools.tools_for_spec(rules.spec_permission()), options: { temperature: None, top_p: None, max_steps: Some(30), max_tokens: None }, permission_spec: None } + ag.with_permission_gate(base, rules.spec_permission()) +} + fn openai_agent() -> [env] ag.AgentLoop { let base := { name: "spec", goal: sp.system(), model: prov.gpt4o(), provider: providers.openai(), tools: tools.tools_for_spec(rules.spec_permission()), options: { temperature: None, top_p: None, max_steps: Some(30), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.spec_permission()) diff --git a/src/agents/test_agent.lex b/src/agents/test_agent.lex index a073751..fad8f9c 100644 --- a/src/agents/test_agent.lex +++ b/src/agents/test_agent.lex @@ -35,6 +35,11 @@ fn vllm_agent() -> [env] ag.AgentLoop { ag.with_permission_gate(base, rules.test_permission()) } +fn lex_gpu_agent() -> [env] ag.AgentLoop { + let base := { name: "test", goal: tp.system(), model: prov.make_model_ref("lex-gpu", providers.lex_gpu_model()), provider: providers.lex_gpu_local(), tools: tools.tools_for_spec(rules.test_permission()), options: { temperature: None, top_p: None, max_steps: Some(30), max_tokens: None }, permission_spec: None } + ag.with_permission_gate(base, rules.test_permission()) +} + fn openai_agent() -> [env] ag.AgentLoop { let base := { name: "test", goal: tp.system(), model: prov.gpt4o_mini(), provider: providers.openai(), tools: tools.tools_for_spec(rules.test_permission()), options: { temperature: None, top_p: None, max_steps: Some(30), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.test_permission()) diff --git a/src/agents/verify.lex b/src/agents/verify.lex index 3b78613..8911507 100644 --- a/src/agents/verify.lex +++ b/src/agents/verify.lex @@ -35,6 +35,11 @@ fn vllm_agent() -> [env] ag.AgentLoop { ag.with_permission_gate(base, rules.verify_permission()) } +fn lex_gpu_agent() -> [env] ag.AgentLoop { + let base := { name: "verify", goal: vp.system(), model: prov.make_model_ref("lex-gpu", providers.lex_gpu_model()), provider: providers.lex_gpu_local(), tools: tools.tools_for_spec(rules.verify_permission()), options: { temperature: None, top_p: None, max_steps: Some(25), max_tokens: None }, permission_spec: None } + ag.with_permission_gate(base, rules.verify_permission()) +} + fn openai_agent() -> [env] ag.AgentLoop { let base := { name: "verify", goal: vp.system(), model: prov.gpt4o(), provider: providers.openai(), tools: tools.tools_for_spec(rules.verify_permission()), options: { temperature: None, top_p: None, max_steps: Some(25), max_tokens: None }, permission_spec: None } ag.with_permission_gate(base, rules.verify_permission()) diff --git a/src/server/session.lex b/src/server/session.lex index 81082c5..5817106 100644 --- a/src/server/session.lex +++ b/src/server/session.lex @@ -130,6 +130,17 @@ fn pick_agent(mode :: AgentMode, provider_tag :: Str) -> [env] ag.AgentLoop { Bar => bar_a.vllm_agent(), Verify => verify_a.vllm_agent(), }, + "lex-gpu" => match mode { + Build => build_agent.lex_gpu_agent(), + Plan => plan_agent.lex_gpu_agent(), + Explore => explore_agent.lex_gpu_agent(), + Refactor => refactor_agent.lex_gpu_agent(), + Spec => spec_a.lex_gpu_agent(), + Test => test_a.lex_gpu_agent(), + Review => review_a.lex_gpu_agent(), + Bar => bar_a.lex_gpu_agent(), + Verify => verify_a.lex_gpu_agent(), + }, "vertex" => match mode { Build => build_agent.vertex_agent(), Plan => plan_agent.google_agent(), diff --git a/src/tui/main.lex b/src/tui/main.lex index cfb7b5d..b59d825 100644 --- a/src/tui/main.lex +++ b/src/tui/main.lex @@ -538,10 +538,14 @@ fn select_provider_tag(argv :: List[Str]) -> Str if has_flag(argv, "--vllm") { "vllm" } else { - if has_flag(argv, "--opencode") { - "opencode" + if has_flag(argv, "--lex-gpu") { + "lex-gpu" } else { - "litellm" + if has_flag(argv, "--opencode") { + "opencode" + } else { + "litellm" + } } } } From 9509ec4874d090a5b5f155db8ec09df9f18cad8a Mon Sep 17 00:00:00 2001 From: Alfonso Sastre Date: Fri, 25 Sep 2026 15:11:56 +0200 Subject: [PATCH 2/2] fix(providers): list --lex-gpu in --help, correct provider count The no-args help banner listed every provider flag except the one just added. README said "nine provider backends" for a list of 8 named + 2 in the table = 10 (already undercounting by one before this branch). Co-Authored-By: Claude Sonnet 5 --- README.md | 2 +- src/tui/main.lex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 42659f3..0877f31 100644 --- a/README.md +++ b/README.md @@ -253,7 +253,7 @@ read-only toolset yet (#88). ## Providers -lex-code can talk to nine provider backends (see `--help` for the full +lex-code can talk to ten provider backends (see `--help` for the full flag list — Anthropic, OpenAI, Google, Mistral, LiteLLM, vLLM, lex-gpu and Vertex are implemented in code alongside the two below), but only these two have actually been run end-to-end against this repo: diff --git a/src/tui/main.lex b/src/tui/main.lex index b59d825..e2af5ec 100644 --- a/src/tui/main.lex +++ b/src/tui/main.lex @@ -803,7 +803,7 @@ fn dispatch(inv :: Invocation, mode :: sess.AgentMode, provider_tag :: Str) -> [ io.print(str.concat("lex-code — Lex-specialized coding assistant", "\n")) io.print(str.concat("modes: --plan | --explore | --refactor | --spec | --test | --review | --verify | --bar | --multi", "\n")) io.print(str.join(["pipelines: --pipeline=", str.join(graph.preset_names(), " | --pipeline="), "\n --pipeline=build,spec,test|review (\",\" in order, \"|\" at once)", "\n"], "")) - io.print(str.concat("providers: --mistral | --openai | --google | --vertex | --litellm | --ollama | --vllm | --opencode (default: anthropic)", "\n")) + io.print(str.concat("providers: --mistral | --openai | --google | --vertex | --litellm | --ollama | --vllm | --lex-gpu | --opencode (default: anthropic)", "\n")) io.print(str.concat("one-shot: lex run src/tui/main.lex -- [flags] \"your task\"", "\n")) io.print(str.concat("issue: --issue= [\"extra guidance\"] implement a typed issue from its acceptance, then verify it", "\n")) io.print(str.concat("refine: --refine= propose a typed acceptance for a free_form issue (you approve it)", "\n"))