feat(spawn): GrokMembrane — local grok CLI as a first-class pinned cell#76
Merged
Conversation
…cell `swarph_shared` added `grok` to VALID_PROVIDERS and `grok-researcher.yaml` was authored referencing a GrokMembrane / _grok_env / _build_grok_argv — but the membrane class was never landed. The import-time MEMBRANES↔ VALID_PROVIDERS lockstep guard correctly fired, which bricked EVERY `swarph spawn` (lab included), not just grok. Add the membrane so the grok CELL path is first-class alongside claude/codex/antigravity: - GrokMembrane(uses_pinned_session=True): reads the R5 pin store like claude and `--resume`s grok's native cwd-keyed session, preserving the cell's continuity + identity + mesh inbox across respawns (a fresh-session-per-spawn would orphan grok's running identity). - _build_grok_argv → `grok --cwd <cwd> [--resume <id>] [--agent <role>] --always-approve` + passthrough. Matches the validated manual launch. - _grok_env: isolated HOME (.grok-cell inside cwd) with the operator's ~/.grok/auth.json symlinked in for $0 OIDC; explicit metered-xAI leak-key pop on top of the canonical scrub. - pre_launch reuses _launch_via_tmux so `swarph spawn grok-researcher` lands the cell in its OWN named tmux session (never a window of another cell's session), matching the claude cell + the cell.yaml tmux_session. Two fixes over the field draft that unblocked this live: - pop MESH_GATEWAY_TOKEN (the per-peer auth CUTOVER): `_TOKEN` is not a scrub suffix, so without the explicit pop the cell rides the operator's shared token instead of its symlinked per-peer file — the mint≠cutover gap. - no `--system-prompt-override`: grok's interactive mode has no such flag (real flags are --prompt-file / -p, single-turn); identity comes from --agent + grok's own durable memory. The draft's flag was a latent crash for any starter-bearing grok cell. Tests: 8 new (lockstep+pinned-membrane, pinned/no-session/sibling-slot argv, always_approve opt-out, env isolation+scrub+token-cutover, auth symlink, dry-run pinned --resume). Full spawn suite 97 passing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t ==) The lockstep guard used strict equality, so shipping GrokMembrane while the installed swarph_shared has NOT yet whitelisted 'grok' crashed the import (MEMBRANES has grok, VALID_PROVIDERS doesn't). My membrane tests masked this: they passed only against a swarph_shared carrying a stray local 'grok' edit; against the released (no-grok) shared the import raised. The guard's real purpose is to prevent a raw KeyError in run_spawn — i.e. every VALID provider must have a membrane (subset), NOT that the two sets are identical. An EXTRA membrane ahead of the shared whitelist is harmless: load_cell still gates which providers are spawnable. Relaxing to subset lets this package release independently of swarph_shared and degrades gracefully (grok cells are rejected cleanly by load_cell as "queued for a future release" until swarph_shared whitelists 'grok', rather than a hard import crash). Test asserts the subset invariant. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GrokMembrane (grok cell spawn path) requires swarph_shared to whitelist 'grok' in VALID_PROVIDERS (released in swarph-shared 0.3.3). Bump the dependency pin so installs/CI resolve a shared that accepts grok cells, and bump swarph-cli to 0.13.0 (new provider membrane = feature). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
darw007d
added a commit
that referenced
this pull request
Jun 16, 2026
… real grok) (#77) * fix(spawn): GrokMembrane 0.13.1 — drop's seat-A blockers, grounded on real grok 0.2.54 Addresses the seat-A review of #76 (which merged + released 0.13.0 under commander expedite ~1min before the verdict landed). Every fix re-derived from the REAL grok 0.2.54 binary (strace + --help + env-symbol probe), not help-text reasoning — the source of the original misses. B2 (auth, BLOCKER): grok reads $HOME/.grok/auth.json; the symlink was placed one level too high at $HOME/auth.json → every fresh cell came up logged-OUT. strace-verified positive control. Now symlink at <HOME>/.grok/auth.json. B3 (genesis-resume, BLOCKER): grok --resume REQUIRES a pre-existing session (claude --session-id MINTS); passing a swarph-minted UUID errored "Session does not exist" on first spawn. Now: grok owns its sessions — --continue when one exists for the cwd, nothing on genesis (grok mints). uses_pinned_session → False; grok moves to the fresh-session dispatch branch. H1 (Windows launch): GrokMembrane.launch was unconditional execve — the exact pane-collapse pattern claude.launch fixed in 0.12.1. Added the win32 subprocess.run / POSIX execve split. H2 (token cutover): stop popping MESH_GATEWAY_TOKEN — the membrane never placed the per-peer token file it promised, so popping muted a fresh cell on the mesh. Match claude/codex/agy (inherit the token); cutover is a separate explicit feature. H3 (scrub vars): _GROK_EXTRA_LEAK_KEYS was partly FICTIONAL (XAI_API_HOST etc aren't real grok vars). Replaced with the real redirect surface incl the sharp GROK_AUTH_PROVIDER_COMMAND / GROK_ASKPASS / GROK_GATEWAY_URL / GROK_OIDC_ISSUER. H4 (sandbox): grok cell was the only sibling both auto-approve AND unconfined. Default --sandbox workspace (real profile; off/workspace/devbox/read-only/ strict), independent of --always-approve; set sandbox: off to disable. M1/M2 (identity): --agent is grok's PROFILE selector (unknown role silently ignored), and grok DOES have --system-prompt-override (earlier claim was wrong). Starter now carried via --system-prompt-override; grok added to the assisted-memory CURRENT_TASK re-injection branch. M3 (symlink robustness): is_symlink + readlink validation replaces a stale/ dangling/foreign link instead of silently keeping it. 10 grok tests rewritten to the corrected design + a Windows launch-split test; full suite 840 passing against the REAL published swarph_shared 0.3.3 (clean venv, no local-grok-edit contamination — the false-green class that masked B1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(spawn): GrokMembrane 0.13.1 r2 — drop's re-review must-fixes BLOCKER — double --system-prompt-override: _build_grok_argv emits it for the starter AND the assisted-memory restore appended a SECOND one for CURRENT_TASK → grok's clap rejects a repeated flag → cell never launches (fires on a grok cell with starter_prompt_path + assisted_memory.enabled, reached on the normal tmux respawn). The M2 fix copied claude's repeated- --append-system-prompt (which concatenates) without grok's reject-on-repeat delta. FIX: CURRENT_TASK now injected via --rules (distinct flag, appends to system prompt); starter keeps --system-prompt-override. + regression test. HIGH — GROK_HOME / GROK_AUTH_PATH unscrubbed: grok honors GROK_HOME OVER $HOME (strace-verified by reviewer), so an inherited one silently bypassed the ENTIRE isolated-HOME scheme (cell reads operator auth + writes sessions back to the shared dir). The enumerated leak-key list missed them. FIX: replace enumeration with DENY-BY-DEFAULT over the whole GROK_*/XAI_* namespace (allowlist empty) — closes the redirect class (GROK_HOME/AUTH_PATH/AUTH, GROK_MANAGED_CONFIG_URL, GROK_OAUTH2_*, *_URL family) instead of whack-a-mole. + namespace-scrub test. MEDIUM — sandbox opt-out doc was a no-op: docstrings said extra.sandbox but the code reads top-level cell.sandbox. Docs → top-level `sandbox: off`. Also corrected the profile comment (devbox is a custom example, not a built-in; real built-ins off/workspace/read-only/strict) and recorded the empirical sandbox finding (workspace keeps the mesh net; strict/read-only mute it). Reviewer empirically confirmed --sandbox workspace permits the cell mesh (Landlock restrict_network=false) so the default stays workspace. Full suite 842 passing in a clean venv on real swarph_shared 0.3.3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(spawn): grok sandbox profile list + delete orphaned leak-key docstring drop's 3 sign-off doc nits (none blocked release): (1) devbox IS a reserved built-in profile — revert the r2 'custom example' comment (her own mis-steer, corrected); (2) delete the orphaned enumerated _GROK_EXTRA_LEAK_KEYS docstring left fused above the deny-by-default block; (3) soften the strict/read-only 'would mute' claim to kernel-dependent (Landlock V4+/full seccomp). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
swarph_sharedaddedgroktoVALID_PROVIDERSandgrok-researcher.yamlwas authored referencing aGrokMembrane/_grok_env/_build_grok_argv— but the membrane class was never landed. The import-timeMEMBRANES↔VALID_PROVIDERSlockstep guard then fired on every import, which bricked everyswarph spawn(lab included), not just grok:This was issue #75's tripwire, now actually fired. Fix = land the membrane so the grok cell path is first-class alongside claude/codex/antigravity.
What
GrokMembrane(uses_pinned_session=True)— reads the R5 pin store like claude and--resumes grok's native cwd-keyed session, preserving the cell's continuity + identity + mesh inbox across respawns. (Fresh-session-per-spawn would orphan grok's running identity — continuity is the whole point of a persistent cell.)_build_grok_argv→grok --cwd <cwd> [--resume <id>] [--agent <role>] --always-approve+ passthrough. Matches the validated manual launch._grok_env— isolatedHOME(.grok-cellinside cwd) with the operator's~/.grok/auth.jsonsymlinked in for $0 OIDC; explicit metered-xAI leak-key pop on top of the canonical billing scrub.pre_launchreuses_launch_via_tmuxsoswarph spawn grok-researcherlands the cell in its OWN named tmux session (never a window of another cell's session), matching the claude cell + thetmux_sessionin cell.yaml.Two fixes over the field draft that unblocked this live
MESH_GATEWAY_TOKEN(the per-peer auth cutover):_TOKENis not a scrub suffix, so without the explicit pop the cell rides the operator's shared token instead of its symlinked per-peer file — the mint≠cutover gap.--system-prompt-override: grok's interactive mode has no such flag (real flags are--prompt-file/-p, single-turn). Identity comes from--agent+ grok's own durable memory. The draft's flag was a latent crash for any starter-bearing grok cell.Verify
Dry-run against the live cell reproduces the validated launch exactly:
always_approveopt-out; env isolation + scrub + token-cutover; auth symlink; dry-run pinned--resume).Out of scope / follow-ups
/tmp/swarph-cli-pr65scratch checkout (currently 0.12.0) onto canonical main after merge — fixes the version skew + scratch-dir fragility.🤖 Generated with Claude Code