Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
- name: Install Lex toolchain
run: |
set -euxo pipefail
LEX_VERSION="0.10.7"
LEX_VERSION="0.10.10"
curl -fsSL -o /tmp/lex.tgz \
"https://github.com/alpibrusl/lex-lang/releases/download/v${LEX_VERSION}/lex-v${LEX_VERSION}-x86_64-unknown-linux-gnu.tar.gz"
tar -xzf /tmp/lex.tgz -C /tmp/
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Install lex
if: steps.ver.outputs.changed == 'true'
run: |
curl -fsSL "https://github.com/alpibrusl/lex-lang/releases/download/v0.10.7/lex-v0.10.7-x86_64-unknown-linux-gnu.tar.gz" \
curl -fsSL "https://github.com/alpibrusl/lex-lang/releases/download/v0.10.10/lex-v0.10.10-x86_64-unknown-linux-gnu.tar.gz" \
| tar -xz --strip-components=1 -C /usr/local/bin

- name: Mint JWT + publish
Expand Down
83 changes: 83 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# lex-code — Agent Guidelines

A Lex-native coding assistant — agents, tools, TUI, and A2A/ACP
servers, all written in Lex. Read `lex agent-guidelines` in full
before writing code. The four highest-leverage discipline rules:

1. **Narrow effects, always.** `fn foo() -> [fs_write("/tmp/x")] T`,
not `[fs_write]`. If the type checker rejects, narrow the body.
2. **Repair, don't regenerate.** `lex check --output json` →
`lex repair --apply`. Only regenerate after two failed repairs.
3. **`examples {}` blocks on every pure fn.** They fold into the
SigId and run at `lex check` time.
4. **Use the stdlib.** `std.crypto` for hashing, `std.regex` over
hand-rolled scanners.

## The loop

```sh
lex pkg install # resolves lex-llm, lex-agent, lex-trail, lex-spec, lex-schema, ...
lex check <each src/*.lex file> # this repo has no tests/ dir — see below
lex fmt --check src/
```

There is no `tests/` directory. CI type-checks every file in `src/`
individually, then gates on the manifesto demo examples:

```sh
lex check examples/manifesto_parallel.lex # must pass
lex check examples/manifesto_parallel_bad.lex # must FAIL (missing [concurrent])
bash examples/manifesto_semantic_diff/run.sh # semantic diff must surface the effect-row change
```

Treat those three as the regression suite until a real `tests/`
directory exists.

## Project-specific overrides — lex-code

- **Two call sites per agent mode, times seven files.** Each
`src/agents/*.lex` (build, explore, plan, refactor, review,
spec_agent, test_agent) defines one `AgentLoop`-builder per
provider: `agent()` (Anthropic), `openai_agent()`, `mistral_agent()`,
`google_agent()`, `vertex_agent()`, `ollama_agent()`,
`vllm_agent()`, `litellm_agent()`, `opencode_agent()`. Adding a
provider means adding the function to **all seven** files *and* a
matching branch in `src/server/session.lex`'s `pick_agent` — a
missing branch is a silent fallthrough, not a type error.
- **`select_provider_tag` (TUI) and `pick_agent` (session) must move
together.** A new `--flag` in `src/tui/main.lex` with no matching
`"tag" => ...` arm in `session.lex`'s `pick_agent` silently no-ops
rather than failing to compile — always change both in the same
commit.
- **Local-model tool budgets are curated, not accidental.**
`ollama_agent`/`litellm_agent` use `tools.minimal_tools()` (build)
or `[]` (every other mode) instead of `all_tools()` — 38 tool
schemas overwhelm small local models (see the local-model
compatibility table in `README.md`). Don't widen this without
re-testing against a real local model.
- **Permission specs gate tool *visibility*, not just execution.**
`ag.with_permission_gate(base, rules.<mode>_permission())` (from
`src/permissions/rules.lex`) filters the tool list at construction
time. Every provider variant for a given mode must be wrapped with
the *same* permission spec — don't special-case one provider's tool
list without updating its spec.
- **`src/server/session.lex` is the single turn-handling path.** The
TUI, the A2A server (`src/server/api.lex`), the BeeAI ACP server
(`src/server/acp.lex`), and the Zed Agent Client Protocol server
(`src/server/client_protocol.lex`) are four transports over the same
`Session`/`run_turn_with_provider` (`run_turn_streaming_with_provider`
for the one transport — ACP — that needs a per-step callback). Extend
`session.lex`, don't duplicate turn logic into a transport file.
- **Two different protocols are both called "ACP" in this repo.**
`src/server/acp.lex` is BeeAI's REST-based Agent Communication
Protocol. `src/server/client_protocol.lex` is Zed's JSON-RPC-over-
stdio Agent Client Protocol — an unrelated standard that happens to
share the acronym. Don't rename either file to disambiguate further;
the doc comments at the top of each already do, and the README's
"Server Protocols" section labels them accordingly.
- **LiteLLM config lives in `litellm/`.** `config.yaml` +
`docker-compose.yml` are the reference proxy setup (Ollama local
models, vLLM, ChatGPT/Claude subscription passthrough, OpenCode Go)
— kept in sync with `lex-loom`'s own `litellm/` directory since both
repos are meant to work against the same proxy. Don't fork the model
list; add new entries to both repos together.
22 changes: 22 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# CLAUDE.md — lex-code

> Copy this file into the root of any Lex project repository as
> `CLAUDE.md` (read by Claude Code), `AGENTS.md` (read by Cursor /
> Aider / Codex CLI / Copilot CLI), or both. This repo ships both,
> kept in sync — read `AGENTS.md` in full before writing code.

This repository is a **Lex** project — a Lex-native coding assistant
(agents, tools, TUI, A2A/ACP servers). `AGENTS.md` carries the full
discipline, including why this repo has no `tests/` directory and how
provider support is wired across seven agent files plus
`session.lex`.

## The loop

```sh
lex pkg install
lex check <each src/*.lex file> # no tests/ dir — see AGENTS.md for the CI gate
lex fmt --check src/
```

See `AGENTS.md` for the full discipline.
23 changes: 23 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
European Union Public Licence v. 1.2 (EUPL-1.2)

Copyright (c) 2026 lex-code contributors

Licensed under the EUPL, Version 1.2 only (the "Licence"); you may not
use this work except in compliance with the Licence.

You may obtain a copy of the Licence at:

https://joinup.ec.europa.eu/sites/default/files/custom-page/attachment/eupl_v1.2_en.pdf
https://eupl.eu/1.2/en/ (HTML, all 23 EU languages)
https://spdx.org/licenses/EUPL-1.2.html (SPDX identifier: EUPL-1.2)

Unless required by applicable law or agreed to in writing, software
distributed under the Licence is distributed on an "AS IS" basis,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied. See the Licence for the specific language governing
permissions and limitations under the Licence.

The EUPL-1.2 is a copyleft licence approved by the European Commission
and is compatible with several other open-source licences listed in
its Appendix (including GPL-2.0, GPL-3.0, AGPL-3.0, LGPL-2.1, LGPL-3.0,
MPL-2.0, EPL-1.0, CeCILL-2.0/2.1, OSL-2.1/3.0, and CC-BY-SA-3.0).
81 changes: 72 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ lex-code --plan --ollama "how should we structure the session module?"
| `--litellm` | LiteLLM proxy | `$LITELLM_MODEL` | none (proxy handles keys) |
| `--ollama` | Ollama (local, native API) | `$OLLAMA_MODEL` | none |
| `--vllm` | vLLM (local/remote) | `$VLLM_MODEL` | none |
| `--opencode` | OpenCode Go plan (cloud, direct) | `$OPENCODE_MODEL` | `OPENCODE_API_KEY` |

### Ollama

Expand Down Expand Up @@ -117,34 +118,68 @@ VLLM_MODEL=deepseek-ai/DeepSeek-Coder-V2-Lite-Instruct \
`VLLM_MODEL` defaults to `mistralai/Mistral-7B-Instruct-v0.3`.
`VLLM_BASE_URL` defaults to `http://localhost:8000/v1/chat/completions`.

### LiteLLM (local models via proxy)
### OpenCode Go plan

[LiteLLM](https://github.com/BerriAI/litellm) is the recommended path for running local models. It provides an OpenAI-compatible endpoint over any backend (Ollama, vLLM, MLX, …), which gives cleaner tool calling than the native Ollama wire format.
[OpenCode Go](https://opencode.ai/docs/zen) bundles cloud access to several open-weight coding models (DeepSeek, Qwen3, Kimi, GLM, MiniMax, MiMo) behind one subscription key. Two ways to reach it — same key either way:

```sh
# start the LiteLLM proxy (config at project root)
litellm --config litellm_config.yaml --port 4000
# native (direct to the Go endpoint, no proxy)
export OPENCODE_API_KEY=$(cat ~/.credentials/opencode/key | tr -d '\n')
lex run --allow-effects env,io,net,llm,proc,sql,fs_write,time,approval \
src/tui/main.lex main -- --opencode "implement list.zip"

# override the default model (kimi-k2.7-code)
OPENCODE_MODEL=qwen3.7-max \
lex run --allow-effects env,io,net,llm,proc,sql,fs_write,time,approval \
src/tui/main.lex main -- --opencode "implement list.zip"

# via the LiteLLM proxy instead (shares one proxy + model list with lex-loom — see below)
LITELLM_MODEL=deepseek-v4-flash \
lex run --allow-effects env,io,net,llm,proc,sql,fs_write,time,approval \
src/tui/main.lex main -- --litellm "implement list.zip"
```

`OPENCODE_MODEL` accepts any Go-plan model id (see `litellm/config.yaml`'s "OpenCode Go plan" section for the full list). `OPENCODE_BASE_URL` overrides the endpoint if you're routing through a local reasoning proxy instead of hitting `opencode.ai` directly.

### LiteLLM (local models + OpenCode Go via proxy)

[LiteLLM](https://github.com/BerriAI/litellm) is the recommended path for running local models, and the only path that gives OpenCode Go's thinking-mode models correct `merge_reasoning_content_in_choices` handling. It provides an OpenAI-compatible endpoint over any backend (Ollama, vLLM, OpenCode Go, MLX, …), which gives cleaner tool calling than the native Ollama wire format.

This repo ships a ready-to-run proxy config at `litellm/config.yaml` + `litellm/docker-compose.yml` — kept in sync with [lex-loom](https://github.com/alpibrusl/lex-loom)'s own `litellm/` directory (same model list, same OpenCode Go entries) so both repos can point at one shared proxy instance.

```sh
# start the bundled proxy
cd litellm
ANTHROPIC_API_KEY=... OPENAI_API_KEY=... OPENCODE_API_KEY=... docker compose up -d
cd ..

# run lex-code against qwen3-coder:30b (recommended local model)
LITELLM_MODEL=qwen3-coder:30b \
lex run --allow-effects env,io,net,llm,proc,sql,fs_write,time \
lex run --allow-effects env,io,net,llm,proc,sql,fs_write,time,approval \
src/tui/main.lex main

# one-shot via the --litellm flag
LITELLM_MODEL=qwen3-coder:30b \
lex run --allow-effects env,io,net,llm,proc,sql,fs_write,time \
lex run --allow-effects env,io,net,llm,proc,sql,fs_write,time,approval \
src/tui/main.lex main -- --litellm "implement list.zip"

# OpenCode Go through the proxy instead of native --opencode
LITELLM_MODEL=kimi-k2.7-code \
lex run --allow-effects env,io,net,llm,proc,sql,fs_write,time,approval \
src/tui/main.lex main -- --litellm "implement list.zip"

# override the proxy URL (default: http://localhost:4000)
LITELLM_BASE_URL=http://gpu-box:4000 \
LITELLM_MODEL=qwen3-coder:30b \
lex run --allow-effects env,io,net,llm,proc,sql,fs_write,time \
lex run --allow-effects env,io,net,llm,proc,sql,fs_write,time,approval \
src/tui/main.lex main -- --litellm
```

`LITELLM_MODEL` is the model name as it appears in your `litellm_config.yaml` `model_name` field.
`LITELLM_MODEL` is the model name as it appears in `litellm/config.yaml`'s `model_name` field.
`LITELLM_BASE_URL` defaults to `http://localhost:4000`.

Running against a standalone LiteLLM install instead of the bundled compose file works the same way — point `litellm --config <your-config.yaml> --port 4000` at any config with the model names you use.

#### Local model compatibility

Tested on [lex-code fizzbuzz bootstrap](src/bootstrap/fizzbuzz_lex.lex) — task: write `fizzbuzz.lex` with `fn fizzbuzz(n :: Int) -> List[Str]` + 4 unit tests, `lex check` clean, `run_all` returns 0.
Expand All @@ -165,7 +200,7 @@ curl -s http://localhost:4000/v1/chat/completions \
> /dev/null

LITELLM_MODEL=qwen3-coder:30b \
lex run --allow-effects env,io,net,llm,proc,sql,fs_write,time \
lex run --allow-effects env,io,net,llm,proc,sql,fs_write,time,approval \
src/bootstrap/fizzbuzz_lex.lex main
# [fizzbuzz_lex] starting build via litellm
# [fizzbuzz_lex] done — steps: 71
Expand Down Expand Up @@ -244,6 +279,26 @@ curl -X POST http://localhost:8080/runs/stream \
# data: {"run_id":"...","agent_id":"lex-code","status":"completed","output":[...]}
```

### Agent Client Protocol (ACP, Zed) — Phase 1

Not the same "ACP" as above — this is [Zed's Agent Client Protocol](https://zed.dev/acp), an unrelated
JSON-RPC-over-stdio standard for launching a coding agent as a subprocess (Zed, JetBrains, Neovim, and
Emacs all speak it; opencode is one of the other agents already on the [ACP Registry](https://zed.dev/blog/acp-registry)).

```sh
LEX_CODE_PROVIDER=anthropic ANTHROPIC_API_KEY=… \
lex run --allow-effects env,io,net,llm,proc,sql,fs_write,time,crypto,random,approval \
src/server/client_protocol.lex main
```

Phase 1 covers `initialize`, `session/new`, `session/prompt` (streaming `session/update`
notifications per step), and `session/close` — enough to work from an ACP-aware editor. Not yet
implemented: `session/request_permission`, `$/cancel_request`, client-mediated `fs/*`/`terminal/*`,
and `auth/login` — see the header comment in `src/server/client_protocol.lex` for why each is
deferred rather than silently missing. The exact `session/update` field shapes are a best-effort
reconstruction of the protocol's v2 schema; validate against a real client before relying on this
for production interop.

## Tools

### Standard tools (all modes)
Expand Down Expand Up @@ -406,6 +461,14 @@ authorised to use.
- [x] v0.3 — parallel multi-agent (`std.conc`), VSCode extension, web frontend, bootstrap script
- [x] v0.4 — lex-vcs tools (17), CLI one-shot mode, Ollama + vLLM providers, install target
- [x] v0.5 — ACP server (`src/server/acp.lex`), ACP helpers in lex-agent
- [x] v0.6 — OpenCode Go provider (native + via the bundled LiteLLM proxy, shared config with lex-loom)
- [x] v0.7 — Agent Client Protocol (Zed) server, Phase 1: `initialize`/`session/new`/`session/prompt`/`session/close`

---

## License

EUPL-1.2 — matches the rest of the lex ecosystem.

---

Expand Down
9 changes: 6 additions & 3 deletions lex.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
[package]
name = "lex-code"
version = "0.1.0"
lex = "0.9.7"
name = "lex-code"
version = "0.1.0"
lex = "0.10.10"
license = "EUPL-1.2"
description = "A Lex-native coding assistant — build/plan/explore/refactor/spec/test/review agents, TUI + A2A + ACP servers, lex-vcs tools."

[dependencies]
lex-llm = { git = "https://github.com/alpibrusl/lex-llm" }
Expand All @@ -14,6 +16,7 @@ lex-web = { git = "https://github.com/alpibrusl/lex-web" }
lex-orm = { git = "https://github.com/alpibrusl/lex-orm" }
lex-mcp = { git = "https://github.com/alpibrusl/lex-mcp" }
lex-agent-llm = { git = "https://github.com/alpibrusl/lex-agent-llm" }
lex-memory = { git = "https://github.com/alpibrusl/lex-memory" }

[bin]
name = "lex-code"
Expand Down
Loading
Loading