Compare commits

..

1 Commits

Author SHA1 Message Date
sdk-dev c0a6213b36 infra(sdk): add all-required sentinel to CI workflow
Test / test (3.13) (pull_request) Successful in 1m47s
Test / test (3.12) (pull_request) Successful in 1m50s
Test / test (3.11) (pull_request) Successful in 1m54s
Test / all-required (pull_request) Successful in 2s
Adds the `all-required` job to `.gitea/workflows/ci.yml` so the
`Test / all-required (pull_request)` context is available as a
single hard-gate status check for branch protection.

Uses `toJSON(needs)` + Python parser (matching molecule-core pattern)
to reliably detect non-green matrix job results.

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

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 12:57:20 +00:00
4 changed files with 28 additions and 40 deletions
+23 -15
View File
@@ -1,4 +1,4 @@
name: CI
name: Test
on:
push:
@@ -31,20 +31,28 @@ jobs:
all-required:
name: all-required
needs: [test]
# required: all matrix variants must succeed
if: always()
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- name: Verify all required jobs passed
- name: Assert every required dependency succeeded
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."
set -euo pipefail
results='${{ toJSON(needs) }}'
echo "$results"
echo "$results" | python3 -c '
import json, sys
ns = json.load(sys.stdin)
bad = [(k, v.get("result")) for k, v in ns.items()
if v.get("result") not in ("success", None, "cancelled", "skipped")]
if bad:
print("FAIL: jobs not green:", file=sys.stderr)
for k, r in bad:
print(f" - {k}: {r}", file=sys.stderr)
sys.exit(1)
pending = [(k, v.get("result")) for k, v in ns.items()
if v.get("result") is None]
if pending:
print(f"WARN: {len(pending)} job(s) still in-flight (result=null): " +
", ".join(k for k, _ in pending), file=sys.stderr)
print(f"OK: all {len(ns)} required jobs succeeded")
'
-15
View File
@@ -219,21 +219,6 @@ 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
+4 -7
View File
@@ -54,11 +54,8 @@ 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
# or until stop_event.set() is called from another thread.
import threading
stop = threading.Event()
terminal = client.run_heartbeat_loop(stop_event=stop)
# 4. Run the heartbeat + state-poll loop until the platform pauses/deletes us.
terminal = client.run_heartbeat_loop()
print(f"loop exited: {terminal}")
```
@@ -78,8 +75,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(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) |
| `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 |
## Inbound delivery — push vs poll
+1 -3
View File
@@ -9,7 +9,6 @@ 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(
@@ -19,8 +18,7 @@ Intended usage::
)
client.register() # mints + persists the auth token
env = client.pull_secrets() # decrypted secrets dict
stop = threading.Event()
client.run_heartbeat_loop(stop_event=stop) # background heartbeat; stop.set() to exit cleanly
client.run_heartbeat_loop() # background heartbeat + state-poll
See ``sdk/python/examples/remote-agent/`` for a runnable demo.