Commit Graph

31 Commits

Author SHA1 Message Date
Hongming Wang
fd92de2591
Merge branch 'main' into fix/wire-up-gh-token-refresh 2026-04-29 00:56:02 -07:00
Hongming Wang
7cb0c6c45b
Merge pull request #15 from Molecule-AI/fix/a2a-sdk-v1-file-part-protobuf
fix(a2a-v1): rewrite FilePart emit using v1 protobuf Part struct
2026-04-29 00:50:32 -07:00
Hongming Wang
1a84de8a61 fix(a2a-v1): rewrite FilePart emit using v1 protobuf Part struct
a2a-sdk v1.0.2 replaced the v0 Pydantic discriminated-union types
(Part(root=TextPart(...))/Part(root=FilePart(file=FileWithUri(...))))
with a single protobuf Part struct that has optional `text`, `url`,
`raw`, `data`, `filename`, `media_type` fields. The classes
FilePart, TextPart, FileWithUri don't exist in v1 — import fails:

    File "claude_sdk_executor.py", line 592
        from a2a.types import FilePart, FileWithUri, Message, Part, Role, TextPart
    ImportError: cannot import name 'FilePart' from 'a2a.types'

Production impact: every claude-code workspace (Design Director, UX
Researcher, all coordinators in molecule-core teams) crashes on
result delivery whenever the response includes a /workspace/* file
reference. The A2A delegation loop is broken at the result-delivery
step. Workspaces can receive tasks but can't ship results back.

Fix:

  - Drop FilePart/TextPart/FileWithUri imports (don't exist in v1).
  - `Part(root=TextPart(text=t))` → `Part(text=t)`.
  - `Part(root=FilePart(file=FileWithUri(uri=u, name=n, mimeType=m)))` →
    `Part(url=u, filename=n, media_type=m)`.
  - `messageId=...` → `message_id=...` (snake_case in protobuf).
  - `Role.agent` → `Role.ROLE_AGENT` (v1 enum).

Verified by constructing the exact shape against v1.0.2 in the
running claude-code template image:

  Message:
    message_id: 03ff9367
    role: ROLE_AGENT
    parts count: 2
    text part: hello
    file part: workspace:foo.txt foo.txt text/plain

Refs: molecule-core memory `reference_a2a_sdk_v0_to_v1_migration`
documents the Pydantic→protobuf shift; this is the fifth migration
finding today (after the new_agent_text_message rename in
crewai/openclaw/autogen/gemini-cli).

Test plan:

  - [x] `python3 -m py_compile claude_sdk_executor.py` clean.
  - [x] Runtime construction smoke verified against the live v1.0.2
        a2a-sdk in the claude-code template image.
  - [ ] End-to-end: provision a claude-code workspace, send a task
        whose response references a /workspace/* file, confirm
        result lands without ImportError.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 00:46:47 -07:00
Hongming Wang
de2ab5ab33 feat: forward client_payload.runtime_version + ARG RUNTIME_VERSION
Closes the cache trap structurally (instead of pin-bumping every
runtime release):
1. publish-image.yml caller now forwards
   github.event.client_payload.runtime_version (set by cascade) to
   the molecule-ci reusable workflow as runtime_version input.
2. Reusable workflow forwards it to docker build as a --build-arg.
3. Dockerfile declares ARG RUNTIME_VERSION near the pip install
   layer so its value becomes part of the cache key.
4. The pip install RUN command does an extra targeted upgrade to
   the exact version when ARG is set — guarantees the version is
   what we expect even if requirements.txt resolves to something
   else.

Pairs with molecule-ci PR #12 + molecule-core PR #2181. Together
the pipeline is now race- and cache-proof end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 07:46:14 -07:00
Hongming Wang
059db9ba14 chore: bump pin to >=0.1.22 (state_transition_history fix)
Forces docker layer cache invalidation. Cascade rebuilt against
0.1.22 but hit GHA cache and shipped 0.1.21 (broken AgentCard
construction). Pin bump invalidates the cache key.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 07:39:20 -07:00
Hongming Wang
b1c4cab460 chore: bump runtime pin to >=0.1.21 (lib/ + manifest)
Forces docker layer cache invalidation. The 13:29 cascade rebuild
hit GHA's cached pip-install layer (requirements.txt unchanged →
same cache key → 0.1.19 baked in). Image shipped with 0.1.19 even
though 0.1.21 was on PyPI. Same race as the 0.1.18 → 0.1.19 cycle
earlier today (task #130 — structural fix is to wait for PyPI
propagation in the cascade step).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 06:52:30 -07:00
Hongming Wang
2b0b0d9fcd fix: migrate claude_sdk_executor to a2a-sdk 1.x (new_text_message)
Same a2a-sdk 1.x rename already shipped in hermes/executor.py and
workspace/a2a_executor.py: a2a-sdk dropped `new_agent_text_message`
in favor of `new_text_message` (role=Role.agent default preserves
behavior). Three call sites in this file.

Symptom: every claude-code workspace died at create_executor →
ImportError: cannot import name 'new_agent_text_message' from
'a2a.helpers'. Why this slipped past every prior fix:

The boot smoke gate only does `import adapter`. adapter.py imports
ClaudeSDKExecutor lazily INSIDE create_executor() (line 106),
which means claude_sdk_executor.py is never loaded at module
import time. The lazy-load pattern hid the bug from CI.

molecule-ci PR #8 (lint + import-every-app-py smoke) catches this
class going forward — the new smoke loop iterates every /app/*.py
including claude_sdk_executor.py, forcing module-level imports to
resolve.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 05:55:19 -07:00
Hongming Wang
e7dea39df2 fix: qualify all bare imports of runtime modules
Five `from <runtime_module> import` statements in adapter.py +
claude_sdk_executor.py were never qualified when the template was
extracted to its own repo (#87). They worked when the runtime was
bundled into workspace/ where bare imports resolved against
sibling files; in the template repo they explode at startup with
ModuleNotFoundError as soon as Python reaches the import.

Caught by manual provision after pipeline-3 wire-real E2E. The
plugins import was the first one tripped because it sits in
adapter.setup() — earlier bare imports inside claude_sdk_executor.py
are deferred until the executor is constructed.

Pattern: any `from <X> import Y` where X is a workspace/ module ->
`from molecule_runtime.X import Y`. Fixes:
- adapter.py:97          plugins
- claude_sdk_executor.py executor_helpers, heartbeat, a2a_client, platform_auth

Same class of bug as the runtime's TOP_LEVEL_MODULES drift but
inverted — instead of forgetting to rewrite imports IN the wheel,
the template authors forgot to qualify imports IN the template
code (the build script's rewriter only runs on workspace/ -> wheel).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 05:20:24 -07:00
Hongming Wang
280e89c50b fix: export Adapter alias so runtime adapter discovery works
`workspace/adapters/__init__.py:get_adapter()` does
`getattr(mod, "Adapter")` after importing ADAPTER_MODULE. Without the
alias the runtime's preflight check fails with:

  [FAIL] Runtime: ADAPTER_MODULE='adapter' imported, but no `Adapter`
  class is exported. Add `Adapter = YourAdapterClass` at module scope

Symptom: workspace container restarts forever, never reaches `online`.

This contract was added (or hardened) in #123's adapter-discovery
refactor. Hermes's adapter.py already has `Adapter = HermesAgentAdapter`
at module scope; claude-code missed the migration. gemini-cli template
has the same bug — file separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 04:55:30 -07:00
Hongming Wang
e18986e7b4 chore: bump runtime pin to >=0.1.19
Forces docker layer cache invalidation. The cascade-triggered build at
10:59:46 UTC raced PyPI propagation: publish-runtime completed at 10:59:47
UTC and the cascade fired repository_dispatch immediately, so the
template build's `pip install` got 0.1.18 (still missing main_sync)
instead of the freshly-uploaded 0.1.19. GHA layer cache then pinned
that for any subsequent build with identical requirements.txt.

Bumping the pin invalidates the cache and forces a fresh resolve. The
proper structural fix is to add a sleep/poll-PyPI step to the cascade
job in publish-runtime.yml so it doesn't fan out until the new version
is actually visible on PyPI's index.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 04:16:51 -07:00
Hongming Wang
d313e45117 chore: bump molecule-ai-workspace-runtime pin to >=0.1.16
Forces docker layer cache invalidation so the next image rebuild
actually pulls 0.1.16 (which has RuntimeCapabilities + post-#87
changes) instead of reusing the cached layer with 0.1.15.

Caught by the new boot-import smoke gate in molecule-ci PR #7 — the
build at 09:34 UTC pushed a SHA-tagged image whose pip layer was
cached with 0.1.15, then ImportError'd on `from
molecule_runtime.adapters.base import RuntimeCapabilities`. Smoke
gate failed red, broken image never reached :latest. Working as
designed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 02:36:49 -07:00
Hongming Wang
1a54cdb308
Merge pull request #13 from Molecule-AI/feat/import-claude-sdk-executor
feat(template): own claude_sdk_executor locally (universal-runtime refactor)
2026-04-27 00:36:48 -07:00
Hongming Wang
033cf33c42
Merge pull request #12 from Molecule-AI/feat/declare-runtime-capabilities
feat(adapter): declare native_session + idle_timeout_override
2026-04-27 00:36:34 -07:00
Hongming Wang
fab7c6a929 feat(template): own claude_sdk_executor locally (universal-runtime refactor)
First half of molecule-core task #87 — move adapter-specific code out
of the universal molecule-runtime package into the template that
actually consumes it.

Adds:
  - claude_sdk_executor.py (757 LOC) — copied verbatim from
    molecule-core/workspace/claude_sdk_executor.py @ commit 186f25c2.
    The adapter at adapter.py:59 already does
    `from claude_sdk_executor import ClaudeSDKExecutor` — once this
    file lands at /app/, Python's import order picks the local copy
    over the same-named module that older molecule-runtime versions
    ship under site-packages.
  - Dockerfile: COPY claude_sdk_executor.py . alongside adapter.py.

Pure additive at this stage — molecule-runtime still ships the
file too, so any image built from this template just has two copies
on disk (local /app shadows the site-packages one). No behavior
change.

Sequencing (the molecule-core PR follows AFTER this image rebuilds):
  1. THIS PR — template gets local copy, image rebuilds with it
     (current PR; safe because no removal yet)
  2. molecule-core PR — drop workspace/claude_sdk_executor.py, bump
     molecule-ai-workspace-runtime PyPI version. Templates that
     haven't pulled the new runtime version still work because their
     local copy is unchanged.
  3. (later) Bump requirements.txt pin in this template once the
     new runtime version is on PyPI, so future builds explicitly
     install the slimmed runtime.

Why local-copy-first:
  - Reverse order (drop from runtime first, then add to template)
    creates a window where any template image build pulling the
    latest runtime would fail to import claude_sdk_executor.
  - This order has zero downtime: every intermediate state is valid.

Validates the capability primitives shipped in molecule-core PRs
#2137-#2144 — once this template image rebuilds and the molecule-
core deletion lands, the claude-code workspace is the FIRST adapter
to live entirely outside molecule-runtime, with native_session +
idle_timeout_override declared via capabilities() (PR #12 here).

Source: molecule-core/workspace/claude_sdk_executor.py @ 186f25c2
(commit hash pinned for traceability of any future divergence).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 23:58:05 -07:00
Hongming Wang
c9ca671cd5 feat(adapter): declare provides_native_session + idle_timeout_override
Wires this template into the platform's capability-primitive layer
(molecule-core task #117). Two declarations:

1. RuntimeCapabilities(provides_native_session=True) — the claude-agent-sdk
   maintains a long-lived streaming session with its own client state.
   The platform's a2a_queue would double-buffer that in-flight state
   if it didn't know the SDK owned it. Once primitive #5 lands in
   molecule-core, the platform's enqueue path will skip workspaces
   declaring this and dispatch directly.

2. idle_timeout_override() returning 900 (15 min) — Opus + multi-step
   tool use legitimately runs 8-10 min between broadcaster events.
   The pre-capability bug (molecule-core PR #2128) hit this: the
   platform's 5min idle timer cancelled mid-flight during long
   packaging steps. The override moves the per-workspace ceiling up
   without leaving genuinely-wedged runs hanging too long. Consumed
   by molecule-core PR #2139 in a2a_proxy.dispatchA2A.

Other capability flags stay False — see inline docstring for the
per-flag rationale (notably native_status_mgmt is partially adapter-
driven via runtime_state="wedged" but the recovery path stays platform-
owned, so we don't claim it yet).

Requires molecule-ai-workspace-runtime with RuntimeCapabilities (PR
#2137 in molecule-core, merged 2026-04-27). The current
requirements.txt pin (>=0.1.0) will pick up the latest released
version on next image rebuild.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 23:07:28 -07:00
Hongming Wang
13eadcc158
ci(publish-image): accept repository_dispatch from monorepo runtime publish (#10)
Adds 'repository_dispatch' trigger (event-type: runtime-published) so
molecule-core's publish-runtime.yml cascade job can fire this template's
image rebuild after a new molecule-ai-workspace-runtime PyPI release.

Without this, every runtime release waited for the next push: main /
manual workflow_dispatch to propagate to the published image. With it,
runtime fixes flow monorepo → PyPI → all 8 template images
automatically.

Part of the runtime CD chain. See molecule-core docs/workspace-runtime-package.md.

Co-authored-by: Hongming Wang <hongmingwangalt@gmail.com>
2026-04-26 12:42:19 -07:00
Hongming Wang
8fbd6689f0
Merge pull request #8 from Molecule-AI/fix/publish-image-pr-trigger
fix(ci): add pull_request trigger to publish-image workflow
2026-04-24 13:25:54 -07:00
rabbitblood
39c5b5b11f chore: enforce LF line endings + fix entrypoint.sh CRLF
Without this, Windows Docker Desktop checks out the entrypoint and
helper scripts with CRLF, and `#!/bin/sh\r` either fails outright or
silently exec's the wrong interpreter, depending on the kernel +
busybox combo.

Adds .gitattributes to pin LF on all shell/Python/YAML files +
renormalises the existing entrypoint.sh.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 17:57:57 -07:00
rabbitblood
d4ab584deb fix: wire up GitHub App token refresh — fixes #1933
Symptoms before this PR:
- After ~60 min of workspace uptime, every git push/clone returns 401
- PMM, DevRel, Social Media Brand and other content agents infinite-loop
  status reports back to PMs ("I tried, GH_TOKEN dead")
- PM A2A queues overflow with retry-status messages (depth 27 on Marketing
  Lead, 18 on Dev Lead, 11 on Core Platform Lead at peak)

Root cause:
- GH_TOKEN/GITHUB_TOKEN injected at provision time has a ~60 min TTL
  (GitHub App installation tokens cap at one hour)
- Workspace env is frozen at container start — no in-process mechanism
  to refresh after expiry
- The credential-helper architecture exists in the codebase but was
  never wired up at template boot. Specifically the claude-code template:
  - did not COPY the helper scripts into the image
  - did not configure git credential.helper at boot
  - did not start the background refresh daemon
  - did not run initial gh auth login

Fix:
1. Dockerfile COPYs scripts/molecule-git-token-helper.sh and
   scripts/molecule-gh-token-refresh.sh into /app/scripts/
2. entrypoint.sh (root half) configures git credential helper for
   github.com and creates the per-user token cache directory
3. entrypoint.sh (agent half) starts the refresh daemon under a
   respawn loop and runs initial `gh auth login --with-token`

The helper hits the platform's /admin/github-installation-token endpoint
(fallback to env-var GH_TOKEN when platform unreachable). The refresh
daemon calls _refresh_gh every ~45 min ± 2 min jitter so cli auth and
helper cache stay warm even when no git operation triggers a refresh.

Acceptance:
- After this image deploys, `gh api /user` from inside a workspace
  should keep returning 200 even after >60 min uptime
- Marketing Lead / Dev Lead a2a queues should drain to <5 within one
  cycle of the new image rolling

Follow-up issues to file (not in this PR):
- Replicate this wiring in the other 7 template repos (autogen, crewai,
  deepagents, gemini-cli, hermes, langgraph, openclaw)
- Lift the wiring into the molecule-runtime PyPI package so future
  templates inherit it instead of re-implementing

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 17:57:30 -07:00
8250fd0008 fix(ci): add pull_request trigger to publish-image workflow
Branch protection on main requires the publish / Build & push template
image check to pass for all PRs. The workflow previously only triggered
on push to main, so PRs could never satisfy branch protection.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-23 05:37:16 +00:00
molecule-ai[bot]
fc6f71194e
fix(security): remove API key from git history + add publish-image CI
Removes the .auth-token file (containing a live API key) from git history.
The file was committed in the initial commit (b8859da, Apr 16) but is now
replaced with an empty placeholder in this branch.

Also adds .github/workflows/publish-image.yml for GHCR image publishing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 00:09:30 +00:00
molecule-ai[bot]
2ef87f2f23
fix(security): remove .auth-token API key from git history
The .auth-token file committed in b8859da contains a live API key.
Remove it from git history and add CI publish-image workflow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 00:06:36 +00:00
335474b71b docs: add known-issues.md and runbooks/local-dev-setup.md
Recovered from prior work. Previously pushed commit 03c6929
was lost during reset-to-origin/main divergence resolution.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 08:36:22 +00:00
Hongming Wang
2ca6ed7dc0
feat(config): split OAuth vs API-key in models[] registry (plugin-dev-agent)
Claude Code supports two auth paths that use different env vars:
- OAuth (via `claude login`) → CLAUDE_CODE_OAUTH_TOKEN, tied to a
  Claude Code subscription
- Direct API key → ANTHROPIC_API_KEY, pay-as-you-go via the Anthropic
  Console

Previously the template only listed CLAUDE_CODE_OAUTH_TOKEN, hiding the
API-key path and forcing API-key users to override manually. Now
models[] exposes both as distinct dropdown entries — users pick the
one matching the credential they have; canvas auto-suggests the right
env var.

Model IDs differ intentionally:
- OAuth entries use CLI aliases (sonnet/opus/haiku — resolve to latest)
- API-key entries use explicit versioned ids (claude-sonnet-4-6, etc.)

claude CLI accepts either auth style transparently — OAuth wins when
both are set, which preserves existing workspace behaviour.

Paired with Molecule-AI/molecule-core#1526 (platform + canvas).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 05:36:03 +00:00
molecule-ai[bot]
1846f62509
docs: add CI validation scripts (#4)
Add the scripts required by validate-workspace-template.yml:
- .molecule-ci/scripts/validate-workspace-template.py
- .molecule-ci/scripts/requirements.txt

Co-authored-by: Molecule AI Plugin-Dev <plugin-dev@agents.moleculesai.app>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 03:17:30 +00:00
Hongming Wang
fef8fd5c57
fix: install git + gh CLI for agent autonomy loop (#2)
Install git + gh CLI in workspace image for agent autonomy loop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 00:50:33 +00:00
Hongming Wang
ed4c996d1f
Merge pull request #1 from Molecule-AI/chore/credentials-gitignore
chore: gitignore credentials
2026-04-16 09:23:24 -07:00
rabbitblood
c809786111 chore: gitignore credentials for molecule-ai-workspace-template-claude-code
Adds standard credential gitignore (.env / *.pem / .secrets/ / .auth_token).
Per-CEO directive 2026-04-16: every plugin and template repo should
gitignore credentials so self-hosters can't accidentally commit real
tokens to public repos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 09:15:36 -07:00
Hongming Wang
1b4ee8e225 ci: add standard workspace template validation workflow 2026-04-16 04:43:13 -07:00
Hongming Wang
7f9b2b4189 feat: add adapter code + Dockerfile for standalone deployment
Adapters extracted from molecule-monorepo/workspace-template.
Uses molecule-ai-workspace-runtime PyPI package for shared infrastructure.

- adapter.py — runtime-specific adapter class
- requirements.txt — runtime-specific deps + molecule-ai-workspace-runtime
- Dockerfile — FROM python:3.11-slim, pip install, COPY adapter, molecule-runtime entrypoint
- ADAPTER_MODULE=adapter tells the runtime to load this repo's Adapter class

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 04:27:22 -07:00
Hongming Wang
b8859da375 feat: initial template content (extracted from molecule-monorepo) 2026-04-16 03:05:40 -07:00