LunarWing v2.1.2.0 Release - Codename Yoroi
Yoroi

鎧
Armor.
After the lacing, the plates. Each hammer-blow remembered. Not one gap remains.
Zanshin stayed aware after the strike.
But awareness alone does not turn the next blade.
It remembers the angle.
It remembers where the guard opened.
It remembers which blow came closest.
And then it builds.
Plate by plate.
Cord by cord.
Gap by gap.
This is Yoroi.
鎧 is armor, but armor is not a single wall. It is a collection of protections fitted together so that the edge of one plate guards the weakness of another.
2.1.2.0 is built the same way.
This is one of the largest releases in the project’s history.
At its center is a new Podman-first agent-shell sandbox, designed for daemonless, per-command execution with a fail-closed posture.
Around it, the armor closes from several directions at once.
The shell no longer needs a privileged container socket.
A sandbox cannot simply point its working directory at tenant credentials.
Workers no longer carry the full gateway bearer merely to authenticate one dispatch lane.
Rootless workloads are launched through the user manager instead of inheriting a daemon context that Podman cannot safely cross.
Kimi gains a command guard that denies dangerous headless execution when upstream approval controls do not hold. Pebble stays within its workspace and can finally hear a cancellation while a task is still running. OpenCode v2 becomes its own worker lane rather than replacing a proven v1 path.
Secrets are scrubbed.
Mounts are examined.
Networks begin closed.
Containers clean up after themselves.
And the system keeps watching after the first layer has done its work.
Yoroi is not a promise of invulnerability.
It is the discipline of remembering where the last blows landed.
The armor closes
Highlights
-
A Podman-first shell sandbox. Agent shell commands now run through direct
podman runordocker runexecution without exposing a daemon socket. Containers run with a non-root identity, all capabilities dropped,no-new-privileges, a read-only root filesystem, bounded temporary storage, an explicit process limit, and cancellation-safe cleanup. -
The network begins closed.
SANDBOX_NETWORK=noneis now the in-code and newly rendered default. Operators can deliberately selectproxyfor allowlisted access orproxy-enforced, which combines--network nonewith a per-execution Unix relay to LunarWing’s public-only, DNS-pinned proxy. -
The mount has a guard at the gate. Sandbox work directories are canonicalized before inspection, so symlinks and
..cannot disguise their destination. Tenantenv,state, andrundirectories cannot be mounted, credential-bearing files are refused anywhere in the tree, and protected directories inside an otherwise valid workspace are covered with empty read-only mounts. -
A refusal stays a refusal. Invalid mount paths and failed sandbox setup produce explicit
[sandbox:*]errors. LunarWing does not quietly fall back to unsandboxed execution when isolation cannot be established. -
Workers receive a narrower key. External workers now authenticate their dispatch connection with a tenant-scoped
WORKER_AUTH_TOKENinstead of the gateway bearer. A worker credential can no longer be reused to access the wider gateway API surface. -
Rootless workloads leave the daemon’s shadow. On systemd multi-tenant hosts, shell, sandbox, and build workloads are spawned as manager-forked transient services under
lunarwing-workloads.slice. This avoids inheriting the daemon’s namespace context and structurally restores rootless Podman operation. -
Containers are not left behind. In-flight registration, cleanup guards, and a socketless reaper prevent cancelled or timed-out shell commands from leaving forgotten containers behind.
-
Shell output admits when it was cut short. Sandboxed and direct execution now return a
truncatedflag and warning when output exceeds its bound. Partial output begins with an explicit truncation marker, so even a short preview carries the truth. -
OpenCode v2 becomes a first-class worker. The new
opencode2lane runs alongside the established v1 worker, with its own reproducible image, SDK bridge, mt-admin lifecycle, protected publication jobs, and dedicated v17 port-registry allocation. The proven v1 lane remains the default. -
Kimi gains a fail-closed command guard. The Kimi worker moves to the 2.x line and now carries a baked
PreToolUseguard for headless execution. Privilege escalation, destructive power and disk operations, fork bombs, remote-content-to-shell pipelines, dangerous recursive permission changes, block-device writes, SSH-agent use, and unsafe recursive removal are denied with an explicit reason. Unparseable requests deny rather than guess. -
Pebble stays inside its workspace. Requested project directories are resolved and confined beneath
WORKSPACE_ROOT, including symlink-aware overlap checks. One task may occupy a workspace subtree at a time, and the WebSocket read loop remains active so mid-task cancellation can actually terminate the running process. -
Daemon secrets stop following workloads. Sandboxed and direct shell execution now begin from a cleared environment and receive only the approved environment surface. Database credentials and sandbox-control variables no longer leak into child processes through inherited daemon state.
-
Conversation insights are rebuilt around evidence. A nine-finding hardening program adds authority checks, isolates automatic recall, prevents generated-history feedback, surfaces durable-save failures, captures eligible user evidence on the host, validates citations during atomic writes, limits each run, scopes event dispatch by project, persists restart-safe cursors, and makes deletion a durable opt-out.
-
Corrupted output cannot become an action. Blocking tool completions now match the streaming path: when provider-output corruption is detected, structured tool calls are removed and the response is demoted to text rather than reaching an executor.
-
Attachments become durable across channels. Incoming attachments are written beneath
attachments/<channel>/<id>/through the normal injection-scanned and search-indexed workspace path. The built-inattachment_storetool can list and read them, allowing a file received on one channel to remain available from another. -
Old tool results are cleared before they can mislead a new turn. Time and date results are always purged before execution, while cached web, HTTP, and shell results older than thirty minutes are removed before the first model call.
-
Role-token protection becomes unconditional. Chat-template token openers are neutralized even when the injection scanner is disabled or does not classify the content as severe. Returned text may change slightly at the byte level, but intact role delimiters cannot slip through merely because another detector stayed quiet.
-
Interrupted missions try again. Mission fires interrupted mid-run gain a durable retry policy with exponential backoff, restart-safe re-arming, and at-least-once event or webhook behavior. The default is three retries beginning after sixty seconds, doubling up to a one-hour ceiling.
max_retries=0disables automatic retry. -
Mission defaults become more honest. The default daily thread budget rises from 10 to 30, giving hourly missions a complete day plus retry headroom.
mission_createnow rejects missing names and goals instead of inventing empty ones, andcurrent_focuscan be set or cleared throughmission_update. -
Knowledge sweeps finally cover the whole store. Keyset pagination replaces the old thousand-note ceiling, and a heartbeat-independent sweep loop allows multi-tenant fleets to perform stale-note hygiene even when the tenant heartbeat is disabled.
-
Fresh tenants receive product skills, not contributor chores. The embedded skill catalog is trimmed from ten entries to seven product-facing capabilities. Internal web testing, local testing, and review workflows no longer appear as tenant defaults.
-
The health system learns the difference between failure and missing visibility. Host reports gain staleness alerts, partial-result tolerance, live gateway and pending-gate probes, knowledge sync and sweep monitoring, and worker resolution for OpenCode v2 and LunarVoice. Runtime and knowledge probes remain monitor-only diagnostics, so an observability gap can escalate without triggering a reckless restart.
-
WASM tools explain their full shape. Compact schemas now retain fields required by discriminated-union variants. Tools such as
gitlab_toolno longer collapse into a lonelyactionfield while hiding the project, title, and other information the model actually needs. -
Approval and argument paths receive another plate. Pending-gate recovery becomes more durable and race-aware, while REPL tool arguments gain pre-execution validation, bounded repair failure, and dedicated corruption telemetry.
-
The release machinery keeps its own armor fitted. OpenCode v2 receives digest-bound candidate, scan, and promotion jobs. ShellCheck 0.11.0 becomes the contributor floor. Worker image gates, Clippy, architecture checks, version-bump validation, and reproducibility contracts continue to block publication when their guarantees fail.
Upgrading
Back up PostgreSQL, tenant environment files, /etc/lunarwing/ports.json, workspace state, rendered units, worker data, voice models, and channel state before upgrading.
There is no new SQL migration in v2.1.2.0. V27__memory_index_outbox.sql remains current.
The multi-tenant port registry moves to v17, adding dedicated opencode2_wss and opencode2_health slots in the 51000-51999 range. The migration is additive and occurs automatically on the next mt-admin registry operation.
Rebuild the daemon and installed WASM extensions through the normal deployment path. All four channel registry versions have advanced:
darkirc0.1.8multica0.1.11weechat0.2.9xmpp0.1.14
Rebuild or reinstall the GitLab WASM tool for registry version 0.1.5. Other tool registry versions remain unchanged.
Sandbox policy
New tenants now render:
SANDBOX_NETWORK=none
Existing tenant environments remain preserved during ordinary re-rendering and receive the setting during upgrade. Review any workload that legitimately requires network access and explicitly choose one of the following:
nonefor no networkproxyfor allowlisted proxy accessproxy-enforcedfor network isolation plus the per-execution Unix relay
Podman is the preferred runtime:
SANDBOX_RUNTIME=podman
Production shells remain fail-closed behind the sandbox. Development-only direct execution still requires ALLOW_LOCAL_TOOLS, and SANDBOX_ENFORCE=true can require sandboxing even in that path.
Tenants with rootless Podman pairings already damaged by the old workload-spawn behavior should run the fix-podman-userns repair verb. New workloads use manager-forked transient services and should not require that repair.
Workers
The worker pins for this release are:
- OpenCode v1:
v1.18.25 - Kimi Code:
2.0.2 - OpenCode v2:
v2.0.15 - Pebble:
2d0f3786
Rebuild the Kimi worker image and allow the updated rootless-launch contract to recreate existing containers. The Kimi lane no longer receives the tenant SSH-agent socket, now runs with no-new-privileges, and includes the fail-closed command guard.
Kimi tasks that legitimately require SSH should move to Pebble, OpenCode, OpenCode v2, or a future scoped credential broker. The shared tenant agent is no longer exposed to the headless Kimi executor.
The new OpenCode v2 lane is optional and remains separately deployable through the mt-admin opencode2 lifecycle and the CI-published worker-opencode2 image.
On the next mt-admin lifecycle operation, legacy tenants receive a scoped WORKER_AUTH_TOKEN. Existing daemon configuration, worker environments, and Quadlet units converge automatically. Expect a one-line migration warning for each legacy tenant; no manual token migration is required.
Pebble now rejects daemon-supplied project directories outside WORKSPACE_ROOT and prevents overlapping concurrent tasks from occupying the same workspace subtree.
Knowledge and missions
Knowledge sweeps now run independently of the heartbeat when KNOWLEDGE_SWEEP_ENABLED is active. Review the documented KNOWLEDGE_SWEEP_* cadence settings before enabling full-store sweeps across a large fleet.
Mission behavior also changes:
- The default daily thread budget is now 30, up from 10.
- Interrupted mission fires retry automatically with exponential backoff.
- The default retry policy allows three retries.
- Set
max_retries=0on a mission to disable retry. - Explicit
max_threads_per_dayvalues remain unchanged.
Service lifecycle
On systemd rootless hosts, mt-admin now stops the owning Quadlet user unit before falling back to a direct container stop for Kimi, OpenCode, OpenCode v2, and PostgreSQL.
This prevents Restart=on-failure from resurrecting a container that an operator believed had been stopped during an upgrade or tenant shutdown.
No separate operator action is required beyond using the updated mt-admin lifecycle.
The release identifier is 2.1.2.0. LunarWing-owned Rust crates and channel packages use 2.1.2. Rust 2024 and MSRV 1.96.1 remain unchanged.
Protected CI can publish verified, digest-bound worker images, but daemon packages and tenant deployments remain outside the automated release path. mt-admin continues to perform tenant-local activation.
Kiyome cleaned the blade.
Inazuma carried it into the storm.
Ma-ai taught it distance.
Zanshin kept the mind after the strike.
Yoroi takes every remembered blow and turns it into a plate.
Not one wall.
Not one lock.
Not one promise that danger has disappeared.
A thousand smaller refusals:
This path cannot be mounted.
This token cannot cross.
This command cannot run.
This worker cannot wander.
This stale result cannot pretend to be fresh.
This corrupted answer cannot become an action.
The plates are laced.
The gaps are watched.
The next blow will still come.
This time, LunarWing is dressed for it.
鎧