From c7531a9fe3dd4b1a86103aa3ce298c5f016d4782 Mon Sep 17 00:00:00 2001 From: yz <43207690+yzim@users.noreply.github.com> Date: Tue, 16 Jun 2026 20:12:12 +0800 Subject: [PATCH 1/4] Add macOS workspace CLI support --- .github/workflows/ci.yml | 4 +- CMakeLists.txt | 3 +- src/cas/platform/macos/main_macos.cpp | 12 +++- src/cas/workspace_cli.cpp | 22 ++++++- tests/system/test_macos_workspace_cli.sh | 74 ++++++++++++++++++++++++ 5 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 tests/system/test_macos_workspace_cli.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 109d6ce..4e957d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -198,4 +198,6 @@ jobs: - name: Build run: cmake --build build -j - name: System tests - run: bash tests/system/test_macos_e2e.sh + run: | + bash tests/system/test_macos_e2e.sh + bash tests/system/test_macos_workspace_cli.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index dfcaa77..cda1dd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -402,7 +402,8 @@ if(APPLE AND AGENTVFS_FUSE_T) add_executable(agentvfs src/cas/platform/macos/main_macos.cpp src/cas/platform/macos/fuse_t_adapter.cpp - src/cas/platform/macos/fuse_t_preflight.cpp) + src/cas/platform/macos/fuse_t_preflight.cpp + src/cas/workspace_cli.cpp) target_include_directories(agentvfs PRIVATE src/cas include ${FUSE_T_INCLUDE_DIRS}) target_compile_options(agentvfs PRIVATE ${FUSE_T_CFLAGS_OTHER}) diff --git a/src/cas/platform/macos/main_macos.cpp b/src/cas/platform/macos/main_macos.cpp index a97e96e..0258491 100644 --- a/src/cas/platform/macos/main_macos.cpp +++ b/src/cas/platform/macos/main_macos.cpp @@ -9,6 +9,7 @@ #include "daemon.h" #include "platform.h" #include "platform/macos/fuse_t_preflight.h" +#include "workspace_cli.h" #include #include @@ -41,8 +42,13 @@ bool parse(int argc, char** argv, Args& out) { if (a == "--source" && need()) out.source = argv[++i]; else if (a == "--mountpoint" && need()) out.mountpoint = argv[++i]; else if (a == "--store" && need()) out.store = argv[++i]; - else if (a == "--sock" && need()) out.control_sock = argv[++i]; + else if ((a == "--sock" || a == "--control-sock") && need()) out.control_sock = argv[++i]; else if (a == "--volume-name" && need()) out.volume_name = argv[++i]; + else if (a == "-o" && need()) { ++i; } + else if (a == "-f") { /* foreground is already the only mode */ } + else if (a.rfind("--telemetry=", 0) == 0) { + /* Workspace CLI can pass Linux telemetry flags; macOS v1 ignores them. */ + } else if (a == "-h" || a == "--help") { usage(); return false; } else { usage(); return false; } } @@ -67,6 +73,10 @@ bool parse(int argc, char** argv, Args& out) { } // namespace int main(int argc, char** argv) { + if (argc >= 2 && std::string(argv[1]) == "workspace") { + return cas::workspace::run_workspace_cli(argc - 1, argv + 1, argv[0]); + } + Args ca; if (!parse(argc, argv, ca)) return 1; diff --git a/src/cas/workspace_cli.cpp b/src/cas/workspace_cli.cpp index eded225..fba4596 100644 --- a/src/cas/workspace_cli.cpp +++ b/src/cas/workspace_cli.cpp @@ -634,6 +634,18 @@ static bool pid_alive(long pid) { } static bool mountpoint_active(const std::string& path) { +#ifdef __APPLE__ + std::string cmd = "mount | grep -F -- "; + cmd += "'"; + for (char c : path) { + if (c == '\'') cmd += "'\\''"; + else cmd.push_back(c); + } + cmd += "'"; + cmd += " >/dev/null"; + int rc = system(cmd.c_str()); + return rc == 0; +#else std::string cmd = "mountpoint -q "; cmd += "'"; for (char c : path) { @@ -643,6 +655,7 @@ static bool mountpoint_active(const std::string& path) { cmd += "'"; int rc = system(cmd.c_str()); return rc == 0; +#endif } static bool path_owned_by_current_user_or_absent(const std::string& path, std::string& error) { @@ -830,6 +843,7 @@ static pid_t spawn_daemon(const std::string& self_path, const std::string& telemetry, bool allow_root, std::string& error) { + (void)telemetry; pid_t pid = fork(); if (pid < 0) { error = "fork: " + std::string(std::strerror(errno)); @@ -849,9 +863,11 @@ static pid_t spawn_daemon(const std::string& self_path, "--mountpoint", paths.mount, "--store", paths.store, "--control-sock", paths.socket, - "--telemetry=" + telemetry, "-f" }; +#ifndef __APPLE__ + args.push_back("--telemetry=" + telemetry); +#endif if (allow_root) { args.push_back("-o"); args.push_back("allow_root"); @@ -870,7 +886,11 @@ static bool unmount_workspace(const std::string& mount_path) { pid_t pid = fork(); if (pid < 0) return false; if (pid == 0) { +#ifdef __APPLE__ + execlp("umount", "umount", "-f", mount_path.c_str(), (char*)nullptr); +#else execlp("fusermount3", "fusermount3", "-u", mount_path.c_str(), (char*)nullptr); +#endif _exit(127); } int status = 0; diff --git a/tests/system/test_macos_workspace_cli.sh b/tests/system/test_macos_workspace_cli.sh new file mode 100644 index 0000000..9665aad --- /dev/null +++ b/tests/system/test_macos_workspace_cli.sh @@ -0,0 +1,74 @@ +#!/bin/bash +set -euo pipefail + +if [[ "$(uname)" != "Darwin" ]]; then + echo "test_workspace_cli: skipping (not macOS)" + exit 0 +fi + +if ! [[ -d /Library/Filesystems/fuse-t.fs ]]; then + brew install --cask macos-fuse-t/cask/fuse-t +fi + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +BIN="$REPO_ROOT/build/agentvfs" +WS_ROOT="$(mktemp -d /tmp/agentvfs-workspace-macos.XXXXXX)" +WS_NAME="macos-workspace-cli" +WS_MNT="$WS_ROOT/$WS_NAME/mount" + +cleanup() { + "$BIN" workspace stop "$WS_NAME" --root "$WS_ROOT" --no-checkpoint >/dev/null 2>&1 || true + umount -f "$WS_MNT" 2>/dev/null || diskutil unmount force "$WS_MNT" 2>/dev/null || true + rm -rf "$WS_ROOT" +} +trap cleanup EXIT + +START_OUT="$("$BIN" workspace start "$WS_NAME" --root "$WS_ROOT")" +echo "$START_OUT" +WS_SOCK="$(grep '^socket=' <<<"$START_OUT" | cut -d= -f2-)" +WS_MNT_FROM_START="$(grep '^mount=' <<<"$START_OUT" | cut -d= -f2-)" +WS_STORE="$(grep '^store=' <<<"$START_OUT" | cut -d= -f2-)" +WS_STATUS="$(grep '^status=' <<<"$START_OUT" | cut -d= -f2-)" +WS_TELEMETRY="$(grep '^telemetry=' <<<"$START_OUT" | cut -d= -f2-)" + +[[ -S "$WS_SOCK" ]] || { echo "FAIL: workspace socket missing"; exit 1; } +[[ "$WS_MNT_FROM_START" == "$WS_MNT" ]] || { echo "FAIL: workspace mount path mismatch"; exit 1; } +[[ -d "$WS_STORE" ]] || { echo "FAIL: workspace store missing"; exit 1; } +[[ "$WS_STATUS" == "started" ]] || { echo "FAIL: workspace status not started"; exit 1; } +[[ "$WS_TELEMETRY" == "auto" || "$WS_TELEMETRY" == "none" ]] || { + echo "FAIL: unexpected workspace telemetry '$WS_TELEMETRY'" + exit 1 +} +mount | grep -q " on $WS_MNT " || { echo "FAIL: workspace mount missing"; exit 1; } + +STATUS_OUT="$("$BIN" workspace status "$WS_NAME" --root "$WS_ROOT")" +grep -q '^status=started$' <<<"$STATUS_OUT" || { echo "FAIL: workspace status command"; exit 1; } +grep -q "^mount=$WS_MNT\$" <<<"$STATUS_OUT" || { echo "FAIL: workspace status mount"; exit 1; } + +LIST_OUT="$("$BIN" workspace list --root "$WS_ROOT")" +grep -q "^$WS_NAME[[:space:]]\\+started[[:space:]]\\+$WS_MNT\$" <<<"$LIST_OUT" || { + echo "FAIL: workspace list missing started session" + exit 1 +} + +[[ "$(cat "$WS_MNT/hello")" == "hello" ]] || { echo "FAIL: workspace bootstrap read"; exit 1; } +echo "v1" > "$WS_MNT/data.txt" +FIRST_CP="$("$BIN" workspace checkpoint "$WS_NAME" baseline --root "$WS_ROOT")" +[[ "$FIRST_CP" =~ ^[0-9a-f]{64}$ ]] || { echo "FAIL: workspace checkpoint"; exit 1; } + +echo "v2" > "$WS_MNT/data.txt" +ROLLBACK_OUT="$("$BIN" workspace rollback "$WS_NAME" baseline --root "$WS_ROOT")" +grep -q '^rolled_back_to=' <<<"$ROLLBACK_OUT" || { echo "FAIL: workspace rollback response"; exit 1; } +[[ "$(cat "$WS_MNT/data.txt")" == "v1" ]] || { echo "FAIL: workspace rollback content"; exit 1; } + +STOP_OUT="$("$BIN" workspace stop "$WS_NAME" --root "$WS_ROOT")" +grep -q '^stopped=true$' <<<"$STOP_OUT" || { echo "FAIL: workspace stop response"; exit 1; } +mount | grep -q " on $WS_MNT " && { echo "FAIL: workspace mount still present after stop"; exit 1; } + +LIST_AFTER_STOP="$("$BIN" workspace list --root "$WS_ROOT")" +grep -q "^$WS_NAME[[:space:]]\\+stopped[[:space:]]\\+$WS_MNT\$" <<<"$LIST_AFTER_STOP" || { + echo "FAIL: workspace list missing stopped session" + exit 1 +} + +echo "PASS test_workspace_cli" From d34d7d19f9913c578298dfc3e0d91bd5aec1e79b Mon Sep 17 00:00:00 2001 From: yz <43207690+yzim@users.noreply.github.com> Date: Tue, 16 Jun 2026 20:21:35 +0800 Subject: [PATCH 2/4] Move macOS workspace CLI test under macos system tests --- .github/workflows/ci.yml | 2 +- .../test_workspace_cli.sh} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename tests/system/{test_macos_workspace_cli.sh => macos/test_workspace_cli.sh} (98%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e957d1..3bdd451 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -200,4 +200,4 @@ jobs: - name: System tests run: | bash tests/system/test_macos_e2e.sh - bash tests/system/test_macos_workspace_cli.sh + bash tests/system/macos/test_workspace_cli.sh diff --git a/tests/system/test_macos_workspace_cli.sh b/tests/system/macos/test_workspace_cli.sh similarity index 98% rename from tests/system/test_macos_workspace_cli.sh rename to tests/system/macos/test_workspace_cli.sh index 9665aad..762e625 100644 --- a/tests/system/test_macos_workspace_cli.sh +++ b/tests/system/macos/test_workspace_cli.sh @@ -10,7 +10,7 @@ if ! [[ -d /Library/Filesystems/fuse-t.fs ]]; then brew install --cask macos-fuse-t/cask/fuse-t fi -REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" BIN="$REPO_ROOT/build/agentvfs" WS_ROOT="$(mktemp -d /tmp/agentvfs-workspace-macos.XXXXXX)" WS_NAME="macos-workspace-cli" From e7af6df7289e6736da63bc87b6548327d07ed62d Mon Sep 17 00:00:00 2001 From: yz <43207690+yzim@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:25:12 +0800 Subject: [PATCH 3/4] macos: fix workspace init and test cli script --- src/cas/workspace_cli.cpp | 7 +- tests/system/macos/test_workspace_cli.sh | 135 +++++++++++++++++------ 2 files changed, 106 insertions(+), 36 deletions(-) diff --git a/src/cas/workspace_cli.cpp b/src/cas/workspace_cli.cpp index fba4596..3aab1e4 100644 --- a/src/cas/workspace_cli.cpp +++ b/src/cas/workspace_cli.cpp @@ -1093,7 +1093,6 @@ static int command_init(const ParsedCommon& opts) { return 1; } - std::string cmd = "cp -a --reflink=auto -- "; auto shell_quote = [](const std::string& s) { std::string out = "'"; for (char c : s) { @@ -1103,6 +1102,12 @@ static int command_init(const ParsedCommon& opts) { out += "'"; return out; }; + std::string cmd; +#ifdef __APPLE__ + cmd = "cp -a "; +#else + cmd = "cp -a --reflink=auto -- "; +#endif cmd += shell_quote(opts.from_dir + "/."); cmd += " "; cmd += shell_quote(paths.source); diff --git a/tests/system/macos/test_workspace_cli.sh b/tests/system/macos/test_workspace_cli.sh index 762e625..93508c7 100644 --- a/tests/system/macos/test_workspace_cli.sh +++ b/tests/system/macos/test_workspace_cli.sh @@ -1,8 +1,10 @@ #!/bin/bash set -euo pipefail +TEST_NAME="test_workspace_cli" + if [[ "$(uname)" != "Darwin" ]]; then - echo "test_workspace_cli: skipping (not macOS)" + echo "$TEST_NAME: skipping (not macOS)" exit 0 fi @@ -10,11 +12,71 @@ if ! [[ -d /Library/Filesystems/fuse-t.fs ]]; then brew install --cask macos-fuse-t/cask/fuse-t fi -REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" -BIN="$REPO_ROOT/build/agentvfs" +REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" +BIN_DIR="${BIN_DIR:-$REPO_ROOT/build}" +BIN="$BIN_DIR/agentvfs" WS_ROOT="$(mktemp -d /tmp/agentvfs-workspace-macos.XXXXXX)" +# macOS /tmp is a symlink to /private/tmp; resolve once so mount(8) +# output and the paths reported by the daemon stay comparable. +WS_ROOT="$(cd "$WS_ROOT" && pwd -P)" WS_NAME="macos-workspace-cli" WS_MNT="$WS_ROOT/$WS_NAME/mount" +SEED_DIR="$WS_ROOT/seed" + +fail() { + echo "FAIL: $*" + exit 1 +} + +note() { + echo + echo "== $* ==" +} + +get_kv() { + local key="$1" + local text="${2:-}" + sed -n "s/^${key}=//p" <<<"$text" +} + +expect_eq() { + local actual="$1" + local expected="$2" + local label="$3" + [[ "$actual" == "$expected" ]] || fail "$label: expected '$expected', got '$actual'" +} + +expect_hex_hash() { + local value="$1" + local label="$2" + [[ "$value" =~ ^[0-9a-f]{64}$ ]] || fail "$label: expected 64-char hex hash, got '$value'" +} + +expect_started_listing() { + local list_out="$1" + if ! grep -q "^$WS_NAME[[:space:]]\\+started[[:space:]]\\+$WS_MNT\$" <<<"$list_out"; then + fail "workspace list missing started session" + fi +} + +expect_stopped_listing() { + local list_out="$1" + if ! grep -q "^$WS_NAME[[:space:]]\\+stopped[[:space:]]\\+$WS_MNT\$" <<<"$list_out"; then + fail "workspace list missing stopped session" + fi +} + +expect_mount_present() { + if ! mount | grep -q " on $WS_MNT "; then + fail "workspace mount missing" + fi +} + +expect_mount_absent() { + if mount | grep -q " on $WS_MNT "; then + fail "workspace mount still present after stop" + fi +} cleanup() { "$BIN" workspace stop "$WS_NAME" --root "$WS_ROOT" --no-checkpoint >/dev/null 2>&1 || true @@ -23,52 +85,55 @@ cleanup() { } trap cleanup EXIT +note "Initialize Workspace" +mkdir -p "$SEED_DIR" +echo "hello" > "$SEED_DIR/hello" +"$BIN" workspace init "$WS_NAME" --from "$SEED_DIR" --root "$WS_ROOT" >/dev/null + +note "Start Workspace" START_OUT="$("$BIN" workspace start "$WS_NAME" --root "$WS_ROOT")" echo "$START_OUT" -WS_SOCK="$(grep '^socket=' <<<"$START_OUT" | cut -d= -f2-)" -WS_MNT_FROM_START="$(grep '^mount=' <<<"$START_OUT" | cut -d= -f2-)" -WS_STORE="$(grep '^store=' <<<"$START_OUT" | cut -d= -f2-)" -WS_STATUS="$(grep '^status=' <<<"$START_OUT" | cut -d= -f2-)" -WS_TELEMETRY="$(grep '^telemetry=' <<<"$START_OUT" | cut -d= -f2-)" - -[[ -S "$WS_SOCK" ]] || { echo "FAIL: workspace socket missing"; exit 1; } -[[ "$WS_MNT_FROM_START" == "$WS_MNT" ]] || { echo "FAIL: workspace mount path mismatch"; exit 1; } -[[ -d "$WS_STORE" ]] || { echo "FAIL: workspace store missing"; exit 1; } -[[ "$WS_STATUS" == "started" ]] || { echo "FAIL: workspace status not started"; exit 1; } -[[ "$WS_TELEMETRY" == "auto" || "$WS_TELEMETRY" == "none" ]] || { - echo "FAIL: unexpected workspace telemetry '$WS_TELEMETRY'" - exit 1 -} -mount | grep -q " on $WS_MNT " || { echo "FAIL: workspace mount missing"; exit 1; } +WS_SOCK="$(get_kv socket "$START_OUT")" +WS_MNT_FROM_START="$(get_kv mount "$START_OUT")" +WS_STORE="$(get_kv store "$START_OUT")" +WS_STATUS="$(get_kv status "$START_OUT")" +WS_TELEMETRY="$(get_kv telemetry "$START_OUT")" +[[ -S "$WS_SOCK" ]] || fail "workspace socket missing: $WS_SOCK" +[[ -d "$WS_STORE" ]] || fail "workspace store missing: $WS_STORE" +expect_eq "$WS_MNT_FROM_START" "$WS_MNT" "workspace mount path mismatch" +expect_eq "$WS_STATUS" "started" "workspace status" +[[ "$WS_TELEMETRY" == "auto" || "$WS_TELEMETRY" == "none" ]] \ + || fail "unexpected workspace telemetry '$WS_TELEMETRY'" +expect_mount_present + +note "Inspect Workspace" STATUS_OUT="$("$BIN" workspace status "$WS_NAME" --root "$WS_ROOT")" -grep -q '^status=started$' <<<"$STATUS_OUT" || { echo "FAIL: workspace status command"; exit 1; } -grep -q "^mount=$WS_MNT\$" <<<"$STATUS_OUT" || { echo "FAIL: workspace status mount"; exit 1; } +expect_eq "$(get_kv status "$STATUS_OUT")" "started" "workspace status command" +expect_eq "$(get_kv mount "$STATUS_OUT")" "$WS_MNT" "workspace status mount" LIST_OUT="$("$BIN" workspace list --root "$WS_ROOT")" -grep -q "^$WS_NAME[[:space:]]\\+started[[:space:]]\\+$WS_MNT\$" <<<"$LIST_OUT" || { - echo "FAIL: workspace list missing started session" - exit 1 -} +expect_started_listing "$LIST_OUT" -[[ "$(cat "$WS_MNT/hello")" == "hello" ]] || { echo "FAIL: workspace bootstrap read"; exit 1; } +note "Checkpoint And Rollback" +expect_eq "$(cat "$WS_MNT/hello")" "hello" "workspace bootstrap read" echo "v1" > "$WS_MNT/data.txt" FIRST_CP="$("$BIN" workspace checkpoint "$WS_NAME" baseline --root "$WS_ROOT")" -[[ "$FIRST_CP" =~ ^[0-9a-f]{64}$ ]] || { echo "FAIL: workspace checkpoint"; exit 1; } +expect_hex_hash "$FIRST_CP" "workspace checkpoint" echo "v2" > "$WS_MNT/data.txt" ROLLBACK_OUT="$("$BIN" workspace rollback "$WS_NAME" baseline --root "$WS_ROOT")" -grep -q '^rolled_back_to=' <<<"$ROLLBACK_OUT" || { echo "FAIL: workspace rollback response"; exit 1; } -[[ "$(cat "$WS_MNT/data.txt")" == "v1" ]] || { echo "FAIL: workspace rollback content"; exit 1; } +expect_hex_hash "$ROLLBACK_OUT" "workspace rollback response" +expect_eq "$(cat "$WS_MNT/data.txt")" "v1" "workspace rollback content" +note "Stop Workspace" STOP_OUT="$("$BIN" workspace stop "$WS_NAME" --root "$WS_ROOT")" -grep -q '^stopped=true$' <<<"$STOP_OUT" || { echo "FAIL: workspace stop response"; exit 1; } -mount | grep -q " on $WS_MNT " && { echo "FAIL: workspace mount still present after stop"; exit 1; } +echo "$STOP_OUT" +expect_eq "$(get_kv status "$STOP_OUT")" "stopped" "workspace stop response" +expect_mount_absent LIST_AFTER_STOP="$("$BIN" workspace list --root "$WS_ROOT")" -grep -q "^$WS_NAME[[:space:]]\\+stopped[[:space:]]\\+$WS_MNT\$" <<<"$LIST_AFTER_STOP" || { - echo "FAIL: workspace list missing stopped session" - exit 1 -} +expect_stopped_listing "$LIST_AFTER_STOP" -echo "PASS test_workspace_cli" +echo +echo "PASS $TEST_NAME" From 118604a9ed17fae52c05687a867e16abfdbd04a9 Mon Sep 17 00:00:00 2001 From: yz <43207690+yzim@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:34:19 +0800 Subject: [PATCH 4/4] docs: note macos workspace support --- README.md | 5 +++-- docs/roadmap.md | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c85edde..896f36a 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,8 @@ agentvfs-ctl.exe --sock \\.\pipe\agentvfs- checkpoint baseline ## Driving the daemon directly ```bash -agentvfs workspace start my-task --from /path/to/project +agentvfs workspace init my-task --from /path/to/project +agentvfs workspace start my-task agentvfs workspace checkpoint my-task before-refactor # ... agent makes changes ... agentvfs workspace rollback my-task before-refactor @@ -77,7 +78,7 @@ FUSE / WinFsp / fuse-t ──► WorkingTree (in-memory, COW) ──► Obje | Content-Addressed Store | ✅ | ✅ | ✅ | | Per-Agent Branches | ✅ | Coming soon | Coming soon | | Pluggable Telemetry | ✅ | Coming soon | Coming soon | -| `agentvfs workspace` CLI | ✅ | Coming soon | Coming soon | +| `agentvfs workspace` CLI | ✅ | ✅ | Coming soon | ## Build from source diff --git a/docs/roadmap.md b/docs/roadmap.md index 808d3b5..1b01775 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -102,7 +102,7 @@ Three parts, built on L0 (bounded memory / GC) and L1 (API + routing token). 2a ### 2c — Platform parity track *(parallel breadth)* - **macOS/Windows branches.** Wire L1.2 token-based routing into the `fuse_t_adapter` and `winfsp_adapter` (cgroup v2 is unavailable off-Linux — the token *is* the routing mechanism there). - **Telemetry off-Linux.** Today Linux-only (eBPF / fanotify / ptrace / `LD_PRELOAD`). Scope a feasible reduced backend — FUSE/WinFsp op-level capture, or FSEvents/ETW — and set expectations that off-Linux telemetry is weaker by nature. -- **`agentvfs workspace` CLI off-Linux.** Port `workspace_cli` state management + control plane (the Windows named-pipe channel already exists; macOS uses the AF_UNIX path). +- **`agentvfs workspace` parity off-Linux.** macOS now has `workspace_cli` state management + control plane; finish the remaining parity work for Windows and close the feature gaps that still differ from Linux. **Parallelism note:** 2a/2b are the dependency-spine continuation; 2c is genuinely independent once the token lands, so a second contributor can drive parity while the first drives scale — but 2c *cannot start its branch work before L1.2*. That is the one hard cross-track edge.