Add a category_routing block to org.yaml schema (defaults + per-workspace,
UNION semantics with per-key replace). The merged routing table is rendered
into each workspace's config.yaml at import time.
PM's system prompt loses the hardcoded security/ui/infra → role mapping
from PR #50; instead it reads category_routing from /configs/config.yaml
and delegates to whatever roles the org template lists for the incoming
audit-summary's category. Future org templates ship their own routing
without prompt churn.
Tests: 4 new TestCategoryRouting_* cases covering YAML parse, UNION+drop
semantics, deterministic config.yaml render, and empty-map handling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a category_routing block to org.yaml schema (defaults + per-workspace,
UNION semantics with per-key replace). The merged routing table is rendered
into each workspace's config.yaml at import time.
PM's system prompt loses the hardcoded security/ui/infra → role mapping
from PR #50; instead it reads category_routing from /configs/config.yaml
and delegates to whatever roles the org template lists for the incoming
audit-summary's category. Future org templates ship their own routing
without prompt churn.
Tests: 4 new TestCategoryRouting_* cases covering YAML parse, UNION+drop
semantics, deterministic config.yaml render, and empty-map handling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
#71 just merged — per-workspace `plugins:` now UNIONs with `defaults.plugins`
instead of replacing it. Simplifies every override in molecule-dev/ from
"defaults+1 = list 10 items" to "defaults+1 = list 1 item":
PM: 11 items → 2 (workflow-triage + workflow-retro)
Research Lead: 10 items → 1 (browser-automation)
Market Analyst: 10 items → 1
Technical Researcher: 10 items → 1
Competitive Intel: 10 items → 1
Security Auditor: 12 items → 3 (code-review + cross-vendor-review + llm-judge)
UIUX Designer: 10 items → 1 (browser-automation)
Every workspace still receives the full 9-plugin default set (ecc,
molecule-dev, superpowers, careful-bash, prompt-watchdog, audit-trail,
session-context, cron-learnings, update-docs) — verified by reading
mergePlugins() in platform/internal/handlers/org.go:645.
Also drops the stale "REPLACE not UNION" warning comments and points
defaults' header comment at the new union behaviour.
Net diff: ~30 lines removed, ~10 added. Template is now meaningfully
easier to extend — each new defaults.plugin propagates everywhere
without sweeping per-role lists.
Closes follow-up scope from PR #70.
#71 just merged — per-workspace `plugins:` now UNIONs with `defaults.plugins`
instead of replacing it. Simplifies every override in molecule-dev/ from
"defaults+1 = list 10 items" to "defaults+1 = list 1 item":
PM: 11 items → 2 (workflow-triage + workflow-retro)
Research Lead: 10 items → 1 (browser-automation)
Market Analyst: 10 items → 1
Technical Researcher: 10 items → 1
Competitive Intel: 10 items → 1
Security Auditor: 12 items → 3 (code-review + cross-vendor-review + llm-judge)
UIUX Designer: 10 items → 1 (browser-automation)
Every workspace still receives the full 9-plugin default set (ecc,
molecule-dev, superpowers, careful-bash, prompt-watchdog, audit-trail,
session-context, cron-learnings, update-docs) — verified by reading
mergePlugins() in platform/internal/handlers/org.go:645.
Also drops the stale "REPLACE not UNION" warning comments and points
defaults' header comment at the new union behaviour.
Net diff: ~30 lines removed, ~10 added. Template is now meaningfully
easier to extend — each new defaults.plugin propagates everywhere
without sweeping per-role lists.
Closes follow-up scope from PR #70.
Merged after 7-gate verification.
Gates: 1 (CI 6/6 + 1 skip) pass, 2 (build/vet) pass, 3 (5 new TestPlugins_* + backward-compat) pass, 4 (security) pass, 5 (design) pass with 1 yellow, 6 (line review) pass, 7 N/A.
Backward-compat verified: molecule-dev/org.yaml re-lists [ecc, molecule-dev, superpowers, browser-automation] in each role; under new UNION+dedupe the merged set is identical to the prior REPLACE result. PR #70's 1 yellow (REPLACE verbosity / re-listing chore) is now closed by this change — orgs can drop the re-listing once confident.
Cross-vendor-review: second-model tooling unavailable in this worktree; Claude-only review applied per standing rule fallback.
Yellow (non-blocking, follow-up): opt-out semantics (`!plugin` / `-plugin`) are documented only in the code comment. Safety plugins like `molecule-careful-bash` can be disabled by an org.yaml using `!molecule-careful-bash` — this is operator-controlled config per I-2 and therefore acceptable, but docs/plugins/ should get an "overriding defaults" page in a follow-up.
noteworthy: plugin-semantics-change
Merged after 7-gate verification.
Gates: 1 (CI 6/6 + 1 skip) pass, 2 (build/vet) pass, 3 (5 new TestPlugins_* + backward-compat) pass, 4 (security) pass, 5 (design) pass with 1 yellow, 6 (line review) pass, 7 N/A.
Backward-compat verified: molecule-dev/org.yaml re-lists [ecc, molecule-dev, superpowers, browser-automation] in each role; under new UNION+dedupe the merged set is identical to the prior REPLACE result. PR #70's 1 yellow (REPLACE verbosity / re-listing chore) is now closed by this change — orgs can drop the re-listing once confident.
Cross-vendor-review: second-model tooling unavailable in this worktree; Claude-only review applied per standing rule fallback.
Yellow (non-blocking, follow-up): opt-out semantics (`!plugin` / `-plugin`) are documented only in the code comment. Safety plugins like `molecule-careful-bash` can be disabled by an org.yaml using `!molecule-careful-bash` — this is operator-controlled config per I-2 and therefore acceptable, but docs/plugins/ should get an "overriding defaults" page in a follow-up.
noteworthy: plugin-semantics-change
- docs/edit-history/2026-04-14.md — append tick-5 section covering PR #69
(PLAN.md backlog stale-ref cleanup) and PR #70 (wire 12 modular plugins
from PR #63 into the default molecule-dev org template; defaults 3 → 9
plus PM + Security Auditor role extras).
- PLAN.md — add tick-5 entries under "Recently launched" noting PR #70
activated the tick-4 plugins and PR #69 cleaned up stale backlog refs.
Both merges are docs/template-only. No code surface moved, no new env
vars, no test-count drift. CLAUDE.md, .env.example, README.md, and
README.zh-CN.md unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- docs/edit-history/2026-04-14.md — append tick-5 section covering PR #69
(PLAN.md backlog stale-ref cleanup) and PR #70 (wire 12 modular plugins
from PR #63 into the default molecule-dev org template; defaults 3 → 9
plus PM + Security Auditor role extras).
- PLAN.md — add tick-5 entries under "Recently launched" noting PR #70
activated the tick-4 plugins and PR #69 cleaned up stale backlog refs.
Both merges are docs/template-only. No code surface moved, no new env
vars, no test-count drift. CLAUDE.md, .env.example, README.md, and
README.zh-CN.md unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per-workspace `plugins:` now UNIONS with `defaults.plugins` instead of
replacing. A leading `!` or `-` on a per-workspace entry opts a default
out. Backward-compatible: re-listing defaults still dedupes to the same
list.
Refactored the inline REPLACE logic into a pure helper `mergePlugins`
in org.go so it's unit-testable. Five TestPlugins_* cases added.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per-workspace `plugins:` now UNIONS with `defaults.plugins` instead of
replacing. A leading `!` or `-` on a per-workspace entry opts a default
out. Backward-compatible: re-listing defaults still dedupes to the same
list.
Refactored the inline REPLACE logic into a pure helper `mergePlugins`
in org.go so it's unit-testable. Five TestPlugins_* cases added.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR #63 just merged 12 new modular plugins (split from a single guardrails
bundle) and the audit pipeline (Security/UIUX/QA crons) is now producing
PRs continuously. Time to wire the new plugins into the molecule-dev
template so every workspace + every cron tick benefits.
## Defaults — universal additions (was 3, now 9)
- molecule-careful-bash — refuse rm -rf, push --force main, DROP TABLE
- molecule-prompt-watchdog — warn on destructive user prompts
- molecule-audit-trail — append every Edit/Write to .claude/audit.jsonl
- molecule-session-context — auto-load cron learnings + PR/issue counts on SessionStart
- molecule-skill-cron-learnings — per-tick learning JSONL format (pairs with session-context)
- molecule-skill-update-docs — keep architecture/README/edit-history aligned
Kept: ecc, molecule-dev, superpowers.
## Per-role overrides
- PM: defaults + molecule-workflow-triage + molecule-workflow-retro
(the /triage and /retro slash commands match PM's coordination role)
- Security Auditor: defaults + molecule-skill-code-review +
molecule-skill-cross-vendor-review + molecule-skill-llm-judge
(security PRs benefit from multi-criteria review, adversarial cross-vendor
second opinion, and an LLM-judge gate that catches "agent shipped the
wrong thing")
- Research Lead + 3 researchers + UIUX Designer: defaults + browser-automation
(existing override; just synced to the new default set)
Other 5 dev roles (Dev Lead, BE, FE, DevOps, QA) inherit defaults — the
new universal set is rich enough for them; code-review skill is a runtime
opt-in if Dev Lead decides per-PR.
## REPLACE-semantics verbosity
`platform/internal/handlers/org.go:~345` treats per-workspace plugins as
REPLACE not UNION. Every override has to re-list the 9 defaults to add 1
extra. Tracked as #68 with a union-proposal; once that lands the per-role
lists shrink to just the additions.
## Test plan
- [x] YAML valid (`python -c "import yaml; yaml.safe_load(...)"`)
- [x] defaults.plugins count = 9
- [ ] After merge + re-import: every workspace's /configs/plugins/ contains
the full set; PM has /triage and /retro commands; Security Auditor
can invoke cross-vendor-review on its findings.
PR #63 just merged 12 new modular plugins (split from a single guardrails
bundle) and the audit pipeline (Security/UIUX/QA crons) is now producing
PRs continuously. Time to wire the new plugins into the molecule-dev
template so every workspace + every cron tick benefits.
## Defaults — universal additions (was 3, now 9)
- molecule-careful-bash — refuse rm -rf, push --force main, DROP TABLE
- molecule-prompt-watchdog — warn on destructive user prompts
- molecule-audit-trail — append every Edit/Write to .claude/audit.jsonl
- molecule-session-context — auto-load cron learnings + PR/issue counts on SessionStart
- molecule-skill-cron-learnings — per-tick learning JSONL format (pairs with session-context)
- molecule-skill-update-docs — keep architecture/README/edit-history aligned
Kept: ecc, molecule-dev, superpowers.
## Per-role overrides
- PM: defaults + molecule-workflow-triage + molecule-workflow-retro
(the /triage and /retro slash commands match PM's coordination role)
- Security Auditor: defaults + molecule-skill-code-review +
molecule-skill-cross-vendor-review + molecule-skill-llm-judge
(security PRs benefit from multi-criteria review, adversarial cross-vendor
second opinion, and an LLM-judge gate that catches "agent shipped the
wrong thing")
- Research Lead + 3 researchers + UIUX Designer: defaults + browser-automation
(existing override; just synced to the new default set)
Other 5 dev roles (Dev Lead, BE, FE, DevOps, QA) inherit defaults — the
new universal set is rich enough for them; code-review skill is a runtime
opt-in if Dev Lead decides per-PR.
## REPLACE-semantics verbosity
`platform/internal/handlers/org.go:~345` treats per-workspace plugins as
REPLACE not UNION. Every override has to re-list the 9 defaults to add 1
extra. Tracked as #68 with a union-proposal; once that lands the per-role
lists shrink to just the additions.
## Test plan
- [x] YAML valid (`python -c "import yaml; yaml.safe_load(...)"`)
- [x] defaults.plugins count = 9
- [ ] After merge + re-import: every workspace's /configs/plugins/ contains
the full set; PM has /triage and /retro commands; Security Auditor
can invoke cross-vendor-review on its findings.
Backlog items 11-14 used sequential enumeration (#64/#65/#66/#67) as
intra-doc bookkeeping. Those numbers now collide with actual merged
PRs and open issues with completely different scopes:
- PR #64 = auto-refresh global_secrets (not "delegations list")
- PR #65 = restart context Layer 1 (not "per-agent repo access")
- Issue #66 = restart_prompt Layer 2 (not "SDK swallows stderr")
- PR #67 = docs sync tick-4 (not "MCP localhost default")
Strip the misleading refs and add a footnote explaining the cleanup.
If/when any of these items get prioritized, file real GitHub issues.
Tracked in cron-learnings tick-3 entry.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backlog items 11-14 used sequential enumeration (#64/#65/#66/#67) as
intra-doc bookkeeping. Those numbers now collide with actual merged
PRs and open issues with completely different scopes:
- PR #64 = auto-refresh global_secrets (not "delegations list")
- PR #65 = restart context Layer 1 (not "per-agent repo access")
- Issue #66 = restart_prompt Layer 2 (not "SDK swallows stderr")
- PR #67 = docs sync tick-4 (not "MCP localhost default")
Strip the misleading refs and add a footnote explaining the cleanup.
If/when any of these items get prioritized, file real GitHub issues.
Tracked in cron-learnings tick-3 entry.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- edit-history/2026-04-14.md: append tick-4 section covering the 12
modular guardrail plugins (#63), global-secrets auto-restart fan-out
(#64, fixes issue #15), and synthetic restart-context A2A message
(#65, fixes issue #19 Layer 1; Layer 2 deferred to issue #66).
- CLAUDE.md: bump Go test count 699 -> 726 (measured); note global
secrets auto-restart on SetGlobal/DeleteGlobal in the route table;
add Workspace Lifecycle paragraph for the restart-context message
and its system:restart-context caller prefix.
- PLAN.md: bump Go test count in the coverage table; record issues
#15 and #19 Layer 1 as launched; add new Backlog entry for the
Layer 2 follow-up (issue #66).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- edit-history/2026-04-14.md: append tick-4 section covering the 12
modular guardrail plugins (#63), global-secrets auto-restart fan-out
(#64, fixes issue #15), and synthetic restart-context A2A message
(#65, fixes issue #19 Layer 1; Layer 2 deferred to issue #66).
- CLAUDE.md: bump Go test count 699 -> 726 (measured); note global
secrets auto-restart on SetGlobal/DeleteGlobal in the route table;
add Workspace Lifecycle paragraph for the restart-context message
and its system:restart-context caller prefix.
- PLAN.md: bump Go test count in the coverage table; record issues
#15 and #19 Layer 1 as launched; add new Backlog entry for the
Layer 2 follow-up (issue #66).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After a workspace restart (HTTP /restart or programmatic RestartByID) and
re-registration, the platform sends a synthetic A2A message/send to the
workspace containing:
- restart timestamp
- previous session end timestamp + human duration
- env-var keys now available (keys only — never values)
The message is rendered in the format proposed in #19 and marked with
metadata.kind=restart_context so agents can detect and handle it
specifically if they choose.
Skip path: if the workspace doesn't re-register within 30s, log and drop.
The Restart HTTP response is unaffected by delivery success.
Layer 2 (user-defined restart_prompt via config.yaml / org.yaml) is
deferred — tracked as a separate follow-up issue.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After a workspace restart (HTTP /restart or programmatic RestartByID) and
re-registration, the platform sends a synthetic A2A message/send to the
workspace containing:
- restart timestamp
- previous session end timestamp + human duration
- env-var keys now available (keys only — never values)
The message is rendered in the format proposed in #19 and marked with
metadata.kind=restart_context so agents can detect and handle it
specifically if they choose.
Skip path: if the workspace doesn't re-register within 30s, log and drop.
The Restart HTTP response is unaffected by delivery success.
Layer 2 (user-defined restart_prompt via config.yaml / org.yaml) is
deferred — tracked as a separate follow-up issue.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Global secrets (e.g. CLAUDE_CODE_OAUTH_TOKEN) are injected as container env
vars at Start() time. Until now, rotating one only propagated to a workspace
on the next full restart-from-zero, which manual ops had to drive via a
`POST /workspaces/:id/restart` loop. Tier-3 Claude Code agents hit the
stale-token path first and surfaced as 401s inside the SDK.
Restart-time re-read of global_secrets + workspace_secrets was already
correct in `provisionWorkspaceOpts` — the missing piece was the trigger.
SetGlobal / DeleteGlobal now enqueue RestartByID for every non-paused,
non-removed, non-external workspace that does NOT shadow the key with a
workspace-level override. Matches the existing behaviour of workspace-scoped
`Set` / `Delete`.
Adds two sqlmock-backed tests exercising both branches.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Global secrets (e.g. CLAUDE_CODE_OAUTH_TOKEN) are injected as container env
vars at Start() time. Until now, rotating one only propagated to a workspace
on the next full restart-from-zero, which manual ops had to drive via a
`POST /workspaces/:id/restart` loop. Tier-3 Claude Code agents hit the
stale-token path first and surfaced as 401s inside the SDK.
Restart-time re-read of global_secrets + workspace_secrets was already
correct in `provisionWorkspaceOpts` — the missing piece was the trigger.
SetGlobal / DeleteGlobal now enqueue RestartByID for every non-paused,
non-removed, non-external workspace that does NOT shadow the key with a
workspace-level override. Matches the existing behaviour of workspace-scoped
`Set` / `Delete`.
Adds two sqlmock-backed tests exercising both branches.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR #63 code-review caught that the SDK copy of AgentskillsAdaptor uses
json.loads/json.dumps in _merge_settings_fragment + _rewrite_hook_paths
+ _deep_merge_hooks but never imports json. The runtime copy
(workspace-template/plugins_registry/builtins.py) already has the
import; this brings the SDK side in line.
Bug surfaces only when a plugin shipping settings-fragment.json (any
of the 5 hook plugins or 2 workflow plugins in this PR) is installed
through the SDK path — would NameError on the first json.loads call.
The drift test catches behavioral drift via fixture install scenarios
but not import-level drift in helper code paths.
Verified: json is now importable (`hasattr(molecule_plugin.builtins,
'json')` → True), drift test still passes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR #63 code-review caught that the SDK copy of AgentskillsAdaptor uses
json.loads/json.dumps in _merge_settings_fragment + _rewrite_hook_paths
+ _deep_merge_hooks but never imports json. The runtime copy
(workspace-template/plugins_registry/builtins.py) already has the
import; this brings the SDK side in line.
Bug surfaces only when a plugin shipping settings-fragment.json (any
of the 5 hook plugins or 2 workflow plugins in this PR) is installed
through the SDK path — would NameError on the first json.loads call.
The drift test catches behavioral drift via fixture install scenarios
but not import-level drift in helper code paths.
Verified: json is now importable (`hasattr(molecule_plugin.builtins,
'json')` → True), drift test still passes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New section before the Temporal footnote capturing the gap analysis
between today's self-hosted posture and a multi-tenant cloud SaaS:
- Tier 1 blockers: multi-tenancy (org_id everywhere), WorkOS AuthKit
for human auth, Fly Machines for container isolation, Stripe
billing, per-org quotas, managed Postgres/Redis (Neon/Upstash),
KMS-backed secrets, migrations out of app boot
- Tier 1 follow-ups: Sentry + Grafana, per-org rate limiting,
Cloudflare, onboarding flow, transactional email, admin panel,
ToS/DPA
- Tier 2 tech-stack upgrades (non-blocking): pgx/v5 + sqlc, River
for platform async (NOT Temporal — that stays in workspace-template
as an agent tool), TanStack Query, Turbopack, uv for Python,
Python MCP client, shadcn/ui CLI
- Tier 3 explicitly NOT doing: Kubernetes, ORMs, framework swaps,
build-auth-yourself, canvas library swaps — with reasons
- Tier 4 compliance (post-revenue): SOC 2, status page, staging,
canary deploys, load testing
- Success criteria: sign-up-to-first-message < 5 min, tenant
isolation red-teamed, Fly Machines cost documented, Stripe
end-to-end, first paying design partner
Derived from a tech-stack audit run against the 2026 best-in-class
landscape (pgx won Postgres, River eats Temporal's small-company
slot, WorkOS beats Clerk for per-org SSO, Fly Machines is the only
isolation option without an SRE).
Co-authored-by: Hongming Wang <hongmingwang.rabbit@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New section before the Temporal footnote capturing the gap analysis
between today's self-hosted posture and a multi-tenant cloud SaaS:
- Tier 1 blockers: multi-tenancy (org_id everywhere), WorkOS AuthKit
for human auth, Fly Machines for container isolation, Stripe
billing, per-org quotas, managed Postgres/Redis (Neon/Upstash),
KMS-backed secrets, migrations out of app boot
- Tier 1 follow-ups: Sentry + Grafana, per-org rate limiting,
Cloudflare, onboarding flow, transactional email, admin panel,
ToS/DPA
- Tier 2 tech-stack upgrades (non-blocking): pgx/v5 + sqlc, River
for platform async (NOT Temporal — that stays in workspace-template
as an agent tool), TanStack Query, Turbopack, uv for Python,
Python MCP client, shadcn/ui CLI
- Tier 3 explicitly NOT doing: Kubernetes, ORMs, framework swaps,
build-auth-yourself, canvas library swaps — with reasons
- Tier 4 compliance (post-revenue): SOC 2, status page, staging,
canary deploys, load testing
- Success criteria: sign-up-to-first-message < 5 min, tenant
isolation red-teamed, Fly Machines cost documented, Stripe
end-to-end, first paying design partner
Derived from a tech-stack audit run against the 2026 best-in-class
landscape (pgx won Postgres, River eats Temporal's small-company
slot, WorkOS beats Clerk for per-org SSO, Fly Machines is the only
isolation option without an SRE).
Co-authored-by: Hongming Wang <hongmingwang.rabbit@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>