Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9a00e5e
ci(release): add interim public release workflow (GHCR + GitHub Release)
Jun 22, 2026
835d08d
ci(release): publish openclaw-sandbox agent image; fix AGT guard layer
Jun 22, 2026
01aed68
ci(release): publish all 6 Python runtime images + AGT wheels build
Jun 22, 2026
5673a74
ci(release): add GitHub-native build provenance attestations
Jun 22, 2026
6f47d82
fix(release): aarch64 aws-lc-sys cross-compile + stale runtime AGT pins
Jun 22, 2026
e266f83
fix(maf-runtime): use agent-framework-core/openai + tool decorator
Jun 22, 2026
f37ab52
fix(release): build mesh-plugin/dist before openclaw-sandbox
Jun 22, 2026
912d547
ci(release): publish AGT mesh relay + registry images
Jun 22, 2026
2bc084d
feat(cli): kars dev --release <version> (run from published images)
Jun 22, 2026
c592f88
test(e2e-harness): KARS_RELEASE for the docker platform
Jun 22, 2026
5cafd40
docs: add no-compile published-images quick start (kars dev --release)
Jun 22, 2026
001f636
docs(security): audit for kars dev --release capability
Jun 22, 2026
4f3fe12
fix(cli): kars dev skips optional prompts when stdin is not a TTY
Jun 23, 2026
ff6e2d6
feat(release): multi-arch sandbox-base + openclaw-sandbox (arm64 for …
Jun 23, 2026
1347ad6
ci(release): build sandbox images on NATIVE arm64 runners (not QEMU)
Jun 23, 2026
c8bf0da
fix(cli): kars dev --release pulls the HOST arch sandbox image
Jun 23, 2026
0e4be76
fix(release): bundle patched POP-aware AGT SDK in published sandbox i…
Jun 23, 2026
3fb70e1
fix(e2e): verify sibling mesh pairs from collected gateway logs
Jun 23, 2026
21c2c69
fix(operator): key per-sandbox state by unique origin, not bare name
Jun 23, 2026
7a52a81
docs: published --release path works on Apple Silicon (multi-arch arm64)
Jun 23, 2026
69cb638
feat(release): kind --release support + multi-arch AGT relay/registry
Jun 23, 2026
291a1a7
fix(deps): bump quinn-proto 0.11.14→0.11.15 (RUSTSEC-2026-0185)
Jun 23, 2026
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
869 changes: 869 additions & 0 deletions .github/workflows/release-public-interim.yml

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 28 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,25 +132,44 @@ Same CRDs. Same router code path. Same audit format. Same governance profiles. T

## Try it in five minutes

**Fastest path (recommended): GitHub Copilot.** If you have an active Copilot seat (Individual / Business / Enterprise), the only thing you need beyond Docker is one device-code login. No Azure account, no PAT, no key files.
**Fastest path — published images, no compile (macOS & Linux, Intel & Apple Silicon).**
On any host with Docker (amd64 Linux, Intel Mac, or Apple Silicon M-series),
two commands get you a running agent — no Rust toolchain, no AGT checkout, no
local image build:

```bash
# 1. Install the CLI from the latest interim release
npm i -g https://github.com/Azure/kars/releases/download/v0.1.0-interim.9/kars-cli-0.1.0.tgz

# 2. Launch a sandbox from the published, cosign-signed images
kars dev --release v0.1.0-interim.9
```

> Use the newest tag from the [Releases page](https://github.com/Azure/kars/releases) — the `npm i -g …` URL and the `--release` flag must match.

> **Apple Silicon (M-series) Macs:** fully supported. The published images are
> multi-arch (`linux/amd64` + `linux/arm64`), built on native arm64 runners,
> and `kars dev --release` automatically pulls the image matching your host
> architecture — no Rosetta, no extra flags.

On first launch you'll pick an inference provider (see below). **GitHub Copilot** is the easiest — one device-code login, no Azure account.

<details>
<summary><strong>Build from source</strong> (to hack on the controller / router / plugin)</summary>

> **Prerequisites:** Docker Desktop · Node.js 22+ · Rust 1.88+ · one of: an active GitHub Copilot seat, an Azure AI Foundry deployment, or a GitHub PAT with `models:read`.

```bash
git clone https://github.com/Azure/kars.git && cd kars
cd cli && npm ci && npm run build && npm link && cd ..

# Launch a sandbox locally — Docker only, no Azure, no AKS
# Launch a sandbox locally — Docker only, no Azure, no AKS.
# Builds the sandbox image natively for your architecture (incl. arm64).
kars dev
```

> **Faster clone (no sandbox base build):** The repo ships ~235 MB of vendored Python wheels under `vendor/sandbox-wheels/` so the sandbox base image builds hermetically (no PyPI dependency). If you only want to read code, run the CLI, or build the controller / router — skip the wheels with a partial clone:
> ```bash
> git clone --filter=blob:none https://github.com/Azure/kars.git
> ```
> Files are fetched on demand when you `git checkout` or `git log` actually needs them. If you later need the wheels (because you're rebuilding `kars-sandbox-base`), run `git lfs install && git lfs pull` (the wheels are tracked via LFS going forward) or simply `git checkout vendor/sandbox-wheels` to materialize them.

The first `kars dev` pulls + caches the sandbox base image (~10 min once) and then launches near-instantly thereafter.
The first source build of `kars dev` builds + caches the sandbox base image (~10 min once) and then launches near-instantly thereafter.
</details>

<details>
<summary><strong>For Microsoft / Azure-org members: skip the build</strong></summary>
Expand Down
133 changes: 109 additions & 24 deletions cli/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ const SANDBOX_BASE_IMAGE =
const AZURELINUX_BASE =
"mcr.microsoft.com/azurelinux/base/core:3.0";

// Public registry for `--release` mode (no-compile, no-AGT-clone bringup).
// Mirrors the names published by .github/workflows/release-public-interim.yml.
const RELEASE_REGISTRY = "ghcr.io/azure";
const releaseImage = (name: string, version: string): string =>
`${RELEASE_REGISTRY}/${name}:${version}`;

const AGT_NETWORK = "kars-dev";
const AGT_POSTGRES = "kars-agt-postgres";
const AGT_RELAY = "kars-agt-relay";
Expand Down Expand Up @@ -166,6 +172,10 @@ NOT overwrite your saved credentials.
"One-off GitHub Models override (does NOT save). Requires a PAT with `models:read`. To save GitHub Models as your default provider, run without this flag and pick GitHub Models at the prompt."
)
// ── Image build ────────────────────────────────────────────────────
.option(
"--release <version>",
"Run from PUBLISHED images instead of building. Pulls ghcr.io/azure/* at the given release tag (e.g. v0.1.0-interim.5) — no AGT clone, no local compile. Skips --build.",
)
.option(
"--image <image>",
"Sandbox container image",
Expand Down Expand Up @@ -261,6 +271,26 @@ Notes:
process.exit(1);
}

// ── Release mode: run from published images, no build/clone ───
// `--release <version>` pulls everything from ghcr.io/azure/* at the
// given tag: the openclaw-sandbox, the AGT relay + registry, and any
// selected runtime image. It forces build OFF so we never compile or
// clone the AGT toolkit. An explicit --build is contradictory.
const releaseMode = typeof options.release === "string" && options.release.length > 0;
const releaseVersion: string | undefined = releaseMode ? options.release : undefined;
if (releaseMode) {
if (options.build === true || options.buildBase === true) {
console.error(chalk.red(`\n Error: --release pulls published images; it cannot be combined with --build/--build-base.\n`));
process.exit(1);
}
options.build = false;
// Use the published sandbox image unless the user pinned --image.
const imagePassed = process.argv.some(a => a === "--image" || a.startsWith("--image="));
if (!imagePassed) {
options.image = releaseImage("openclaw-sandbox", releaseVersion!);
}
}

// ── First-run target prompt ──────────────────────────────────
// Brand-new user with no saved creds AND no explicit --target
// flag: ask docker vs local-k8s up front, before we get into
Expand Down Expand Up @@ -462,7 +492,7 @@ Notes:
// users testing local changes can opt in here without
// remembering the --build flag. Skipped if --build was passed
// explicitly.
if (!options.build) {
if (!options.build && !releaseMode) {
const { rebuild } = await inquirer.prompt([{
type: "confirm",
name: "rebuild",
Expand Down Expand Up @@ -502,20 +532,24 @@ Notes:
? "Local (recommended; relay + registry in the kind cluster alongside the sandbox)"
: "Local (recommended; spin up relay + registry in Docker on this laptop)";

const { default: inquirer } = await import("inquirer");
const { meshSource } = await inquirer.prompt([{
type: "list",
name: "meshSource",
message: "Where should the mesh live?",
default: "local",
choices: [
{
name: localLabel,
value: "local",
},
{ name: remoteLabel, value: "remote" },
],
}]);
let meshSource = "local";
if (process.stdin.isTTY) {
const { default: inquirer } = await import("inquirer");
const ans = await inquirer.prompt([{
type: "list",
name: "meshSource",
message: "Where should the mesh live?",
default: "local",
choices: [
{
name: localLabel,
value: "local",
},
{ name: remoteLabel, value: "remote" },
],
}]);
meshSource = ans.meshSource;
}

if (meshSource === "remote") {
if (!cachedRegistryUrl && !aksAvailable) {
Expand Down Expand Up @@ -572,7 +606,7 @@ Notes:
addChannel("telegram", "telegram-token", "Telegram");
addChannel("slack", "slack-token", "Slack");
addChannel("discord", "discord-token", "Discord");
if (available.length > 0) {
if (available.length > 0 && process.stdin.isTTY) {
const { default: inquirer } = await import("inquirer");
const { picked } = await inquirer.prompt([{
type: "checkbox",
Expand Down Expand Up @@ -617,6 +651,7 @@ Notes:
agtRepo: options.agtRepo ?? process.env.KARS_AGT_REPO ?? DEFAULT_AGT_REPO,
noMesh: options.noMesh === true,
globalRegistry: typeof options.globalRegistry === "string" ? options.globalRegistry : undefined,
releaseVersion: releaseMode ? releaseVersion : undefined,
});
return;
} catch (e) {
Expand Down Expand Up @@ -648,13 +683,19 @@ Notes:
// machines can `kars dev --build` without first cloning AGT by
// hand. Explicit --agt-repo / $KARS_AGT_REPO still win.
let agtRepo: string;
try {
agtRepo = await ensureAgtRepo(options.agtRepo, repoRoot);
} catch (e: unknown) {
if (releaseMode) {
// Release mode pulls the published relay/registry images — no AGT
// checkout needed. Keep a harmless default so later refs don't NPE.
agtRepo = options.agtRepo ?? process.env.KARS_AGT_REPO ?? DEFAULT_AGT_REPO;
if (meshProvider === "agt" && options.build) {
console.error(chalk.red(`\n Auto-cloning AGT failed:\n ${(e as Error).message}\n`));
process.exit(1);
} else {
try {
agtRepo = await ensureAgtRepo(options.agtRepo, repoRoot);
} catch (e: unknown) {
agtRepo = options.agtRepo ?? process.env.KARS_AGT_REPO ?? DEFAULT_AGT_REPO;
if (meshProvider === "agt" && options.build) {
console.error(chalk.red(`\n Auto-cloning AGT failed:\n ${(e as Error).message}\n`));
process.exit(1);
}
}
}
if (meshProvider === "agt" && options.build) {
Expand Down Expand Up @@ -783,7 +824,29 @@ Notes:

stepper.step("Resolving sandbox image...");
let imageExists = false;
if (!options.build) {
if (releaseMode) {
stepper.update(`Pulling published sandbox image (${image})...`);
try {
// openclaw-sandbox is published multi-arch (amd64 + arm64); pull
// the HOST architecture so Apple Silicon gets the native arm64
// image (the amd64 image crashes under Rosetta with
// rt_tgsigqueueinfo). dockerPlatform = linux/<host arch>.
await execa("docker", ["pull", "--platform", dockerPlatform, image], { stdio: "inherit" });
imageExists = true;
} catch {
stepper.fail("Could not pull published sandbox image");
console.log(chalk.yellow(`
Failed to pull ${chalk.bold(image)}.

Verify the release tag exists and the package is public:
${chalk.cyan(`docker pull ${image}`)}

Browse tags: https://github.com/Azure/kars/pkgs/container/openclaw-sandbox
`));
process.exit(1);
}
}
if (!options.build && !releaseMode) {
stepper.update("Checking for sandbox image...");
try {
const { stdout: cachedArch } = await execa("docker", [
Expand All @@ -805,7 +868,7 @@ Notes:
}
}

if (options.build || !imageExists) {
if (!releaseMode && (options.build || !imageExists)) {
const baseImage = options.baseImage;

// Check if Azure Linux base image exists locally, pull if not
Expand Down Expand Up @@ -1090,6 +1153,28 @@ Notes:
// Local registry mode — deploy AGT relay/registry locally
stepper.step("Starting mesh infrastructure (agt)...");

// Release mode: pull the published relay + registry and tag them
// as the local :dev names the run/build steps below expect. This
// satisfies the build-fallback's haveImage() checks so nothing is
// built and the AGT checkout is never needed.
if (releaseMode) {
for (const [name, devTag] of [
["kars-agentmesh-relay", "agentmesh-relay:dev"],
["kars-agentmesh-registry", "agentmesh-registry:dev"],
] as const) {
const ref = releaseImage(name, releaseVersion!);
stepper.update(`Pulling published ${name}...`);
try {
await execa("docker", ["pull", "--platform", "linux/amd64", ref], { stdio: "pipe" });
await execa("docker", ["tag", ref, devTag], { stdio: "pipe" });
} catch {
stepper.fail(`Could not pull ${ref}`);
console.log(chalk.yellow(`\n Failed to pull ${chalk.bold(ref)}. Verify the release tag exists and the package is public.\n`));
process.exit(1);
}
}
}

// Helper: check if a docker image exists locally
async function haveImage(tag: string): Promise<boolean> {
try {
Expand Down
Loading
Loading