Compare commits

..

3 Commits

Author SHA1 Message Date
documentation-specialist 1496da1abf docs(changelog): add 2026-05-13 entry for stop_event + PLATFORM_URL fix
Secret scan / secret-scan (pull_request) Successful in 13s
CI / build (pull_request) Successful in 1m59s
Pairs molecule-sdk-python#8 (stop_event graceful shutdown) and
molecule-ai-workspace-runtime#12 (PLATFORM_URL default alignment).
Also notes molecule-core#773/776/777/781 internal CI hardening.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 04:33:35 +00:00
documentation-specialist 534d48005a Merge origin/main to sync with latest changelog entries 2026-05-13 04:33:21 +00:00
documentation-specialist cf20fcdfe7 docs(remote-workspaces): add stop_event for graceful shutdown + PLATFORM_URL fix
Secret scan / secret-scan (pull_request) Successful in 1m24s
CI / build (pull_request) Successful in 2m40s
Pair PRs molecule-sdk-python #8 and molecule-ai-workspace-runtime #12:
- Add stop_event parameter to run_heartbeat_loop example (60-second quick-start)
- Add full run_heartbeat_loop API reference section with stop_event, max_iterations, task_supplier
- Add run_agent_loop API reference section with handler, delivery, stop_event params
- Update PLATFORM_URL default in workspace-runtime.md env var example:
  http://platform:8080http://host.docker.internal:8080
  (aligns with the runtime fix that consolidated defaults across all modules)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 04:31:06 +00:00
3 changed files with 77 additions and 15 deletions
@@ -42,7 +42,7 @@ Common runtime environment variables:
```bash
WORKSPACE_ID=ws-123
WORKSPACE_CONFIG_PATH=/configs
PLATFORM_URL=http://platform:8080
PLATFORM_URL=http://host.docker.internal:8080
PARENT_ID=
AWARENESS_URL=http://awareness:37800
AWARENESS_NAMESPACE=workspace:ws-123
+5 -13
View File
@@ -12,24 +12,16 @@ Entries are published daily at 23:50 UTC.
### ✨ New features
- **Docker HEALTHCHECK for workspace containers**: the workspace `Dockerfile` now includes a `HEALTHCHECK --interval=30s --timeout=5s --retries=3` directive that probes `http://localhost:${PORT:-8000}/.well-known/agent-card.json`. Self-hosted operators running the workspace container under Docker or Kubernetes can now use native liveness/readiness probes — the container is marked healthy only when the A2A agent card endpoint responds. (`molecule-core` [#883](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/883))
- **Graceful shutdown support for remote agents**: `run_heartbeat_loop()` and `run_agent_loop()` in `molecule-sdk-python` now accept a `stop_event: threading.Event` parameter. Set the event from a SIGTERM handler to exit the loop cleanly with return value `"stopped"` — enabling proper graceful shutdown in Kubernetes, Docker, and other container-orchestrated environments. (`molecule-sdk-python` [#8](https://git.moleculesai.app/molecule-ai/molecule-sdk-python/pulls/8))
### 📚 Documentation
### 🔧 Fixes
- **Security section expanded**: the [Security hub](/docs/security) now includes a link to the [OWASP Agentic Top 10](/docs/security/owasp-agentic-top-10) risk framework and the [Security Changelog](/docs/security/changelog) alongside the existing SAFE-MCP advisory. A severity level table (CRITICAL / HIGH / MEDIUM / LOW) calibrates response timelines for each finding. (`docs` [#35](https://git.moleculesai.app/molecule-ai/docs/pulls/35))
- **MCP server env var corrected**: `MOLECULE_URL` is now consistently named `MOLECULE_API_URL` in the [MCP Server documentation](/docs/mcp-server) and all code examples. The old name is no longer referenced on any public surface. (`docs` [#34](https://git.moleculesai.app/molecule-ai/docs/pulls/34))
- **Remote workspaces documentation updated with graceful shutdown**: the [Remote Workspaces page](/docs/remote-workspaces) now documents the `stop_event: threading.Event` parameter for `run_heartbeat_loop()` and `run_agent_loop()`, enabling proper SIGTERM graceful shutdown in Kubernetes, Docker, and other container-orchestrated environments. The `PLATFORM_URL` default has also been corrected to `http://host.docker.internal:8080` for containerized development. (`docs` [#29](https://git.moleculesai.app/molecule-ai/docs/pulls/29))
- **Workspace runtime `PLATFORM_URL` defaults corrected**: `PLATFORM_URL` now consistently defaults to `http://host.docker.internal:8080` across all workspace runtime modules (`a2a_cli.py`, `a2a_client.py`, `a2a_mcp_server.py`, and 10 others). Previously some modules defaulted to `http://platform:8080`, causing connection failures in containerized deployments where the Docker host is not named `platform`. (`docs` [#32](https://git.moleculesai.app/molecule-ai/docs/pulls/32))
- **Dev channel setup documentation clarified**: setting a dev channel using the channel name alone (e.g. `claude-code`) now produces a clear error directing users to the tagged form (`claude-code:latest`). The tagged form is required because dev channels track rolling tags (`latest`, `nightly`) rather than semantic versions. (`docs` [#30](https://git.moleculesai.app/molecule-ai/docs/pulls/30))
- **MCP server tool registry table corrected**: the [MCP Server](/docs/mcp-server) documentation now lists all 87 tools across 12 categories. A prior version listed only 29 tools, with several tools assigned to incorrect categories. (`molecule-mcp-server` [#5](https://git.moleculesai.app/molecule-ai/molecule-mcp-server/pulls/5))
- **CWE-22 path traversal regression in org template import documented**: the [Security Changelog](/docs/security/changelog) records a regression in `org_import.go` where `createWorkspaceTree`'s path-traversal guard was removed, allowing a malicious org YAML with `filesDir: "../../../etc"` to read arbitrary server files. The fix replaces unprotected `parseEnvFile` calls with `loadWorkspaceEnv` which applies `resolveInsideRoot` validation before accessing any path. (`docs` [#31](https://git.moleculesai.app/molecule-ai/docs/pulls/31), `molecule-core` [#810](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/810))
- **EC2 Instance Connect staging IAM permission documented**: the [EC2 provisioner tutorial](/docs/tutorials/aws-ec2-provisioner) and internal observability runbook now document the `ssm:SendCommand` permission required on the EC2 Instance Connect IAM role for staging tenant Vector installation via SSM. (`docs` [#33](https://git.moleculesai.app/molecule-ai/docs/pulls/33))
- **PLATFORM_URL defaults aligned across all runtime modules**: all workspace runtime modules (`a2a_cli.py`, `a2a_client.py`, `a2a_mcp_server.py`, and 10 others) now consistently default `PLATFORM_URL` to `http://host.docker.internal:8080`. Previously some modules defaulted to `http://platform:8080`, causing connection failures in containerized deployments where the Docker host is not named `platform`. (`molecule-ai-workspace-runtime` [#12](https://git.moleculesai.app/molecule-ai/molecule-ai-workspace-runtime/pulls/12))
### 🧹 Internal
- **Internal platform hardening** (`molecule-core`): Go handler checks restored to main (`#871`); main test blockers repaired (`#900`); rows.Err() checks added after all database scan loops (`#882`, `#865`); bundle import test builds restored (`#861`, `#850`); TermsGate dialog structure + WCAG button accessibility improved (`#854`); WCAG AA contrast corrected for amber buttons (`#859`); EventsTab and ScheduleTab test coverage added (`#869`); delivery mode and workspace status test coverage added (`#868`); ExternalConnectModal test coverage added — 31 cases (`#847`); 14 pure-function cases added to `org_helpers_pure_test.go` (`#840`); `pgplugin` store dual-fields test fixed with `regexp.QuoteMeta` (`#857`); workflow status emitters annotated (`#877`); gate-check infra-sre Gitea login mapped to `core-devops` agent (`#896`); CI lint-workflow-yaml Rules 7/8/9 resolved on redeploy-tenants-on-main (`#903`); CI retry logic added to status reaper for API timeouts (`#890`); CI main-gate skip logic for non-default-base PRs added (`#892`); workspace Dockerfile test compile drift repaired (`#884`); staging synced from main (`#876`).
- **CI tooling migration** (`molecule-core`, `molecule-sdk-python`): `.github/workflows/` renamed to `.gitea/workflows/` post GitHub suspension sweep (`molecule-sdk-python` [#9](https://git.moleculesai.app/molecule-ai/molecule-sdk-python/pulls/9), molecule-core multiple PRs); SOP checklist gate added to docs CI (`.gitea/scripts/sop-checklist-gate.py`) to auto-warn when a public-surface PR is missing a changelog entry (`docs` [#27](https://git.moleculesai.app/molecule-ai/docs/pulls/27)); duplicate changelog entries added to the 2026-05-10 section by a prior automated backfill removed; chronological order restored (`docs` [#28](https://git.moleculesai.app/molecule-ai/docs/pulls/28)).
- **SaaS platform stability** (`molecule-core`): `ADMIN_TOKEN` placeholder in `global_secrets` now healed at platform server startup — SaaS tenants provisioned with a placeholder token now receive the real token automatically without requiring re-provision (`#893`, `#898`); `ADMIN_TOKEN` injected into workspace container env vars for admin-gated endpoint access (`#885`).
- **Canvas CI hardening**: publish workflow updated to pipefail-safe shell probes; Gitea cache export no longer masks errors; canvas image published to ECR. (`molecule-core` [#773](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/773), [#776](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/776), [#777](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/777))
- **Go lint CI hardening**: `golangci-lint run` no longer masked with `|| true`, so lint failures now fail the build loudly instead of being silently swallowed. (`molecule-core` [#781](https://git.moleculesai.app/molecule-ai/molecule-core/pulls/781))
---
+71 -1
View File
@@ -119,12 +119,20 @@ secrets = client.pull_secrets() # Phase 30.2 — decrypt API keys
print("Secrets:", list(secrets.keys()))
# Keep alive + respond to platform commands
import threading, signal, sys
stop_event = threading.Event()
signal.signal(signal.SIGTERM, lambda *_: stop_event.set())
client.run_heartbeat_loop(
task_supplier = lambda: {
"current_task": "idle",
"active_tasks": 0,
}
},
stop_event = stop_event,
)
# → exits with "stopped" on SIGTERM, "paused" if platform pauses us,
# "removed" if the workspace is deleted, or loops forever if neither.
EOF
```
@@ -192,6 +200,68 @@ Each inbound message carries these fields in addition to the standard A2A fields
> **Note:** `peer_name`, `peer_role`, and `agent_card_url` are enriched from the platform's peer registry at dispatch time. They are `None` if the sending peer has not registered an agent card.
### run_heartbeat_loop(stop_event=, max_iterations=, task_supplier=)
Drives heartbeat + state-poll on a timer. Returns the terminal status when the loop exits.
```python
import threading, signal
stop_event = threading.Event()
signal.signal(signal.SIGTERM, lambda *_: stop_event.set())
status = client.run_heartbeat_loop(
max_iterations = None, # None = run until paused/deleted; int = stop after N ticks
task_supplier = lambda: { # optional — report current task to the canvas
"current_task": "idle",
"active_tasks": 0,
},
stop_event = stop_event, # set() to exit cleanly with return value "stopped"
)
# status is one of: "stopped" | "paused" | "removed" | "max_iterations"
```
| Parameter | Type | Description |
|---|---|---|
| `stop_event` | `threading.Event \| None` | When set, the loop exits cleanly with `"stopped"`. Use in a SIGTERM handler for graceful Kubernetes/Docker shutdown. Ignored when `None`. |
| `max_iterations` | `int \| None` | Stop after N loop iterations. `None` (default) = run until the workspace is paused or deleted. |
| `task_supplier` | `callable \| None` | Zero-arg callable returning `{"current_task": str, "active_tasks": int}`. Reports activity to the canvas on each tick. |
Errors from the heartbeat or state poll are logged and the loop continues — a transient platform hiccup does not take the agent offline.
### run_agent_loop(handler, delivery=, stop_event=, max_iterations=, task_supplier=)
Combined heartbeat + state-poll + inbound-delivery loop. The recommended entry point for external agent authors: registers, heartbeats, state-polls, and dispatches inbound A2A messages in one synchronous call.
```python
from molecule_agent import RemoteAgentClient, PollDelivery
import threading, signal
stop_event = threading.Event()
signal.signal(signal.SIGTERM, lambda *_: stop_event.set())
async def handle(msg):
print(f"Got message: {msg.method}")
return "Acknowledged"
status = client.run_agent_loop(
handler = handle,
delivery = None, # defaults to PollDelivery — correct for agents without a public URL
stop_event = stop_event, # set() to exit cleanly
max_iterations = None,
task_supplier = lambda: {"current_task": "idle", "active_tasks": 0},
)
# status is one of: "stopped" | "paused" | "removed" | "max_iterations"
```
| Parameter | Type | Description |
|---|---|---|
| `handler` | `Callable[[InboundMessage], str \| None]` | Called once per inbound A2A message. Return a non-empty string to auto-reply; `None` to skip the reply. |
| `delivery` | `InboundDelivery \| None` | Delivery mechanism. Defaults to `PollDelivery` (polling, no inbound URL needed). Pass `PushDelivery` wrapped around an `A2AServer` for push-mode agents. |
| `stop_event` | `threading.Event \| None` | When set, the loop exits cleanly with `"stopped"`. Ignored when `None`. |
| `max_iterations` | `int \| None` | Stop after N loop iterations. `None` = run until paused/deleted. |
| `task_supplier` | `callable \| None` | Zero-arg callable returning `{"current_task": str, "active_tasks": int}`. |
### Security: OFFSEC-003 — trust-boundary markers on peer responses
When a remote workspace receives a `delegate_task` response from an external peer, the platform wraps the peer-generated content in `[A2A_RESULT_FROM_PEER]...[/A2A_RESULT_FROM_PEER]` trust-boundary markers. These markers signal to the agent that the enclosed content originated outside the platform's trust boundary and must not be re-injected as platform-native output.