From b81d8e9fc5ca2cc1d36ddd1aed619a206b94fcec Mon Sep 17 00:00:00 2001 From: rabbitblood Date: Sun, 26 Apr 2026 21:43:22 -0700 Subject: [PATCH 1/2] chore(secret-scan): add sk-cp- MiniMax pattern (F1088 retroactive fix) --- .github/workflows/secret-scan.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml index 74a5dac0..2d1e557e 100644 --- a/.github/workflows/secret-scan.yml +++ b/.github/workflows/secret-scan.yml @@ -89,6 +89,7 @@ jobs: 'sk-ant-[A-Za-z0-9_-]{40,}' # Anthropic API key 'sk-proj-[A-Za-z0-9_-]{40,}' # OpenAI project key 'sk-svcacct-[A-Za-z0-9_-]{40,}' # OpenAI service-account key + 'sk-cp-[A-Za-z0-9_-]{60,}' # MiniMax API key (F1088 vector — caught only after the fact) 'xox[baprs]-[A-Za-z0-9-]{20,}' # Slack tokens 'AKIA[0-9A-Z]{16}' # AWS access key ID 'ASIA[0-9A-Z]{16}' # AWS STS temp access key ID From 205a454c09f0bd77f9a1627d1441df70d9ad9abb Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Sun, 26 Apr 2026 22:17:49 -0700 Subject: [PATCH 2/2] feat(runtime): RuntimeCapabilities dataclass + BaseAdapter.capabilities() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Foundation primitive for the native+pluggable runtime principle (task #117, blocks #87). Lets each adapter declare which cross-cutting capabilities it owns natively (heartbeat, scheduler, durable session, status mgmt, retry, activity decoration, channel dispatch) versus delegates to the platform's fallback implementation. Pure additive: every existing adapter inherits BaseAdapter.capabilities() which returns RuntimeCapabilities() — every flag False — so today's "platform owns everything" behavior is preserved exactly. Subsequent PRs land platform-side consumers (idle-timeout override, scheduler skip, status-transition hook, etc.) one capability at a time. Why a frozen dataclass instead of class attributes: capabilities are declared at class-load time and read by the platform on every heartbeat. A mutable value would let a runtime change capabilities mid-flight, creating impossible-to-debug state where the platform's idea of who- owns-heartbeat drifts from the adapter's actual code. Why a `to_dict()` with explicit short keys: the Go side will read these from the heartbeat payload by string key. The dict's wire names are pinned independently of Python field names so a Python-side rename doesn't silently break the Go consumer (test pins this). Tests (9 new): - is a frozen dataclass (mutation rejected) - all 7 default flags are False (load-bearing — flipping any default silently moves ownership for langgraph/crewai/deepagents) - to_dict() keys are stable wire names (Go contract) - BaseAdapter.capabilities() default returns all-False - subclass override mechanism works - sibling adapters' defaults aren't affected by an override Verification: - 1300/1300 workspace pytest pass (was 1291, +9) - Zero behavior change for any existing code path See project memory `project_runtime_native_pluggable.md`. Co-Authored-By: Claude Opus 4.7 (1M context) --- workspace/adapter_base.py | 92 +++++++++++ workspace/tests/test_runtime_capabilities.py | 154 +++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 workspace/tests/test_runtime_capabilities.py diff --git a/workspace/adapter_base.py b/workspace/adapter_base.py index 8cb5cb8d..98cccca2 100644 --- a/workspace/adapter_base.py +++ b/workspace/adapter_base.py @@ -35,6 +35,83 @@ class AdapterConfig: heartbeat: Any = None # HeartbeatLoop instance +@dataclass(frozen=True) +class RuntimeCapabilities: + """Adapter-declared ownership of cross-cutting platform capabilities. + + The platform provides FALLBACK implementations of heartbeat, cron, + durable session, etc. When a runtime SDK provides one of these + natively (e.g. claude-code's streaming session model, hermes-agent's + sidecar lifecycle), the adapter sets the corresponding flag to True. + The platform reads these flags and skips its fallback for that + capability — the adapter is responsible instead. + + Observability is NEVER skipped: A2A protocol, activity_logs, and the + broadcaster always run regardless of who owns the capability. These + flags only switch WHO IMPLEMENTS the behavior, not whether the + platform sees it. + + All defaults are False so introducing this dataclass is a no-op: + every existing adapter inherits BaseAdapter.capabilities() which + returns RuntimeCapabilities() with everything off, matching today's + "platform does it all" behavior. Each capability gets a platform- + side consumer in a follow-up PR; this class is the foundation. + + See project memory `project_runtime_native_pluggable.md` for the + architecture principle these flags encode. + """ + # Heartbeat — adapter sends its own keep-alive signal to the platform's + # broadcaster instead of relying on workspace/heartbeat.py's 30s loop. + # Set True when the SDK already maintains a long-lived session that + # produces natural progress events (e.g. claude-code streaming). + provides_native_heartbeat: bool = False + + # Cron / schedule — adapter handles scheduled triggers internally + # (Temporal workflows, Durable Functions, sidecar daemons). Platform + # scheduler skips polling workspace_schedules for this workspace, + # avoiding double-fire on restart. + provides_native_scheduler: bool = False + + # Durable session — adapter persists in-flight session state across + # restarts and exposes it via pre_stop_state/restore_state. When True, + # the platform's a2a_queue does not need to enqueue mid-session + # requests; the adapter handles QUEUED-state on its own. + provides_native_session: bool = False + + # Status lifecycle — adapter reports its own ready/degraded/failed + # state (e.g. via heartbeat metadata). Platform respects the adapter + # report instead of inferring status from heartbeat error rate. + provides_native_status_mgmt: bool = False + + # Retry — adapter handles transient errors (rate limits, 5xx) with + # its own backoff. Platform stops re-dispatching A2A requests that + # the adapter explicitly marked as "retrying internally". + provides_native_retry: bool = False + + # Activity log decoration — adapter contributes runtime-specific + # fields (model, token_count, latency breakdown) into activity_log + # rows alongside the platform-defined columns. + provides_activity_decoration: bool = False + + # Channel dispatch — adapter sends to external channels (Slack, + # Lark, etc.) directly instead of routing through platform channels + # manager. Used when the SDK has built-in channel integrations. + provides_channel_dispatch: bool = False + + def to_dict(self) -> dict[str, bool]: + """Serializable shape for the heartbeat payload + /capabilities + endpoint. Plain dict avoids leaking dataclass internals to Go.""" + return { + "heartbeat": self.provides_native_heartbeat, + "scheduler": self.provides_native_scheduler, + "session": self.provides_native_session, + "status_mgmt": self.provides_native_status_mgmt, + "retry": self.provides_native_retry, + "activity_decoration": self.provides_activity_decoration, + "channel_dispatch": self.provides_channel_dispatch, + } + + class BaseAdapter(ABC): """Interface every agent infrastructure adapter must implement. @@ -72,6 +149,21 @@ class BaseAdapter(ABC): Override in subclasses for adapter-specific settings.""" return {} + def capabilities(self) -> "RuntimeCapabilities": + """Declare which cross-cutting capabilities this adapter owns + natively vs delegates to platform fallback. + + Default returns RuntimeCapabilities() — every flag False, meaning + the platform owns everything (today's behavior). Adapters override + to declare native ownership; e.g. claude-code's adapter returns + RuntimeCapabilities(provides_native_heartbeat=True, + provides_native_session=True). + + Subsequent platform-side consumers (idle-timeout override, + scheduler skip, etc.) read this and route accordingly. See + project memory `project_runtime_native_pluggable.md`.""" + return RuntimeCapabilities() + # ------------------------------------------------------------------ # Plugin install hooks # ------------------------------------------------------------------ diff --git a/workspace/tests/test_runtime_capabilities.py b/workspace/tests/test_runtime_capabilities.py new file mode 100644 index 00000000..9e48795f --- /dev/null +++ b/workspace/tests/test_runtime_capabilities.py @@ -0,0 +1,154 @@ +"""Tests for RuntimeCapabilities + BaseAdapter.capabilities() — the +foundation primitive for the native+pluggable runtime principle (task +#117). The dataclass + default method are intentionally a no-op +addition; these tests pin that contract so a future change can't +accidentally flip a default and silently move ownership. +""" +from dataclasses import is_dataclass + +import pytest + +from adapter_base import BaseAdapter, RuntimeCapabilities + + +class _MinimalAdapter(BaseAdapter): + """Concrete subclass with only the abstract members satisfied — + every other behavior should fall through to BaseAdapter defaults + so we can assert what those defaults are.""" + + @staticmethod + def name() -> str: + return "test-minimal" + + @staticmethod + def display_name() -> str: + return "Test Minimal" + + @staticmethod + def description() -> str: + return "Minimal adapter for capability default tests" + + async def setup(self, config) -> None: + return None + + async def create_executor(self, config): # pragma: no cover + raise NotImplementedError + + +class _NativeHeartbeatAdapter(_MinimalAdapter): + """Models a runtime that owns heartbeat natively — declares it via + capabilities() override. Used to verify the override mechanism + works without touching defaults.""" + + def capabilities(self) -> RuntimeCapabilities: + return RuntimeCapabilities(provides_native_heartbeat=True) + + +class TestRuntimeCapabilitiesDataclass: + """The dataclass surface itself.""" + + def test_is_a_dataclass(self): + assert is_dataclass(RuntimeCapabilities) + + def test_is_frozen(self): + # Immutability matters: capabilities are declared at class-load + # time and read by the platform on every heartbeat. A mutable + # value would let a runtime change capabilities mid-flight, + # creating impossible-to-debug state where the platform's idea + # of who-owns-heartbeat drifts from the adapter's actual code. + c = RuntimeCapabilities() + with pytest.raises((AttributeError, Exception)): + c.provides_native_heartbeat = True # type: ignore[misc] + + def test_all_defaults_false(self): + # Every flag MUST default to False — that's what makes adding + # the dataclass a no-op for existing adapters. If any default + # flips to True, every adapter that didn't override capabilities + # silently switches who-owns-that-capability and the platform + # stops providing the fallback. Catastrophic for langgraph / + # crewai / deepagents which have no native impl. + c = RuntimeCapabilities() + assert c.provides_native_heartbeat is False + assert c.provides_native_scheduler is False + assert c.provides_native_session is False + assert c.provides_native_status_mgmt is False + assert c.provides_native_retry is False + assert c.provides_activity_decoration is False + assert c.provides_channel_dispatch is False + + def test_to_dict_keys_are_stable_wire_names(self): + # The Go side reads these by string key from the heartbeat + # payload. If Python renames a field (provides_native_heartbeat + # → has_native_heartbeat) the dict's wire name should NOT change + # — pin the JSON keys here so a refactor on the Python side + # doesn't silently break the Go consumer. + c = RuntimeCapabilities() + assert set(c.to_dict().keys()) == { + "heartbeat", + "scheduler", + "session", + "status_mgmt", + "retry", + "activity_decoration", + "channel_dispatch", + } + + def test_to_dict_values_match_flags(self): + c = RuntimeCapabilities( + provides_native_heartbeat=True, + provides_native_session=True, + ) + d = c.to_dict() + assert d["heartbeat"] is True + assert d["session"] is True + # Untouched flags stay False — we don't want a "True for one + # capability flips siblings via dataclass inheritance" surprise. + assert d["scheduler"] is False + assert d["status_mgmt"] is False + + +class TestBaseAdapterCapabilitiesDefault: + """The BaseAdapter.capabilities() default — the contract every + existing adapter inherits without changes.""" + + def test_default_returns_all_false(self): + # The whole point of landing this primitive as a separate PR + # is that it's behavior-preserving for everyone. If this test + # fails, every adapter in the project has just had its + # capability declarations silently changed. + a = _MinimalAdapter() + caps = a.capabilities() + assert caps == RuntimeCapabilities() + assert caps.to_dict() == { + "heartbeat": False, + "scheduler": False, + "session": False, + "status_mgmt": False, + "retry": False, + "activity_decoration": False, + "channel_dispatch": False, + } + + def test_default_returns_RuntimeCapabilities_instance(self): + a = _MinimalAdapter() + assert isinstance(a.capabilities(), RuntimeCapabilities) + + def test_subclass_can_override_capabilities(self): + # Without this working, the entire native+pluggable principle + # is unimplementable. Pin it with a fixture that flips one flag. + a = _NativeHeartbeatAdapter() + caps = a.capabilities() + assert caps.provides_native_heartbeat is True + # Sibling flags untouched — overriding one doesn't accidentally + # move ownership of the others. + assert caps.provides_native_scheduler is False + assert caps.provides_native_session is False + + def test_override_does_not_affect_default_for_other_subclasses(self): + # Method-level dispatch, not class-attribute mutation. A + # subclass declaring native_heartbeat must NOT change what + # _MinimalAdapter (a sibling) reports. + minimal = _MinimalAdapter().capabilities() + native = _NativeHeartbeatAdapter().capabilities() + assert minimal.provides_native_heartbeat is False + assert native.provides_native_heartbeat is True