Multi-tenant workspace isolation for ava. Currently all users share ~/ava as a single workspace with hardcoded paths. Need per-chat workspaces, layered skills, role-based prompts, and safe tool scoping.
Architecture
Workspace layout:
~/ava/
├── shared/
│ └── skills/ # shared skills (migrated from current ~/ava/skills)
├── names.json # chatId -> friendly name mapping
├── ben/ # Ben's DM workspace (symlink target for chatId dir)
│ ├── downloads/ # telegram file downloads
│ ├── skills/ # user-specific skills
│ ├── projects.md # project brief
│ ├── heartbeat.md # heartbeat checklist (owner only)
│ └── ... # scratch files
├── 33193730 -> ben/ # chatId symlink to friendly name
├── sarah/ # another user's workspace
│ ├── downloads/
│ ├── skills/
│ └── ...
└── group-family/ # group chat workspace
├── downloads/
└── ...
Roles
- Owner (Ben, tgOwnerUserId): full access to all tools, all workspaces, omnirepo read, heartbeat
- Authorized user (others in ALLOWED_USER_IDS): their own workspace, memory tools, web search, sandboxed bash/read_file (restricted to their workspace + shared paths)
- Group chats get shared workspace keyed by group chatId
Phases
Phase 1: Per-chat workspace dirs + migration
- Add
Workspace.hs module with chatWorkspace, ensureWorkspace, resolveWorkspaceName names.json maps chatId -> friendly name (user's first name, lowercased)- Workspace created on first message from a chat
- Update scratchDir in system prompt (2 locations in Bot.hs)
- Update telegram-downloads path
- Update heartbeat.md path
- Migrate existing ~/ava/ files to ~/ava/ben/ (Ben's DM = chatId 33193730)
- Symlink ~/ava/33193730 -> ben/
- Move ~/ava/skills to ~/ava/shared/skills, symlink back for Obsidian compat
Phase 2: Layered skills
- Skill resolution: chat workspace skills -> shared skills -> repo skills
- Thread chatId through to skill resolution
- Update skillDirs in Tools.hs to accept workspace path
Phase 3: Per-user system prompt
- Replace isBenAuthorized (string match on "ben") with isOwner (userId == tgOwnerUserId)
- Owner prompt: full project context, repo access, research instructions
- Authorized user prompt: scoped to their workspace, no repo references
- Group prompt: engagement classifier + group-scoped
- Per-chat projects.md instead of global
Phase 4: Safe tool scoping for non-owner users
- run_bash: default CWD set to user's workspace, blocked dangerous commands (dd, rm -rf /, mkfs, etc.)
- read_file: restricted to user workspace + ~/ava/shared/ + /tmp
- write_file: restricted to user workspace
- Owner keeps unrestricted access
- Dangerous command blocklist: dd, mkfs, fdisk, mount, umount, rm -rf /, chmod -R, chown -R on system dirs
Key decisions
- Workspace key: chatId (matches memory scoping)
- Group chats: shared workspace per group
- names.json: auto-populated from user's first name on first message
- Authorized users get bash but with workspace sandboxing + command blocklist
- Owner is unrestricted
- Obsidian vault stays at ~/ava/shared/skills with symlink from old location
All 4 phases implemented and deployed:
Filesystem migration done: ~/ava/ben/ has Ben's files, ~/ava/shared/skills/ has Obsidian vault, ~/ava/skills symlinks to shared/skills for backwards compat.