Skip to content

feat: add persistent agent host mounts - #20

Merged
bmdavis419 merged 5 commits into
mainfrom
feat/agent-bind-mounts
Jul 11, 2026
Merged

feat: add persistent agent host mounts#20
bmdavis419 merged 5 commits into
mainfrom
feat/agent-bind-mounts

Conversation

@bmdavis419

@bmdavis419 bmdavis419 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add tx9 mount add|list|remove for persistent per-box agent bind mounts
  • restrict external targets to /mnt so portable /data backups never traverse NAS content
  • support read-only mounts and a --require-mountpoint guard for offline network shares
  • preserve mounts across upgrades and recreate only the disposable agent container when mounts change
  • propagate required host GIDs to interactive shells and long-running runuser agent processes
  • document the host/NAS workflow and lifecycle behavior

Why

TX9 only modeled its two named Docker volumes, so the existing Nexus CIFS share at ~/agents could not be attached through the CLI or reapplied after container recreation. A plain Docker bind was also insufficient because the host share is UID/GID 1000 while the TX9 agent is UID/GID 1001; interactive execs and long-running Hermes processes need the supplemental host group.

Impact

Operators can now attach host storage with:

tx9 mount add media-bot "$HOME/agents" /mnt/agents --require-mountpoint

The desired configuration is stored in the per-box state file and reused by tx9 upgrade <box>. Host credentials and the mount itself remain host-managed, and imported portable backups intentionally start without host-local mounts.

Validation

  • live Nexus bind: /home/davis/agents -> /mnt/agents in media-bot
  • read/write probe succeeded from the agent and appeared on the NAS
  • long-running Hermes process verified with supplemental GID 1000
  • tx9 doctor media-bot passed
  • make syntax lint test
  • Go 1.26 container: go vet -buildvcs=false ./...
  • Go 1.26 container: go test -buildvcs=false ./...
  • git diff --check

Note

Add persistent host mount support via tx9 mount CLI commands

  • Adds tx9 mount add/list/remove subcommands in cmd_mount.go to configure host directory bind mounts for agent containers, persisted in per-box env as JSON.
  • Mounts must target paths under /mnt; validation enforces absolute paths, no duplicate targets, and optional --require-mountpoint to reject plain directories.
  • Adds box.RecreateAgent in create.go to replace only the agent container (preserving executor, network, and volumes) when mounts change, with supplemental GID injection for access control.
  • The entrypoint in entrypoint.sh parses TX9_AGENT_MOUNT_GIDS at startup to register host-mount groups into the agent user account.
  • Upgrades in cmd_upgrade.go now preflight persisted mounts before container removal and reapply them after recreation.
  • Risk: RecreateAgent on older images without TX9_AGENT_MOUNT_GIDS support falls back to exec-based group setup, requiring the container to be running.

Macroscope summarized c1448d1.

Greptile Summary

This PR adds persistent host mounts for TX9 agent containers. The main changes are:

  • New tx9 mount add, list, and remove commands for per-box bind mounts.
  • Mount state persisted in the per-box env file and reapplied during upgrades.
  • Mount targets restricted below /mnt so portable /data backups stay isolated.
  • Read-only mount support and --require-mountpoint preflight checks for host/NAS paths.
  • Supplemental host GIDs propagated to agent containers and long-running agent processes.
  • Host mount workflow documented in the README and new guide.

Confidence Score: 5/5

Safe to merge with low risk.

The mount feature is scoped to agent bind mount configuration and preserves existing create, import, upgrade, and runtime behavior. The changed paths validate mount targets, preflight host sources before destructive recreation, and clear host-local state for fresh create/import flows. No blocking correctness or security issues were found.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Generated the validation runner and captured full command transcripts, including command, cwd, stdout, stderr, and exit codes, as part of the proof.
  • Tried to run the Docker probe but the environment did not have Docker installed, so the probe exited with code 127 and an error message 'docker: command not found'.
  • Executed Go, static, and diff checks and all tests completed with exit code 0.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
internal/box/agent_mounts.go Adds persisted mount validation, source preflight, mountpoint checks, Docker bind generation, and supplemental group derivation.
internal/box/create.go Threads agent mounts through create and agent recreation paths, including compatibility handling for older images.
internal/cli/cmd_mount.go Implements mount add, list, and remove commands and recreates only the agent container when mount state changes.
internal/cli/cmd_upgrade.go Loads and preflights persisted agent mounts before upgrade teardown and reapplies them during container recreation.
docker/entrypoint.sh Registers configured supplemental mount GIDs before the agent workload starts.
internal/docker/client.go Adds supplemental group support to Docker container creation.
internal/box/agent_mounts_test.go Covers mount normalization, target restrictions, persistence, permissions, and container spec group propagation.
docs/host-mounts.md Adds operational documentation for adding, listing, removing, and safely using host or NAS mounts.

Reviews (5): Last reviewed commit: "fix: make the mount quiesce check fail c..." | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

Agent host mounts

Layer / File(s) Summary
Mount model and persistence
internal/box/agent_mounts.go, internal/box/agent_mounts_test.go
Defines AgentMount, normalizes and validates paths under /mnt, persists desired mounts in box state, performs preflight checks, and tests persistence and validation behavior.
Mount preparation and container groups
internal/box/agent_mounts.go, internal/box/create.go, internal/docker/client.go, docker/entrypoint.sh, tests/static.sh, internal/box/agent_mounts_test.go
Builds bind mounts, derives supplemental GIDs from host permissions, passes them to Docker, and configures matching groups in the agent container at startup.
Mount commands and lifecycle wiring
internal/cli/cmd_mount.go, internal/cli/dispatch.go, internal/cli/cmd_create.go, internal/cli/cmd_import.go, internal/cli/cmd_upgrade.go
Adds tx9 mount add, list, and remove, recreates agents with configured mounts, clears mounts during create/import, and reapplies validated mounts during upgrades.
Operator documentation
README.md, docs/host-mounts.md, docs/tx9-cli-design.md
Documents mount commands, /mnt target restrictions, persistence, NAS prerequisites, permission handling, and host-side storage responsibilities.

Possibly related PRs

  • davis7dotsh/tx9#13: Adds related CLI command-surface changes used by the new mount command.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately summarizes the main change: persistent agent host mounts.
Description check ✅ Passed The description directly matches the changeset and explains the mount feature, validation, persistence, and lifecycle behavior.

Comment @coderabbitai help to get the list of available commands.

macroscopeapp[bot]

This comment was marked as resolved.

@bmdavis419
bmdavis419 marked this pull request as ready for review July 10, 2026 05:42

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

coderabbitai[bot]

This comment was marked as resolved.

bmdavis419 and others added 2 commits July 10, 2026 14:57
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- RecreateAgent no longer boots a deliberately-stopped box to register
  mount groups: a stopped box briefly running hb-workload's reconcile
  loop could start the Hermes gateway and violate the single-writer
  discipline. Current images register TX9_AGENT_MOUNT_GIDS in their
  entrypoint at boot, so the exec+restart dance now only runs for old
  images (detected by grepping the image's shipped entrypoint) on
  running boxes; old image + stopped box is refused up front, before
  any destructive step. This also removes the groupadd race between
  the CLI exec and the new entrypoint.
- applyAgentMounts skips PrepareRuntime on a quiesced box: hb up clears
  the quiesce marker and wires MCP, which cmd_import deliberately
  avoids on restored boxes (single-writer safety gate).
- mount add/remove no-ops (identical mount re-added, unknown target
  removed) return early instead of destroying and recreating a healthy
  agent container.
- supplementalGroup now models kernel permission-class precedence:
  owner/group classes govern exclusively, so e.g. an agent-owned 0577
  directory no longer passes validation via the other bits, and a
  directory in the agent's primary GID no longer borrows other bits.
- Upgrade runs the mount preflight after the image build, immediately
  before teardown, so a source that goes offline during a long build
  fails while the old containers still exist.
- RecreateAgent failure guidance points at tx9 upgrade (re-running the
  mount command cannot recover once the old container is gone).
- SaveAgentMounts reuses setOrDelete; docs mention remove-path
  preflight blocking.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread internal/cli/cmd_mount.go Outdated
return preparedAgentMount{bind: bind, group: group}, nil
}

func supplementalGroup(info os.FileInfo, readOnly bool) (string, error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium box/agent_mounts.go:210

supplementalGroup rejects valid mount directories on filesystems that use POSIX ACLs. When access for UID 1001 or a supplemental group is granted via an ACL entry but the plain owner/group/other mode bits are restrictive, the function returns a spurious "does not give agent ... access" error, so tx9 mount add fails for a mount the container could actually use. The check only inspects info.Mode().Perm() and Stat_t.{Uid,Gid}, ignoring ACL entries that Linux applies before falling back to mode bits. Consider reading the directory's ACL (e.g. via system.posix_acl_access xattr) and accounting for it before concluding access is denied, or document that ACL-managed paths are unsupported.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @internal/box/agent_mounts.go around line 210:

`supplementalGroup` rejects valid mount directories on filesystems that use POSIX ACLs. When access for UID `1001` or a supplemental group is granted via an ACL entry but the plain `owner/group/other` mode bits are restrictive, the function returns a spurious "does not give agent ... access" error, so `tx9 mount add` fails for a mount the container could actually use. The check only inspects `info.Mode().Perm()` and `Stat_t.{Uid,Gid}`, ignoring ACL entries that Linux applies before falling back to mode bits. Consider reading the directory's ACL (e.g. via `system.posix_acl_access` xattr) and accounting for it before concluding access is denied, or document that ACL-managed paths are unsupported.

Comment thread internal/box/create.go Outdated
- applyAgentMounts now owns persistence: save the desired mount set
  only after RecreateAgent succeeds (a failed recreation leaves the
  old state, so retrying the same command is not short-circuited by
  the idempotent no-op guard) and before PrepareRuntime (so a runtime
  prep failure cannot leave a live mount that the next upgrade would
  silently drop).
- imageConfiguresMountGroups distinguishes grep exit 2 (entrypoint
  missing/unreadable — an inspection failure) from exit 1 (old image).
- docs: note the permission check reads classic mode bits only (POSIX
  ACL grants are rejected) and that mount remove preflight can be
  blocked by a different offline --require-mountpoint source.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
macroscopeapp[bot]

This comment was marked as resolved.

hb is-paused through box.HB collapses "not paused" (exit 1) and a
transient exec failure into the same error, so a replacement agent
that had not finished booting could be misread as active and
PrepareRuntime would unquiesce a deliberately paused box. The new
box.AgentQuiesced maps the exit code inside the container: paused and
active are explicit results, anything else is an error that skips
runtime preparation (mounts are already applied and persisted).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bmdavis419
bmdavis419 merged commit 594b18d into main Jul 11, 2026
4 checks passed
@bmdavis419
bmdavis419 deleted the feat/agent-bind-mounts branch July 11, 2026 00:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant