2f7beb9bce
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2f7beb9bce |
feat: drop shared_context — use memory v2 team namespace instead
Parent → child knowledge sharing previously lived behind a `shared_context` list in config.yaml: at boot, every child workspace HTTP-fetched its parent's listed files via GET /workspaces/:id/shared-context and prepended them as a "## Parent Context" block. That paid the full transfer cost on every boot regardless of whether the agent needed it, single-parent SPOF, no team or org scope, and broken if the parent was unreachable. Replace with memory v2's team:<id> namespace: agents call recall_memory on demand. For large blob-shaped artefacts see RFC #2789 (platform-owned shared file storage). Removed: - workspace/coordinator.py: get_parent_context() - workspace/prompt.py: parent_context arg + injection block - workspace/adapter_base.py: import + call + arg pass - workspace/config.py: shared_context field + parser entry - workspace-server/internal/handlers/templates.go: SharedContext handler - workspace-server/internal/router/router.go: GET /shared-context route - canvas/src/components/tabs/ConfigTab.tsx: Shared Context tag input - canvas/src/components/tabs/config/form-inputs.tsx: schema field + default - canvas/src/components/tabs/config/yaml-utils.ts: serializer entry - 6 tests pinning the removed behavior; 5 doc references Added regression gates so any reintroduction is loud: - workspace/tests/test_prompt.py: build_system_prompt must NOT emit "## Parent Context" - workspace/tests/test_config.py: legacy YAML key loads cleanly but shared_context attr must NOT exist on WorkspaceConfig - tests/e2e/test_staging_full_saas.sh §9d: GET /shared-context must NOT return 200 against a live tenant Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
0fc2531250 |
feat(workspace): event_log module + EventLogConfig (#119 PR-2)
Adds workspace/event_log.py with an in-memory EventLog backend and a disabled no-op variant, plus EventLogConfig nested in ObservabilityConfig (backend / ttl_seconds / max_entries). The event log is the append-and-query buffer that the canvas Activity tab and platform `/activity` endpoint will read in PR-3 of the #119 stack. Two backends ship in this PR: - InMemoryEventLog: bounded ring buffer with TTL eviction, monotonic ids that survive eviction so cursors don't break, thread-safe for concurrent appends from heartbeat + main loop + A2A executor. - DisabledEventLog: no-op for `backend: disabled` — opts the workspace out without crashing callers that propagate event ids. Schema-only PR — no consumers wired yet. Wiring lands in PR-3. Test coverage: - 34 new test_event_log.py tests (100% line coverage on event_log.py) - 9 new test_config.py tests for EventLogConfig parsing - Concurrency stress with 8 threads × 200 appends — verifies unique monotonic ids under contention - TTL + max_entries eviction with injected clock (no time.sleep) - Disabled backend contract pinned Closes #207. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
97ebd1910a |
fix(runtime): canvas-picked model wins universally + per-model required_env
Two surgical edits to the molecule-runtime workspace package that fix
Bug B (canvas-picked model silently dropped for templated workspaces)
and Bug D (preflight rejects valid auth for non-default models),
universally for every adapter.
Bug B — canvas-picked model dropped (config.py)
================================================
Before: load_config resolved runtime_config.model as
runtime_raw.get("model") or model
which means a template's `runtime_config.model: sonnet` always wins
over the canvas-picked MODEL_PROVIDER env var. Surfaced 2026-05-02
during MiniMax E2E — picking MiniMax-M2.7 in canvas, server plumbed
MODEL_PROVIDER=MiniMax-M2.7 correctly, but the workspace booted with
sonnet because the template's verbatim config.yaml won.
After:
os.environ.get("MODEL_PROVIDER") or runtime_raw.get("model") or model
Centralising in load_config means EVERY adapter (claude-code, hermes,
codex, langgraph, future ones) gets canvas-picked-model passthrough
for free — no per-adapter env-reading code required.
Bug D — preflight per-model required_env (preflight.py)
========================================================
Before: preflight read the top-level required_env list, which
declares the auth needed by the *default* model. A template like
claude-code-default declares CLAUDE_CODE_OAUTH_TOKEN at the top
level. When a user picked MiniMax instead and only set
MINIMAX_API_KEY, preflight rejected the workspace with
"missing CLAUDE_CODE_OAUTH_TOKEN" and the workspace crash-looped
despite the user having satisfied the picked model's actual auth.
After: when runtime_config.models[] declares per-entry required_env,
preflight matches the picked model id (case-insensitive) and uses
that entry's required_env outright instead of the top-level list.
REPLACE semantics, not union — different models have *different*
auth paths (OAuth vs API key vs third-party provider key); unioning
would re-introduce the very crash-loop this fix closes.
Surface enabling both fixes (config.py)
========================================
RuntimeConfig now carries `models: list[dict]` so the canvas Model
dropdown source flows through to preflight without forcing the
parser schema to grow. Malformed entries are silently dropped to
match the rest of the lenient parser.
Tests
=====
- workspace/tests/test_preflight.py: 9 new tests covering the
per-model lookup (case-insensitive, REPLACE not union, fallback
to top-level when no models[] or no match, multi-entry, malformed
entries dropped, etc.)
- workspace/tests/test_config.py: existing 48 pass; field
initialisation already covered by parser tests.
- All 75 targeted tests pass locally; CI runs the full suite
including coverage gate.
Closes part of #246. Sibling PR opens against
molecule-ai-workspace-template-claude-code for per-template
defensive fixes + boot debug logging.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
59902bce83 |
feat(config): add observability block schema (#119 PR-1 of 4)
Hermes-style declarative block grouping cadence + verbosity knobs into one place. Schema-only in this PR — wiring into heartbeat.py and main.py lands in PR-3 of the #119 stack. Two fields with live consumers waiting: - heartbeat_interval_seconds (default 30, clamped to [5, 300]) → heartbeat.py:134 currently has hard-coded HEARTBEAT_INTERVAL = 30 - log_level (default "INFO", uppercased at parse) → main.py:465 currently has hard-coded log_level="info" Clamp band [5, 300] is intentional: sub-5s flooded the platform during IR-2026-03-11; >5min lets crashed workspaces look healthy long enough to mask failure. Coerce at parse so adapters and heartbeat.py can read the value without re-validating. Tests pin defaults, explicit YAML override, partial override, and parametrized clamp behavior (10 cases including garbage strings + None). Part of: task #119 (adopt hermes-style architecture) Stack: PR-1 schema → PR-2 event_log → PR-3 wire consumers → PR-4 skill compat |
||
|
|
067ad83ce5 |
feat(config): add explicit provider: field alongside model:
Adds a top-level `provider` slug to WorkspaceConfig and RuntimeConfig so
adapters can route to a specific gateway without re-implementing
slug-prefix parsing across hermes / claude-code / codex.
Resolution chain in load_config (mirrors how `model` resolves):
1. ``LLM_PROVIDER`` env var — what canvas Save+Restart sets so the
operator's Provider dropdown choice survives a CP-driven restart
(the regenerated /configs/config.yaml drops most user fields).
2. Explicit YAML ``provider:`` — operator pinned it in the file.
3. Derive from the model slug prefix for backward compat:
``anthropic:claude-opus-4-7`` → ``anthropic``
``minimax/abab7-chat-preview`` → ``minimax``
bare model names → ``""`` (let the adapter decide).
`runtime_config.provider` falls back to the top-level resolved
provider, the same shape PR #2438 added for `runtime_config.model`.
Why a separate field at all (we already parse the slug):
- Custom model aliases without a recognizable prefix need an
explicit signal — the canvas Provider dropdown writes it.
- Adapters were each rolling their own slug-parse (hermes's
derive-provider.sh, claude-code's adapter-default branch, etc.);
one resolution point in load_config kills that drift class.
- Canvas needs a stable storage field that doesn't get clobbered
every time the user picks a new model.
Backward-compatible: when `provider:` is absent, slug derivation
keeps every existing config.yaml working without a migration.
PR-1 of a multi-PR stack (Option B from RFC discussion). Subsequent
PRs plumb the field through workspace-server env, CP user-data,
adapters (hermes prefers explicit over derive-provider.sh), and
canvas Provider dropdown UI.
Tests cover all four resolution paths + runtime_config inheritance:
- test_provider_default_empty_when_bare_model
- test_provider_derived_from_colon_slug
- test_provider_derived_from_slash_slug
- test_provider_yaml_explicit_wins_over_derived
- test_provider_env_override_beats_yaml_and_derived
- test_runtime_config_provider_yaml_wins_over_top_level
- test_provider_default_from_default_model
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
0070d0bd59 |
fix(config): runtime_config.model falls back to top-level model
External feedback (2026-04-30): "Provisioner doesn't read model from config.yaml and doesn't set MODEL env var. Without MODEL, the adapter defaults to sonnet and bypasses the mimo routing." Confirmed accurate for SaaS workspaces. Trace: claude-code-default/adapter.py reads `runtime_config.model or "sonnet"` (and hermes reads HERMES_DEFAULT_MODEL via install.sh, which IS plumbed). For claude-code there's nothing — workspace/config.py loaded `runtime_config.model` only from YAML, ignoring MODEL_PROVIDER env. The CP user-data script regenerates /configs/config.yaml at every boot with only `name`, `runtime`, `a2a` keys (intentionally minimal so it doesn't carry stale state) — so any user-set runtime_config.model is wiped on every restart, and the adapter falls back to "sonnet" even when the user picked Opus in the canvas Config tab. Fix: when YAML omits runtime_config.model, fall back to the top-level resolved `model`, which already honors MODEL_PROVIDER env override. One-line in workspace/config.py. Now MODEL_PROVIDER → top-level model → runtime_config.model → adapter sees the user's selection. Sticky across CP-driven restarts; the canvas Save+Restart loop works as intended for every runtime, not just hermes. Tests: test_runtime_config_model_falls_back_to_top_level — top-level set, runtime_config empty → fallback wins test_runtime_config_model_yaml_wins_over_top_level — YAML explicit → fallback skipped (precedence) test_runtime_config_model_picks_up_env_via_top_level — full canvas Save+Restart simulation: env → top-level → runtime_config.model Negative-control verified: removing the `or model` flips both fallback tests red with the expected "" vs expected-model mismatch; restoring flips them green. The yaml-wins test passes either way (correctly, because precedence is preserved). Replaces closed PR #2435 — that PR's commit was on a contaminated branch and accidentally captured unrelated WIP changes (build script + a2a_mcp_server refactor) instead of this fix. Self-review caught it and closed the PR. This branch is clean off main + diff verified before push. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9af058b82d |
fix(compliance): flip default mode to owasp_agentic (detect-only)
Prior state: compliance.mode default was "" (fully off) and no template in the repo set it explicitly — so prompt-injection detection, PII redaction, and agency-limit checks were silently disabled on every live workspace, despite the machinery being present in workspace/builtin_tools/compliance.py. This was surfaced during a 2026-04-24 review of the A2A inbound path: a2a_executor.py gates three security checks on _compliance_cfg.mode == "owasp_agentic" and default config never matches, so every A2A message skipped all three. Fix: default is now owasp_agentic + prompt_injection=detect. Detect mode logs injection attempts as audit events without blocking — no UX cost, just visibility. Operators who want stricter enforcement set `prompt_injection: block` per workspace. Operators who genuinely want compliance fully off can set `mode: ""` (not recommended; documented). Changes: - ComplianceConfig.mode default: "" → "owasp_agentic" - Yaml parser fallback default: "" → "owasp_agentic" (must match dataclass) - Docstring updated with rationale + opt-out snippet Tests: 66/66 test_compliance.py + test_a2a_executor.py pass. 19/19 test_config.py pass. The one test asserting compliance_mode == "" is for the "config load failed" fallback path (different from the default config path) — correctly unchanged. Security posture improvement: prompt-injection detection is now always on for every workspace created after this ships, with zero behavior change for legitimate inputs. Block mode remains an opt-in when an operator wants to actively reject injection attempts rather than just log them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
479a027e4b |
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> |