From 67594dc6a6686d0bcff289a0d2e18e2199c9821e Mon Sep 17 00:00:00 2001 From: Molecule AI SDK-Dev Date: Sat, 16 May 2026 21:03:46 +0000 Subject: [PATCH 1/2] feat(ci): add CI / all-required sentinel job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .gitea/workflows/ci.yml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 7dfd1d0..49bf3ce 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -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." -- 2.52.0 From 1fbe150cdaa21780f2a3b02986f0ad0f5f199e78 Mon Sep 17 00:00:00 2001 From: Molecule AI SDK-Dev Date: Sat, 16 May 2026 21:16:45 +0000 Subject: [PATCH 2/2] docs(sdk): document stop_event parameter in CLAUDE.md, README, and __init__ 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 --- CLAUDE.md | 15 +++++++++++++++ molecule_agent/README.md | 11 +++++++---- molecule_agent/__init__.py | 4 +++- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1ebea23..2f8044e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/molecule_agent/README.md b/molecule_agent/README.md index 535125b..0c2e711 100644 --- a/molecule_agent/README.md +++ b/molecule_agent/README.md @@ -54,8 +54,11 @@ 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}") ``` @@ -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 diff --git a/molecule_agent/__init__.py b/molecule_agent/__init__.py index a9f4f83..2b50dc6 100644 --- a/molecule_agent/__init__.py +++ b/molecule_agent/__init__.py @@ -9,6 +9,7 @@ and detect pause/resume/delete — all via the Phase 30.1–30.5 HTTP contract. Intended usage:: + import threading from molecule_agent import RemoteAgentClient client = RemoteAgentClient( @@ -18,7 +19,8 @@ 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 ``sdk/python/examples/remote-agent/`` for a runnable demo. -- 2.52.0