SecAFS ↔ OpenClaw Integration
This directory makes SecAFS own its OpenClaw integration, so the OpenClaw
checkout can stay pristine upstream (v2026.6.8-alpha.1, 0 local changes)
and OpenClaw upgrades never conflict with SecAFS.
Target architecture (gateway-client form)
┌──────────────────────────────┐
│ SecAFS-first frontend (SPA) │ standalone single-file gateway client
│ primary surface = the FS │
└───────────────┬──────────────┘
│ Gateway WebSocket protocol (operator / ACP role, token auth)
│ calls chat.* + secafs.* + sessions.patch
┌───────────────▼──────────────┐
│ OpenClaw Gateway (PRISTINE) │ upstream v2026.6.8-alpha.1, 0 changes
│ loads secafs-chat as an │
│ EXTERNAL third-party plugin │
└───────────────┬──────────────┘
│ Unix socket JSON-RPC
┌───────────────▼──────────────┐
│ secafs serve api (Rust) │ ../../cli — independent component
│ PostgreSQL backend │
└──────────────────────────────┘
The frontend is a gateway client, not an OpenClaw "channel" (channels are backend messaging adapters, not UIs).
The key that makes OpenClaw 0-change: Path C (cwd redirect via upstream seam)
SecAFS needs the agent's working directory to point into the per-conversation FUSE mount. Path C achieves this using only upstream seams, so no OpenClaw core change is required.
The plugin writes spawnedCwd + spawnedWorkspaceDir + spawnedBy to
all key forms of the session entry (bare main:<uuid> and canonical
agent:<id>:main:<uuid> — the first run migrates bare→canonical without merging
fields, so a single-form write would be lost). The agent run then honors
spawnedCwd for the bash/file cwd and spawnedWorkspaceDir+spawnedBy for the
workspace override.
Mechanism (upstream-only — no core change):
- Create each SecAFS conversation as a
main:<uuid>session. - Patch the cwd/workspace fields onto the session over the gateway:
gateway.call("sessions.patch", { sessionKey: "<sessionKey>", patch: { spawnedBy: "secafs", spawnedCwd: "<FUSE mount path>", spawnedWorkspaceDir: "<FUSE mount path>", }, }) - The agent run honors it via upstream
resolveSessionRuntimeWorkspace→resolveIngressWorkspaceOverrideForSpawnedRun(gated onspawnedBybeing truthy) for the workspace, andresolveSessionRuntimeCwd(read unconditionally) for the cwd. Bash/file tools then run withcwd = <FUSE mount path>.
All pieces are present in upstream v2026.6.8-alpha.1
(src/gateway/sessions-patch.ts handles spawnedWorkspaceDir;
src/gateway/server-methods/agent.ts resolveSessionRuntimeWorkspace).
Constraints:
spawnedWorkspaceDiris only allowed onsubagent:*/acp:*session keys.- It is write-once (cannot be cleared once set) → SecAFS per-conversation
mount path must be deterministic & stable (
mountRoot/<conversationId>). spawnedBymust be set (also patchable).
Net result: agent bash/file tools run with cwd inside the per-conversation FUSE
mount, and files persist to the SecAFS backend. See README.md
to reproduce.
What is in here
| Subdir | Role |
|---|---|
plugin/ |
The OpenClaw plugin (daemon supervision + secafs.* gateway methods + mount/rollback logic). An external plugin. |
frontend/ |
Standalone single-file SecAFS-first gateway client. |
bridge/ |
Frontend↔gateway proxy and local stack runner. |
docs/ |
Rollback plugin doc. |
SecAFS depends only on two stable public contracts — the gateway WebSocket
protocol (incl. sessions.patch) and @openclaw/plugin-sdk — so there is 0
secafs-specific code in the OpenClaw checkout.
Running it end-to-end
See README.md and RUNBOOK.md for setup and the
full bring-up.