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>
14 KiB
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/*pathfor individual files,GET /workspaces/:id/filesfor tree listing - Path traversal protection via
validateRelPath() useMemofor tree building, ref-based cursor positioning
Agent Import & Replace via UI
- Import Agent Folder: button in template palette, uses
webkitdirectoryfile picker, uploads toPOST /templates/import - Replace Agent Files: button in DetailsTab, uploads folder to
PUT /workspaces/:id/files, auto-restarts, confirmation before destructive replace - Auto-generates
config.yamldetecting prompt_files and skills from uploaded files - CLI script
import-agent.shalso 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.jsononto canvas to import, visual overlay, progress spinner, toast - Context menu: Export Bundle downloads file, Duplicate exports+re-imports
- Team expansion APIs:
POST /workspaces/:id/expandcreates sub-workspaces from config,POST /collapseremoves them - Context menu: Expand to Team / Collapse Team
- bundle-compile.sh: compiles all templates to
.bundle.jsonartifacts
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_idin A2A metadata - Workspace forwarding: discovery follows
forwarded_tochain (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
astreamtoainvokefor reliable responses across models - Chat response parsing: handle
result.parts[]withkindfield (a2a-sdk v0.3 format) - Terminal handler: tries multiple container name patterns (ws-{id}, workspace-name)
- A2A proxy: injects
messageIdintoparams.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 directoriesplugins/ecc/: ECC guardrails rules + 5 shared skills (coding-standards, tdd-workflow, security-review, api-design, deep-research) + AGENTS.md prompt fragmentplugins/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:rointo every container - docker-compose.yml mounts
./pluginsfor platform
Prompt Integration
prompt.py: acceptsplugin_rulesandplugin_promptsparameters- 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,useMemofor 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.jsonfor 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: truein 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
useMemoto 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_INTERVALandCONSOLIDATION_THRESHOLDenv 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
SandboxConfigdataclass in config.py, loaded fromsandbox:in config.yaml- Configurable via
SANDBOX_BACKEND,SANDBOX_TIMEOUT,SANDBOX_MEMORY_LIMITenv 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— globalshowToast()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_requeststable 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/pendingendpoint, shows approve/deny cards with workspace name, slide-in-from-top animation, toast feedback. - Agent decides when to call
request_approvalbased on system prompt guidelines.
Hierarchical Memory Architecture (Phase 9, 15a-15f)
- Migration 008_agent_memories.sql:
agent_memoriestable 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)andsearch_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/tracesproxies 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, addsroute_task_to_teamtool- 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 clientsws/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 viaSECRETS_ENCRYPTION_KEYenv var (32 bytes raw or base64), graceful degradation to plaintext if not set, warnings on invalid keysecrets.go: encrypt on write, decrypt on readworkspace.go: decrypt secrets before injecting into provisioned containers
Delete Team Cascade (13h)
workspace.goDelete handler: without?confirm=truereturns children list for confirmation, with confirm cascades delete to all sub-workspaces (stops containers, removes from DB, broadcasts events)DetailsTab.tsx: passes?confirm=trueafter user confirms