<< All versions

Skill v1.0.0

currentLLM-judged scan90/100
stage-11-agentics/c11/c11-hotload
──Details
PublishedOctober 21, 2026 at 08:26 AM
Content Hashsha256:2b73de1ad3a97478...
Git SHA2c82b53463ad
──Files
Files (1 file, 16.7 KB)
SKILL.md16.7 KBactive
SKILL.md · 208 lines · 16.7 KB

version: "1.0.0" name: c11-hotload description: "Hot-reload workflow for c11 development: initial setup, tagged Debug builds via reload.sh, Release variants, the debug event log, and the tagged-build reporting format. Use when building, rebuilding, or launching c11 during development."


c11 hotload

How to build, reload, and tail a c11 dev instance.

Default: build on Atlas

From a provisioned delegator worktree on the laptop:

bash
./scripts/remote-build.sh --tag <your-branch-slug>

This runs no local xcodebuild. It bundles the exact parent/submodule commits, overlays tracked modifications and untracked non-ignored files with content hashes, and retrieves the tagged Debug app plus logs and result.json under build-remote/<invocation>/. Tracked deletions, executable modes and symlinks are preserved. Dirty submodules are refused: commit and pin their changes first. Initialize the required submodules with git submodule update --init --recursive ghostty vendor/bonsplit; do not run setup.sh on the laptop to build GhosttyKit.

Staging uploads only the commits Atlas lacks: Atlas keeps one mirror per repository (~/c11-builds/mirrors/), and each bundle excludes everything reachable from a head the mirror holds, so a head whose parent was built before sends a few KB; with no shared base it falls back to a full bundle. The client prints staged <MB> in <s>; bundles: … per run, and Atlas checks out the requested SHAs from the mirror and verifies them before building.

Atlas uses process-scoped Xcode 26.3 (/Applications/Xcode-26.3.app/Contents/Developer) and Zig 0.15.2 (~/zig-0.15.2). C11_REMOTE_HOST defaults to atlas; --host overrides it. C11_REMOTE_DEVELOPER_DIR and C11_REMOTE_ZIG_DIR override paths but the versions are checked. No global Xcode selection or credential provisioning happens.

The Atlas route has two slots with separate per-tag caches: slot 1 is reserved for the PR Swift poller; owner gates and all other Atlas builds share slot 2, one at a time. At normal load, one poller build and one fleet build may overlap. After one-minute load stays above 40 for 60 seconds, starts serialize: a fleet build takes slot 2 only while slot 1 is idle, then holds slot 1 as a guard; a poller build takes slot 1 only while slot 2 is idle, then holds slot 2 as a guard. Both slots stay held through the build, so new builds wait; builds admitted before load rises may finish. Same-tag requests serialize. The ordinary laptop with-build-lock.sh remains single-slot. Atlas builds outside this route must be coordinated with its capacity.

GitHub's native CI for main runs on the free GitHub-hosted macos-15 runner. PRs keep the cheap Ubuntu lane. ci-hourly.yml ("CI main (macOS)") runs on each push to main and by dispatch. One build runs at a time, never cancelled, with one pending run that each newer push replaces. An admitted run tests the ref's tip at that moment, not its trigger, so every push is followed by a run that tests a main containing it. A pass posts a CI main (macOS) commit status on the tested commit; a run whose tip already has one skips. A failed tip lookup fails the run; a failed green-record lookup builds. ci-macos-compat.yml and build-ghosttykit.yml run hourly/manual against main. Each heavy workflow command uses scripts/with-build-lock.sh; the process-scoped Xcode/Zig setup does not install into /usr/local or change global Xcode state. No self-hosted runner is registered or used in this PR. An access-restricted runner is a follow-up that requires Atin to decide the repository, trigger, and network boundary.

For landing, the Merge Captain gates the exact PR head with fresh review, the cheap PR checks, and an Atlas exact-head remote build for every Swift or native change after the branch includes current origin/main. A docs-only change is exempt only when the diff is limited to documentation or prose and contains no Swift, native workflow, script, project, submodule, test, or build-input change. The main backstop result is the post-merge authority; red main is fixed forward. For Ghostty pointer changes, manually dispatch Build GhosttyKit on the internal bump branch, wait for its prerelease non-latest artifact and bot checksum commit, refresh PR checks at that bot-created head, then run the exact-head Atlas gate before landing. The workflow keys on the ghostty SHA, so a Bonsplit-only pointer bump needs no dispatch.

Remote failure returns nonzero, retrieves available logs, and preserves the previous local app without launching it. The default stages only; it never launches or restarts c11. Successful Debug retrieval rewrites only the app's host-specific daemon/repository paths and ad-hoc signs it; result.json records both Atlas and client executable hashes. Launch with QA startup dialogs suppressed only when a launch is authorized:

bash
./scripts/launch-tagged-automation.sh <tag> --qa fresh
# Equivalent convenience on an authorized client:
./scripts/remote-build.sh --tag <tag> --launch

In the c11 1.0 run, packaged-app validation and computer use run on Atlas only. The laptop receives the app but does not launch it. On Atlas, launch the retained tagged app using its source checkout's launch-tagged-automation.sh. Never launch an untagged c11 DEV app.

Tagged builds can run behind production during socket work: c11 holds a user-initiated activity while a socket client is connected and for 60 seconds after the last disconnect. Shell telemetry sends a socket report every 45 seconds, so an open shell keeps the activity held and the idle release rarely fires; the activity still allows system idle sleep.

Live proofs that need real agent panels (Claude Code, Codex, Grok receiving mail or running hooks) run in an Atlas sandbox guest: scripts/sandbox-up.sh <run-id> <tagged.app> --agents claude,codex,grok, then scripts/sandbox-agent.sh <run-id> launch|c11|screen …, then sandbox-down and sandbox-agent.sh <run-id> verify-clean. The retained Atlas copy of a remote build is under ~/c11-builds/<tag>/artifacts/<invocation>/; running the sandbox scripts on Atlas with C11_SANDBOX_HOST=local against it skips the upload from the laptop. Details: the c11-computer-use skill.

Isolate activity-history validation

C11_ACTIVITY_HISTORY_DIRECTORY=/absolute/owned/path is a validation hook, not a user storage setting. Pass it at launch with launch-tagged-automation.sh <tag> --qa fresh --env C11_ACTIVITY_HISTORY_DIRECTORY=/absolute/owned/path. The app, Messages, mailbox receipts and offline readers share that path; relative values are ignored. The environment is captured once per process. Use synthetic fixtures and preserve production history. Details and evidence requirements: docs/validation/c11-349/README.md.

Remote variants

CommandResult
./scripts/remote-build.sh --tag <tag>Tagged Debug app, logs, source identity; no launch
./scripts/remote-build.sh --tag <tag> --mode test -- -only-testing:c11LogicTests/<Class>Actual test action on Atlas; assertion results separate from compilation
./scripts/remote-build.sh --tag <tag> --mode releaseAd-hoc staging Release app; no publish or launch
./scripts/remote-build.sh --tag <tag> --mode release --wmo --universalRelease staging with production compilation settings

Test selection uses the safe per-PID socket wrapper. Pass -resultBundlePath <relative-path>.xcresult to retrieve an xcresult. Signing/notarization for publication remains in GitHub Actions, with named approval of exact signed bytes; this route does not copy credentials. For c11Tests and c11LogicTests test runs, remote-build.sh reads scripts/host-test-quarantine.tsv and automatically skips both all and atlas rows, logging each scope. An exact quarantined method named in -only-testing:<target>/<Class>/testMethod runs without the automatic skip. Use --no-host-quarantine to disable automatic skips.

The c11-logic scheme uses eight parallel workers through remote-build.sh. scripts/test-unit-local.sh enables parallel testing when C11_TEST_PARALLEL_WORKERS is set; the Atlas route sets it to eight. ci-hourly.yml uses three workers on macos-15.

Existing on-Atlas entry points

A person working on Atlas may use ./scripts/reload.sh --tag <tag> to build and launch Debug, or ./scripts/reloads.sh --tag <tag> for staging. For coordinated builds prefer the remote route, including from an SSH client. Both scripts support --no-launch to stage and sign without CLI-shim/socket writes, quits or launches. Their default remains launch. Never run reloadp.sh over another agent's session: it terminates the running production app.

A clean quit preserves agent resume; do not pre-kill the app before a reload. Use QA fresh/resume deliberately for validation.

Driving a Release/staging build over the socket

A Release/staging build launched by reloads.sh binds its own socket in automation mode, so a CLI in another local shell can write to it when pointed at C11_SOCKET_PATH=/tmp/c11-<slug>.sock, where the slug is the tag lowercased with every non-alphanumeric run collapsed to a hyphen (--tag rel-v0.65.2 binds /tmp/c11-rel-v0-65-2.sock; the script prints the exact path at launch). The script also clears the launching panel's inherited c11 identity before opening the app. For socket-level validation during development, a tagged Debug build uses the same externally reachable automation mode via C11_SOCKET=/tmp/c11-debug-<tag>.sock.

QA / automation launch (suppress the startup dialogs)

When a change needs the operator to see or try it, launch the tagged build yourself (./scripts/launch-tagged-automation.sh <tag> --qa fresh), seed it with whatever the check needs through its socket, and report that it is open. Don't ask permission to launch a tagged build.

A normal launch can present three blocking dialogs before the GUI is usable: the Agent Skills install/update sheet, the "Resume previous session?" picker, and, when notifications are already off, the "Turn on notifications for c11" sheet. For automated QA they're just modals in the way. The C11_QA_LAUNCH env var suppresses all three and makes the resume decision deterministic:

C11_QA_LAUNCHSkill sheetResume pickerNotifications-off sheetSession
fresh (or any non-resume value)suppressedsuppressedsuppressedclean slate, no restore
resumesuppressedsuppressedsuppressedsilently restores the prior session
unset / emptymay showmay showmay show when notifications are offnormal interactive launch

It's read fresh each launch and never persisted, so it can't leak into a later non-QA run. Default is fresh — automation usually wants a known-empty start; resume is the deliberate opt-in.

When notification permission is denied, QA mode also suppresses the Settings page's explicit enable and send test prompts for the duration of that launch; they share the notification-settings prompt gate.

QA launches also skip creating the first-run welcome workspace.

bash
# Tagged automation launcher — dedicated flag:
./scripts/launch-tagged-automation.sh <tag> --qa # fresh (clean slate)
./scripts/launch-tagged-automation.sh <tag> --qa resume # restore prior session
# Any other launch path — set the env var directly:
C11_QA_LAUNCH=fresh open "/path/to/c11 DEV <tag>.app"
# Release/staging build — QA mode is passed through to the app:
C11_QA_LAUNCH=fresh ./scripts/reloads.sh --tag <tag>
C11_QA_LAUNCH=resume ./scripts/reloads.sh --tag <tag>

The launcher unsets any inherited C11_QA_LAUNCH and only sets it when --qa is passed, so a stray value in your shell can't silently flip a normal run into QA mode.

Rebuilding GhosttyKit

When rebuilding GhosttyKit.xcframework, always use Release optimizations:

bash
# On Atlas only, coordinated with its build slots:
cd ghostty && ../scripts/with-build-lock.sh zig build -Demit-xcframework=true -Dxcframework-target=universal -Doptimize=ReleaseFast

Reporting a tagged reload in chat

When reporting a tagged reload result to the user, use the format for your agent type.

Claude Code (markdown link, cmd+clickable):

markdown
=======================================================
[c11 DEV <tag-name>.app](file:///Users/<user>/Library/Developer/Xcode/DerivedData/c11-<tag-name>/Build/Products/Debug/c11%20DEV%20<tag-name>.app)
=======================================================

Codex (plain text):

=======================================================
[<tag-name>: file:///Users/<user>/Library/Developer/Xcode/DerivedData/c11-<tag-name>/Build/Products/Debug/c11%20DEV%20<tag-name>.app](file:///Users/<user>/Library/Developer/Xcode/DerivedData/c11-<tag-name>/Build/Products/Debug/c11%20DEV%20<tag-name>.app)
=======================================================

Never use /tmp/c11-<tag>/... app links in chat output. If the expected DerivedData path is missing, resolve the real .app path and report that file:// URL.

Tag hygiene

Before launching a new tagged run, clean up any older tags you started in this session (quit the old tagged app, remove its /tmp socket / derived data).

In a fleet, one tag per seat, reused on every `remote-build.sh` call, named for the seat (md-358), never a tag per attempt. The per-tag cache on Atlas is what makes the second build take seconds: measured 2026-10-08, a debug build is 15 s unchanged to about 5 min on a fresh tag, a narrowed test slice 15 s to 3 min, the landing gate (compile plus c11LogicTests) 5 to 10 min. Each tag costs about 4.7 GB on Atlas; seven seats with per-attempt tags filled the volume. Prune a seat's tag when its ticket completes (scripts/prune-tags.sh --yes on Atlas, or remove ~/c11-builds/<tag>). A build loop slower than these numbers is a problem to raise, not a cost to absorb.

Prune stale tags periodically. Each reload.sh --tag leaves ~3.5G behind in DerivedData and /tmp that nothing auto-cleans — across many iterations this consumes hundreds of GB:

bash
./scripts/prune-tags.sh # dry run
./scripts/prune-tags.sh --yes # actually delete
./scripts/prune-tags.sh --keep <tag> # protect an additional tag

Running tags are auto-protected. A weekly launchd job (scripts/launchd/com.stage11.c11-prune-tags.plist) runs --yes automatically; reach for manual prune when you want space back sooner.

Debug event log

All debug events (keys, mouse, focus, splits, panels) go to a unified log in DEBUG builds:

bash
tail -f "$(cat /tmp/c11-last-debug-log-path 2>/dev/null || echo /tmp/c11-debug.log)"
  • Untagged Debug app: /tmp/c11-debug.log
  • Tagged Debug app (reload.sh --tag <tag>): /tmp/c11-debug-<tag>.log
  • reload.sh writes the current path to /tmp/c11-last-debug-log-path
  • reload.sh writes the selected dev CLI path to /tmp/c11-last-cli-path
  • reload.sh updates /tmp/c11-cli, $HOME/.local/bin/c11-dev, and $HOME/.local/bin/cmux-dev (compat alias) to that CLI

Adding a log call

The dlog("message") free function lives in vendor/bonsplit/Sources/Bonsplit/Public/DebugEventLog.swift. The whole file is #if DEBUG, so every call site must also be wrapped in #if DEBUG / #endif. Existing event names include focus.*, tab.*, pane.*, and divider.*; grep for a nearby category before inventing a new one.

Close confirmation diagnostics

Close routing is recorded in Debug and Release with Logger(subsystem: "com.stage11.c11", category: "close"). dlog is absent from Release, so use this after a close attempt on a tagged Release app:

bash
log show --last 2h --style compact --predicate 'subsystem == "com.stage11.c11" AND category == "close"'

close.request names the route (workspaceConfirm, panelConfirm, areaConfirm, pendingSkip, pendingReveal, pinned, direct). pendingReveal is a repeat X on a panel whose confirm is still armed but hidden behind another tab: that tab is selected so the same card is on screen. A hidden ancestor is not on screen, so Return does not accept the card until the tab is showing again. close.overlay.unmounted names why the card had no host (no_anchor, anchor_window_nil, no_themeFrame, stale_runtime, owner_window_not_visible, no_content_rect). owner_window_not_visible releases the confirm as cancel when the window is hidden, minimized, or missing. no_content_rect is that same release when the window is on screen but has no usable content rect. close.overlay.fallback means the card was mounted on the window that owns the workspace or panel. A SwiftUI confirm card (markdown, empty browser) reports its own visibility, so Return works once it appears. That report cannot override an AppKit host that already reported the same card hidden. A visible workspace card accepts Return even when the terminal keeps first responder.

All versions