Renames: - platform/ → workspace-server/ (Go module path stays as "platform" for external dep compat — will update after plugin module republish) - workspace-template/ → workspace/ Removed (moved to separate repos or deleted): - PLAN.md — internal roadmap (move to private project board) - HANDOFF.md, AGENTS.md — one-time internal session docs - .claude/ — gitignored entirely (local agent config) - infra/cloudflare-worker/ → Molecule-AI/molecule-tenant-proxy - org-templates/molecule-dev/ → standalone template repo - .mcp-eval/ → molecule-mcp-server repo - test-results/ — ephemeral, gitignored Security scrubbing: - Cloudflare account/zone/KV IDs → placeholders - Real EC2 IPs → <EC2_IP> in all docs - CF token prefix, Neon project ID, Fly app names → redacted - Langfuse dev credentials → parameterized - Personal runner username/machine name → generic Community files: - CONTRIBUTING.md — build, test, branch conventions - CODE_OF_CONDUCT.md — Contributor Covenant 2.1 All Dockerfiles, CI workflows, docker-compose, railway.toml, render.yaml, README, CLAUDE.md updated for new directory names. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
7.5 KiB
Edit History — 2026-04-07
Adapter Architecture
Introduced a pluggable adapter system for agent infrastructure providers. Each adapter bridges our A2A protocol to a different agent runtime:
workspace/adapters/base.py— BaseAdapter ABC with setup/create_executor interfaceworkspace/adapters/__init__.py— Auto-discovery registry (scan subdirs for Adapter class)workspace/adapters/langgraph/— Ported from main.py (LangGraph ReAct agent)workspace/adapters/claude_code/— Wraps CLIAgentExecutorworkspace/adapters/openclaw/— Real OpenClaw integration: npm install, onboard, gateway start, CLI proxyworkspace/adapters/deepagents/,crewai/,autogen/— Stubs with real dep requirementsworkspace/main.pyrefactored: 232-line if/else → 160-line adapter flowworkspace/entrypoint.sh— Installs adapter deps (pip install --user) at container startupworkspace/requirements.txtstripped to bare minimum (A2A SDK + HTTP only)workspace/agent.py— Added Groq provider support
Adding a new agent infra: create adapters/<name>/ with adapter.py + requirements.txt + __init__.py exporting Adapter.
Docker Volume Isolation
Workspace configs now live entirely in Docker named volumes, not on the host:
- Provisioner creates
ws-{id[:12]}-configsvolume per workspace - Template files copied via
CopyToContainerafter container start ensureDefaultConfig()returnsmap[string][]byte(in-memory), no host dirs- Files API writes via ephemeral Alpine containers when workspace is offline
- Delete cleans up config volume via
RemoveVolume() - Zero
ws-*dirs created on host filesystem
Auto-Restart on Secret Change
Setting or deleting a workspace secret via POST/DELETE /workspaces/:id/secrets now auto-restarts the workspace so the new env var takes effect immediately:
SecretsHandlertakes arestartFunccallback wired toWorkspaceHandler.RestartByIDRestartByIDforce-stops current container (even if provisioning — last-write-wins), then re-provisions with secrets from DB- No manual restart needed after setting API keys — frictionless UX
- Brief 10s wait if container is still provisioning to ensure Stop() can find it
Templates → Framework Presets
Templates are now agent infrastructure presets, not pre-configured personalities:
- Deleted 6 personality templates + 15 org-* templates + 29 ws-* orphan dirs
- 4 framework presets:
claude-code-default,langgraph,openclaw,deepagents - All non-Claude templates default to
openai:gpt-4.1-mini - Agent roles configured after deployment via Config tab or API
scripts/setup-default-org.shcreates PM + 3 teams via API calls
Settings + Config Tab Merge
Merged the Settings tab (API keys/secrets) into the Config tab:
- Settings tab removed —
SettingsTab.tsxdeleted,"settings"removed fromPanelTabtype - Secrets UI moved into ConfigTab as a collapsible "Secrets & API Keys" section
- Environment section (required/optional env vars from config.yaml) removed — redundant with secrets
- Description field changed from text input to textarea (multi-line)
- Config tab now uses ⚙ icon, appears between Terminal and Files
- Tab order: Details, Activity, Chat, Terminal, Config, Files, Memory, Traces, Events
- Removed "Agent Files" section from DetailsTab (Replace Agent Folder button) — redundant with Files tab upload
- Removed "Agent" section from DetailsTab (model assign/replace/remove) — redundant with Config tab Runtime section
- Removed "Install Skill" section from DetailsTab — redundant with Config tab Skills & Tools section
- Moved "Agent Card" editor from DetailsTab to ConfigTab as collapsible section
- Deleted orphaned
SkillInstaller.tsxandSettingsTab.tsx - DetailsTab now focused: identity (name/role/tier), status/restart, skills (read-only from card), peers, delete
Files API → Container File Explorer
Rewrote the Files API and tab to browse the container's actual filesystem via Docker exec, not just host-side config templates:
resolveConfigDir()helper: checks ID-based dir (ws-{id[:12]}/) → name-based dir → template match viaconfig.yamlname field. Defaults to ID-based for writes.ListFiles/ReadFileexec into running container (find+cat), falling back to host-side when container is offline?root=query param:/configs(default),/home,/workspace,/plugins— validated against allowlist- Portable
find+statscript (works on both GNU and BusyBox/Alpine) stdcopy.StdCopyfor Docker stream demux,io.LimitReader(5MB) to prevent OOM- Container lookup matches terminal handler (provisioner name + full ID + DB name)
- Files tab: root selector dropdown, read-only mode for non-
/configsroots
Structured Config Tab
Redesigned ConfigTab from raw JSON editor to a form-based config.yaml editor:
- Sections: General, Runtime, Skills/Tools, A2A, Delegation, Sandbox, Environment
- Proper inputs: text fields, dropdowns (tier T1-T3, runtime, sandbox backend), checkboxes (streaming, escalate), tag lists (skills, tools, env vars)
- Raw YAML toggle for power users
- Reads/writes
config.yamlvia Files API (not the DB config endpoint) - Custom YAML parser handles flat keys, 1-level objects, 2-level nesting (
env.required: [...])
Claude Code OAuth Fix
--bareflag disables OAuth — changed claude-code preset to useCLAUDE_CODE_OAUTH_TOKENenv var instead ofapiKeyHelper- Removed
--barefrom base_args (keeps--dangerously-skip-permissions,--allowed-tools Bash) - Each workspace is a full agentic agent, not just an LLM provider — hooks, CLAUDE.md discovery, etc. are intentionally enabled
Terminal Fix
- Changed exec shell from
/bin/shto/bin/bashfor tab completion and history - Removed 30-minute
SetReadDeadline/SetWriteDeadlinethat killed terminal WebSocket connections — sessions now stay open as long as the connection is alive, ending when user typesexitor container stops
Needs Restart Banner
Added needsRestart flag to WorkspaceNodeData — when config, secrets, or files change, workspace cards and side panel show a "Restart to apply changes" button:
SettingsTab: flag set on secret add AND deleteConfigTab: flag set on config saveFilesTab: flag set on file save, create, delete, and folder uploadWorkspaceNode: sky-blue restart button on canvas card (hidden while agent has active task)SidePanel: "Config changed — restart to apply" banner with "Restart Now" button- Both buttons trigger
POST /workspaces/:id/restartand clear the flag - Error toast shown on restart failure (not silently swallowed)
Code Review Fixes (Round 3)
- Restart buttons show error toast via
showToast("Restart failed", "error")on failure needsRestartadded to all file mutation paths (create, delete, upload — was only on save)needsRestartadded to secret delete (was only on add)- Extracted
restartWorkspace(id)store action — deduplicated restart logic from SidePanel and WorkspaceNode intocanvas/src/store/canvas.ts; removed unusedapiimports from both components - Fixed
selectedNodeId!non-null assertion in SidePanel restart handler — replaced with properselectedNodeId &&guard - Terminal bash fallback now retries at attach level, not create —
ContainerExecCreatesucceeds even if the binary doesn't exist; the error only surfaces atContainerExecAttach. Loop now creates+attaches for each shell candidate (/bin/bash→/bin/sh) - Updated terminal comment to reference "the WebSocket→stdin bridge loop below" instead of hardcoded line number