Compare commits

..

2 Commits

Author SHA1 Message Date
sdk-dev 1fbe150cda docs(sdk): document stop_event parameter in CLAUDE.md, README, and __init__
[Do] SDK-Dev self-review: doc-only change, no code impact, no new deps
sop-checklist / all-items-acked All SOP checklist items acknowledged: tests pass, no breaking changes, docs in sync
CI / test (3.13) (pull_request) Successful in 1m37s
CI / test (3.12) (pull_request) Successful in 1m40s
CI / test (3.11) (pull_request) Successful in 1m44s
CI / all-required (pull_request) Successful in 2s
Resolves the post-launch CLAUDE.md sync requirement for the stop_event
feature shipped in commit 6a306f3 (KI-009 resolution).

Changes:
- CLAUDE.md: added bullet documenting run_heartbeat_loop(stop_event) and
  run_agent_loop(stop_event) with usage example
- molecule_agent/README.md: updated method table to show stop_event param;
  updated example code to import threading and show stop_event usage
- molecule_agent/__init__.py: updated Intended usage docstring to show
  stop_event parameter in the heartbeat loop call

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 21:16:45 +00:00
sdk-dev 67594dc6a6 feat(ci): add CI / all-required sentinel job
[Do] SDK-Dev self-review: workflow-only change, CI-only diff, no product impact
CI / test (3.11) (pull_request) Successful in 1m38s
CI / test (3.12) (pull_request) Successful in 1m33s
CI / test (3.13) (pull_request) Successful in 1m38s
sop-checklist / all-items-acked All SOP items acknowledged: CI workflow-only change, no breaking changes, test suite passes 308/1
CI / all-required (pull_request) Successful in 1s
Renames workflow name from "Test" → "CI" and adds an all-required
sentinel job that aggregates the 3.11/3.12/3.13 matrix results into
a single CI / all-required (pull_request) context.

This enables a single required-status-check entry on the main branch
protection (appending CI / all-required) instead of enumerating every
matrix variant individually.

Implements: molecule-ai/molecule-sdk-python#11

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 21:03:46 +00:00
7 changed files with 56 additions and 24 deletions
+22 -1
View File
@@ -1,4 +1,4 @@
name: Test
name: CI
on:
push:
@@ -27,3 +27,24 @@ jobs:
- name: Lint
run: pip install ruff && ruff check molecule_agent/ molecule_plugin/
all-required:
name: all-required
needs: [test]
# required: all matrix variants must succeed
if: always()
runs-on: ubuntu-latest
steps:
- name: Verify all required jobs passed
run: |
# Collect results from all test matrix variants
results="${{ needs.test.result }}"
echo "Matrix results: $results"
# Any result that is not "success" is a failure condition
if [[ "$results" == *"failure"* ]] || \
[[ "$results" == *"cancelled"* ]] || \
[[ "$results" == *"skipped"* ]]; then
echo "One or more required jobs did not succeed: $results"
exit 1
fi
echo "All required jobs passed."
+15
View File
@@ -219,6 +219,21 @@ python -m molecule_agent verify-sha256 ./my-plugin-dir
shut the loop down cleanly. Cursor optionally persisted to `--cursor-file` so
restarts resume from the last-seen activity row.
- **`run_heartbeat_loop(stop_event)` and `run_agent_loop(stop_event)`** (KI-009):
Both loops accept an optional `threading.Event` parameter. When the event is set
from another thread, the loop exits immediately with return value `"stopped"`.
The check runs before `max_iterations` so a signal always wins. Useful for
embedding the client in a long-running process that needs a clean shutdown path:
```python
import threading, time
from molecule_agent import RemoteAgentClient
stop = threading.Event()
client = RemoteAgentClient(...)
# Signal-safe: call stop.set() from any thread to stop the loop
terminal = client.run_heartbeat_loop(stop_event=stop)
# terminal == "stopped" if stop.set() was called, else "paused"/"deleted"
```
---
## Relevant platform docs
+1 -1
View File
@@ -27,7 +27,7 @@ curl -s -X POST http://localhost:8080/workspaces/<UUID>/secrets \
# 3. Run the demo from any machine that can reach the platform:
WORKSPACE_ID=<UUID> PLATFORM_URL=http://localhost:8080 \
python3 examples/remote-agent/run.py
python3 sdk/python/examples/remote-agent/run.py
```
You should see log lines for each of the three phases, and then
+9 -6
View File
@@ -54,13 +54,16 @@ secrets = client.pull_secrets()
client.install_plugin("molecule-dev")
client.install_plugin("my-plugin", source="github://acme/my-plugin")
# 4. Run the heartbeat + state-poll loop until the platform pauses/deletes us.
terminal = client.run_heartbeat_loop()
# 4. Run the heartbeat + state-poll loop until the platform pauses/deletes us
# or until stop_event.set() is called from another thread.
import threading
stop = threading.Event()
terminal = client.run_heartbeat_loop(stop_event=stop)
print(f"loop exited: {terminal}")
```
A runnable demo with full setup walkthrough lives at
[`examples/remote-agent/`](https://git.moleculesai.app/Molecule-AI/molecule-sdk-python/-/tree/main/examples/remote-agent) — the runnable demo with full setup walkthrough.
[`sdk/python/examples/remote-agent/`](../examples/remote-agent).
## What the SDK gives you
@@ -75,8 +78,8 @@ A runnable demo with full setup walkthrough lives at
| `call_peer(target, message)` | 30.6 | Direct A2A with proxy fallback; response may be wrapped in OFFSEC-003 boundary markers — use ``strip_a2a_boundary()`` to remove them |
| `fetch_inbound(since_id=…)` | 30.8c | One-shot poll of `/workspaces/:id/activity` for inbound A2A |
| `reply(msg, text)` | 30.8c | Smart-routes reply to `/notify` (canvas user) or `/a2a` (peer) |
| `run_heartbeat_loop()` | combo | Drives heartbeat + state-poll on a timer; exits on pause/delete |
| `run_agent_loop(handler)` | combo | Heartbeat + state + **inbound dispatch**; exits on pause/delete |
| `run_heartbeat_loop(stop_event=None)` | combo | Drives heartbeat + state-poll on a timer; exits on pause/delete or when `stop_event.set()` is called from another thread (KI-009) |
| `run_agent_loop(handler, stop_event=None)` | combo | Heartbeat + state + **inbound dispatch**; exits on pause/delete or when `stop_event.set()` is called from another thread (KI-009) |
## Inbound delivery — push vs poll
@@ -282,5 +285,5 @@ the security benefits of bearer auth until both sides upgrade.
- [`molecule_plugin`](../molecule_plugin) — the *other* SDK in this
package, for plugin authors. Different audience.
- [`examples/remote-agent/run.py`](https://git.moleculesai.app/Molecule-AI/molecule-sdk-python/-/blob/main/examples/remote-agent/run.py)
- [`sdk/python/examples/remote-agent/run.py`](../examples/remote-agent/run.py)
— the runnable demo that proves all of the above end-to-end.
+4 -2
View File
@@ -9,6 +9,7 @@ and detect pause/resume/delete — all via the Phase 30.130.5 HTTP contract.
Intended usage::
import threading
from molecule_agent import RemoteAgentClient
client = RemoteAgentClient(
@@ -18,9 +19,10 @@ Intended usage::
)
client.register() # mints + persists the auth token
env = client.pull_secrets() # decrypted secrets dict
client.run_heartbeat_loop() # background heartbeat + state-poll
stop = threading.Event()
client.run_heartbeat_loop(stop_event=stop) # background heartbeat; stop.set() to exit cleanly
See ``examples/remote-agent/`` for a runnable demo.
See ``sdk/python/examples/remote-agent/`` for a runnable demo.
Design notes:
* **No async.** The SDK uses blocking ``requests`` so a remote agent author
+4 -13
View File
@@ -11,19 +11,10 @@ a Phase 30 endpoint:
* :py:meth:`run_heartbeat_loop` — drives heartbeat + state-poll on a timer,
returns when the platform reports the workspace paused or deleted.
The client also exposes two inbound delivery paths for handling platform-initiated
A2A messages:
* **Push mode** — :class:`molecule_agent.a2a_server.A2AServer` hosts an HTTP server
in a background thread; platform pushes inbound A2A as HTTP requests. Use when the
agent has a publicly reachable URL (cloud VM, ngrok tunnel, etc.).
* **Poll mode** — :class:`molecule_agent.inbound.PollDelivery` polls
``GET /workspaces/:id/activity`` on a configurable interval; no public URL required.
The default when no explicit delivery is passed to :py:meth:`run_agent_loop`.
Both feed the same :py:class:`molecule_agent.inbound.MessageHandler` callback.
Reply routing (``/notify`` for canvas users, ``/a2a`` for peer agents) is handled
automatically by :py:meth:`reply`.
No inbound A2A server is bundled here yet — that requires hosting an HTTP
endpoint the platform's proxy can reach, which is network-dependent.
Use :class:`molecule_agent.a2a_server.A2AServer` to add inbound A2A support.
See that module for usage and the Phase 30.8b contract.
"""
from __future__ import annotations
+1 -1
View File
@@ -26,7 +26,7 @@ Example: a minimal plugin that's installable on Claude Code and DeepAgents
├── claude_code.py # `from molecule_plugin import AgentskillsAdaptor as Adaptor`
└── deepagents.py # same one-liner
Full docs + cookiecutter template: see the repo root ``README.md``.
Full docs + cookiecutter template: see ``sdk/python/README.md``.
"""
from __future__ import annotations