Skip to content
Merged
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
16 changes: 15 additions & 1 deletion .github/buildcheck/natives.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,19 @@ def check_agent_class_path(natives, agent_pom_text):
return compare("llama-atmosphere-agent/pom.xml Class-Path", fatjar_targets(natives), named)


def check_agent_version(root_pom_text, agent_pom_text):
"""The agent is published to Maven Central at the core's version and depends on the core of its
own version, so a release that bumps the reactor without it would publish an agent naming a
core that does not exist (or an old one). It is not a reactor module, so `versions:set` misses it."""
def version(text):
element = ET.fromstring(text).find("m:version", NS)
return None if element is None else (element.text or "").strip()
core, agent = version(root_pom_text), version(agent_pom_text)
if core == agent:
return []
return [f"llama-atmosphere-agent/pom.xml is version {agent}, the reactor {core}: set both to the same version"]


def read(root, path):
with open(os.path.join(root, path), encoding="utf-8") as f:
return f.read()
Expand All @@ -271,4 +284,5 @@ def check(root):
+ check_cmake(natives, read(root, "llama/CMakeLists.txt"))
+ check_dependency_allowlist(natives, nativedeps.ALLOWED)
+ check_readme(natives, read(root, "README.md"))
+ check_agent_class_path(natives, read(root, "llama-atmosphere-agent/pom.xml")))
+ check_agent_class_path(natives, read(root, "llama-atmosphere-agent/pom.xml"))
+ check_agent_version(read(root, "pom.xml"), read(root, "llama-atmosphere-agent/pom.xml")))
9 changes: 9 additions & 0 deletions .github/buildcheck/tests/test_natives.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,15 @@ def test_agent_class_path(self):
["llama-atmosphere-agent/pom.xml Class-Path: missing linux-x86-64"])


def test_agent_version(self):
def pom(version):
return ('<project xmlns="http://maven.apache.org/POM/4.0.0"><modelVersion>4.0.0</modelVersion>'
f'<parent><version>9</version></parent><version>{version}</version></project>')
self.assertEqual(natives.check_agent_version(pom("5.2.0-SNAPSHOT"), pom("5.2.0-SNAPSHOT")), [])
self.assertEqual(natives.check_agent_version(pom("5.2.0"), pom("5.2.0-SNAPSHOT")),
["llama-atmosphere-agent/pom.xml is version 5.2.0-SNAPSHOT, the reactor 5.2.0: "
"set both to the same version"])

class RepositoryTest(unittest.TestCase):
"""The checks over this repository: what code-style runs, so a red here is a red there."""

Expand Down
36 changes: 31 additions & 5 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ jobs:
# job — never in the dockcross cross-compilers (which have no node) or per-platform.
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# llama-atmosphere-agent: the standalone (non-reactor, never on Maven Central) local
# llama-atmosphere-agent: the standalone (non-reactor) local
# coding-agent project that wires Atmosphere's built-in OpenAI-compatible agent runtime to
# this project's OpenAiCompatServer. Three jobs, all publish gates:
# - model-free: unit tests + the wire-contract tests, which drive the REAL
Expand All @@ -430,7 +430,10 @@ jobs:
# - smoke-agent-linux (further down, after package-fatjars): the release asset itself,
# started next to the real core fat jar.
# The project is built with -Dllama.version=<reactor version> against the core that was just
# installed to the local repo, so it always tests the code of this checkout.
# installed to the local repo, so it always tests the code of this checkout. Only the classes and
# the llama-platform pom are installed; -Dllama.natives=none keeps Maven from resolving the natives
# jars that pom names (they exist only after the package job). The agent itself is published to
# Maven Central by the two publish jobs, after the reactor deploy.
# ---------------------------------------------------------------------------

test-java-llama-atmosphere-agent:
Expand All @@ -443,8 +446,10 @@ jobs:
with:
java-version-file: .java-version
distribution: temurin
- name: Install parent + core net.ladenthin:llama into the local repo (Java only)
- name: Install parent + core net.ladenthin:llama + the llama-platform pom into the local repo (Java only)
uses: ./.github/actions/build-core
with:
modules: llama,llama-platform
- name: Resolve the reactor version
run: echo "VERSION=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version | tail -n1)" >> "$GITHUB_ENV"
- name: Spotless check
Expand All @@ -453,7 +458,8 @@ jobs:
run: mvn -B --no-transfer-progress -f llama-atmosphere-agent/pom.xml "-Dllama.version=${VERSION}" -Dllama.natives=none verify
# The GitHub Release asset llama-atmosphere-agent-<core version>-jar-with-dependencies.jar:
# the agent plus Atmosphere/JLine, WITHOUT the core (src/assembly/agent-jar.xml), so it is a
# few MB and the natives are not in the release twice. Never deployed to Maven Central.
# few MB and the natives are not in the release twice. Never deployed to Maven Central (the
# thin jar is; see the publish jobs).
# smoke-agent-linux launches it next to the real core fat jar; the attach jobs sign it.
- name: Build the agent release jar (without the core)
run: >
Expand Down Expand Up @@ -490,8 +496,10 @@ jobs:
with:
distribution: 'temurin'
java-version-file: .java-version
- name: Install parent + core net.ladenthin:llama (classes; the test JVM loads the downloaded native library via lib.path)
- name: Install parent + core net.ladenthin:llama + the llama-platform pom (classes; the test JVM loads the downloaded native library via lib.path)
uses: ./.github/actions/build-core
with:
modules: llama,llama-platform
- name: Resolve the reactor version
run: echo "VERSION=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version | tail -n1)" >> "$GITHUB_ENV"
- name: Run the Atmosphere tool-loop integration test (cached Qwen2.5-1.5B tool model, CPU)
Expand Down Expand Up @@ -2802,6 +2810,15 @@ jobs:
MAVEN_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
# The agent's thin jar + pom (not a reactor module, so a deploy of its own). It resolves the
# core and the natives jars llama-platform names from the local repository, where the reactor
# deploy above has just installed them; check-natives.py holds its version to the reactor's.
- name: Publish snapshot (llama-atmosphere-agent)
run: mvn --batch-mode --no-transfer-progress -f llama-atmosphere-agent/pom.xml -P release -Dmaven.test.skip=true deploy
env:
MAVEN_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
# Android AARs (llama-android, llama-android-opencl): assembled and published by
# the plain-Gradle build in llama-android/ — Maven cannot deploy
# <packaging>aar</packaging>. Natives are already on disk from the artifact
Expand Down Expand Up @@ -2987,6 +3004,15 @@ jobs:
MAVEN_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
# The agent's thin jar + pom (not a reactor module, so a deploy of its own). It resolves the
# core and the natives jars llama-platform names from the local repository, where the reactor
# deploy above has just installed them; check-natives.py holds its version to the reactor's.
- name: Publish release (llama-atmosphere-agent)
run: mvn --batch-mode --no-transfer-progress -f llama-atmosphere-agent/pom.xml -P release -Dmaven.test.skip=true deploy
env:
MAVEN_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
# Android AARs (llama-android, llama-android-opencl): Maven cannot deploy
# <packaging>aar</packaging>, so the plain-Gradle build signs + publishes them
# into a local Maven-layout staging repo, which is zipped into a Central
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ from version 5.0.0 onward. Pre-fork releases (`1.x`–`4.2.0`) were authored by

## [Unreleased]

### Added
- **`net.ladenthin:llama-atmosphere-agent` on Maven Central**, at the core's version: the agent's thin jar
(with `Main-Class`), sources and javadoc, published right after the reactor. Its pom names
`llama-platform` as a runtime dependency, so `jbang net.ladenthin:llama-atmosphere-agent:<version>`
starts it with the CPU natives of every desktop platform, no checkout and no download by hand. The
release-asset jar without the core stays on the GitHub release. The agent's version now moves with the
core's; `check-natives.py` fails when they differ.

### Changed
- **`ProcessRunner` rewritten on `ProcessBuilder`** (the helper `OSInfo` runs `uname` with): the timeout
is now real -- a command that does not end in time is killed and reported as an `IOException`, where
Expand Down
33 changes: 23 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

Java bindings for [llama.cpp](https://github.com/ggerganov/llama.cpp) via JNI, providing a high-level API for LLM inference in Java. The Java layer communicates with a native C++ library through JNI.

Current llama.cpp pinned version: **b11259**
Current llama.cpp pinned version: **b11320**

## Natives jars: one directory per backend (`.github/natives.csv`)

Expand Down Expand Up @@ -690,7 +690,7 @@ needs no extra step here, `build-webui` re-reads the tag and rebuilds the matchi
ships no UI):
```bash
# needs node/npm + network for the asset build; the embed step is plain cmake -P
git clone --depth 1 --branch b11259 https://github.com/ggml-org/llama.cpp /tmp/lc
git clone --depth 1 --branch b11320 https://github.com/ggml-org/llama.cpp /tmp/lc
( cd /tmp/lc/tools/ui && npm ci && npm run build )
mkdir -p webui-generated /tmp/ui-gen
cmake -DUI_SOURCE_DIR=/tmp/lc/tools/ui -DUI_BINARY_DIR=/tmp/ui-gen \
Expand Down Expand Up @@ -730,7 +730,7 @@ cache lives in **Depot Cache** over sccache's **WebDAV** backend:
- `SCCACHE_WEBDAV_TOKEN: ${{ secrets.DEPOT_TOKEN }}` — a Depot **organization** token, stored
as the repo secret **`DEPOT_TOKEN`**.

Because `sccache` is **content-addressed** and llama.cpp is pinned (`GIT_TAG b11259`), the
Because `sccache` is **content-addressed** and llama.cpp is pinned (`GIT_TAG b11320`), the
~280 upstream object files are byte-identical every run, so a warm cache recompiles only the
*changed* files. Depot's cache is **shared across all branches** (unlike GitHub's
per-branch `actions/cache`), so every branch builds incrementally; a `b<nnnn>` version bump
Expand Down Expand Up @@ -1842,7 +1842,7 @@ ctest --test-dir build --output-on-failure -R "ResultsToJson"

#### Upstream source location (in CMake build tree)

llama.cpp is fetched via CMake FetchContent, pinned to `GIT_TAG b11259`.
llama.cpp is fetched via CMake FetchContent, pinned to `GIT_TAG b11320`.

**GoogleTest** is a separate `BUILD_TESTING`-only FetchContent (`GIT_TAG v1.18.0`), used solely
by the `jllama_test` C++ unit-test binary — not by the shipped library, and not coupled to the
Expand Down Expand Up @@ -2169,7 +2169,10 @@ The repo root is a thin **aggregator/parent POM** (`net.ladenthin:llama-parent`,

All modules inherit the single `<version>` from the parent, so they **ship in lockstep by
construction** (no CI guard needed). The parent also holds the shared `release` profile (GPG +
Central Publishing), so one reactor `mvn -P release,natives deploy` signs and publishes all five
Central Publishing) and a `<pluginManagement>` with every plugin version more than one module uses
(compiler, jar, resources, surefire, source, javadoc, gpg, central-publishing -- a module names such a
plugin without a version; without it a module that pins nothing builds with the default of whatever
Maven runs it, which differed between CI and a local build), so one reactor `mvn -P release,natives deploy` signs and publishes all five
Maven artifacts (`llama-parent` pom, `llama` with its natives jars, `llama-langchain4j`,
`llama-kotlin`, `llama-platform` pom) at the same version.

Expand Down Expand Up @@ -2223,8 +2226,9 @@ missed again.)
release version now appears in only ~4 spots here, not ~20 — the runtime details live once in the
classifier table.)
- **`llama-langchain4j/README.md`** — its own `<dependency>` snippet.
- **`llama-atmosphere-agent/pom.xml`** — the `llama.version` property (the **release** version;
standalone project outside the reactor, so `versions:set` skips it), plus the fat-jar filename
- **`llama-atmosphere-agent/pom.xml`** — its own `<version>`, which must equal the reactor's
(standalone project outside the reactor, so `versions:set` skips it; `check-natives.py` fails until
they agree), plus the JBang coordinates and the fat-jar filename
`llama-<version>-jar-with-dependencies.jar` in the root README's "Local coding agent" section and
the project's own README.
- **`llama-android/README.md`** and **`llama-kotlin/README.md`** — their Gradle dependency
Expand Down Expand Up @@ -2364,19 +2368,28 @@ releases as a signed Central Portal bundle upload (staging repo → zip → Publ

A copy-and-run terminal agent (console, `--web`, `--acp`) pairing Atmosphere's OpenAI-compatible
agent runtime with this project's `OpenAiCompatServer`. A **standalone Maven project** (Java 21,
Atmosphere's floor), **not** a reactor module and **never** on Maven Central. Everything about the
Atmosphere's floor), **not** a reactor module: an application, built without a parent so the folder
can be copied out and run. **Published to Maven Central** at the core's version as a thin jar whose
pom names `llama-platform` as a runtime dependency, so `jbang net.ladenthin:llama-atmosphere-agent:<v>`
starts it with the CPU natives of every desktop platform. Everything about the
agent itself -- the REPL, approval gate, file tools, JLine console and its eight carried JLine fixes,
the three front ends -- is in **[`llama-atmosphere-agent/CLAUDE.md`](llama-atmosphere-agent/CLAUDE.md)**,
which Claude Code loads when working in that directory. What matters from the rest of the repository:

- **Maven Central:** a step of its own in `publish-snapshot` / `publish-release`, right after the
reactor deploy (`-f llama-atmosphere-agent/pom.xml -P release deploy`), resolving the core and its
natives jars from the local repository that deploy just filled. Its CI jobs install only the classes
and the `llama-platform` pom and pass `-Dllama.natives=none` (a profile that excludes everything that
pom names), since no natives jar exists before `package`.
- **Release asset:** `llama-atmosphere-agent-<llama.version>-jar-with-dependencies.jar`, built
**without** the core; its manifest `Class-Path` names the four `all-<os>-<arch>` fat jars and the
default fat jar. Renaming a core fat jar means updating that list -- `check-natives.py` holds it
to the fat-jar targets of `natives.csv`, and `smoke-agent-linux` launches the pair.
- **CI:** the model-free job, the model-backed integration job and `smoke-agent-linux` all gate both
publish jobs.
- **Version bump:** the pom's `llama.version` is the **release** version and `versions:set` does not
touch this standalone pom -- bump it by hand with the fat-jar filename in the two READMEs.
- **Version bump:** the pom's `<version>` is the reactor's, and `versions:set` does not touch this
standalone pom -- bump it by hand (`check-natives.py` fails until it agrees) with the JBang
coordinates and fat-jar filename in the two READMEs; `llama.version` follows (`${project.version}`).
- **The one core change it needed:** `OpenAiBackend`, `ChunkSink` and
`OpenAiCompatServer(OpenAiBackend, OpenAiServerConfig)` are public, so the agent's tests can drive
the real server without a model. Keep them public.
Expand Down
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
**Build:**
![Java 8+](https://img.shields.io/badge/Java-8%2B-informational)
![Platform](https://img.shields.io/badge/Platform-Linux%20%7C%20macOS%20%7C%20Windows%20%7C%20Android-lightgrey)
[![llama.cpp b11259](https://img.shields.io/badge/llama.cpp-%23b11259-informational)](https://github.com/ggml-org/llama.cpp/releases/tag/b11259)
[![llama.cpp b11320](https://img.shields.io/badge/llama.cpp-%23b11320-informational)](https://github.com/ggml-org/llama.cpp/releases/tag/b11320)
[![JPMS](https://img.shields.io/badge/JPMS-modular%20JAR-25A162)](https://openjdk.org/projects/jigsaw/)
![JUnit](https://img.shields.io/badge/tested%20with-JUnit6-25A162)
[![JSpecify](https://img.shields.io/badge/JSpecify-1.0.0%20%40NullMarked-25A162)](https://jspecify.dev)
Expand Down Expand Up @@ -1114,9 +1114,17 @@ OpenCode reduced to the essentials, fully offline; it edits files and, with `--a
(`docker`, `git`, build tools) — built from [Atmosphere](https://github.com/Atmosphere/atmosphere)'s
built-in OpenAI-compatible agent runtime (streaming, tool loop, workspace file tools) driven
**headless** against this project's OpenAI-compatible server. It is a standalone Maven project (not a
reactor module, never on Maven Central). Either download it from a release (below, JDK 21+ only), or
clone the repository and run it from that folder, which needs only JDK 21+ and Maven — the core jar
from Maven Central ships the natives:
reactor module) published to Maven Central at the core's version, so the quickest way needs only
JDK 21+ and [JBang](https://www.jbang.dev) — it resolves the agent and the core with the natives of
every desktop platform:

```bash
jbang net.ladenthin:llama-atmosphere-agent:5.2.0 \
--model Qwen3-4B-Instruct-2507-Q4_K_M.gguf --workspace /path/to/project
```

Or download it from a release (below, JDK 21+ only), or clone the repository and run it from that
folder, which needs only JDK 21+ and Maven — the core jar from Maven Central ships the natives:

```bash
# get the folder and a tool-capable model (Qwen3-4B-Instruct-2507, 2.3 GB)
Expand Down
8 changes: 7 additions & 1 deletion docs/RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ mvn -q versions:set -DnewVersion={VERSION} -DgenerateBackupPoms=false
from the repo root — it updates the root `<version>` plus every child's `<parent><version>` at
once. See the "Version bump" note in [CLAUDE.md](../CLAUDE.md) for the rationale.

**`llama-atmosphere-agent/pom.xml` is not in the reactor**, so `versions:set` skips it: set its
`<version>` to `{VERSION}` in the same commit (`check-natives.py`, in the `code-style` job, fails while
the two differ). It is published at that version right after the reactor deploy.

## Extra README dependency snippet

Besides the root `README.md`, the `llama-langchain4j/README.md` `## Dependency` section, the
Expand All @@ -33,4 +37,6 @@ One reactor `mvn -P release deploy` signs and publishes the parent pom, `llama`,
`llama-langchain4j`, and `llama-kotlin` together at the same version. The **Android AARs**
(`llama-android`, `llama-android-opencl`) are published by the `publish-release` job's separate
Gradle step (signed Central Portal bundle upload) — no manual action, but they appear as their own
deployment named `llama-android-{VERSION}` in the Central Portal UI.
deployment named `llama-android-{VERSION}` in the Central Portal UI. The agent
(`net.ladenthin:llama-atmosphere-agent`) is likewise its own deployment, from the step after the
reactor deploy.
Loading
Loading