Run Qwen Code as a custom ACP harness in Buzz, using an OpenAI-compatible model endpoint on the same computer, another machine, a GPU cluster, a container platform, or a hosted service.
This repository also provides a narrow workaround for model servers that reject Qwen Code's tool grammar when a tool schema contains a large maxLength. The patcher caps only maxLength values above 2000 that are reachable from actual Qwen Code tool definitions. It does not rewrite JSON Schema libraries or arbitrary bundled code.
Status: Qwen Code
0.22.3is the currently tested release. Patching another release requires an explicit override and should begin with a dry run.
- A Buzz ACP launcher for Qwen Code.
- Optional fail-open, metadata-only ACP telemetry for Buzz Agent Observability.
- A syntax-aware tool-schema auditor that understands decimal values, scientific notation such as
1e4, and imported constants. - A backup-first patch and restore workflow.
- A secret-free Buzz manifest generator.
- Synthetic regression tests; no Qwen Code bundles are redistributed here.
It does not install Qwen Code, run a model server, select a model for you, or alter Qwen Code user settings. In particular, it never changes ~/.qwen/settings.json, API tokens, model configuration, or server configuration.
Buzz chat window
│ ACP over stdin/stdout
▼
codex-acp launcher ──► Qwen Code
│ OpenAI-compatible HTTP (/v1)
▼
model-serving endpoint
local │ LAN │ cluster │ cloud
The client and model-serving layers are independent:
- Buzz and this harness run where the user works.
- Qwen Code supplies the coding-agent loop and tools.
- The model endpoint can run anywhere reachable from the client.
- The endpoint must implement the OpenAI-compatible behavior required by Qwen Code, including streaming and tool calls.
The launcher is intentionally named codex-acp. Some Buzz releases attach their authenticated reply MCP sidecar only to recognized ACP harness command names. Keeping this basename lets Qwen Code receive that integration without pretending that the underlying runtime is Codex.
| Model location | Base URL example | Best for | Important consideration |
|---|---|---|---|
| Same machine | http://127.0.0.1:8000/v1 |
Simple development and single-GPU systems | Buzz must be able to reach the listening port |
| LAN server | http://model-host.example:8000/v1 |
Workstations and dedicated inference servers | Bind intentionally and restrict the network |
| Multi-node cluster | http://cluster-gateway.example:8000/v1 |
Models spread across several accelerators | Expose one stable gateway; cluster internals are the server's concern |
| Container platform | http://inference.example:8000/v1 |
Reproducible or shared deployments | Publish the endpoint and persist model caches as needed |
| Hosted API | https://provider.example/v1 |
No local inference administration | Use TLS and inject the provider key at runtime |
This project is hardware-neutral. CPU, CUDA, ROCm, Apple Silicon, distributed inference, and cloud accelerators are all valid behind the endpoint. Performance and model fit are properties of the serving stack, not the ACP bridge.
- Buzz with custom ACP harness support.
- Node.js 22 or newer.
- Qwen Code installed separately.
- An OpenAI-compatible model endpoint.
- A coding model whose chat template and tool-calling behavior work with that endpoint.
Confirm the basics before changing anything:
node --version
qwen --version
curl http://model-host.example:8000/v1/modelsFor an authenticated endpoint:
curl -H "Authorization: Bearer $MODEL_API_KEY" \
https://provider.example/v1/modelsNever paste a real key into a repository file or command history.
git clone https://github.com/Bostonvex/buzz-qwen-code-harness.git
cd buzz-qwen-code-harness
npm ci
npm run checkThe project is currently source-installed. Run commands as node bin/buzz-qwen-code.mjs …, or use npm link if you want the buzz-qwen-code command on your PATH.
The default standalone installation path is ~/.local/lib/qwen-code:
node bin/buzz-qwen-code.mjs auditUse an explicit path for a different installation:
node bin/buzz-qwen-code.mjs audit \
--qwen-root /opt/qwen-codeExit status 0 means no offending tool-schema value remains. Exit status 2 means the audit found one or more tool-schema maxLength values above the cap.
node bin/buzz-qwen-code.mjs patch --dry-runEach proposed edit is printed as:
lib/chunks/example.js:123:24 [ExampleTool] 1e4 (10000) -> 2000
The report identifies the file, line, column, tool class, source expression, evaluated value, and replacement.
node bin/buzz-qwen-code.mjs patchBefore writing a bundle file, the command copies the complete installation to a timestamped sibling directory such as:
~/.local/lib/qwen-code.backup-20260830T120000Z
It then:
- groups edits by actual tool bundle;
- changes only offending
maxLengthexpressions to2000; - parses and audits the installation again;
- rolls changed files back if verification fails; and
- records original and patched SHA-256 hashes in
buzz-qwen-code-patch.jsoninside the backup.
The patch is intentionally version-gated. For an untested release, first inspect a dry run and then opt in explicitly:
node bin/buzz-qwen-code.mjs patch \
--allow-untested-version \
--dry-runDo not assume that a patch validated for one Qwen Code bundle layout is safe for another release.
Use the exact backup path printed by patch:
node bin/buzz-qwen-code.mjs restore \
--backup ~/.local/lib/qwen-code.backup-20260830T120000ZRestore checks that every current file still has the patched hash. It refuses to overwrite a file changed by a later upgrade or manual edit. --force exists for an operator who has reviewed that conflict.
Stop Buzz before changing its harness manifests. Then run:
node bin/buzz-qwen-code.mjs install-buzz \
--qwen-bin ~/.local/bin/qwen \
--base-url http://model-host.example:8000/v1 \
--model coding-modelThe command writes custom_harnesses/qwen-code.json under the platform's Buzz data directory. Existing manifests are renamed to timestamped backups instead of being overwritten.
Default Buzz data locations are:
| Platform | Default |
|---|---|
| macOS | ~/Library/Application Support/Buzz |
| Linux | $XDG_CONFIG_HOME/Buzz or ~/.config/Buzz |
| Windows | %APPDATA%\Buzz |
If your Buzz build uses another location, pass it explicitly:
node bin/buzz-qwen-code.mjs install-buzz \
--buzz-data-dir /path/to/Buzz \
--base-url https://provider.example/v1 \
--model coding-model \
--api-key-env MODEL_API_KEYThe manifest stores only the environment-variable name, not its value. Make that variable available to the Buzz process before starting Buzz:
export MODEL_API_KEY='replace-with-your-provider-key'
open -a Buzz # macOS exampleUse your desktop session manager, keychain, secrets manager, or service configuration in production. For endpoints that accept any non-empty placeholder, the launcher defaults to the non-secret value local when the named variable is absent.
Restart Buzz after installing the manifest. Add a new agent and select Qwen Code as its harness. Agent-level model selection and harness registration are separate: edit the harness when the executable or endpoint changes; edit the individual agent when its role, prompt, or other seat-specific behavior changes.
| Harness variable | Meaning | Secret? |
|---|---|---|
QWEN_CODE_BIN |
Absolute path to the Qwen Code executable | No |
QWEN_BASE_URL |
OpenAI-compatible API root, normally ending in /v1 |
Usually no; private hostnames may still be sensitive |
QWEN_MODEL_ID |
Model identifier sent to the endpoint | No |
QWEN_MODEL_API_KEY_ENV |
Name of the inherited variable holding the key | No |
| Named API-key variable | Actual model endpoint credential | Yes; inject at runtime |
QWEN_UNAUTHENTICATED_API_KEY |
Optional non-secret placeholder for an unauthenticated server | No |
BUZZ_TELEMETRY_ENABLED |
Explicitly enable local metadata-only observation | No |
BUZZ_TELEMETRY_TOKEN_FILE |
Path to the collector's private ingest-token file | Sensitive path; file contents are secret |
BUZZ_TELEMETRY_IDENTITY_SALT_FILE |
Path to the collector's private identity-salt file | Sensitive path; file contents are secret |
The launcher maps these settings to Qwen Code's OPENAI_API_KEY, OPENAI_BASE_URL, and OPENAI_MODEL variables, then starts:
qwen --acp --model <model-id>
Additional arguments supplied by Buzz are forwarded unchanged.
The launcher can observe the real ACP byte streams without modifying them. It uses native piped backpressure, caps the amount retained for line inspection, and sends only timing, lifecycle, tool kind/status, usage semantics, and hashed identity metadata to the loopback collector. Telemetry is disabled by default.
The launcher declares ACP-update tool coverage, so a terminal 0 means no
live tool notification was observed. If that observation path is unavailable,
the collector displays an unavailable value instead of a false zero.
BUZZ_TELEMETRY_ENABLED=1
BUZZ_TELEMETRY_URL=http://127.0.0.1:7900/api/v1/events
BUZZ_TELEMETRY_TOKEN_FILE=~/.config/buzz-agent-observability/ingest-token
BUZZ_TELEMETRY_IDENTITY_SALT_FILE=~/.config/buzz-agent-observability/identity-salt
BUZZ_TELEMETRY_ENDPOINT_ID=local-model-primary
An unavailable collector, invalid telemetry configuration, send timeout, or
observer error cannot reject an ACP request. Exit delivery has a 50 millisecond
deadline. Protocol stdout remains byte-for-byte Qwen output; telemetry never
writes there. BUZZ_TELEMETRY_MAX_ACP_LINE_BYTES may lower or raise the
inspection limit from its 1 MiB default within the enforced 1 KiB–16 MiB
range. Oversized lines still pass through unchanged and are reported only by
byte count.
First verify the model path through Qwen Code itself:
OPENAI_API_KEY=local \
OPENAI_BASE_URL=http://model-host.example:8000/v1 \
OPENAI_MODEL=coding-model \
qwen --model coding-model \
--max-tool-calls -1 \
-p "Reply with exactly: Qwen Code tool grammar initialized successfully."Then inspect the integration:
node bin/buzz-qwen-code.mjs doctordoctor reports the Qwen installation and version, whether the release is tested, remaining tool-schema violations, and the active Buzz model and endpoint. It never prints an API-key value.
Some llama.cpp-compatible servers convert JSON tool schemas into a constrained grammar. Large string bounds can make that conversion fail before generation begins, producing an error similar to:
Failed to initialize samplers: failed to parse grammar
The failure is server- and grammar-converter-dependent; Qwen Code is not necessarily invalid. Reducing tool-string bounds is a compatibility workaround with a tradeoff: affected tool arguments cannot exceed 2,000 characters.
The auditor avoids a broad search-and-replace. It parses every JavaScript chunk and starts only from classes extending Qwen Code's BaseDeclarativeTool. It follows nested schema objects and resolvable local or imported numeric constants. A maxLength in an unrelated parser, linkifier, validator, or other bundled dependency is outside that graph and is left untouched.
node bin/buzz-qwen-code.mjs auditIf the audit is clean, the remaining incompatibility is probably another part of the generated grammar, the model's tool-call template, or the serving stack. Inspect the model-server log at the matching request time. Do not keep lowering unrelated bundle constants.
Confirm that:
curl <base-url>/modelssucceeds from the Buzz machine;- the configured model ID exactly matches a returned or accepted identifier;
- Qwen Code starts from the same desktop environment as Buzz; and
- the API-key variable is available to the Buzz process, not only an unrelated terminal.
Run the command-line test above, then check Buzz and Qwen Code logs for the same timestamp. A healthy model HTTP response does not prove that the ACP client received a terminal turn event. Also confirm the manifest uses this repository's bin/codex-acp launcher and restart Buzz after replacing a manifest.
This is usually model behavior or a mismatch between the model's tool template and the server. Use a model trained for tool calling, confirm the server applies its expected chat template, and test one simple file-writing request outside Buzz. The schema cap fixes grammar construction; it does not improve a model's tool-use judgment.
Upgrades normally replace bundled chunks and therefore remove this patch. Do not copy old chunks into a new release. Run audit, review patch --dry-run, and wait for this project to list the new release as tested before patching without the override.
node bin/buzz-qwen-code.mjs uninstall-buzzThe manifest is renamed to a recoverable timestamped file. Qwen Code and the model server are not uninstalled.
- Bind unauthenticated model servers to loopback unless other clients truly need access.
- Put authentication and TLS in front of endpoints crossing untrusted networks.
- Restrict Buzz manifests and logs because endpoints, prompts, and file paths may be sensitive.
- Give coding agents access only to intended workspaces.
- Treat model-generated tool arguments as untrusted input.
- Keep Qwen Code backups until the patched installation has passed a real workflow.
- Never publish the generated Buzz manifest if it contains a private hostname, even though it contains no API-key value.
This is an independent integration project. It does not vendor or redistribute Qwen Code. Install Qwen Code from its official distribution and review its Apache-2.0 license.
This repository's original integration code is MIT-licensed. Contributions should arrive through forks and pull requests; direct repository write access remains limited to the owner.