molecule-core/docs/edit-history/2026-04-07.md
Hongming Wang d8026347e5 chore: open-source restructure — rename dirs, remove internal files, scrub secrets
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>
2026-04-18 00:24:44 -07:00

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 interface
  • workspace/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 CLIAgentExecutor
  • workspace/adapters/openclaw/ — Real OpenClaw integration: npm install, onboard, gateway start, CLI proxy
  • workspace/adapters/deepagents/, crewai/, autogen/ — Stubs with real dep requirements
  • workspace/main.py refactored: 232-line if/else → 160-line adapter flow
  • workspace/entrypoint.sh — Installs adapter deps (pip install --user) at container startup
  • workspace/requirements.txt stripped 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]}-configs volume per workspace
  • Template files copied via CopyToContainer after container start
  • ensureDefaultConfig() returns map[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:

  • SecretsHandler takes a restartFunc callback wired to WorkspaceHandler.RestartByID
  • RestartByID force-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.sh creates 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.tsx deleted, "settings" removed from PanelTab type
  • 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.tsx and SettingsTab.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 via config.yaml name field. Defaults to ID-based for writes.
  • ListFiles/ReadFile exec 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+stat script (works on both GNU and BusyBox/Alpine)
  • stdcopy.StdCopy for 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-/configs roots

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.yaml via 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

  • --bare flag disables OAuth — changed claude-code preset to use CLAUDE_CODE_OAUTH_TOKEN env var instead of apiKeyHelper
  • Removed --bare from 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/sh to /bin/bash for tab completion and history
  • Removed 30-minute SetReadDeadline/SetWriteDeadline that killed terminal WebSocket connections — sessions now stay open as long as the connection is alive, ending when user types exit or 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 delete
  • ConfigTab: flag set on config save
  • FilesTab: flag set on file save, create, delete, and folder upload
  • WorkspaceNode: 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/restart and 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
  • needsRestart added to all file mutation paths (create, delete, upload — was only on save)
  • needsRestart added to secret delete (was only on add)
  • Extracted restartWorkspace(id) store action — deduplicated restart logic from SidePanel and WorkspaceNode into canvas/src/store/canvas.ts; removed unused api imports from both components
  • Fixed selectedNodeId! non-null assertion in SidePanel restart handler — replaced with proper selectedNodeId && guard
  • Terminal bash fallback now retries at attach level, not create — ContainerExecCreate succeeds even if the binary doesn't exist; the error only surfaces at ContainerExecAttach. 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