# 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-template/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-template/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
