molecule-core/docs/edit-history/2026-04-04.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

14 KiB

Edit History — 2026-04-04

Summary

Major session covering file explorer, template import/replace, bundle drop zone, team expansion, canvas toolbar, viewport persistence, hot-reload, WebSocket events, Docker Compose, plugin system (ECC + Superpowers), production hardening (graceful shutdown, rate limiting, AES-256 secrets), human-in-the-loop approval chain, HMA memory (L1/L2/L3), coordinator pattern, code sandbox, memory consolidation, Langfuse trace preview, search dialog, toast notifications, OpenRouter provider, skill installer, ClawHub integration, team zoom-in, connection breakage visualization, and 15 code review rounds. UI E2E tested via browser: 95/104 plan items done (91%).

File Explorer Tab (FilesTab)

  • Tree view with collapsible directories and file type icons
  • Inline code editor with monospace font, Ctrl/Cmd+S save, Tab inserts spaces
  • Create new files with path input, delete with confirmation
  • Platform endpoints: GET/PUT/DELETE /workspaces/:id/files/*path for individual files, GET /workspaces/:id/files for tree listing
  • Path traversal protection via validateRelPath()
  • useMemo for tree building, ref-based cursor positioning

Agent Import & Replace via UI

  • Import Agent Folder: button in template palette, uses webkitdirectory file picker, uploads to POST /templates/import
  • Replace Agent Files: button in DetailsTab, uploads folder to PUT /workspaces/:id/files, auto-restarts, confirmation before destructive replace
  • Auto-generates config.yaml detecting prompt_files and skills from uploaded files
  • CLI script import-agent.sh also available

Canvas Visual Overhaul

  • Toolbar: fixed top-center bar with Molecule AI logo, live status counts, workspace total
  • WorkspaceNode redesign: status gradient bar, team badge (child count), pill-shaped handles, 4 skill badges
  • SidePanel: slide-in animation, tab icons, UUID footer, darker backdrop blur
  • ContextMenu: status dot header, expand/collapse team actions
  • Animations: node fade-in on mount, panel slide-in from right
  • Scrollbar: custom thin zinc styling

Bundle & Team Features

  • BundleDropZone: drag .bundle.json onto canvas to import, visual overlay, progress spinner, toast
  • Context menu: Export Bundle downloads file, Duplicate exports+re-imports
  • Team expansion APIs: POST /workspaces/:id/expand creates sub-workspaces from config, POST /collapse removes them
  • Context menu: Expand to Team / Collapse Team
  • bundle-compile.sh: compiles all templates to .bundle.json artifacts

Runtime & Infrastructure

  • Viewport persistence: GET/PUT /canvas/viewport, debounced save on pan/zoom, restore on load
  • Hot-reload watcher (watcher.py): polls config dir for hash changes, debounced reload
  • WebSocket subscriber (events.py): connects to platform /ws, triggers prompt rebuild on peer events
  • Langfuse auto-injection: detects env vars, creates CallbackHandler
  • Cross-workspace trace linking: delegation tool passes parent_task_id in A2A metadata
  • Workspace forwarding: discovery follows forwarded_to chain (max 5 hops)
  • Full Docker Compose: 6 services with health checks on shared molecule-monorepo-net
  • Auto-discover configs dir: findConfigsDir() searches parent directories

Bug Fixes

  • A2A executor: switched from astream to ainvoke for reliable responses across models
  • Chat response parsing: handle result.parts[] with kind field (a2a-sdk v0.3 format)
  • Terminal handler: tries multiple container name patterns (ws-{id}, workspace-name)
  • A2A proxy: injects messageId into params.message (required by a2a-sdk)
  • Path traversal: validateRelPath() blocks ../ and absolute paths in file uploads
  • Shared utilities: normalizeName(), writeFiles(), generateDefaultConfig() extracted to eliminate 3x duplication

ECC & Superpowers Integration

Workspace Templates

  • ecc-coding-agent: Everything Claude Code with 10 curated skills (coding-standards, tdd-workflow, e2e-testing, security-review, api-design, backend-patterns, frontend-patterns, deep-research, shell-exec), CLAUDE.md + AGENTS.md + WORKING-CONTEXT.md as prompt files
  • superpowers-agent: obra/superpowers with 16 skills (14 from superpowers + shell-exec + file-ops), code-reviewer agent, 3 commands (brainstorm, write-plan, execute-plan)
  • import-ecc.sh: CLI script to import individual or all 156 ECC skills as templates

Plugin System (integrated into every workspace)

  • workspace/plugins.py: scans /plugins/ for installed plugins, loads rules/*.md, prompt fragments, and skills directories
  • plugins/ecc/: ECC guardrails rules + 5 shared skills (coding-standards, tdd-workflow, security-review, api-design, deep-research) + AGENTS.md prompt fragment
  • plugins/superpowers/: 5 shared skills (test-driven-development, systematic-debugging, writing-plans, executing-plans, verification-before-completion)
  • Every workspace agent auto-inherits plugin rules + skills (deduplicated by ID, workspace skills take priority)
  • Provisioner mounts /plugins:ro into every container
  • docker-compose.yml mounts ./plugins for platform

Prompt Integration

  • prompt.py: accepts plugin_rules and plugin_prompts parameters
  • Rules injected as "Platform Rules" section
  • Prompt fragments injected as "Platform Guidelines" section
  • Plugin skills merged after workspace skills (deduplicated)

Code Review Fixes (Rounds 7-9)

  • Round 7: path traversal fix, dedup normalization/config generation, file upload limit (200), delete confirmation for replace
  • Round 8: remove unused getLang, success timer ref cleanup, delete confirmation for files, textarea ref for Tab key, useMemo for tree
  • Round 9: deduplicate plugin skills by ID (workspace takes priority), remove arbitrary 50-char prompt size filter
  • Round 10: rate limiter goroutine leak (accept context), crypto key warning on invalid, .env.example sync
  • Round 11: unused useReactFlow import, search state lifted to store, synthetic event replaced
  • Round 12: remove unnecessary comment in Toolbar
  • Round 13: N+1 approval polling → single endpoint, auto-expiry, TEAM scope query fix, configurable poll env vars
  • Round 14: sandbox shell injection (mount temp file), consolidation fallback path, SandboxConfig wired to load_config
  • Round 15: executor returns only AI messages (skip tool results), locale middleware narrowed to known codes
  • Round 16: MCP server network error handling + startup platform validation
  • Round 17: parallel file downloads in Files tab

MCP Server

  • mcp-server/ — TypeScript MCP server using @modelcontextprotocol/sdk, stdio transport
  • 20 tools: list/create/get/delete/restart workspaces, chat with agent, assign model, set/list secrets, list/read/write/delete files, commit/search memory, list templates, expand/collapse team, list/decide approvals
  • Works with Claude Code, Cursor, Codex, OpenCode — any MCP client
  • Added to .mcp.json for immediate Claude Code integration
  • Startup validates platform connectivity, logs warning with fix instructions
  • Network errors return friendly messages instead of crashing

Embedded Sub-Workspaces

  • WorkspaceNode redesign: parent nodes expand in size (320-450px) when they have children. Children render as embedded mini-cards in a "Team Members" 2-column grid inside the parent node.
  • No separate nodes/edges: child nodes set hidden: true in React Flow, no parent→child edges created. Children exist in store for data access but render visually inside the parent.
  • Click child chip: selects the child and opens its side panel.
  • Toolbar: shows "4 workspaces + 1 sub" to distinguish root vs embedded counts.
  • Fix: children selector memoized with useMemo to prevent infinite render loop (Zustand .filter() creates new array reference every render).

Files Tab Enhancements

  • Upload: folder picker to upload multiple files into workspace
  • Export: download all workspace files as JSON bundle (parallel via Promise.allSettled)
  • Clear: delete all files with red confirmation dialog
  • Download: ↓ button for currently open file
  • Folder delete: ✕ on hover for directories, confirmation shows "and all its contents", platform uses os.RemoveAll

Memory Consolidation Loop (Phase 9, 15g)

  • consolidation.py: runs every 5min, checks if LOCAL memories exceed threshold (10), uses agent to summarize into dense TEAM knowledge, deletes originals. Falls back to concatenation if agent unavailable/rate-limited with error-level logging.
  • Configurable via CONSOLIDATION_INTERVAL and CONSOLIDATION_THRESHOLD env vars.

Code Sandbox (Phase 12, 18a-18b, 18e)

  • tools/sandbox.py: run_code(code, language) tool
  • Subprocess backend: direct execution with timeout (default for Tier 1-2)
  • Docker backend: code written to temp file, mounted read-only at /sandbox/code.py (no shell injection). Container runs with --network none --memory 256m --cpus 0.5 --read-only --tmpfs /tmp:size=32m.
  • Supports python, javascript, shell/bash
  • SandboxConfig dataclass in config.py, loaded from sandbox: in config.yaml
  • Configurable via SANDBOX_BACKEND, SANDBOX_TIMEOUT, SANDBOX_MEMORY_LIMIT env vars

Connection Breakage Visualization (Phase 11, 17p)

  • Edges in canvas styled by child workspace status:
    • Online: animated, dark zinc stroke
    • Degraded: amber stroke, thicker (2px), no animation
    • Offline/Failed: dashed gray stroke, no animation

Interactive Canvas Improvements

  • Search dialog (⌘K): SearchDialog.tsx — fuzzy search across name/role/status, click to select + open Details, state managed in Zustand store
  • Toast notifications: Toaster.tsx — global showToast() callable anywhere, success/error/info variants, auto-dismiss 4s, slide-up animation. Context menu actions show toasts.
  • Empty state: EmptyState.tsx — shown when no workspaces, visual guide with keyboard shortcuts
  • Keyboard shortcuts: Escape cascades (close context menu → close panel), ⌘K opens search
  • Toolbar search button: magnifying glass + ⌘K badge, calls store.setSearchOpen(true)

Human-in-the-Loop Approval Chain (Phase 8, 14a-14f)

  • Migration 007_approvals.sql: approval_requests table with status (pending/approved/denied/escalated)
  • handlers/approvals.go: POST create, GET list, GET /approvals/pending (single query for all), POST decide. Auto-expires pending requests older than 10 minutes.
  • Platform events: APPROVAL_REQUESTED, APPROVAL_ESCALATED (to parent), APPROVAL_APPROVED, APPROVAL_DENIED
  • tools/approval.py: request_approval(action, reason) tool. Pauses agent, polls for decision (configurable via APPROVAL_POLL_INTERVAL/APPROVAL_TIMEOUT env vars).
  • ApprovalBanner.tsx: polls single /approvals/pending endpoint, shows approve/deny cards with workspace name, slide-in-from-top animation, toast feedback.
  • Agent decides when to call request_approval based on system prompt guidelines.

Hierarchical Memory Architecture (Phase 9, 15a-15f)

  • Migration 008_agent_memories.sql: agent_memories table with pgvector extension, scope (LOCAL/TEAM/GLOBAL)
  • handlers/memories.go: POST commit, GET search (with text ILIKE), DELETE
    • LOCAL: workspace_id only
    • TEAM: parent + siblings via parent_id join, CanCommunicate check, excludes removed workspaces
    • GLOBAL: readable by all, write restricted to root (no parent_id)
  • tools/memory.py: commit_memory(content, scope) and search_memory(query, scope) tools
  • Every agent now has 4 built-in tools: delegate, approve, commit_memory, search_memory

OpenRouter Provider + E2E Testing

  • agent.py: added OpenRouter as 5th LLM provider (uses langchain-openai with custom base URL)
  • requirements.txt: added langchain-openai>=0.3.0
  • Supported providers: anthropic, openai, openrouter, google_genai, ollama
  • E2E test results: 40/41 pass (SEO + Echo agents via OpenRouter claude-3.5-haiku). Tests: workspace CRUD, agent management, registry/discovery, heartbeat/status, A2A chat, secrets, HMA memory, approvals, config, templates, viewport, events, files, cascade delete.

Langfuse Trace Preview (Phase 10, 16c)

  • handlers/traces.go: GET /workspaces/:id/traces proxies to Langfuse API
  • TracesTab.tsx: expandable trace list with input/output, latency, tokens, cost
  • SidePanel: 9 tabs now (added Traces)

Team Zoom-in + Skill Installer + ClawHub

  • Canvas zoom-in (13g): double-click team node → fitBounds animation
  • SkillInstaller (17n): type skill name → creates SKILL.md in workspace files
  • ClawHub (17q): "Install from ClawHub" button sends install command via A2A

Coordinator Pattern (Phase 7, 13c)

  • workspace/coordinator.py: auto-detects children on startup, injects team description into prompt, adds route_task_to_team tool
  • When workspace has children → becomes coordinator that routes A2A messages to best-suited child based on capabilities
  • Coordination rules injected: analyze task, choose member, delegate, aggregate, fallback

Production Hardening (Phase 14)

Graceful Shutdown (20e)

  • main.go: signal handler for SIGINT/SIGTERM, context cancellation stops liveness monitor + Redis subscriber, HTTP server drains connections (30s timeout), WebSocket hub Close() disconnects all clients
  • ws/hub.go: added Close() method

Rate Limiting (20d)

  • middleware/ratelimit.go: token bucket rate limiter (100 req/min/IP), accepts context for clean shutdown, auto-cleanup of stale buckets every 5min
  • Applied globally via router middleware

Secrets Encryption (20c)

  • crypto/aes.go: AES-256-GCM encrypt/decrypt, enabled via SECRETS_ENCRYPTION_KEY env var (32 bytes raw or base64), graceful degradation to plaintext if not set, warnings on invalid key
  • secrets.go: encrypt on write, decrypt on read
  • workspace.go: decrypt secrets before injecting into provisioned containers

Delete Team Cascade (13h)

  • workspace.go Delete handler: without ?confirm=true returns children list for confirmation, with confirm cascades delete to all sub-workspaces (stops containers, removes from DB, broadcasts events)
  • DetailsTab.tsx: passes ?confirm=true after user confirms