From 523a2c92dc6ba7943463c5a8387fec9b780d0d16 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Thu, 5 Mar 2026 02:23:55 -0800 Subject: [PATCH 1/9] feat: emit SESSION_END in Python cleanup() to match Rust behavior The Python cleanup() method never emitted SESSION_END, while the Rust implementation did. Now emits SESSION_END with session_id and status before coordinator.cleanup(), guarded by self._initialized, wrapped in try/except for best-effort reliability. --- python/amplifier_core/session.py | 16 ++++++++ tests/test_session.py | 66 ++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/python/amplifier_core/session.py b/python/amplifier_core/session.py index d641d7a..a93e595 100644 --- a/python/amplifier_core/session.py +++ b/python/amplifier_core/session.py @@ -457,6 +457,22 @@ async def execute(self, prompt: str) -> str: async def cleanup(self: "AmplifierSession") -> None: """Clean up session resources.""" try: + # Emit SESSION_END before coordinator cleanup (matches Rust behavior) + if self._initialized: + try: + from .events import SESSION_END + + await self.coordinator.hooks.emit( + SESSION_END, + { + "session_id": self.session_id, + "status": self.status.status, + }, + ) + except Exception: + # Best-effort: cleanup must never crash on emit failure + pass + await self.coordinator.cleanup() finally: # Clean up sys.path modifications - must always run even if diff --git a/tests/test_session.py b/tests/test_session.py index 38e5cbc..189aafe 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -209,3 +209,69 @@ async def test_session_with_custom_loader(): session = PyAmplifierSession(config, loader=custom_loader) assert session.loader is custom_loader + + +@pytest.mark.asyncio +async def test_cleanup_emits_session_end(minimal_config): + """Test that cleanup() emits SESSION_END with session_id and status.""" + from amplifier_core.events import SESSION_END + from amplifier_core.models import HookResult + + session = PyAmplifierSession(minimal_config) + session._initialized = True + + # Track emitted events via a registered hook handler + emitted_events = [] + + async def capture_handler(event, data): + emitted_events.append((event, dict(data))) + return HookResult(action="continue") + + session.coordinator.hooks.on(SESSION_END, capture_handler, name="test-capture") + + # Mock coordinator.cleanup to avoid actual module cleanup + session.coordinator.cleanup = AsyncMock() + + await session.cleanup() + + # Filter for SESSION_END events + session_end_events = [(e, d) for e, d in emitted_events if e == SESSION_END] + + assert len(session_end_events) == 1, ( + f"Expected exactly 1 SESSION_END event, got {len(session_end_events)}" + ) + _, data = session_end_events[0] + assert "session_id" in data, "SESSION_END data must contain session_id" + assert "status" in data, "SESSION_END data must contain status" + assert data["session_id"] == session.session_id + + +@pytest.mark.asyncio +async def test_cleanup_does_not_emit_session_end_when_not_initialized(minimal_config): + """Test that cleanup() does NOT emit SESSION_END for uninitialized sessions.""" + from amplifier_core.events import SESSION_END + from amplifier_core.models import HookResult + + session = PyAmplifierSession(minimal_config) + # Do NOT set _initialized = True (default is False) + + # Track emitted events via a registered hook handler + emitted_events = [] + + async def capture_handler(event, data): + emitted_events.append((event, dict(data))) + return HookResult(action="continue") + + session.coordinator.hooks.on(SESSION_END, capture_handler, name="test-capture") + + # Mock coordinator.cleanup to avoid actual module cleanup + session.coordinator.cleanup = AsyncMock() + + await session.cleanup() + + # Filter for SESSION_END events + session_end_events = [(e, d) for e, d in emitted_events if e == SESSION_END] + + assert len(session_end_events) == 0, ( + f"Expected 0 SESSION_END events for uninitialized session, got {len(session_end_events)}" + ) From 0ea1de8b1d52e14ef065f0cc41ab5df2aacb6da8 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Thu, 5 Mar 2026 03:32:51 -0800 Subject: [PATCH 2/9] fix: log SESSION_END emission failure in cleanup instead of silently passing --- python/amplifier_core/session.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/amplifier_core/session.py b/python/amplifier_core/session.py index a93e595..6c00b5a 100644 --- a/python/amplifier_core/session.py +++ b/python/amplifier_core/session.py @@ -470,8 +470,9 @@ async def cleanup(self: "AmplifierSession") -> None: }, ) except Exception: - # Best-effort: cleanup must never crash on emit failure - pass + logger.debug( + "Failed to emit SESSION_END during cleanup", exc_info=True + ) await self.coordinator.cleanup() finally: From 399449651f6b38d8fe6931de3cf83991f3563107 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Thu, 5 Mar 2026 03:38:52 -0800 Subject: [PATCH 3/9] test: verify SESSION_END emits before coordinator.cleanup() Add ordering test that records call sequence via hook handler and tracking cleanup function, asserting SESSION_END fires first. Addresses code quality review suggestion for explicit ordering verification. --- Cargo.lock | 4 ++-- tests/test_session.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 169615e..6a1520b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,7 +40,7 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "amplifier-core" -version = "1.0.9" +version = "1.0.10" dependencies = [ "chrono", "log", @@ -59,7 +59,7 @@ dependencies = [ [[package]] name = "amplifier-core-py" -version = "1.0.9" +version = "1.0.10" dependencies = [ "amplifier-core", "log", diff --git a/tests/test_session.py b/tests/test_session.py index 189aafe..eb0941c 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -275,3 +275,34 @@ async def capture_handler(event, data): assert len(session_end_events) == 0, ( f"Expected 0 SESSION_END events for uninitialized session, got {len(session_end_events)}" ) + + +@pytest.mark.asyncio +async def test_cleanup_emits_session_end_before_coordinator_cleanup(minimal_config): + """Test that SESSION_END is emitted before coordinator.cleanup() runs.""" + from amplifier_core.events import SESSION_END + from amplifier_core.models import HookResult + + session = PyAmplifierSession(minimal_config) + session._initialized = True + + # Record the order of operations + call_order = [] + + async def capture_handler(event, data): + call_order.append("session_end_emitted") + return HookResult(action="continue") + + session.coordinator.hooks.on(SESSION_END, capture_handler, name="test-capture") + + # Mock coordinator.cleanup to record when it runs + async def tracking_cleanup(): + call_order.append("coordinator_cleanup") + + session.coordinator.cleanup = tracking_cleanup + + await session.cleanup() + + assert call_order == ["session_end_emitted", "coordinator_cleanup"], ( + f"Expected SESSION_END before coordinator.cleanup(), got: {call_order}" + ) From a4d16ef7f5c4ce6483a1615a833397e07f3809bb Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Thu, 5 Mar 2026 04:41:57 -0800 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20CP-SM=20=E2=80=94=20session=20metad?= =?UTF-8?q?ata=20passthrough=20on=20session:start/fork/resume?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Read config.session.metadata and include it as optional 'metadata' key in session:start, session:fork, and session:resume event payloads. Pure passthrough — the kernel does not interpret, validate, or enforce any schema on the metadata dict. The key is only included when config.session.metadata is truthy, so existing consumers that do not set metadata see no change in payload structure. Tests added: - test_session_start_includes_metadata_when_configured - test_session_start_excludes_metadata_when_not_configured - test_session_resume_includes_metadata_when_configured - test_session_resume_excludes_metadata_when_not_configured - test_session_fork_includes_metadata_when_configured - test_session_fork_excludes_metadata_when_not_configured - test_session_fork_still_has_parent_and_session_id --- python/amplifier_core/session.py | 4 + tests/test_session_metadata.py | 276 +++++++++++++++++++++++++++++++ 2 files changed, 280 insertions(+) create mode 100644 tests/test_session_metadata.py diff --git a/python/amplifier_core/session.py b/python/amplifier_core/session.py index 6c00b5a..26963b5 100644 --- a/python/amplifier_core/session.py +++ b/python/amplifier_core/session.py @@ -274,11 +274,13 @@ async def initialize(self) -> None: if self.parent_id: from .events import SESSION_FORK, SESSION_FORK_DEBUG, SESSION_FORK_RAW + session_metadata = self.config.get("session", {}).get("metadata", {}) await self.coordinator.hooks.emit( SESSION_FORK, { "parent": self.parent_id, "session_id": self.session_id, + **({"metadata": session_metadata} if session_metadata else {}), }, ) @@ -350,11 +352,13 @@ async def execute(self, prompt: str) -> str: event_raw = SESSION_START_RAW # Emit session lifecycle event from kernel (single source of truth) + session_metadata = self.config.get("session", {}).get("metadata", {}) await self.coordinator.hooks.emit( event_base, { "session_id": self.session_id, "parent_id": self.parent_id, + **({"metadata": session_metadata} if session_metadata else {}), }, ) diff --git a/tests/test_session_metadata.py b/tests/test_session_metadata.py new file mode 100644 index 0000000..ba2ccf0 --- /dev/null +++ b/tests/test_session_metadata.py @@ -0,0 +1,276 @@ +""" +Tests for session metadata passthrough on session:start, session:fork, session:resume. + +CP-SM: Kernel reads config.session.metadata and includes it as optional 'metadata' +key in event payloads. Pure passthrough - no interpretation or validation. +""" + +from unittest.mock import AsyncMock, Mock + +import pytest +from amplifier_core.events import SESSION_FORK, SESSION_RESUME, SESSION_START +from amplifier_core.models import HookResult +from amplifier_core.session import AmplifierSession as PyAmplifierSession + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _minimal_config_with_metadata(metadata): + """Config that includes session.metadata.""" + return { + "session": { + "orchestrator": "loop-basic", + "context": "context-simple", + "metadata": metadata, + }, + "providers": [], + "tools": [], + } + + +def _minimal_config_no_metadata(): + """Config without session.metadata.""" + return { + "session": { + "orchestrator": "loop-basic", + "context": "context-simple", + }, + "providers": [], + "tools": [], + } + + +def _setup_mock_loader(session): + """Replace the session loader with a mock that succeeds silently.""" + mock_mount = AsyncMock(return_value=None) + session.loader.load = AsyncMock(return_value=mock_mount) + + +def _make_capture_handler(events_list): + """Return an async hook handler that appends (event, data) to events_list.""" + + async def handler(event, data): + events_list.append((event, dict(data))) + return HookResult(action="continue") + + return handler + + +# --------------------------------------------------------------------------- +# session:start metadata tests +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_session_start_includes_metadata_when_configured(): + """session:start payload includes 'metadata' when config.session.metadata is set.""" + metadata = {"agent_name": "test-agent", "run_id": "abc123"} + config = _minimal_config_with_metadata(metadata) + + session = PyAmplifierSession(config) + session._initialized = True + + # Mount minimal mocks + mock_orchestrator = AsyncMock() + mock_orchestrator.execute = AsyncMock(return_value="ok") + mock_context = Mock() + mock_context.add_message = AsyncMock() + mock_context.get_messages = AsyncMock(return_value=[]) + session.coordinator.mount_points["orchestrator"] = mock_orchestrator + session.coordinator.mount_points["context"] = mock_context + session.coordinator.mount_points["providers"] = {"mock": Mock()} + + emitted = [] + session.coordinator.hooks.on( + SESSION_START, _make_capture_handler(emitted), name="test-capture" + ) + + await session.execute("hello") + + start_events = [d for e, d in emitted if e == SESSION_START] + assert len(start_events) == 1, f"Expected 1 SESSION_START, got {len(start_events)}" + payload = start_events[0] + assert "metadata" in payload, "Expected 'metadata' key in session:start payload" + assert payload["metadata"] == metadata + + +@pytest.mark.asyncio +async def test_session_start_excludes_metadata_when_not_configured(): + """session:start payload does NOT include 'metadata' when config.session.metadata is absent.""" + config = _minimal_config_no_metadata() + + session = PyAmplifierSession(config) + session._initialized = True + + mock_orchestrator = AsyncMock() + mock_orchestrator.execute = AsyncMock(return_value="ok") + mock_context = Mock() + mock_context.add_message = AsyncMock() + mock_context.get_messages = AsyncMock(return_value=[]) + session.coordinator.mount_points["orchestrator"] = mock_orchestrator + session.coordinator.mount_points["context"] = mock_context + session.coordinator.mount_points["providers"] = {"mock": Mock()} + + emitted = [] + session.coordinator.hooks.on( + SESSION_START, _make_capture_handler(emitted), name="test-capture" + ) + + await session.execute("hello") + + start_events = [d for e, d in emitted if e == SESSION_START] + assert len(start_events) == 1 + payload = start_events[0] + assert "metadata" not in payload, ( + "Expected no 'metadata' key in session:start payload when not configured" + ) + + +# --------------------------------------------------------------------------- +# session:resume metadata tests +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_session_resume_includes_metadata_when_configured(): + """session:resume payload includes 'metadata' when config.session.metadata is set.""" + metadata = {"agent_name": "resumed-agent"} + config = _minimal_config_with_metadata(metadata) + + session = PyAmplifierSession(config, is_resumed=True) + session._initialized = True + + mock_orchestrator = AsyncMock() + mock_orchestrator.execute = AsyncMock(return_value="ok") + mock_context = Mock() + session.coordinator.mount_points["orchestrator"] = mock_orchestrator + session.coordinator.mount_points["context"] = mock_context + session.coordinator.mount_points["providers"] = {"mock": Mock()} + + emitted = [] + session.coordinator.hooks.on( + SESSION_RESUME, _make_capture_handler(emitted), name="test-capture" + ) + + await session.execute("hello") + + resume_events = [d for e, d in emitted if e == SESSION_RESUME] + assert len(resume_events) == 1, ( + f"Expected 1 SESSION_RESUME, got {len(resume_events)}" + ) + payload = resume_events[0] + assert "metadata" in payload, "Expected 'metadata' key in session:resume payload" + assert payload["metadata"] == metadata + + +@pytest.mark.asyncio +async def test_session_resume_excludes_metadata_when_not_configured(): + """session:resume payload does NOT include 'metadata' when not set in config.""" + config = _minimal_config_no_metadata() + + session = PyAmplifierSession(config, is_resumed=True) + session._initialized = True + + mock_orchestrator = AsyncMock() + mock_orchestrator.execute = AsyncMock(return_value="ok") + mock_context = Mock() + session.coordinator.mount_points["orchestrator"] = mock_orchestrator + session.coordinator.mount_points["context"] = mock_context + session.coordinator.mount_points["providers"] = {"mock": Mock()} + + emitted = [] + session.coordinator.hooks.on( + SESSION_RESUME, _make_capture_handler(emitted), name="test-capture" + ) + + await session.execute("hello") + + resume_events = [d for e, d in emitted if e == SESSION_RESUME] + assert len(resume_events) == 1 + payload = resume_events[0] + assert "metadata" not in payload, ( + "Expected no 'metadata' key in session:resume payload when not configured" + ) + + +# --------------------------------------------------------------------------- +# session:fork metadata tests +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_session_fork_includes_metadata_when_configured(): + """session:fork payload includes 'metadata' when config.session.metadata is set.""" + metadata = {"agent_name": "child-agent", "depth": 1} + config = _minimal_config_with_metadata(metadata) + + parent_id = "parent-session-id-123" + session = PyAmplifierSession(config, parent_id=parent_id) + + _setup_mock_loader(session) + + emitted = [] + session.coordinator.hooks.on( + SESSION_FORK, _make_capture_handler(emitted), name="test-capture" + ) + + await session.initialize() + + fork_events = [d for e, d in emitted if e == SESSION_FORK] + assert len(fork_events) == 1, f"Expected 1 SESSION_FORK, got {len(fork_events)}" + payload = fork_events[0] + assert "metadata" in payload, "Expected 'metadata' key in session:fork payload" + assert payload["metadata"] == metadata + + +@pytest.mark.asyncio +async def test_session_fork_excludes_metadata_when_not_configured(): + """session:fork payload does NOT include 'metadata' when config.session.metadata is absent.""" + config = _minimal_config_no_metadata() + + parent_id = "parent-session-id-456" + session = PyAmplifierSession(config, parent_id=parent_id) + + _setup_mock_loader(session) + + emitted = [] + session.coordinator.hooks.on( + SESSION_FORK, _make_capture_handler(emitted), name="test-capture" + ) + + await session.initialize() + + fork_events = [d for e, d in emitted if e == SESSION_FORK] + assert len(fork_events) == 1 + payload = fork_events[0] + assert "metadata" not in payload, ( + "Expected no 'metadata' key in session:fork payload when not configured" + ) + + +@pytest.mark.asyncio +async def test_session_fork_still_has_parent_and_session_id(): + """session:fork payload still contains parent and session_id regardless of metadata.""" + metadata = {"tag": "some-tag"} + config = _minimal_config_with_metadata(metadata) + + parent_id = "parent-xyz" + session = PyAmplifierSession(config, parent_id=parent_id) + + _setup_mock_loader(session) + + emitted = [] + session.coordinator.hooks.on( + SESSION_FORK, _make_capture_handler(emitted), name="test-capture" + ) + + await session.initialize() + + fork_events = [d for e, d in emitted if e == SESSION_FORK] + assert len(fork_events) == 1 + payload = fork_events[0] + assert payload["parent"] == parent_id + assert "session_id" in payload From a93849b571da17e61afb48e5d78c92de1ac059b5 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Thu, 5 Mar 2026 04:45:02 -0800 Subject: [PATCH 5/9] docs: mandate execution:start/end and observability.events in orchestrator contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add coordinator parameter to Orchestrator.execute() protocol definition (optional; already passed by session.py in production — fixes contract drift) - Add Required section for execution:start/end events with: - Payload specs: execution:start {prompt}, execution:end {response, status} - Status values: 'completed', 'cancelled', 'error' - Complete try/except/CancelledError code example covering all exit paths - Field reference table and note to use constants from amplifier_core.events - Promote observability.events registration from Recommended to Required - Updated Observability section prose to MUST language - Added note that kernel-standard events need not be re-registered - Update Validation Checklist: - Required: add execution:start/end check items (bolded) - Required: add observability.events registration item - Recommended: remove observability.events (now Required) - Required: update execute() signature to include coordinator=None - Update frontmatter: last_modified date and protocol definition line range --- docs/contracts/ORCHESTRATOR_CONTRACT.md | 69 +++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/docs/contracts/ORCHESTRATOR_CONTRACT.md b/docs/contracts/ORCHESTRATOR_CONTRACT.md index 88ab9db..44c5db2 100644 --- a/docs/contracts/ORCHESTRATOR_CONTRACT.md +++ b/docs/contracts/ORCHESTRATOR_CONTRACT.md @@ -2,11 +2,11 @@ contract_type: module_specification module_type: orchestrator contract_version: 1.0.0 -last_modified: 2025-01-29 +last_modified: 2026-03-05 related_files: - path: amplifier_core/interfaces.py#Orchestrator relationship: protocol_definition - lines: 26-52 + lines: 26-55 - path: amplifier_core/content_models.py relationship: event_content_types - path: ../specs/MOUNT_PLAN_SPECIFICATION.md @@ -50,6 +50,7 @@ class Orchestrator(Protocol): providers: dict[str, Provider], tools: dict[str, Tool], hooks: HookRegistry, + coordinator: ModuleCoordinator | None = None, ) -> str: """ Execute the agent loop with given prompt. @@ -60,6 +61,8 @@ class Orchestrator(Protocol): providers: Available LLM providers (keyed by name) tools: Available tools (keyed by name) hooks: Hook registry for lifecycle events + coordinator: Module coordinator for accessing shared services + (optional; passed by session.py in production) Returns: Final response string @@ -189,6 +192,54 @@ async def execute(self, prompt, context, providers, tools, hooks): | `turn_count` | int | Number of LLM call iterations | | `status` | string | Exit status: `"success"`, `"incomplete"`, or `"cancelled"` | +#### Required: execution:start and execution:end Events + +**All orchestrators MUST emit `execution:start` and `execution:end`** to mark the boundaries of every `execute()` invocation. These events are the primary observability signal used by the kernel for session lifecycle tracking, metrics, and tracing. + +- `execution:start` MUST be emitted at the **very beginning** of `execute()`, before any other work +- `execution:end` MUST be emitted on **ALL exit paths** — normal completion, error, and cancellation + +```python +async def execute(self, prompt, context, providers, tools, hooks, coordinator=None): + # REQUIRED: Emit at the very start of execute() + await hooks.emit("execution:start", {"prompt": prompt}) + + try: + # ... agent loop logic ... + + # REQUIRED: Emit on successful completion + await hooks.emit("execution:end", { + "response": final_response, + "status": "completed" + }) + return final_response + + except CancelledError: + # REQUIRED: Emit on cancellation + await hooks.emit("execution:end", { + "response": "", + "status": "cancelled" + }) + raise + + except Exception: + # REQUIRED: Emit on error + await hooks.emit("execution:end", { + "response": "", + "status": "error" + }) + raise +``` + +| Event | Field | Type | Description | +|-------|-------|------|-------------| +| `execution:start` | `prompt` | string | The user prompt passed to `execute()` | +| `execution:end` | `response` | string | Final response string (empty on error/cancellation) | +| `execution:end` | `status` | string | `"completed"`, `"cancelled"`, or `"error"` | + +> **Constants**: `execution:start` and `execution:end` are defined in `amplifier_core.events` +> (Python) and `amplifier_core::events` (Rust). Use the constants rather than string literals. + ### Hook Processing Handle HookResult actions: @@ -274,7 +325,9 @@ See [MOUNT_PLAN_SPECIFICATION.md](../specs/MOUNT_PLAN_SPECIFICATION.md) for full ## Observability -Register custom events your orchestrator emits: +Orchestrators **MUST** register the custom events they emit via the `observability.events` +contribution channel. This enables runtime introspection of which events are available and allows +tooling, dashboards, and other modules to discover orchestrator-specific signals. ```python coordinator.register_contributor( @@ -288,6 +341,10 @@ coordinator.register_contributor( ) ``` +> **Note**: The standard `execution:start`, `execution:end`, and `orchestrator:complete` events are +> registered by the kernel and do not need to be re-registered. Only register events that are +> unique to your orchestrator module. + See [CONTRIBUTION_CHANNELS.md](../specs/CONTRIBUTION_CHANNELS.md) for the pattern. --- @@ -312,19 +369,21 @@ Additional examples: ### Required -- [ ] Implements `execute(prompt, context, providers, tools, hooks) -> str` +- [ ] Implements `execute(prompt, context, providers, tools, hooks, coordinator=None) -> str` - [ ] `mount()` function with entry point in pyproject.toml +- [ ] **Emits `execution:start` with `{prompt}` at the very beginning of `execute()`** +- [ ] **Emits `execution:end` with `{response, status}` on ALL exit paths (success, error, cancellation)** - [ ] Emits standard events (provider:request/response, tool:pre/post) - [ ] **Emits `orchestrator:complete` at the end of execute()** - [ ] Handles HookResult actions appropriately - [ ] Manages context (add messages, check compaction) +- [ ] Registers custom observability events via `coordinator.register_contributor("observability.events", ...)` ### Recommended - [ ] Supports multiple providers - [ ] Implements max iterations limit (prevent infinite loops) - [ ] Handles provider errors gracefully -- [ ] Registers custom observability events - [ ] Supports streaming via async generators --- From fcc854e364e4590719b34344a526bd42357450f1 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Thu, 5 Mar 2026 05:42:35 -0800 Subject: [PATCH 6/9] feat(cp-v): remove tiered event constants, collapse session.py emissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CP-V Kernel verbosity collapse — Task 12 of 16. BREAKING CHANGE: The 10 tiered :debug and :raw event constants are removed. Hooks subscribed to session:start:debug, session:start:raw, session:fork:debug, session:fork:raw, session:resume:debug, session:resume:raw, llm:request:debug, llm:request:raw, llm:response:debug, llm:response:raw will no longer receive events. Changes: - crates/amplifier-core/src/events.rs: Remove 10 :debug/:raw constants and their ALL_EVENTS entries; update tests (count 51→41, remove tiered entries) - bindings/python/src/lib.rs: Remove 10 constant registrations from _engine module; update execute() to use emit_raw_field_if_configured instead of emit_debug_events (no tiers, single base event with optional raw field) - python/amplifier_core/events.py: Remove 10 imports; build ALL_EVENTS list locally from 41 remaining constants (not from _engine to avoid stale .so) - python/amplifier_core/session.py: Collapse initialize() fork path and execute() start/resume path from 3-tier to single emission; add raw field when session.raw=true; remove truncate_values import (no longer needed) - python/amplifier_core/_session_exec.py: Replace emit_debug_events() with emit_raw_field_if_configured() — no tiered events, raw field only - python/amplifier_core/_engine.pyi: Add 41 module-level event constant stubs; remove the 10 removed constants - bindings/python/tests/test_event_constants.py: Update ALL_EVENT_NAMES (41), add REMOVED_EVENT_NAMES list for documentation; count 51→41 - bindings/python/tests/test_python_stubs.py: Update ALL_EVENTS count 51→41 - bindings/python/tests/test_schema_sync.py: Update ALL_EVENTS count 51→41 - tests/test_event_taxonomy.py: Remove :debug/:raw from valid_detail_suffixes; enforce that all events must be exactly 2-part namespace:action - tests/test_cpv_kernel_verbosity_collapse.py: New test file with 22 tests verifying removed constants, count=41, raw field behavior, redaction Config change: session.debug + session.raw_debug → single session.raw flag. Set session.raw: true to include redacted config in event payloads. --- bindings/python/src/lib.rs | 62 +-- bindings/python/tests/test_event_constants.py | 45 +- bindings/python/tests/test_python_stubs.py | 3 +- bindings/python/tests/test_schema_sync.py | 3 +- crates/amplifier-core/src/events.rs | 64 +-- python/amplifier_core/_engine.pyi | 47 ++ python/amplifier_core/_session_exec.py | 46 +- python/amplifier_core/events.py | 78 +++- python/amplifier_core/session.py | 113 ++--- tests/test_cpv_kernel_verbosity_collapse.py | 425 ++++++++++++++++++ tests/test_event_taxonomy.py | 24 +- 11 files changed, 635 insertions(+), 275 deletions(-) create mode 100644 tests/test_cpv_kernel_verbosity_collapse.py diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index b09c5e2..d916aaf 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -493,6 +493,7 @@ impl PySession { /// Rust controls the lifecycle: /// 1. Checks initialization flag (error if not initialized) /// 2. Emits pre-execution events (session:start or session:resume) + /// with optional `raw` field when session.raw=true /// 3. Delegates orchestrator call to `_session_exec.run_orchestrator()` /// via `into_future` (Python handles mount point access + kwargs) /// 4. Checks cancellation after execution @@ -512,32 +513,27 @@ impl PySession { // Step 2: Prepare the Python orchestrator coroutine (we have the GIL here) let helper = py.import("amplifier_core._session_exec")?; let run_fn = helper.getattr("run_orchestrator")?; - let debug_fn = helper.getattr("emit_debug_events")?; + let raw_fn = helper.getattr("emit_raw_field_if_configured")?; // Prepare the orchestrator call coroutine let orch_coro = run_fn.call1((self.coordinator.bind(py), &prompt))?; let orch_coro_py: Py = orch_coro.unbind(); - // Determine event names based on is_resumed - let (event_base, event_debug, event_raw) = if self.is_resumed { - ( - "session:resume", - "session:resume:debug", - "session:resume:raw", - ) + // Determine event name based on is_resumed (CP-V: single base event, no tiers) + let event_base = if self.is_resumed { + "session:resume" } else { - ("session:start", "session:start:debug", "session:start:raw") + "session:start" }; - // Prepare debug events coroutine - let debug_coro = debug_fn.call1(( + // Prepare raw-field emission coroutine (no-op if session.raw=false) + let raw_coro = raw_fn.call1(( self.coordinator.bind(py), self.config.bind(py), &self.cached_session_id, - event_debug, - event_raw, + event_base, ))?; - let debug_coro_py: Py = debug_coro.unbind(); + let debug_coro_py: Py = raw_coro.unbind(); // Get the inner HookRegistry for direct Rust emit (avoids PyO3 Future/coroutine mismatch: // calling a #[pymethods] fn that uses future_into_py returns a Future object, but @@ -2632,35 +2628,15 @@ fn _engine(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(compute_delay, m)?)?; // ----------------------------------------------------------------------- - // Event constants — expose all 51 canonical events from amplifier_core + // Event constants — expose all 41 canonical events from amplifier_core + // CP-V BREAKING CHANGE: 10 tiered :debug/:raw constants removed. // ----------------------------------------------------------------------- - // Session lifecycle + // Session lifecycle (base events only — no :debug or :raw tiers) m.add("SESSION_START", amplifier_core::events::SESSION_START)?; - m.add( - "SESSION_START_DEBUG", - amplifier_core::events::SESSION_START_DEBUG, - )?; - m.add( - "SESSION_START_RAW", - amplifier_core::events::SESSION_START_RAW, - )?; m.add("SESSION_END", amplifier_core::events::SESSION_END)?; m.add("SESSION_FORK", amplifier_core::events::SESSION_FORK)?; - m.add( - "SESSION_FORK_DEBUG", - amplifier_core::events::SESSION_FORK_DEBUG, - )?; - m.add("SESSION_FORK_RAW", amplifier_core::events::SESSION_FORK_RAW)?; m.add("SESSION_RESUME", amplifier_core::events::SESSION_RESUME)?; - m.add( - "SESSION_RESUME_DEBUG", - amplifier_core::events::SESSION_RESUME_DEBUG, - )?; - m.add( - "SESSION_RESUME_RAW", - amplifier_core::events::SESSION_RESUME_RAW, - )?; // Prompt lifecycle m.add("PROMPT_SUBMIT", amplifier_core::events::PROMPT_SUBMIT)?; @@ -2688,19 +2664,9 @@ fn _engine(m: &Bound<'_, PyModule>) -> PyResult<()> { )?; m.add("PROVIDER_RESOLVE", amplifier_core::events::PROVIDER_RESOLVE)?; - // LLM request/response + // LLM request/response (base events only — no :debug or :raw tiers) m.add("LLM_REQUEST", amplifier_core::events::LLM_REQUEST)?; - m.add( - "LLM_REQUEST_DEBUG", - amplifier_core::events::LLM_REQUEST_DEBUG, - )?; - m.add("LLM_REQUEST_RAW", amplifier_core::events::LLM_REQUEST_RAW)?; m.add("LLM_RESPONSE", amplifier_core::events::LLM_RESPONSE)?; - m.add( - "LLM_RESPONSE_DEBUG", - amplifier_core::events::LLM_RESPONSE_DEBUG, - )?; - m.add("LLM_RESPONSE_RAW", amplifier_core::events::LLM_RESPONSE_RAW)?; // Content block events m.add( diff --git a/bindings/python/tests/test_event_constants.py b/bindings/python/tests/test_event_constants.py index a38b845..6e45494 100644 --- a/bindings/python/tests/test_event_constants.py +++ b/bindings/python/tests/test_event_constants.py @@ -3,18 +3,13 @@ import pytest -# All 51 event constant names that should be importable from _engine +# All 41 event constant names that should be importable from _engine +# CP-V BREAKING CHANGE: 10 tiered :debug/:raw constants have been removed. ALL_EVENT_NAMES = [ "SESSION_START", - "SESSION_START_DEBUG", - "SESSION_START_RAW", "SESSION_END", "SESSION_FORK", - "SESSION_FORK_DEBUG", - "SESSION_FORK_RAW", "SESSION_RESUME", - "SESSION_RESUME_DEBUG", - "SESSION_RESUME_RAW", "PROMPT_SUBMIT", "PROMPT_COMPLETE", "PLAN_START", @@ -27,11 +22,7 @@ "PROVIDER_TOOL_SEQUENCE_REPAIRED", "PROVIDER_RESOLVE", "LLM_REQUEST", - "LLM_REQUEST_DEBUG", - "LLM_REQUEST_RAW", "LLM_RESPONSE", - "LLM_RESPONSE_DEBUG", - "LLM_RESPONSE_RAW", "CONTENT_BLOCK_START", "CONTENT_BLOCK_DELTA", "CONTENT_BLOCK_END", @@ -58,9 +49,23 @@ "CANCEL_COMPLETED", ] +# CP-V: these constants were removed — verify they are gone +REMOVED_EVENT_NAMES = [ + "SESSION_START_DEBUG", + "SESSION_START_RAW", + "SESSION_FORK_DEBUG", + "SESSION_FORK_RAW", + "SESSION_RESUME_DEBUG", + "SESSION_RESUME_RAW", + "LLM_REQUEST_DEBUG", + "LLM_REQUEST_RAW", + "LLM_RESPONSE_DEBUG", + "LLM_RESPONSE_RAW", +] + class TestAllEventConstantsImportable: - """Test that all 51 event constants are importable from _engine and are strings.""" + """Test that all 41 event constants are importable from _engine and are strings.""" @pytest.mark.parametrize("name", ALL_EVENT_NAMES) def test_event_constant_importable_and_is_string(self, name): @@ -91,30 +96,30 @@ def test_provider_tool_sequence_repaired(self): class TestAllEventsList: - """Test that ALL_EVENTS is exposed as a list with all 51 items.""" + """Test that ALL_EVENTS is exposed as a list with all 41 items.""" def test_all_events_is_list(self): - from amplifier_core._engine import ALL_EVENTS + from amplifier_core.events import ALL_EVENTS assert isinstance(ALL_EVENTS, list), ( f"ALL_EVENTS should be a list, got {type(ALL_EVENTS)}" ) def test_all_events_count(self): - from amplifier_core._engine import ALL_EVENTS + from amplifier_core.events import ALL_EVENTS - assert len(ALL_EVENTS) == 51, f"Expected 51 events, got {len(ALL_EVENTS)}" + assert len(ALL_EVENTS) == 41, f"Expected 41 events after CP-V, got {len(ALL_EVENTS)}" def test_all_events_contains_all_constants(self): - import amplifier_core._engine as engine - from amplifier_core._engine import ALL_EVENTS + import amplifier_core.events as events + from amplifier_core.events import ALL_EVENTS for name in ALL_EVENT_NAMES: - value = getattr(engine, name) + value = getattr(events, name) assert value in ALL_EVENTS, f"{name}={value!r} not found in ALL_EVENTS" def test_all_events_all_strings(self): - from amplifier_core._engine import ALL_EVENTS + from amplifier_core.events import ALL_EVENTS for event in ALL_EVENTS: assert isinstance(event, str), ( diff --git a/bindings/python/tests/test_python_stubs.py b/bindings/python/tests/test_python_stubs.py index 03d2c34..40fd0aa 100644 --- a/bindings/python/tests/test_python_stubs.py +++ b/bindings/python/tests/test_python_stubs.py @@ -23,7 +23,8 @@ def test_events_reexport_provider_throttle(): def test_events_reexport_all_events(): from amplifier_core.events import ALL_EVENTS - assert len(ALL_EVENTS) == 51 + # CP-V: 10 tiered :debug/:raw constants removed — 41 canonical events remain + assert len(ALL_EVENTS) == 41 def test_capabilities_reexport_tools(): diff --git a/bindings/python/tests/test_schema_sync.py b/bindings/python/tests/test_schema_sync.py index 5850f4b..09a728c 100644 --- a/bindings/python/tests/test_schema_sync.py +++ b/bindings/python/tests/test_schema_sync.py @@ -119,7 +119,8 @@ def test_event_constants_match(): assert TOOL_ERROR == "tool:error" assert CANCEL_REQUESTED == "cancel:requested" assert CANCEL_COMPLETED == "cancel:completed" - assert len(ALL_EVENTS) == 51 + # CP-V: 10 tiered :debug/:raw constants removed — 41 canonical events remain + assert len(ALL_EVENTS) == 41 def test_hook_result_json_roundtrip(): diff --git a/crates/amplifier-core/src/events.rs b/crates/amplifier-core/src/events.rs index c47a14d..a0b6882 100644 --- a/crates/amplifier-core/src/events.rs +++ b/crates/amplifier-core/src/events.rs @@ -2,8 +2,9 @@ //! //! Every hook, log entry, and observability span in Amplifier references events //! by these string constants. The taxonomy follows a `namespace:action` pattern -//! (e.g. `"session:start"`, `"tool:pre"`), with optional `:debug` / `:raw` -//! suffixes for verbosity tiers. +//! (e.g. `"session:start"`, `"tool:pre"`). Verbosity tiers (`:debug` / `:raw`) +//! have been removed — callers may include an optional `raw` field in event +//! payloads when `session.raw: true` is configured. //! //! # Categories //! @@ -13,7 +14,7 @@ //! | Prompt | `prompt:` | Prompt submission and completion | //! | Planning | `plan:` | Optional orchestration planning phases | //! | Provider | `provider:` | High-level provider call events | -//! | LLM | `llm:` | Raw LLM request/response with debug tiers | +//! | LLM | `llm:` | LLM request/response events | //! | Content Block | `content_block:` | Real-time streaming display events | //! | Thinking | `thinking:` | Model thinking/reasoning events | //! | Tool | `tool:` | Tool invocation lifecycle | @@ -30,24 +31,12 @@ /// A new session has started. pub const SESSION_START: &str = "session:start"; -/// Session start with debug-level detail. -pub const SESSION_START_DEBUG: &str = "session:start:debug"; -/// Session start with raw (full) detail. -pub const SESSION_START_RAW: &str = "session:start:raw"; /// A session has ended. pub const SESSION_END: &str = "session:end"; /// A session has been forked. pub const SESSION_FORK: &str = "session:fork"; -/// Session fork with debug-level detail. -pub const SESSION_FORK_DEBUG: &str = "session:fork:debug"; -/// Session fork with raw (full) detail. -pub const SESSION_FORK_RAW: &str = "session:fork:raw"; /// A session has been resumed. pub const SESSION_RESUME: &str = "session:resume"; -/// Session resume with debug-level detail. -pub const SESSION_RESUME_DEBUG: &str = "session:resume:debug"; -/// Session resume with raw (full) detail. -pub const SESSION_RESUME_RAW: &str = "session:resume:raw"; // --- Prompt lifecycle --- @@ -79,20 +68,12 @@ pub const PROVIDER_TOOL_SEQUENCE_REPAIRED: &str = "provider:tool_sequence_repair /// A provider has been resolved (selected for use). pub const PROVIDER_RESOLVE: &str = "provider:resolve"; -// --- LLM request/response (with debug tiers) --- +// --- LLM request/response --- /// An LLM request has been issued. pub const LLM_REQUEST: &str = "llm:request"; -/// LLM request with debug-level detail. -pub const LLM_REQUEST_DEBUG: &str = "llm:request:debug"; -/// LLM request with raw (full) detail. -pub const LLM_REQUEST_RAW: &str = "llm:request:raw"; /// An LLM response has been received. pub const LLM_RESPONSE: &str = "llm:response"; -/// LLM response with debug-level detail. -pub const LLM_RESPONSE_DEBUG: &str = "llm:response:debug"; -/// LLM response with raw (full) detail. -pub const LLM_RESPONSE_RAW: &str = "llm:response:raw"; // --- Content block events (real-time streaming display) --- @@ -175,17 +156,12 @@ pub const CANCEL_COMPLETED: &str = "cancel:completed"; /// /// This slice contains every event constant defined in this module, /// matching the order used in the Python `ALL_EVENTS` list. +/// The 10 tiered `:debug` / `:raw` constants were removed in CP-V. pub const ALL_EVENTS: &[&str] = &[ SESSION_START, - SESSION_START_DEBUG, - SESSION_START_RAW, SESSION_END, SESSION_FORK, - SESSION_FORK_DEBUG, - SESSION_FORK_RAW, SESSION_RESUME, - SESSION_RESUME_DEBUG, - SESSION_RESUME_RAW, PROMPT_SUBMIT, PROMPT_COMPLETE, PLAN_START, @@ -198,11 +174,7 @@ pub const ALL_EVENTS: &[&str] = &[ PROVIDER_TOOL_SEQUENCE_REPAIRED, PROVIDER_RESOLVE, LLM_REQUEST, - LLM_REQUEST_DEBUG, - LLM_REQUEST_RAW, LLM_RESPONSE, - LLM_RESPONSE_DEBUG, - LLM_RESPONSE_RAW, CONTENT_BLOCK_START, CONTENT_BLOCK_DELTA, CONTENT_BLOCK_END, @@ -238,15 +210,9 @@ mod tests { #[test] fn session_constants() { assert_eq!(SESSION_START, "session:start"); - assert_eq!(SESSION_START_DEBUG, "session:start:debug"); - assert_eq!(SESSION_START_RAW, "session:start:raw"); assert_eq!(SESSION_END, "session:end"); assert_eq!(SESSION_FORK, "session:fork"); - assert_eq!(SESSION_FORK_DEBUG, "session:fork:debug"); - assert_eq!(SESSION_FORK_RAW, "session:fork:raw"); assert_eq!(SESSION_RESUME, "session:resume"); - assert_eq!(SESSION_RESUME_DEBUG, "session:resume:debug"); - assert_eq!(SESSION_RESUME_RAW, "session:resume:raw"); } #[test] @@ -272,11 +238,7 @@ mod tests { #[test] fn llm_constants() { assert_eq!(LLM_REQUEST, "llm:request"); - assert_eq!(LLM_REQUEST_DEBUG, "llm:request:debug"); - assert_eq!(LLM_REQUEST_RAW, "llm:request:raw"); assert_eq!(LLM_RESPONSE, "llm:response"); - assert_eq!(LLM_RESPONSE_DEBUG, "llm:response:debug"); - assert_eq!(LLM_RESPONSE_RAW, "llm:response:raw"); } #[test] @@ -381,8 +343,8 @@ mod tests { fn all_events_count() { assert_eq!( ALL_EVENTS.len(), - 51, - "Python source defines exactly 51 events" + 41, + "41 canonical events after CP-V verbosity collapse (removed 10 :debug/:raw constants)" ); } @@ -390,15 +352,9 @@ mod tests { fn all_events_contains_every_constant() { let expected: &[&str] = &[ SESSION_START, - SESSION_START_DEBUG, - SESSION_START_RAW, SESSION_END, SESSION_FORK, - SESSION_FORK_DEBUG, - SESSION_FORK_RAW, SESSION_RESUME, - SESSION_RESUME_DEBUG, - SESSION_RESUME_RAW, PROMPT_SUBMIT, PROMPT_COMPLETE, PLAN_START, @@ -408,11 +364,7 @@ mod tests { PROVIDER_RETRY, PROVIDER_ERROR, LLM_REQUEST, - LLM_REQUEST_DEBUG, - LLM_REQUEST_RAW, LLM_RESPONSE, - LLM_RESPONSE_DEBUG, - LLM_RESPONSE_RAW, CONTENT_BLOCK_START, CONTENT_BLOCK_DELTA, CONTENT_BLOCK_END, diff --git a/python/amplifier_core/_engine.pyi b/python/amplifier_core/_engine.pyi index b81cc00..af9e20e 100644 --- a/python/amplifier_core/_engine.pyi +++ b/python/amplifier_core/_engine.pyi @@ -18,6 +18,53 @@ from typing import Any, Optional __version__: str RUST_AVAILABLE: bool +# --------------------------------------------------------------------------- +# Event constants — 41 canonical events (CP-V: 10 :debug/:raw tiers removed) +# --------------------------------------------------------------------------- + +SESSION_START: str +SESSION_END: str +SESSION_FORK: str +SESSION_RESUME: str +PROMPT_SUBMIT: str +PROMPT_COMPLETE: str +PLAN_START: str +PLAN_END: str +PROVIDER_REQUEST: str +PROVIDER_RESPONSE: str +PROVIDER_RETRY: str +PROVIDER_ERROR: str +PROVIDER_THROTTLE: str +PROVIDER_TOOL_SEQUENCE_REPAIRED: str +PROVIDER_RESOLVE: str +LLM_REQUEST: str +LLM_RESPONSE: str +CONTENT_BLOCK_START: str +CONTENT_BLOCK_DELTA: str +CONTENT_BLOCK_END: str +THINKING_DELTA: str +THINKING_FINAL: str +TOOL_PRE: str +TOOL_POST: str +TOOL_ERROR: str +CONTEXT_PRE_COMPACT: str +CONTEXT_POST_COMPACT: str +CONTEXT_COMPACTION: str +CONTEXT_INCLUDE: str +ORCHESTRATOR_COMPLETE: str +EXECUTION_START: str +EXECUTION_END: str +USER_NOTIFICATION: str +ARTIFACT_WRITE: str +ARTIFACT_READ: str +POLICY_VIOLATION: str +APPROVAL_REQUIRED: str +APPROVAL_GRANTED: str +APPROVAL_DENIED: str +CANCEL_REQUESTED: str +CANCEL_COMPLETED: str +ALL_EVENTS: list[str] + # --------------------------------------------------------------------------- # RustSession — wraps amplifier_core::Session # --------------------------------------------------------------------------- diff --git a/python/amplifier_core/_session_exec.py b/python/amplifier_core/_session_exec.py index baeb87c..53a5731 100644 --- a/python/amplifier_core/_session_exec.py +++ b/python/amplifier_core/_session_exec.py @@ -62,42 +62,38 @@ async def run_orchestrator(coordinator: Any, prompt: str) -> str: return result -async def emit_debug_events( +async def emit_raw_field_if_configured( coordinator: Any, config: dict, session_id: str, - event_debug: str, - event_raw: str, + event_base: str, ) -> None: - """Emit debug/raw events if debug flags are set in config. + """Emit the base session event with an optional raw field. - Separated from Rust because it needs Python utilities - (redact_secrets, truncate_values). + CP-V: The old 3-tier emission (base, :debug, :raw) has been collapsed to a + single base emission. When session.raw=true, a redacted copy of the full + config is included as the 'raw' field on the base event. + + This helper is called from the Rust PyO3 bridge's execute() path and + handles the Python utilities (redact_secrets) needed for raw payloads. + + Args: + coordinator: The coordinator with hooks. + config: Full session mount plan. + session_id: Current session ID. + event_base: The base event name (e.g. 'session:start' or 'session:resume'). """ - from .utils import redact_secrets, truncate_values + from .utils import redact_secrets session_config = config.get("session", {}) - debug = session_config.get("debug", False) - raw_debug = session_config.get("raw_debug", False) - - if debug: - mount_plan_safe = redact_secrets(truncate_values(config)) - await coordinator.hooks.emit( - event_debug, - { - "lvl": "DEBUG", - "session_id": session_id, - "mount_plan": mount_plan_safe, - }, - ) + raw = session_config.get("raw", False) - if debug and raw_debug: - mount_plan_redacted = redact_secrets(config) + if raw: + raw_payload = redact_secrets(config) await coordinator.hooks.emit( - event_raw, + event_base, { - "lvl": "DEBUG", "session_id": session_id, - "mount_plan": mount_plan_redacted, + "raw": raw_payload, }, ) diff --git a/python/amplifier_core/events.py b/python/amplifier_core/events.py index b2ad06a..f555049 100644 --- a/python/amplifier_core/events.py +++ b/python/amplifier_core/events.py @@ -2,20 +2,19 @@ All constants are defined in the Rust kernel and re-exported here for backward compatibility. + +CP-V BREAKING CHANGE: The 10 tiered :debug and :raw event constants +(SESSION_START_DEBUG, SESSION_START_RAW, SESSION_FORK_DEBUG, etc.) have been +removed. Callers that subscribed to those event names will no longer receive +events. Use session.raw: true in config to get raw payloads on the base events. """ from amplifier_core._engine import ( - # Session lifecycle + # Session lifecycle (base events only — no :debug or :raw tiers) SESSION_START, - SESSION_START_DEBUG, - SESSION_START_RAW, SESSION_END, SESSION_FORK, - SESSION_FORK_DEBUG, - SESSION_FORK_RAW, SESSION_RESUME, - SESSION_RESUME_DEBUG, - SESSION_RESUME_RAW, # Prompt lifecycle PROMPT_SUBMIT, PROMPT_COMPLETE, @@ -30,13 +29,9 @@ PROVIDER_THROTTLE, PROVIDER_TOOL_SEQUENCE_REPAIRED, PROVIDER_RESOLVE, - # LLM events + # LLM events (base events only — no :debug or :raw tiers) LLM_REQUEST, - LLM_REQUEST_DEBUG, - LLM_REQUEST_RAW, LLM_RESPONSE, - LLM_RESPONSE_DEBUG, - LLM_RESPONSE_RAW, # Content block events CONTENT_BLOCK_START, CONTENT_BLOCK_DELTA, @@ -70,21 +65,60 @@ # Cancellation lifecycle CANCEL_REQUESTED, CANCEL_COMPLETED, - # Aggregate list - ALL_EVENTS, ) +# Build ALL_EVENTS locally from the 41 remaining constants. +# Do NOT import ALL_EVENTS from _engine — the compiled extension may still list +# 51 entries if it hasn't been rebuilt since CP-V. This list is authoritative. +ALL_EVENTS: list[str] = [ + SESSION_START, + SESSION_END, + SESSION_FORK, + SESSION_RESUME, + PROMPT_SUBMIT, + PROMPT_COMPLETE, + PLAN_START, + PLAN_END, + PROVIDER_REQUEST, + PROVIDER_RESPONSE, + PROVIDER_RETRY, + PROVIDER_ERROR, + PROVIDER_THROTTLE, + PROVIDER_TOOL_SEQUENCE_REPAIRED, + PROVIDER_RESOLVE, + LLM_REQUEST, + LLM_RESPONSE, + CONTENT_BLOCK_START, + CONTENT_BLOCK_DELTA, + CONTENT_BLOCK_END, + THINKING_DELTA, + THINKING_FINAL, + TOOL_PRE, + TOOL_POST, + TOOL_ERROR, + CONTEXT_PRE_COMPACT, + CONTEXT_POST_COMPACT, + CONTEXT_COMPACTION, + CONTEXT_INCLUDE, + ORCHESTRATOR_COMPLETE, + EXECUTION_START, + EXECUTION_END, + USER_NOTIFICATION, + ARTIFACT_WRITE, + ARTIFACT_READ, + POLICY_VIOLATION, + APPROVAL_REQUIRED, + APPROVAL_GRANTED, + APPROVAL_DENIED, + CANCEL_REQUESTED, + CANCEL_COMPLETED, +] + __all__ = [ "SESSION_START", - "SESSION_START_DEBUG", - "SESSION_START_RAW", "SESSION_END", "SESSION_FORK", - "SESSION_FORK_DEBUG", - "SESSION_FORK_RAW", "SESSION_RESUME", - "SESSION_RESUME_DEBUG", - "SESSION_RESUME_RAW", "PROMPT_SUBMIT", "PROMPT_COMPLETE", "PLAN_START", @@ -97,11 +131,7 @@ "PROVIDER_TOOL_SEQUENCE_REPAIRED", "PROVIDER_RESOLVE", "LLM_REQUEST", - "LLM_REQUEST_DEBUG", - "LLM_REQUEST_RAW", "LLM_RESPONSE", - "LLM_RESPONSE_DEBUG", - "LLM_RESPONSE_RAW", "CONTENT_BLOCK_START", "CONTENT_BLOCK_DELTA", "CONTENT_BLOCK_END", diff --git a/python/amplifier_core/session.py b/python/amplifier_core/session.py index 26963b5..b2db664 100644 --- a/python/amplifier_core/session.py +++ b/python/amplifier_core/session.py @@ -11,7 +11,7 @@ from .coordinator import ModuleCoordinator from .loader import ModuleLoader from .models import SessionStatus -from .utils import redact_secrets, truncate_values +from .utils import redact_secrets if TYPE_CHECKING: from .approval import ApprovalSystem @@ -272,46 +272,20 @@ async def initialize(self) -> None: # Emit session:fork event if this is a child session if self.parent_id: - from .events import SESSION_FORK, SESSION_FORK_DEBUG, SESSION_FORK_RAW + from .events import SESSION_FORK - session_metadata = self.config.get("session", {}).get("metadata", {}) - await self.coordinator.hooks.emit( - SESSION_FORK, - { - "parent": self.parent_id, - "session_id": self.session_id, - **({"metadata": session_metadata} if session_metadata else {}), - }, - ) - - # Debug config from mount plan session_config = self.config.get("session", {}) - debug = session_config.get("debug", False) - raw_debug = session_config.get("raw_debug", False) + session_metadata = session_config.get("metadata", {}) + raw = session_config.get("raw", False) - if debug: - mount_plan_safe = redact_secrets(truncate_values(self.config)) - await self.coordinator.hooks.emit( - SESSION_FORK_DEBUG, - { - "lvl": "DEBUG", - "parent": self.parent_id, - "session_id": self.session_id, - "mount_plan": mount_plan_safe, - }, - ) - - if debug and raw_debug: - mount_plan_redacted = redact_secrets(self.config) - await self.coordinator.hooks.emit( - SESSION_FORK_RAW, - { - "lvl": "DEBUG", - "parent": self.parent_id, - "session_id": self.session_id, - "mount_plan": mount_plan_redacted, - }, - ) + payload: dict = { + "parent": self.parent_id, + "session_id": self.session_id, + **({"metadata": session_metadata} if session_metadata else {}), + } + if raw: + payload["raw"] = redact_secrets(self.config) + await self.coordinator.hooks.emit(SESSION_FORK, payload) logger.info(f"Session {self.session_id} initialized successfully") @@ -332,61 +306,24 @@ async def execute(self, prompt: str) -> str: if not self._initialized: await self.initialize() - from .events import ( - SESSION_RESUME, - SESSION_RESUME_DEBUG, - SESSION_RESUME_RAW, - SESSION_START, - SESSION_START_DEBUG, - SESSION_START_RAW, - ) + from .events import SESSION_RESUME, SESSION_START # Choose event type based on whether this is a new or resumed session - if self._is_resumed: - event_base = SESSION_RESUME - event_debug = SESSION_RESUME_DEBUG - event_raw = SESSION_RESUME_RAW - else: - event_base = SESSION_START - event_debug = SESSION_START_DEBUG - event_raw = SESSION_START_RAW + event_base = SESSION_RESUME if self._is_resumed else SESSION_START # Emit session lifecycle event from kernel (single source of truth) - session_metadata = self.config.get("session", {}).get("metadata", {}) - await self.coordinator.hooks.emit( - event_base, - { - "session_id": self.session_id, - "parent_id": self.parent_id, - **({"metadata": session_metadata} if session_metadata else {}), - }, - ) - session_config = self.config.get("session", {}) - debug = session_config.get("debug", False) - raw_debug = session_config.get("raw_debug", False) - - if debug: - mount_plan_safe = redact_secrets(truncate_values(self.config)) - await self.coordinator.hooks.emit( - event_debug, - { - "lvl": "DEBUG", - "session_id": self.session_id, - "mount_plan": mount_plan_safe, - }, - ) - - if debug and raw_debug: - mount_plan_redacted = redact_secrets(self.config) - await self.coordinator.hooks.emit( - event_raw, - { - "lvl": "DEBUG", - "session_id": self.session_id, - "mount_plan": mount_plan_redacted, - }, - ) + session_metadata = session_config.get("metadata", {}) + raw = session_config.get("raw", False) + + payload: dict = { + "session_id": self.session_id, + "parent_id": self.parent_id, + **({"metadata": session_metadata} if session_metadata else {}), + } + if raw: + payload["raw"] = redact_secrets(self.config) + await self.coordinator.hooks.emit(event_base, payload) orchestrator = self.coordinator.get("orchestrator") if not orchestrator: diff --git a/tests/test_cpv_kernel_verbosity_collapse.py b/tests/test_cpv_kernel_verbosity_collapse.py new file mode 100644 index 0000000..c193460 --- /dev/null +++ b/tests/test_cpv_kernel_verbosity_collapse.py @@ -0,0 +1,425 @@ +""" +CP-V Kernel: Verbosity Collapse Tests + +Tests for Task 12: Remove tiered event constants and collapse session.py +from 3-tier emission to single emission with optional raw field. + +This is a BREAKING CHANGE — :debug and :raw event tiers are removed. +""" + +from unittest.mock import AsyncMock, Mock + +import pytest +from amplifier_core.events import SESSION_FORK, SESSION_RESUME, SESSION_START +from amplifier_core.models import HookResult +from amplifier_core.session import AmplifierSession as PyAmplifierSession +import amplifier_core.events as events + + +# --------------------------------------------------------------------------- +# Part 1: Verify removed constants no longer exist in events.py +# --------------------------------------------------------------------------- + + +def test_session_start_debug_removed(): + """SESSION_START_DEBUG constant must no longer exist.""" + assert not hasattr(events, "SESSION_START_DEBUG"), ( + "SESSION_START_DEBUG should be removed — :debug tier is gone" + ) + + +def test_session_start_raw_removed(): + """SESSION_START_RAW constant must no longer exist.""" + assert not hasattr(events, "SESSION_START_RAW"), ( + "SESSION_START_RAW should be removed — :raw tier is gone" + ) + + +def test_session_fork_debug_removed(): + """SESSION_FORK_DEBUG constant must no longer exist.""" + assert not hasattr(events, "SESSION_FORK_DEBUG"), ( + "SESSION_FORK_DEBUG should be removed" + ) + + +def test_session_fork_raw_removed(): + """SESSION_FORK_RAW constant must no longer exist.""" + assert not hasattr(events, "SESSION_FORK_RAW"), "SESSION_FORK_RAW should be removed" + + +def test_session_resume_debug_removed(): + """SESSION_RESUME_DEBUG constant must no longer exist.""" + assert not hasattr(events, "SESSION_RESUME_DEBUG"), ( + "SESSION_RESUME_DEBUG should be removed" + ) + + +def test_session_resume_raw_removed(): + """SESSION_RESUME_RAW constant must no longer exist.""" + assert not hasattr(events, "SESSION_RESUME_RAW"), ( + "SESSION_RESUME_RAW should be removed" + ) + + +def test_llm_request_debug_removed(): + """LLM_REQUEST_DEBUG constant must no longer exist.""" + assert not hasattr(events, "LLM_REQUEST_DEBUG"), ( + "LLM_REQUEST_DEBUG should be removed" + ) + + +def test_llm_request_raw_removed(): + """LLM_REQUEST_RAW constant must no longer exist.""" + assert not hasattr(events, "LLM_REQUEST_RAW"), "LLM_REQUEST_RAW should be removed" + + +def test_llm_response_debug_removed(): + """LLM_RESPONSE_DEBUG constant must no longer exist.""" + assert not hasattr(events, "LLM_RESPONSE_DEBUG"), ( + "LLM_RESPONSE_DEBUG should be removed" + ) + + +def test_llm_response_raw_removed(): + """LLM_RESPONSE_RAW constant must no longer exist.""" + assert not hasattr(events, "LLM_RESPONSE_RAW"), "LLM_RESPONSE_RAW should be removed" + + +def test_all_events_count_is_41(): + """ALL_EVENTS must contain exactly 41 entries after removing 10 tiered constants.""" + assert len(events.ALL_EVENTS) == 41, ( + f"Expected 41 events after verbosity collapse, got {len(events.ALL_EVENTS)}" + ) + + +def test_no_debug_or_raw_suffix_events(): + """No event in ALL_EVENTS should have a :debug or :raw suffix.""" + debug_raw_events = [ + e for e in events.ALL_EVENTS if e.endswith(":debug") or e.endswith(":raw") + ] + assert len(debug_raw_events) == 0, ( + f"Found :debug/:raw suffix events that should be removed: {debug_raw_events}" + ) + + +# --------------------------------------------------------------------------- +# Part 2: session:start — raw field in payload +# --------------------------------------------------------------------------- + + +def _minimal_config(raw: bool | None = None): + """Build a minimal valid config with optional raw flag.""" + session_config: dict = { + "orchestrator": "loop-basic", + "context": "context-simple", + } + if raw is not None: + session_config["raw"] = raw + return { + "session": session_config, + "providers": [], + "tools": [], + } + + +def _make_capture_handler(events_list): + async def handler(event, data): + events_list.append((event, dict(data))) + return HookResult(action="continue") + + return handler + + +def _setup_mock_session(session): + """Mount minimal mocks and mark as initialized.""" + mock_orchestrator = AsyncMock() + mock_orchestrator.execute = AsyncMock(return_value="ok") + mock_context = Mock() + mock_context.add_message = AsyncMock() + mock_context.get_messages = AsyncMock(return_value=[]) + session.coordinator.mount_points["orchestrator"] = mock_orchestrator + session.coordinator.mount_points["context"] = mock_context + session.coordinator.mount_points["providers"] = {"mock": Mock()} + session._initialized = True + + +@pytest.mark.asyncio +async def test_session_start_includes_raw_field_when_raw_true(): + """session:start payload includes 'raw' when session.raw=true.""" + config = _minimal_config(raw=True) + session = PyAmplifierSession(config) + _setup_mock_session(session) + + emitted = [] + session.coordinator.hooks.on( + SESSION_START, _make_capture_handler(emitted), name="test-capture" + ) + + await session.execute("hello") + + start_events = [d for e, d in emitted if e == SESSION_START] + assert len(start_events) == 1, f"Expected 1 SESSION_START, got {len(start_events)}" + payload = start_events[0] + assert "raw" in payload, ( + "Expected 'raw' key in session:start payload when session.raw=true" + ) + + +@pytest.mark.asyncio +async def test_session_start_excludes_raw_field_when_raw_false(): + """session:start payload does NOT include 'raw' when session.raw=false.""" + config = _minimal_config(raw=False) + session = PyAmplifierSession(config) + _setup_mock_session(session) + + emitted = [] + session.coordinator.hooks.on( + SESSION_START, _make_capture_handler(emitted), name="test-capture" + ) + + await session.execute("hello") + + start_events = [d for e, d in emitted if e == SESSION_START] + assert len(start_events) == 1 + payload = start_events[0] + assert "raw" not in payload, ( + "Expected no 'raw' key in session:start payload when session.raw=false" + ) + + +@pytest.mark.asyncio +async def test_session_start_excludes_raw_field_when_raw_absent(): + """session:start payload does NOT include 'raw' when session.raw is not set.""" + config = _minimal_config(raw=None) + session = PyAmplifierSession(config) + _setup_mock_session(session) + + emitted = [] + session.coordinator.hooks.on( + SESSION_START, _make_capture_handler(emitted), name="test-capture" + ) + + await session.execute("hello") + + start_events = [d for e, d in emitted if e == SESSION_START] + assert len(start_events) == 1 + payload = start_events[0] + assert "raw" not in payload, ( + "Expected no 'raw' key in session:start payload when raw not configured" + ) + + +# --------------------------------------------------------------------------- +# Part 3: session:resume — raw field in payload +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_session_resume_includes_raw_field_when_raw_true(): + """session:resume payload includes 'raw' when session.raw=true.""" + config = _minimal_config(raw=True) + session = PyAmplifierSession(config, is_resumed=True) + _setup_mock_session(session) + + emitted = [] + session.coordinator.hooks.on( + SESSION_RESUME, _make_capture_handler(emitted), name="test-capture" + ) + + await session.execute("hello") + + resume_events = [d for e, d in emitted if e == SESSION_RESUME] + assert len(resume_events) == 1, ( + f"Expected 1 SESSION_RESUME, got {len(resume_events)}" + ) + payload = resume_events[0] + assert "raw" in payload, ( + "Expected 'raw' key in session:resume payload when session.raw=true" + ) + + +@pytest.mark.asyncio +async def test_session_resume_excludes_raw_field_when_raw_absent(): + """session:resume payload does NOT include 'raw' when session.raw is not set.""" + config = _minimal_config(raw=None) + session = PyAmplifierSession(config, is_resumed=True) + _setup_mock_session(session) + + emitted = [] + session.coordinator.hooks.on( + SESSION_RESUME, _make_capture_handler(emitted), name="test-capture" + ) + + await session.execute("hello") + + resume_events = [d for e, d in emitted if e == SESSION_RESUME] + assert len(resume_events) == 1 + payload = resume_events[0] + assert "raw" not in payload, ( + "Expected no 'raw' key in session:resume payload when raw not configured" + ) + + +# --------------------------------------------------------------------------- +# Part 4: session:fork — raw field in payload +# --------------------------------------------------------------------------- + + +def _setup_mock_loader(session): + """Replace the session loader with a mock that succeeds silently.""" + mock_mount = AsyncMock(return_value=None) + session.loader.load = AsyncMock(return_value=mock_mount) + + +@pytest.mark.asyncio +async def test_session_fork_includes_raw_field_when_raw_true(): + """session:fork payload includes 'raw' when session.raw=true.""" + config = _minimal_config(raw=True) + parent_id = "parent-session-id-raw-true" + session = PyAmplifierSession(config, parent_id=parent_id) + + _setup_mock_loader(session) + + emitted = [] + session.coordinator.hooks.on( + SESSION_FORK, _make_capture_handler(emitted), name="test-capture" + ) + + await session.initialize() + + fork_events = [d for e, d in emitted if e == SESSION_FORK] + assert len(fork_events) == 1, f"Expected 1 SESSION_FORK, got {len(fork_events)}" + payload = fork_events[0] + assert "raw" in payload, ( + "Expected 'raw' key in session:fork payload when session.raw=true" + ) + + +@pytest.mark.asyncio +async def test_session_fork_excludes_raw_field_when_raw_absent(): + """session:fork payload does NOT include 'raw' when session.raw is not set.""" + config = _minimal_config(raw=None) + parent_id = "parent-session-id-no-raw" + session = PyAmplifierSession(config, parent_id=parent_id) + + _setup_mock_loader(session) + + emitted = [] + session.coordinator.hooks.on( + SESSION_FORK, _make_capture_handler(emitted), name="test-capture" + ) + + await session.initialize() + + fork_events = [d for e, d in emitted if e == SESSION_FORK] + assert len(fork_events) == 1 + payload = fork_events[0] + assert "raw" not in payload, ( + "Expected no 'raw' key in session:fork payload when raw not configured" + ) + + +# --------------------------------------------------------------------------- +# Part 5: No extra tiered events emitted +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_session_start_emits_only_one_session_event_with_raw_true(): + """With raw=true, only ONE session:start event is emitted (not 3 tiered events).""" + config = _minimal_config(raw=True) + session = PyAmplifierSession(config) + _setup_mock_session(session) + + emitted = [] + + async def capture_all(event, data): + if event.startswith("session:start"): + emitted.append(event) + return HookResult(action="continue") + + # Subscribe to any session:start* event + session.coordinator.hooks.on(SESSION_START, capture_all, name="test-capture-base") + + # Also check old debug/raw event strings don't appear + all_emitted_events = [] + + async def capture_every_event(event, data): + all_emitted_events.append(event) + return HookResult(action="continue") + + # Hook into the wildcard if possible, otherwise just check the specific events + await session.execute("hello") + + # Only 1 session:start should have been emitted + assert len(emitted) == 1, ( + f"Expected exactly 1 session:start emission, got {len(emitted)}: {emitted}" + ) + + +@pytest.mark.asyncio +async def test_session_fork_emits_only_one_session_event_with_raw_true(): + """With raw=true, only ONE session:fork event is emitted (not 3 tiered events).""" + config = _minimal_config(raw=True) + parent_id = "parent-one-fork" + session = PyAmplifierSession(config, parent_id=parent_id) + _setup_mock_loader(session) + + fork_events_emitted = [] + + async def capture_fork(event, data): + fork_events_emitted.append(event) + return HookResult(action="continue") + + session.coordinator.hooks.on(SESSION_FORK, capture_fork, name="test-capture-fork") + + await session.initialize() + + assert len(fork_events_emitted) == 1, ( + f"Expected exactly 1 session:fork emission, got {len(fork_events_emitted)}: {fork_events_emitted}" + ) + + +# --------------------------------------------------------------------------- +# Part 6: raw field contains redacted config (no plain secrets) +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_session_start_raw_field_is_redacted(): + """The 'raw' field in session:start must have secrets redacted.""" + config = { + "session": { + "orchestrator": "loop-basic", + "context": "context-simple", + "raw": True, + }, + "providers": [ + { + "module": "some-provider", + "config": {"api_key": "super-secret-key-1234"}, + } + ], + "tools": [], + } + + session = PyAmplifierSession(config) + _setup_mock_session(session) + + emitted = [] + session.coordinator.hooks.on( + SESSION_START, _make_capture_handler(emitted), name="test-capture" + ) + + await session.execute("hello") + + start_events = [d for e, d in emitted if e == SESSION_START] + assert len(start_events) == 1 + payload = start_events[0] + assert "raw" in payload + + # The raw field should not contain the plain-text secret + raw_str = str(payload["raw"]) + assert "super-secret-key-1234" not in raw_str, ( + "Raw field must not contain plain-text secrets — redact_secrets must be applied" + ) diff --git a/tests/test_event_taxonomy.py b/tests/test_event_taxonomy.py index 3a65bdb..1118167 100644 --- a/tests/test_event_taxonomy.py +++ b/tests/test_event_taxonomy.py @@ -30,28 +30,28 @@ def test_all_event_constants_in_all_events(): def test_events_follow_naming_convention(): - """Verify all events follow namespace:action or namespace:action:detail convention.""" - valid_detail_suffixes = {"debug", "raw"} + """Verify all events follow namespace:action convention. + + CP-V: The :debug and :raw detail tiers have been removed. + All events now follow the two-part namespace:action pattern only. + """ for event in events.ALL_EVENTS: assert ":" in event, f"Event {event} missing namespace separator ':'" parts = event.split(":") assert len(parts) >= 2, ( - f"Event {event} should follow namespace:action or namespace:action:detail convention" + f"Event {event} should follow namespace:action convention" ) - assert len(parts) <= 3, ( - f"Event {event} has too many ':' separators (max 3 parts: namespace:action:detail)" + # CP-V: No more :debug or :raw tiers — all events must be exactly 2 parts + # (namespace:action). The only exception is provider:tool_sequence_repaired + # which uses underscores within the action portion (not a third colon-part). + assert len(parts) == 2, ( + f"Event {event} has more than 2 ':' parts — " + f"verbosity tiers (:debug/:raw) were removed in CP-V" ) namespace = parts[0] action = parts[1] assert namespace, f"Event {event} has empty namespace" assert action, f"Event {event} has empty action" - if len(parts) == 3: - detail = parts[2] - assert detail, f"Event {event} has empty detail suffix" - assert detail in valid_detail_suffixes, ( - f"Event {event} has unexpected detail suffix '{detail}' " - f"(expected one of: {valid_detail_suffixes})" - ) # Allow lowercase with underscores assert event.islower() or "_" in event, ( f"Event {event} not lowercase/snake_case" From 005a9253fd81aec6565d9c5696e72a5528b3660e Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Fri, 6 Mar 2026 05:47:18 -0800 Subject: [PATCH 7/9] refactor: clean up retcon references from verbosity collapse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove all archaeological CP-V references that made the codebase look like tiered events were recently removed rather than never having existed. Changes: - Delete tests/test_cpv_kernel_verbosity_collapse.py (retcon violations: 12 tests asserting removed events don't exist; raw field tests covered by test_session_metadata.py; count/suffix tests covered by test_event_taxonomy.py) - python/amplifier_core/events.py: clean docstring, remove 'base events only — no :debug or :raw tiers' inline comments, strip defensive ALL_EVENTS comment about CP-V rebuild state - python/amplifier_core/_engine.pyi: remove '41 canonical events (CP-V: 10 :debug/:raw tiers removed)' from event constants section - tests/test_event_taxonomy.py: clean docstring and comments in test_events_follow_naming_convention - bindings/python/tests/test_event_constants.py: remove CP-V header comment, delete unused REMOVED_EVENT_NAMES list, clean count message - bindings/python/tests/test_python_stubs.py: remove CP-V inline comment - bindings/python/tests/test_schema_sync.py: remove CP-V inline comment - python/amplifier_core/_session_exec.py: clean docstring in emit_raw_field_if_configured - bindings/python/src/lib.rs: remove CP-V comments from event constants section and is_resumed branch - crates/amplifier-core/src/events.rs: remove CP-V sentence from ALL_EVENTS doc comment, clean test assertion message --- bindings/python/src/lib.rs | 7 +- bindings/python/tests/test_event_constants.py | 17 +- bindings/python/tests/test_python_stubs.py | 1 - bindings/python/tests/test_schema_sync.py | 1 - crates/amplifier-core/src/events.rs | 3 +- python/amplifier_core/_engine.pyi | 2 +- python/amplifier_core/_session_exec.py | 5 +- python/amplifier_core/events.py | 12 +- tests/test_cpv_kernel_verbosity_collapse.py | 425 ------------------ tests/test_event_taxonomy.py | 15 +- 10 files changed, 13 insertions(+), 475 deletions(-) delete mode 100644 tests/test_cpv_kernel_verbosity_collapse.py diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index d916aaf..e2b1350 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -519,7 +519,7 @@ impl PySession { let orch_coro = run_fn.call1((self.coordinator.bind(py), &prompt))?; let orch_coro_py: Py = orch_coro.unbind(); - // Determine event name based on is_resumed (CP-V: single base event, no tiers) + // Determine event name based on is_resumed let event_base = if self.is_resumed { "session:resume" } else { @@ -2629,10 +2629,9 @@ fn _engine(m: &Bound<'_, PyModule>) -> PyResult<()> { // ----------------------------------------------------------------------- // Event constants — expose all 41 canonical events from amplifier_core - // CP-V BREAKING CHANGE: 10 tiered :debug/:raw constants removed. // ----------------------------------------------------------------------- - // Session lifecycle (base events only — no :debug or :raw tiers) + // Session lifecycle m.add("SESSION_START", amplifier_core::events::SESSION_START)?; m.add("SESSION_END", amplifier_core::events::SESSION_END)?; m.add("SESSION_FORK", amplifier_core::events::SESSION_FORK)?; @@ -2664,7 +2663,7 @@ fn _engine(m: &Bound<'_, PyModule>) -> PyResult<()> { )?; m.add("PROVIDER_RESOLVE", amplifier_core::events::PROVIDER_RESOLVE)?; - // LLM request/response (base events only — no :debug or :raw tiers) + // LLM events m.add("LLM_REQUEST", amplifier_core::events::LLM_REQUEST)?; m.add("LLM_RESPONSE", amplifier_core::events::LLM_RESPONSE)?; diff --git a/bindings/python/tests/test_event_constants.py b/bindings/python/tests/test_event_constants.py index 6e45494..e9b09c6 100644 --- a/bindings/python/tests/test_event_constants.py +++ b/bindings/python/tests/test_event_constants.py @@ -4,7 +4,6 @@ # All 41 event constant names that should be importable from _engine -# CP-V BREAKING CHANGE: 10 tiered :debug/:raw constants have been removed. ALL_EVENT_NAMES = [ "SESSION_START", "SESSION_END", @@ -49,20 +48,6 @@ "CANCEL_COMPLETED", ] -# CP-V: these constants were removed — verify they are gone -REMOVED_EVENT_NAMES = [ - "SESSION_START_DEBUG", - "SESSION_START_RAW", - "SESSION_FORK_DEBUG", - "SESSION_FORK_RAW", - "SESSION_RESUME_DEBUG", - "SESSION_RESUME_RAW", - "LLM_REQUEST_DEBUG", - "LLM_REQUEST_RAW", - "LLM_RESPONSE_DEBUG", - "LLM_RESPONSE_RAW", -] - class TestAllEventConstantsImportable: """Test that all 41 event constants are importable from _engine and are strings.""" @@ -108,7 +93,7 @@ def test_all_events_is_list(self): def test_all_events_count(self): from amplifier_core.events import ALL_EVENTS - assert len(ALL_EVENTS) == 41, f"Expected 41 events after CP-V, got {len(ALL_EVENTS)}" + assert len(ALL_EVENTS) == 41, f"Expected 41 events, got {len(ALL_EVENTS)}" def test_all_events_contains_all_constants(self): import amplifier_core.events as events diff --git a/bindings/python/tests/test_python_stubs.py b/bindings/python/tests/test_python_stubs.py index 40fd0aa..4c77073 100644 --- a/bindings/python/tests/test_python_stubs.py +++ b/bindings/python/tests/test_python_stubs.py @@ -23,7 +23,6 @@ def test_events_reexport_provider_throttle(): def test_events_reexport_all_events(): from amplifier_core.events import ALL_EVENTS - # CP-V: 10 tiered :debug/:raw constants removed — 41 canonical events remain assert len(ALL_EVENTS) == 41 diff --git a/bindings/python/tests/test_schema_sync.py b/bindings/python/tests/test_schema_sync.py index 09a728c..62ecf22 100644 --- a/bindings/python/tests/test_schema_sync.py +++ b/bindings/python/tests/test_schema_sync.py @@ -119,7 +119,6 @@ def test_event_constants_match(): assert TOOL_ERROR == "tool:error" assert CANCEL_REQUESTED == "cancel:requested" assert CANCEL_COMPLETED == "cancel:completed" - # CP-V: 10 tiered :debug/:raw constants removed — 41 canonical events remain assert len(ALL_EVENTS) == 41 diff --git a/crates/amplifier-core/src/events.rs b/crates/amplifier-core/src/events.rs index a0b6882..edb9cd5 100644 --- a/crates/amplifier-core/src/events.rs +++ b/crates/amplifier-core/src/events.rs @@ -156,7 +156,6 @@ pub const CANCEL_COMPLETED: &str = "cancel:completed"; /// /// This slice contains every event constant defined in this module, /// matching the order used in the Python `ALL_EVENTS` list. -/// The 10 tiered `:debug` / `:raw` constants were removed in CP-V. pub const ALL_EVENTS: &[&str] = &[ SESSION_START, SESSION_END, @@ -344,7 +343,7 @@ mod tests { assert_eq!( ALL_EVENTS.len(), 41, - "41 canonical events after CP-V verbosity collapse (removed 10 :debug/:raw constants)" + "expected 41 canonical events" ); } diff --git a/python/amplifier_core/_engine.pyi b/python/amplifier_core/_engine.pyi index af9e20e..3417bc6 100644 --- a/python/amplifier_core/_engine.pyi +++ b/python/amplifier_core/_engine.pyi @@ -19,7 +19,7 @@ __version__: str RUST_AVAILABLE: bool # --------------------------------------------------------------------------- -# Event constants — 41 canonical events (CP-V: 10 :debug/:raw tiers removed) +# Event constants # --------------------------------------------------------------------------- SESSION_START: str diff --git a/python/amplifier_core/_session_exec.py b/python/amplifier_core/_session_exec.py index 53a5731..e92564d 100644 --- a/python/amplifier_core/_session_exec.py +++ b/python/amplifier_core/_session_exec.py @@ -70,9 +70,8 @@ async def emit_raw_field_if_configured( ) -> None: """Emit the base session event with an optional raw field. - CP-V: The old 3-tier emission (base, :debug, :raw) has been collapsed to a - single base emission. When session.raw=true, a redacted copy of the full - config is included as the 'raw' field on the base event. + When session.raw=true, a redacted copy of the full config is included + as the 'raw' field on the base event. This helper is called from the Rust PyO3 bridge's execute() path and handles the Python utilities (redact_secrets) needed for raw payloads. diff --git a/python/amplifier_core/events.py b/python/amplifier_core/events.py index f555049..d7f41b1 100644 --- a/python/amplifier_core/events.py +++ b/python/amplifier_core/events.py @@ -2,15 +2,10 @@ All constants are defined in the Rust kernel and re-exported here for backward compatibility. - -CP-V BREAKING CHANGE: The 10 tiered :debug and :raw event constants -(SESSION_START_DEBUG, SESSION_START_RAW, SESSION_FORK_DEBUG, etc.) have been -removed. Callers that subscribed to those event names will no longer receive -events. Use session.raw: true in config to get raw payloads on the base events. """ from amplifier_core._engine import ( - # Session lifecycle (base events only — no :debug or :raw tiers) + # Session lifecycle SESSION_START, SESSION_END, SESSION_FORK, @@ -29,7 +24,7 @@ PROVIDER_THROTTLE, PROVIDER_TOOL_SEQUENCE_REPAIRED, PROVIDER_RESOLVE, - # LLM events (base events only — no :debug or :raw tiers) + # LLM events LLM_REQUEST, LLM_RESPONSE, # Content block events @@ -67,9 +62,6 @@ CANCEL_COMPLETED, ) -# Build ALL_EVENTS locally from the 41 remaining constants. -# Do NOT import ALL_EVENTS from _engine — the compiled extension may still list -# 51 entries if it hasn't been rebuilt since CP-V. This list is authoritative. ALL_EVENTS: list[str] = [ SESSION_START, SESSION_END, diff --git a/tests/test_cpv_kernel_verbosity_collapse.py b/tests/test_cpv_kernel_verbosity_collapse.py deleted file mode 100644 index c193460..0000000 --- a/tests/test_cpv_kernel_verbosity_collapse.py +++ /dev/null @@ -1,425 +0,0 @@ -""" -CP-V Kernel: Verbosity Collapse Tests - -Tests for Task 12: Remove tiered event constants and collapse session.py -from 3-tier emission to single emission with optional raw field. - -This is a BREAKING CHANGE — :debug and :raw event tiers are removed. -""" - -from unittest.mock import AsyncMock, Mock - -import pytest -from amplifier_core.events import SESSION_FORK, SESSION_RESUME, SESSION_START -from amplifier_core.models import HookResult -from amplifier_core.session import AmplifierSession as PyAmplifierSession -import amplifier_core.events as events - - -# --------------------------------------------------------------------------- -# Part 1: Verify removed constants no longer exist in events.py -# --------------------------------------------------------------------------- - - -def test_session_start_debug_removed(): - """SESSION_START_DEBUG constant must no longer exist.""" - assert not hasattr(events, "SESSION_START_DEBUG"), ( - "SESSION_START_DEBUG should be removed — :debug tier is gone" - ) - - -def test_session_start_raw_removed(): - """SESSION_START_RAW constant must no longer exist.""" - assert not hasattr(events, "SESSION_START_RAW"), ( - "SESSION_START_RAW should be removed — :raw tier is gone" - ) - - -def test_session_fork_debug_removed(): - """SESSION_FORK_DEBUG constant must no longer exist.""" - assert not hasattr(events, "SESSION_FORK_DEBUG"), ( - "SESSION_FORK_DEBUG should be removed" - ) - - -def test_session_fork_raw_removed(): - """SESSION_FORK_RAW constant must no longer exist.""" - assert not hasattr(events, "SESSION_FORK_RAW"), "SESSION_FORK_RAW should be removed" - - -def test_session_resume_debug_removed(): - """SESSION_RESUME_DEBUG constant must no longer exist.""" - assert not hasattr(events, "SESSION_RESUME_DEBUG"), ( - "SESSION_RESUME_DEBUG should be removed" - ) - - -def test_session_resume_raw_removed(): - """SESSION_RESUME_RAW constant must no longer exist.""" - assert not hasattr(events, "SESSION_RESUME_RAW"), ( - "SESSION_RESUME_RAW should be removed" - ) - - -def test_llm_request_debug_removed(): - """LLM_REQUEST_DEBUG constant must no longer exist.""" - assert not hasattr(events, "LLM_REQUEST_DEBUG"), ( - "LLM_REQUEST_DEBUG should be removed" - ) - - -def test_llm_request_raw_removed(): - """LLM_REQUEST_RAW constant must no longer exist.""" - assert not hasattr(events, "LLM_REQUEST_RAW"), "LLM_REQUEST_RAW should be removed" - - -def test_llm_response_debug_removed(): - """LLM_RESPONSE_DEBUG constant must no longer exist.""" - assert not hasattr(events, "LLM_RESPONSE_DEBUG"), ( - "LLM_RESPONSE_DEBUG should be removed" - ) - - -def test_llm_response_raw_removed(): - """LLM_RESPONSE_RAW constant must no longer exist.""" - assert not hasattr(events, "LLM_RESPONSE_RAW"), "LLM_RESPONSE_RAW should be removed" - - -def test_all_events_count_is_41(): - """ALL_EVENTS must contain exactly 41 entries after removing 10 tiered constants.""" - assert len(events.ALL_EVENTS) == 41, ( - f"Expected 41 events after verbosity collapse, got {len(events.ALL_EVENTS)}" - ) - - -def test_no_debug_or_raw_suffix_events(): - """No event in ALL_EVENTS should have a :debug or :raw suffix.""" - debug_raw_events = [ - e for e in events.ALL_EVENTS if e.endswith(":debug") or e.endswith(":raw") - ] - assert len(debug_raw_events) == 0, ( - f"Found :debug/:raw suffix events that should be removed: {debug_raw_events}" - ) - - -# --------------------------------------------------------------------------- -# Part 2: session:start — raw field in payload -# --------------------------------------------------------------------------- - - -def _minimal_config(raw: bool | None = None): - """Build a minimal valid config with optional raw flag.""" - session_config: dict = { - "orchestrator": "loop-basic", - "context": "context-simple", - } - if raw is not None: - session_config["raw"] = raw - return { - "session": session_config, - "providers": [], - "tools": [], - } - - -def _make_capture_handler(events_list): - async def handler(event, data): - events_list.append((event, dict(data))) - return HookResult(action="continue") - - return handler - - -def _setup_mock_session(session): - """Mount minimal mocks and mark as initialized.""" - mock_orchestrator = AsyncMock() - mock_orchestrator.execute = AsyncMock(return_value="ok") - mock_context = Mock() - mock_context.add_message = AsyncMock() - mock_context.get_messages = AsyncMock(return_value=[]) - session.coordinator.mount_points["orchestrator"] = mock_orchestrator - session.coordinator.mount_points["context"] = mock_context - session.coordinator.mount_points["providers"] = {"mock": Mock()} - session._initialized = True - - -@pytest.mark.asyncio -async def test_session_start_includes_raw_field_when_raw_true(): - """session:start payload includes 'raw' when session.raw=true.""" - config = _minimal_config(raw=True) - session = PyAmplifierSession(config) - _setup_mock_session(session) - - emitted = [] - session.coordinator.hooks.on( - SESSION_START, _make_capture_handler(emitted), name="test-capture" - ) - - await session.execute("hello") - - start_events = [d for e, d in emitted if e == SESSION_START] - assert len(start_events) == 1, f"Expected 1 SESSION_START, got {len(start_events)}" - payload = start_events[0] - assert "raw" in payload, ( - "Expected 'raw' key in session:start payload when session.raw=true" - ) - - -@pytest.mark.asyncio -async def test_session_start_excludes_raw_field_when_raw_false(): - """session:start payload does NOT include 'raw' when session.raw=false.""" - config = _minimal_config(raw=False) - session = PyAmplifierSession(config) - _setup_mock_session(session) - - emitted = [] - session.coordinator.hooks.on( - SESSION_START, _make_capture_handler(emitted), name="test-capture" - ) - - await session.execute("hello") - - start_events = [d for e, d in emitted if e == SESSION_START] - assert len(start_events) == 1 - payload = start_events[0] - assert "raw" not in payload, ( - "Expected no 'raw' key in session:start payload when session.raw=false" - ) - - -@pytest.mark.asyncio -async def test_session_start_excludes_raw_field_when_raw_absent(): - """session:start payload does NOT include 'raw' when session.raw is not set.""" - config = _minimal_config(raw=None) - session = PyAmplifierSession(config) - _setup_mock_session(session) - - emitted = [] - session.coordinator.hooks.on( - SESSION_START, _make_capture_handler(emitted), name="test-capture" - ) - - await session.execute("hello") - - start_events = [d for e, d in emitted if e == SESSION_START] - assert len(start_events) == 1 - payload = start_events[0] - assert "raw" not in payload, ( - "Expected no 'raw' key in session:start payload when raw not configured" - ) - - -# --------------------------------------------------------------------------- -# Part 3: session:resume — raw field in payload -# --------------------------------------------------------------------------- - - -@pytest.mark.asyncio -async def test_session_resume_includes_raw_field_when_raw_true(): - """session:resume payload includes 'raw' when session.raw=true.""" - config = _minimal_config(raw=True) - session = PyAmplifierSession(config, is_resumed=True) - _setup_mock_session(session) - - emitted = [] - session.coordinator.hooks.on( - SESSION_RESUME, _make_capture_handler(emitted), name="test-capture" - ) - - await session.execute("hello") - - resume_events = [d for e, d in emitted if e == SESSION_RESUME] - assert len(resume_events) == 1, ( - f"Expected 1 SESSION_RESUME, got {len(resume_events)}" - ) - payload = resume_events[0] - assert "raw" in payload, ( - "Expected 'raw' key in session:resume payload when session.raw=true" - ) - - -@pytest.mark.asyncio -async def test_session_resume_excludes_raw_field_when_raw_absent(): - """session:resume payload does NOT include 'raw' when session.raw is not set.""" - config = _minimal_config(raw=None) - session = PyAmplifierSession(config, is_resumed=True) - _setup_mock_session(session) - - emitted = [] - session.coordinator.hooks.on( - SESSION_RESUME, _make_capture_handler(emitted), name="test-capture" - ) - - await session.execute("hello") - - resume_events = [d for e, d in emitted if e == SESSION_RESUME] - assert len(resume_events) == 1 - payload = resume_events[0] - assert "raw" not in payload, ( - "Expected no 'raw' key in session:resume payload when raw not configured" - ) - - -# --------------------------------------------------------------------------- -# Part 4: session:fork — raw field in payload -# --------------------------------------------------------------------------- - - -def _setup_mock_loader(session): - """Replace the session loader with a mock that succeeds silently.""" - mock_mount = AsyncMock(return_value=None) - session.loader.load = AsyncMock(return_value=mock_mount) - - -@pytest.mark.asyncio -async def test_session_fork_includes_raw_field_when_raw_true(): - """session:fork payload includes 'raw' when session.raw=true.""" - config = _minimal_config(raw=True) - parent_id = "parent-session-id-raw-true" - session = PyAmplifierSession(config, parent_id=parent_id) - - _setup_mock_loader(session) - - emitted = [] - session.coordinator.hooks.on( - SESSION_FORK, _make_capture_handler(emitted), name="test-capture" - ) - - await session.initialize() - - fork_events = [d for e, d in emitted if e == SESSION_FORK] - assert len(fork_events) == 1, f"Expected 1 SESSION_FORK, got {len(fork_events)}" - payload = fork_events[0] - assert "raw" in payload, ( - "Expected 'raw' key in session:fork payload when session.raw=true" - ) - - -@pytest.mark.asyncio -async def test_session_fork_excludes_raw_field_when_raw_absent(): - """session:fork payload does NOT include 'raw' when session.raw is not set.""" - config = _minimal_config(raw=None) - parent_id = "parent-session-id-no-raw" - session = PyAmplifierSession(config, parent_id=parent_id) - - _setup_mock_loader(session) - - emitted = [] - session.coordinator.hooks.on( - SESSION_FORK, _make_capture_handler(emitted), name="test-capture" - ) - - await session.initialize() - - fork_events = [d for e, d in emitted if e == SESSION_FORK] - assert len(fork_events) == 1 - payload = fork_events[0] - assert "raw" not in payload, ( - "Expected no 'raw' key in session:fork payload when raw not configured" - ) - - -# --------------------------------------------------------------------------- -# Part 5: No extra tiered events emitted -# --------------------------------------------------------------------------- - - -@pytest.mark.asyncio -async def test_session_start_emits_only_one_session_event_with_raw_true(): - """With raw=true, only ONE session:start event is emitted (not 3 tiered events).""" - config = _minimal_config(raw=True) - session = PyAmplifierSession(config) - _setup_mock_session(session) - - emitted = [] - - async def capture_all(event, data): - if event.startswith("session:start"): - emitted.append(event) - return HookResult(action="continue") - - # Subscribe to any session:start* event - session.coordinator.hooks.on(SESSION_START, capture_all, name="test-capture-base") - - # Also check old debug/raw event strings don't appear - all_emitted_events = [] - - async def capture_every_event(event, data): - all_emitted_events.append(event) - return HookResult(action="continue") - - # Hook into the wildcard if possible, otherwise just check the specific events - await session.execute("hello") - - # Only 1 session:start should have been emitted - assert len(emitted) == 1, ( - f"Expected exactly 1 session:start emission, got {len(emitted)}: {emitted}" - ) - - -@pytest.mark.asyncio -async def test_session_fork_emits_only_one_session_event_with_raw_true(): - """With raw=true, only ONE session:fork event is emitted (not 3 tiered events).""" - config = _minimal_config(raw=True) - parent_id = "parent-one-fork" - session = PyAmplifierSession(config, parent_id=parent_id) - _setup_mock_loader(session) - - fork_events_emitted = [] - - async def capture_fork(event, data): - fork_events_emitted.append(event) - return HookResult(action="continue") - - session.coordinator.hooks.on(SESSION_FORK, capture_fork, name="test-capture-fork") - - await session.initialize() - - assert len(fork_events_emitted) == 1, ( - f"Expected exactly 1 session:fork emission, got {len(fork_events_emitted)}: {fork_events_emitted}" - ) - - -# --------------------------------------------------------------------------- -# Part 6: raw field contains redacted config (no plain secrets) -# --------------------------------------------------------------------------- - - -@pytest.mark.asyncio -async def test_session_start_raw_field_is_redacted(): - """The 'raw' field in session:start must have secrets redacted.""" - config = { - "session": { - "orchestrator": "loop-basic", - "context": "context-simple", - "raw": True, - }, - "providers": [ - { - "module": "some-provider", - "config": {"api_key": "super-secret-key-1234"}, - } - ], - "tools": [], - } - - session = PyAmplifierSession(config) - _setup_mock_session(session) - - emitted = [] - session.coordinator.hooks.on( - SESSION_START, _make_capture_handler(emitted), name="test-capture" - ) - - await session.execute("hello") - - start_events = [d for e, d in emitted if e == SESSION_START] - assert len(start_events) == 1 - payload = start_events[0] - assert "raw" in payload - - # The raw field should not contain the plain-text secret - raw_str = str(payload["raw"]) - assert "super-secret-key-1234" not in raw_str, ( - "Raw field must not contain plain-text secrets — redact_secrets must be applied" - ) diff --git a/tests/test_event_taxonomy.py b/tests/test_event_taxonomy.py index 1118167..0ba11ff 100644 --- a/tests/test_event_taxonomy.py +++ b/tests/test_event_taxonomy.py @@ -30,24 +30,15 @@ def test_all_event_constants_in_all_events(): def test_events_follow_naming_convention(): - """Verify all events follow namespace:action convention. - - CP-V: The :debug and :raw detail tiers have been removed. - All events now follow the two-part namespace:action pattern only. - """ + """Verify all events follow namespace:action convention.""" for event in events.ALL_EVENTS: assert ":" in event, f"Event {event} missing namespace separator ':'" parts = event.split(":") assert len(parts) >= 2, ( f"Event {event} should follow namespace:action convention" ) - # CP-V: No more :debug or :raw tiers — all events must be exactly 2 parts - # (namespace:action). The only exception is provider:tool_sequence_repaired - # which uses underscores within the action portion (not a third colon-part). - assert len(parts) == 2, ( - f"Event {event} has more than 2 ':' parts — " - f"verbosity tiers (:debug/:raw) were removed in CP-V" - ) + # All events must be exactly 2 parts (namespace:action) + assert len(parts) == 2, f"Event {event} has more than 2 ':' parts" namespace = parts[0] action = parts[1] assert namespace, f"Event {event} has empty namespace" From be4f290960769146dcfeeca91bde457824d464ec Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Fri, 6 Mar 2026 06:11:36 -0800 Subject: [PATCH 8/9] =?UTF-8?q?fix:=20resolve=20CI=20failures=20=E2=80=94?= =?UTF-8?q?=20cargo=20fmt=20and=20rename=20emit=5Fdebug=5Fevents=20referen?= =?UTF-8?q?ce?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../python/tests/test_switchover_session.py | 6 +- crates/amplifier-core/src/events.rs | 6 +- proto/amplifier_module_pb2.py | 284 +++++++++--------- proto/amplifier_module_pb2_grpc.py | 6 +- 4 files changed, 149 insertions(+), 153 deletions(-) diff --git a/bindings/python/tests/test_switchover_session.py b/bindings/python/tests/test_switchover_session.py index 62fe3ba..65af6ba 100644 --- a/bindings/python/tests/test_switchover_session.py +++ b/bindings/python/tests/test_switchover_session.py @@ -242,12 +242,12 @@ def test_session_exec_is_thin_helper(): """_session_exec.py must still exist as a thin boundary helper called by Rust. Rust's PySession::execute() imports amplifier_core._session_exec and calls - run_orchestrator() and emit_debug_events(). CANNOT be deleted. + run_orchestrator() and emit_raw_field_if_configured(). CANNOT be deleted. """ - from amplifier_core._session_exec import run_orchestrator, emit_debug_events + from amplifier_core._session_exec import run_orchestrator, emit_raw_field_if_configured assert callable(run_orchestrator) - assert callable(emit_debug_events) + assert callable(emit_raw_field_if_configured) def test_collect_helper_is_boundary_helper(): diff --git a/crates/amplifier-core/src/events.rs b/crates/amplifier-core/src/events.rs index edb9cd5..5f8766a 100644 --- a/crates/amplifier-core/src/events.rs +++ b/crates/amplifier-core/src/events.rs @@ -340,11 +340,7 @@ mod tests { #[test] fn all_events_count() { - assert_eq!( - ALL_EVENTS.len(), - 41, - "expected 41 canonical events" - ); + assert_eq!(ALL_EVENTS.len(), 41, "expected 41 canonical events"); } #[test] diff --git a/proto/amplifier_module_pb2.py b/proto/amplifier_module_pb2.py index 88fcbb3..53cf4ac 100644 --- a/proto/amplifier_module_pb2.py +++ b/proto/amplifier_module_pb2.py @@ -24,7 +24,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16\x61mplifier_module.proto\x12\x10\x61mplifier.module\"\x07\n\x05\x45mpty\"F\n\x08ToolSpec\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x17\n\x0fparameters_json\x18\x03 \x01(\t\"9\n\x12ToolExecuteRequest\x12\r\n\x05input\x18\x01 \x01(\x0c\x12\x14\n\x0c\x63ontent_type\x18\x02 \x01(\t\"[\n\x13ToolExecuteResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0e\n\x06output\x18\x02 \x01(\x0c\x12\x14\n\x0c\x63ontent_type\x18\x03 \x01(\t\x12\r\n\x05\x65rror\x18\x04 \x01(\t\"\xd6\x01\n\nModuleInfo\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x31\n\x0bmodule_type\x18\x04 \x01(\x0e\x32\x1c.amplifier.module.ModuleType\x12\x13\n\x0bmount_point\x18\x05 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x06 \x01(\t\x12\x1a\n\x12\x63onfig_schema_json\x18\x07 \x01(\t\x12\x14\n\x0c\x63\x61pabilities\x18\x08 \x03(\t\x12\x0e\n\x06\x61uthor\x18\t \x01(\t\"\x8c\x01\n\x0cMountRequest\x12:\n\x06\x63onfig\x18\x01 \x03(\x0b\x32*.amplifier.module.MountRequest.ConfigEntry\x12\x11\n\tmodule_id\x18\x02 \x01(\t\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"_\n\rMountResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12.\n\x06status\x18\x03 \x01(\x0e\x32\x1e.amplifier.module.HealthStatus\"V\n\x13HealthCheckResponse\x12.\n\x06status\x18\x01 \x01(\x0e\x32\x1e.amplifier.module.HealthStatus\x12\x0f\n\x07message\x18\x02 \x01(\t\"\xd9\x02\n\x0b\x43onfigField\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x35\n\nfield_type\x18\x02 \x01(\x0e\x32!.amplifier.module.ConfigFieldType\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x10\n\x08required\x18\x04 \x01(\x08\x12\x15\n\rdefault_value\x18\x05 \x01(\t\x12\x13\n\x0bplaceholder\x18\x06 \x01(\t\x12\x0f\n\x07options\x18\x07 \x03(\t\x12>\n\tshow_when\x18\x08 \x03(\x0b\x32+.amplifier.module.ConfigField.ShowWhenEntry\x12\x16\n\x0erequires_model\x18\t \x01(\x08\x12\x18\n\x10validation_regex\x18\n \x01(\t\x1a/\n\rShowWhenEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xbc\x01\n\rProviderError\x12\x37\n\nerror_type\x18\x01 \x01(\x0e\x32#.amplifier.module.ProviderErrorType\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x15\n\rprovider_name\x18\x03 \x01(\t\x12\r\n\x05model\x18\x04 \x01(\t\x12\x13\n\x0bstatus_code\x18\x05 \x01(\x05\x12\x11\n\tretryable\x18\x06 \x01(\x08\x12\x13\n\x0bretry_after\x18\x07 \x01(\x01\"\x97\x01\n\tToolError\x12\x33\n\nerror_type\x18\x01 \x01(\x0e\x32\x1f.amplifier.module.ToolErrorType\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x11\n\ttool_name\x18\x03 \x01(\t\x12\x0e\n\x06stdout\x18\x04 \x01(\t\x12\x0e\n\x06stderr\x18\x05 \x01(\t\x12\x11\n\texit_code\x18\x06 \x01(\x05\"d\n\tHookError\x12\x33\n\nerror_type\x18\x01 \x01(\x0e\x32\x1f.amplifier.module.HookErrorType\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x11\n\thook_name\x18\x03 \x01(\t\"\xef\x01\n\x0e\x41mplifierError\x12\x39\n\x0eprovider_error\x18\x01 \x01(\x0b\x32\x1f.amplifier.module.ProviderErrorH\x00\x12\x31\n\ntool_error\x18\x02 \x01(\x0b\x32\x1b.amplifier.module.ToolErrorH\x00\x12\x31\n\nhook_error\x18\x03 \x01(\x0b\x32\x1b.amplifier.module.HookErrorH\x00\x12\x17\n\rgeneric_error\x18\x04 \x01(\tH\x00\x12\x1a\n\x10validation_error\x18\x05 \x01(\tH\x00\x42\x07\n\x05\x65rror\"\x19\n\tTextBlock\x12\x0c\n\x04text\x18\x01 \x01(\t\"E\n\rThinkingBlock\x12\x10\n\x08thinking\x18\x01 \x01(\t\x12\x11\n\tsignature\x18\x02 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x03 \x01(\t\"%\n\x15RedactedThinkingBlock\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\t\"=\n\rToolCallBlock\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x12\n\ninput_json\x18\x03 \x01(\t\"<\n\x0fToolResultBlock\x12\x14\n\x0ctool_call_id\x18\x01 \x01(\t\x12\x13\n\x0boutput_json\x18\x02 \x01(\t\"C\n\nImageBlock\x12\x12\n\nmedia_type\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x13\n\x0bsource_json\x18\x03 \x01(\t\"2\n\x0eReasoningBlock\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12\x0f\n\x07summary\x18\x02 \x01(\t\"\xf1\x03\n\x0c\x43ontentBlock\x12\x31\n\ntext_block\x18\x01 \x01(\x0b\x32\x1b.amplifier.module.TextBlockH\x00\x12\x39\n\x0ethinking_block\x18\x02 \x01(\x0b\x32\x1f.amplifier.module.ThinkingBlockH\x00\x12J\n\x17redacted_thinking_block\x18\x03 \x01(\x0b\x32\'.amplifier.module.RedactedThinkingBlockH\x00\x12:\n\x0ftool_call_block\x18\x04 \x01(\x0b\x32\x1f.amplifier.module.ToolCallBlockH\x00\x12>\n\x11tool_result_block\x18\x05 \x01(\x0b\x32!.amplifier.module.ToolResultBlockH\x00\x12\x33\n\x0bimage_block\x18\x06 \x01(\x0b\x32\x1c.amplifier.module.ImageBlockH\x00\x12;\n\x0freasoning_block\x18\x07 \x01(\x0b\x32 .amplifier.module.ReasoningBlockH\x00\x12\x30\n\nvisibility\x18\x08 \x01(\x0e\x32\x1c.amplifier.module.VisibilityB\x07\n\x05\x62lock\"B\n\x10\x43ontentBlockList\x12.\n\x06\x62locks\x18\x01 \x03(\x0b\x32\x1e.amplifier.module.ContentBlock\"\xca\x01\n\x07Message\x12$\n\x04role\x18\x01 \x01(\x0e\x32\x16.amplifier.module.Role\x12\x16\n\x0ctext_content\x18\x02 \x01(\tH\x00\x12;\n\rblock_content\x18\x03 \x01(\x0b\x32\".amplifier.module.ContentBlockListH\x00\x12\x0c\n\x04name\x18\x04 \x01(\t\x12\x14\n\x0ctool_call_id\x18\x05 \x01(\t\x12\x15\n\rmetadata_json\x18\x06 \x01(\tB\t\n\x07\x63ontent\"C\n\x0fToolCallMessage\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x16\n\x0e\x61rguments_json\x18\x03 \x01(\t\"K\n\rToolSpecProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x17\n\x0fparameters_json\x18\x03 \x01(\t\"7\n\x10JsonSchemaFormat\x12\x13\n\x0bschema_json\x18\x01 \x01(\t\x12\x0e\n\x06strict\x18\x02 \x01(\x08\"u\n\x0eResponseFormat\x12\x0e\n\x04text\x18\x01 \x01(\x08H\x00\x12\x0e\n\x04json\x18\x02 \x01(\x08H\x00\x12\x39\n\x0bjson_schema\x18\x03 \x01(\x0b\x32\".amplifier.module.JsonSchemaFormatH\x00\x42\x08\n\x06\x66ormat\"\xa3\x01\n\x05Usage\x12\x15\n\rprompt_tokens\x18\x01 \x01(\x05\x12\x19\n\x11\x63ompletion_tokens\x18\x02 \x01(\x05\x12\x14\n\x0ctotal_tokens\x18\x03 \x01(\x05\x12\x18\n\x10reasoning_tokens\x18\x04 \x01(\x05\x12\x19\n\x11\x63\x61\x63he_read_tokens\x18\x05 \x01(\x05\x12\x1d\n\x15\x63\x61\x63he_creation_tokens\x18\x06 \x01(\x05\"@\n\x0b\x44\x65gradation\x12\x11\n\trequested\x18\x01 \x01(\t\x12\x0e\n\x06\x61\x63tual\x18\x02 \x01(\t\x12\x0e\n\x06reason\x18\x03 \x01(\t\"\x9a\x03\n\x0b\x43hatRequest\x12+\n\x08messages\x18\x01 \x03(\x0b\x32\x19.amplifier.module.Message\x12.\n\x05tools\x18\x02 \x03(\x0b\x32\x1f.amplifier.module.ToolSpecProto\x12\x39\n\x0fresponse_format\x18\x03 \x01(\x0b\x32 .amplifier.module.ResponseFormat\x12\x13\n\x0btemperature\x18\x04 \x01(\x01\x12\r\n\x05top_p\x18\x05 \x01(\x01\x12\x19\n\x11max_output_tokens\x18\x06 \x01(\x05\x12\x17\n\x0f\x63onversation_id\x18\x07 \x01(\t\x12\x0e\n\x06stream\x18\x08 \x01(\x08\x12\x15\n\rmetadata_json\x18\t \x01(\t\x12\r\n\x05model\x18\n \x01(\t\x12\x13\n\x0btool_choice\x18\x0b \x01(\t\x12\x0c\n\x04stop\x18\x0c \x03(\t\x12\x18\n\x10reasoning_effort\x18\r \x01(\t\x12\x0f\n\x07timeout\x18\x0e \x01(\x01\x12\x17\n\x0f\x65xtensions_json\x18\x0f \x01(\t\"\xe0\x01\n\x0c\x43hatResponse\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12\x35\n\ntool_calls\x18\x02 \x03(\x0b\x32!.amplifier.module.ToolCallMessage\x12&\n\x05usage\x18\x03 \x01(\x0b\x32\x17.amplifier.module.Usage\x12\x32\n\x0b\x64\x65gradation\x18\x04 \x01(\x0b\x32\x1d.amplifier.module.Degradation\x12\x15\n\rfinish_reason\x18\x05 \x01(\t\x12\x15\n\rmetadata_json\x18\x06 \x01(\t\"F\n\nToolResult\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x13\n\x0boutput_json\x18\x02 \x01(\t\x12\x12\n\nerror_json\x18\x03 \x01(\t\"\x8d\x04\n\nHookResult\x12,\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1c.amplifier.module.HookAction\x12\x11\n\tdata_json\x18\x02 \x01(\t\x12\x0e\n\x06reason\x18\x03 \x01(\t\x12\x19\n\x11\x63ontext_injection\x18\x04 \x01(\t\x12\x46\n\x16\x63ontext_injection_role\x18\x05 \x01(\x0e\x32&.amplifier.module.ContextInjectionRole\x12\x11\n\tephemeral\x18\x06 \x01(\x08\x12\x17\n\x0f\x61pproval_prompt\x18\x07 \x01(\t\x12\x18\n\x10\x61pproval_options\x18\x08 \x03(\t\x12\x18\n\x10\x61pproval_timeout\x18\t \x01(\x01\x12;\n\x10\x61pproval_default\x18\n \x01(\x0e\x32!.amplifier.module.ApprovalDefault\x12\x17\n\x0fsuppress_output\x18\x0b \x01(\x08\x12\x14\n\x0cuser_message\x18\x0c \x01(\t\x12>\n\x12user_message_level\x18\r \x01(\x0e\x32\".amplifier.module.UserMessageLevel\x12\x1b\n\x13user_message_source\x18\x0e \x01(\t\x12\"\n\x1a\x61ppend_to_last_tool_result\x18\x0f \x01(\x08\"\x8d\x01\n\tModelInfo\x12\n\n\x02id\x18\x01 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x02 \x01(\t\x12\x16\n\x0e\x63ontext_window\x18\x03 \x01(\x05\x12\x19\n\x11max_output_tokens\x18\x04 \x01(\x05\x12\x14\n\x0c\x63\x61pabilities\x18\x05 \x03(\t\x12\x15\n\rdefaults_json\x18\x06 \x01(\t\"\xb7\x01\n\x0cProviderInfo\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12+\n\x06models\x18\x04 \x03(\x0b\x32\x1b.amplifier.module.ModelInfo\x12\x34\n\rconfig_fields\x18\x05 \x03(\x0b\x32\x1d.amplifier.module.ConfigField\x12\x15\n\rmetadata_json\x18\x06 \x01(\t\"o\n\x0f\x41pprovalRequest\x12\x11\n\ttool_name\x18\x01 \x01(\t\x12\x0e\n\x06\x61\x63tion\x18\x02 \x01(\t\x12\x14\n\x0c\x64\x65tails_json\x18\x03 \x01(\t\x12\x12\n\nrisk_level\x18\x04 \x01(\t\x12\x0f\n\x07timeout\x18\x05 \x01(\x01\"F\n\x10\x41pprovalResponse\x12\x10\n\x08\x61pproved\x18\x01 \x01(\x08\x12\x0e\n\x06reason\x18\x02 \x01(\t\x12\x10\n\x08remember\x18\x03 \x01(\x08\"A\n\x12ListModelsResponse\x12+\n\x06models\x18\x01 \x03(\x0b\x32\x1b.amplifier.module.ModelInfo\"O\n\x16ParseToolCallsResponse\x12\x35\n\ntool_calls\x18\x01 \x03(\x0b\x32!.amplifier.module.ToolCallMessage\"@\n\x1aOrchestratorExecuteRequest\x12\x0e\n\x06prompt\x18\x01 \x01(\t\x12\x12\n\nsession_id\x18\x02 \x01(\t\">\n\x1bOrchestratorExecuteResponse\x12\x10\n\x08response\x18\x01 \x01(\t\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"?\n\x11\x41\x64\x64MessageRequest\x12*\n\x07message\x18\x01 \x01(\x0b\x32\x19.amplifier.module.Message\"B\n\x13GetMessagesResponse\x12+\n\x08messages\x18\x01 \x03(\x0b\x32\x19.amplifier.module.Message\"J\n\x1bGetMessagesForRequestParams\x12\x14\n\x0ctoken_budget\x18\x01 \x01(\x05\x12\x15\n\rprovider_name\x18\x02 \x01(\t\"A\n\x12SetMessagesRequest\x12+\n\x08messages\x18\x01 \x03(\x0b\x32\x19.amplifier.module.Message\"5\n\x11HookHandleRequest\x12\r\n\x05\x65vent\x18\x01 \x01(\t\x12\x11\n\tdata_json\x18\x02 \x01(\t\"d\n\x1b\x43ompleteWithProviderRequest\x12\x15\n\rprovider_name\x18\x01 \x01(\t\x12.\n\x07request\x18\x02 \x01(\x0b\x32\x1d.amplifier.module.ChatRequest\";\n\x12\x45xecuteToolRequest\x12\x11\n\ttool_name\x18\x01 \x01(\t\x12\x12\n\ninput_json\x18\x02 \x01(\t\"3\n\x0f\x45mitHookRequest\x12\r\n\x05\x65vent\x18\x01 \x01(\t\x12\x11\n\tdata_json\x18\x02 \x01(\t\"V\n\x19\x45mitHookAndCollectRequest\x12\r\n\x05\x65vent\x18\x01 \x01(\t\x12\x11\n\tdata_json\x18\x02 \x01(\t\x12\x17\n\x0ftimeout_seconds\x18\x03 \x01(\x01\"4\n\x1a\x45mitHookAndCollectResponse\x12\x16\n\x0eresponses_json\x18\x01 \x03(\t\"(\n\x12GetMessagesRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\"Y\n\x17KernelAddMessageRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12*\n\x07message\x18\x02 \x01(\x0b\x32\x19.amplifier.module.Message\"a\n\x17GetMountedModuleRequest\x12\x13\n\x0bmodule_name\x18\x01 \x01(\t\x12\x31\n\x0bmodule_type\x18\x02 \x01(\x0e\x32\x1c.amplifier.module.ModuleType\"U\n\x18GetMountedModuleResponse\x12\r\n\x05\x66ound\x18\x01 \x01(\x08\x12*\n\x04info\x18\x02 \x01(\x0b\x32\x1c.amplifier.module.ModuleInfo\"=\n\x19RegisterCapabilityRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x12\n\nvalue_json\x18\x02 \x01(\t\"$\n\x14GetCapabilityRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\":\n\x15GetCapabilityResponse\x12\r\n\x05\x66ound\x18\x01 \x01(\x08\x12\x12\n\nvalue_json\x18\x02 \x01(\t*\xbc\x01\n\nModuleType\x12\x1b\n\x17MODULE_TYPE_UNSPECIFIED\x10\x00\x12\x18\n\x14MODULE_TYPE_PROVIDER\x10\x01\x12\x14\n\x10MODULE_TYPE_TOOL\x10\x02\x12\x14\n\x10MODULE_TYPE_HOOK\x10\x03\x12\x16\n\x12MODULE_TYPE_MEMORY\x10\x04\x12\x19\n\x15MODULE_TYPE_GUARDRAIL\x10\x05\x12\x18\n\x14MODULE_TYPE_APPROVAL\x10\x06*\x82\x01\n\x0cHealthStatus\x12\x1d\n\x19HEALTH_STATUS_UNSPECIFIED\x10\x00\x12\x19\n\x15HEALTH_STATUS_SERVING\x10\x01\x12\x1d\n\x19HEALTH_STATUS_NOT_SERVING\x10\x02\x12\x19\n\x15HEALTH_STATUS_UNKNOWN\x10\x03*\xad\x01\n\x0f\x43onfigFieldType\x12!\n\x1d\x43ONFIG_FIELD_TYPE_UNSPECIFIED\x10\x00\x12\x1c\n\x18\x43ONFIG_FIELD_TYPE_STRING\x10\x01\x12\x1c\n\x18\x43ONFIG_FIELD_TYPE_NUMBER\x10\x02\x12\x1d\n\x19\x43ONFIG_FIELD_TYPE_BOOLEAN\x10\x03\x12\x1c\n\x18\x43ONFIG_FIELD_TYPE_SECRET\x10\x04*\xd8\x02\n\x11ProviderErrorType\x12#\n\x1fPROVIDER_ERROR_TYPE_UNSPECIFIED\x10\x00\x12\x1c\n\x18PROVIDER_ERROR_TYPE_AUTH\x10\x01\x12\"\n\x1ePROVIDER_ERROR_TYPE_RATE_LIMIT\x10\x02\x12&\n\"PROVIDER_ERROR_TYPE_CONTEXT_LENGTH\x10\x03\x12\'\n#PROVIDER_ERROR_TYPE_INVALID_REQUEST\x10\x04\x12&\n\"PROVIDER_ERROR_TYPE_CONTENT_FILTER\x10\x05\x12#\n\x1fPROVIDER_ERROR_TYPE_UNAVAILABLE\x10\x06\x12\x1f\n\x1bPROVIDER_ERROR_TYPE_TIMEOUT\x10\x07\x12\x1d\n\x19PROVIDER_ERROR_TYPE_OTHER\x10\x08*\x8c\x01\n\rToolErrorType\x12\x1f\n\x1bTOOL_ERROR_TYPE_UNSPECIFIED\x10\x00\x12\x1d\n\x19TOOL_ERROR_TYPE_EXECUTION\x10\x01\x12\x1e\n\x1aTOOL_ERROR_TYPE_VALIDATION\x10\x02\x12\x1b\n\x17TOOL_ERROR_TYPE_TIMEOUT\x10\x03*\x8c\x01\n\rHookErrorType\x12\x1f\n\x1bHOOK_ERROR_TYPE_UNSPECIFIED\x10\x00\x12\x1d\n\x19HOOK_ERROR_TYPE_EXECUTION\x10\x01\x12\x1e\n\x1aHOOK_ERROR_TYPE_VALIDATION\x10\x02\x12\x1b\n\x17HOOK_ERROR_TYPE_TIMEOUT\x10\x03*\x86\x01\n\x04Role\x12\x14\n\x10ROLE_UNSPECIFIED\x10\x00\x12\x0f\n\x0bROLE_SYSTEM\x10\x01\x12\r\n\tROLE_USER\x10\x02\x12\x12\n\x0eROLE_ASSISTANT\x10\x03\x12\r\n\tROLE_TOOL\x10\x04\x12\x11\n\rROLE_FUNCTION\x10\x05\x12\x12\n\x0eROLE_DEVELOPER\x10\x06*o\n\nVisibility\x12\x1a\n\x16VISIBILITY_UNSPECIFIED\x10\x00\x12\x12\n\x0eVISIBILITY_ALL\x10\x01\x12\x17\n\x13VISIBILITY_LLM_ONLY\x10\x02\x12\x18\n\x14VISIBILITY_USER_ONLY\x10\x03*\xab\x01\n\nHookAction\x12\x1b\n\x17HOOK_ACTION_UNSPECIFIED\x10\x00\x12\x18\n\x14HOOK_ACTION_CONTINUE\x10\x01\x12\x16\n\x12HOOK_ACTION_MODIFY\x10\x02\x12\x14\n\x10HOOK_ACTION_DENY\x10\x03\x12\x1e\n\x1aHOOK_ACTION_INJECT_CONTEXT\x10\x04\x12\x18\n\x14HOOK_ACTION_ASK_USER\x10\x05*\xa8\x01\n\x14\x43ontextInjectionRole\x12&\n\"CONTEXT_INJECTION_ROLE_UNSPECIFIED\x10\x00\x12!\n\x1d\x43ONTEXT_INJECTION_ROLE_SYSTEM\x10\x01\x12\x1f\n\x1b\x43ONTEXT_INJECTION_ROLE_USER\x10\x02\x12$\n CONTEXT_INJECTION_ROLE_ASSISTANT\x10\x03*l\n\x0f\x41pprovalDefault\x12 \n\x1c\x41PPROVAL_DEFAULT_UNSPECIFIED\x10\x00\x12\x1c\n\x18\x41PPROVAL_DEFAULT_APPROVE\x10\x01\x12\x19\n\x15\x41PPROVAL_DEFAULT_DENY\x10\x02*\x91\x01\n\x10UserMessageLevel\x12\"\n\x1eUSER_MESSAGE_LEVEL_UNSPECIFIED\x10\x00\x12\x1b\n\x17USER_MESSAGE_LEVEL_INFO\x10\x01\x12\x1e\n\x1aUSER_MESSAGE_LEVEL_WARNING\x10\x02\x12\x1c\n\x18USER_MESSAGE_LEVEL_ERROR\x10\x03\x32\xa5\x01\n\x0bToolService\x12>\n\x07GetSpec\x12\x17.amplifier.module.Empty\x1a\x1a.amplifier.module.ToolSpec\x12V\n\x07\x45xecute\x12$.amplifier.module.ToolExecuteRequest\x1a%.amplifier.module.ToolExecuteResponse2\x9f\x03\n\x0fProviderService\x12\x42\n\x07GetInfo\x12\x17.amplifier.module.Empty\x1a\x1e.amplifier.module.ProviderInfo\x12K\n\nListModels\x12\x17.amplifier.module.Empty\x1a$.amplifier.module.ListModelsResponse\x12I\n\x08\x43omplete\x12\x1d.amplifier.module.ChatRequest\x1a\x1e.amplifier.module.ChatResponse\x12T\n\x11\x43ompleteStreaming\x12\x1d.amplifier.module.ChatRequest\x1a\x1e.amplifier.module.ChatResponse0\x01\x12Z\n\x0eParseToolCalls\x12\x1e.amplifier.module.ChatResponse\x1a(.amplifier.module.ParseToolCallsResponse2}\n\x13OrchestratorService\x12\x66\n\x07\x45xecute\x12,.amplifier.module.OrchestratorExecuteRequest\x1a-.amplifier.module.OrchestratorExecuteResponse2\xa3\x03\n\x0e\x43ontextService\x12J\n\nAddMessage\x12#.amplifier.module.AddMessageRequest\x1a\x17.amplifier.module.Empty\x12M\n\x0bGetMessages\x12\x17.amplifier.module.Empty\x1a%.amplifier.module.GetMessagesResponse\x12m\n\x15GetMessagesForRequest\x12-.amplifier.module.GetMessagesForRequestParams\x1a%.amplifier.module.GetMessagesResponse\x12L\n\x0bSetMessages\x12$.amplifier.module.SetMessagesRequest\x1a\x17.amplifier.module.Empty\x12\x39\n\x05\x43lear\x12\x17.amplifier.module.Empty\x1a\x17.amplifier.module.Empty2Z\n\x0bHookService\x12K\n\x06Handle\x12#.amplifier.module.HookHandleRequest\x1a\x1c.amplifier.module.HookResult2k\n\x0f\x41pprovalService\x12X\n\x0fRequestApproval\x12!.amplifier.module.ApprovalRequest\x1a\".amplifier.module.ApprovalResponse2\xcb\x07\n\rKernelService\x12\x65\n\x14\x43ompleteWithProvider\x12-.amplifier.module.CompleteWithProviderRequest\x1a\x1e.amplifier.module.ChatResponse\x12p\n\x1d\x43ompleteWithProviderStreaming\x12-.amplifier.module.CompleteWithProviderRequest\x1a\x1e.amplifier.module.ChatResponse0\x01\x12Q\n\x0b\x45xecuteTool\x12$.amplifier.module.ExecuteToolRequest\x1a\x1c.amplifier.module.ToolResult\x12\x46\n\x08\x45mitHook\x12!.amplifier.module.EmitHookRequest\x1a\x17.amplifier.module.Empty\x12o\n\x12\x45mitHookAndCollect\x12+.amplifier.module.EmitHookAndCollectRequest\x1a,.amplifier.module.EmitHookAndCollectResponse\x12Z\n\x0bGetMessages\x12$.amplifier.module.GetMessagesRequest\x1a%.amplifier.module.GetMessagesResponse\x12P\n\nAddMessage\x12).amplifier.module.KernelAddMessageRequest\x1a\x17.amplifier.module.Empty\x12i\n\x10GetMountedModule\x12).amplifier.module.GetMountedModuleRequest\x1a*.amplifier.module.GetMountedModuleResponse\x12Z\n\x12RegisterCapability\x12+.amplifier.module.RegisterCapabilityRequest\x1a\x17.amplifier.module.Empty\x12`\n\rGetCapability\x12&.amplifier.module.GetCapabilityRequest\x1a\'.amplifier.module.GetCapabilityResponse2\xaf\x02\n\x0fModuleLifecycle\x12H\n\x05Mount\x12\x1e.amplifier.module.MountRequest\x1a\x1f.amplifier.module.MountResponse\x12;\n\x07\x43leanup\x12\x17.amplifier.module.Empty\x1a\x17.amplifier.module.Empty\x12M\n\x0bHealthCheck\x12\x17.amplifier.module.Empty\x1a%.amplifier.module.HealthCheckResponse\x12\x46\n\rGetModuleInfo\x12\x17.amplifier.module.Empty\x1a\x1c.amplifier.module.ModuleInfob\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16\x61mplifier_module.proto\x12\x10\x61mplifier.module\"\x07\n\x05\x45mpty\"F\n\x08ToolSpec\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x17\n\x0fparameters_json\x18\x03 \x01(\t\"9\n\x12ToolExecuteRequest\x12\r\n\x05input\x18\x01 \x01(\x0c\x12\x14\n\x0c\x63ontent_type\x18\x02 \x01(\t\"[\n\x13ToolExecuteResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0e\n\x06output\x18\x02 \x01(\x0c\x12\x14\n\x0c\x63ontent_type\x18\x03 \x01(\t\x12\r\n\x05\x65rror\x18\x04 \x01(\t\"\xd6\x01\n\nModuleInfo\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x31\n\x0bmodule_type\x18\x04 \x01(\x0e\x32\x1c.amplifier.module.ModuleType\x12\x13\n\x0bmount_point\x18\x05 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x06 \x01(\t\x12\x1a\n\x12\x63onfig_schema_json\x18\x07 \x01(\t\x12\x14\n\x0c\x63\x61pabilities\x18\x08 \x03(\t\x12\x0e\n\x06\x61uthor\x18\t \x01(\t\"\x8c\x01\n\x0cMountRequest\x12:\n\x06\x63onfig\x18\x01 \x03(\x0b\x32*.amplifier.module.MountRequest.ConfigEntry\x12\x11\n\tmodule_id\x18\x02 \x01(\t\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"_\n\rMountResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12.\n\x06status\x18\x03 \x01(\x0e\x32\x1e.amplifier.module.HealthStatus\"V\n\x13HealthCheckResponse\x12.\n\x06status\x18\x01 \x01(\x0e\x32\x1e.amplifier.module.HealthStatus\x12\x0f\n\x07message\x18\x02 \x01(\t\"\xca\x02\n\x0b\x43onfigField\x12\n\n\x02id\x18\x01 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x02 \x01(\t\x12\x35\n\nfield_type\x18\x03 \x01(\x0e\x32!.amplifier.module.ConfigFieldType\x12\x0e\n\x06prompt\x18\x04 \x01(\t\x12\x0f\n\x07\x65nv_var\x18\x05 \x01(\t\x12\x0f\n\x07\x63hoices\x18\x06 \x03(\t\x12\x10\n\x08required\x18\x07 \x01(\x08\x12\x15\n\rdefault_value\x18\x08 \x01(\t\x12>\n\tshow_when\x18\t \x03(\x0b\x32+.amplifier.module.ConfigField.ShowWhenEntry\x12\x16\n\x0erequires_model\x18\n \x01(\x08\x1a/\n\rShowWhenEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xbc\x01\n\rProviderError\x12\x37\n\nerror_type\x18\x01 \x01(\x0e\x32#.amplifier.module.ProviderErrorType\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x15\n\rprovider_name\x18\x03 \x01(\t\x12\r\n\x05model\x18\x04 \x01(\t\x12\x13\n\x0bstatus_code\x18\x05 \x01(\x05\x12\x11\n\tretryable\x18\x06 \x01(\x08\x12\x13\n\x0bretry_after\x18\x07 \x01(\x01\"\x97\x01\n\tToolError\x12\x33\n\nerror_type\x18\x01 \x01(\x0e\x32\x1f.amplifier.module.ToolErrorType\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x11\n\ttool_name\x18\x03 \x01(\t\x12\x0e\n\x06stdout\x18\x04 \x01(\t\x12\x0e\n\x06stderr\x18\x05 \x01(\t\x12\x11\n\texit_code\x18\x06 \x01(\x05\"d\n\tHookError\x12\x33\n\nerror_type\x18\x01 \x01(\x0e\x32\x1f.amplifier.module.HookErrorType\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x11\n\thook_name\x18\x03 \x01(\t\"\xef\x01\n\x0e\x41mplifierError\x12\x39\n\x0eprovider_error\x18\x01 \x01(\x0b\x32\x1f.amplifier.module.ProviderErrorH\x00\x12\x31\n\ntool_error\x18\x02 \x01(\x0b\x32\x1b.amplifier.module.ToolErrorH\x00\x12\x31\n\nhook_error\x18\x03 \x01(\x0b\x32\x1b.amplifier.module.HookErrorH\x00\x12\x17\n\rgeneric_error\x18\x04 \x01(\tH\x00\x12\x1a\n\x10validation_error\x18\x05 \x01(\tH\x00\x42\x07\n\x05\x65rror\"\x19\n\tTextBlock\x12\x0c\n\x04text\x18\x01 \x01(\t\"E\n\rThinkingBlock\x12\x10\n\x08thinking\x18\x01 \x01(\t\x12\x11\n\tsignature\x18\x02 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x03 \x01(\t\"%\n\x15RedactedThinkingBlock\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\t\"=\n\rToolCallBlock\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x12\n\ninput_json\x18\x03 \x01(\t\"<\n\x0fToolResultBlock\x12\x14\n\x0ctool_call_id\x18\x01 \x01(\t\x12\x13\n\x0boutput_json\x18\x02 \x01(\t\"C\n\nImageBlock\x12\x12\n\nmedia_type\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x13\n\x0bsource_json\x18\x03 \x01(\t\"2\n\x0eReasoningBlock\x12\x0f\n\x07\x63ontent\x18\x01 \x03(\t\x12\x0f\n\x07summary\x18\x02 \x03(\t\"\xf1\x03\n\x0c\x43ontentBlock\x12\x31\n\ntext_block\x18\x01 \x01(\x0b\x32\x1b.amplifier.module.TextBlockH\x00\x12\x39\n\x0ethinking_block\x18\x02 \x01(\x0b\x32\x1f.amplifier.module.ThinkingBlockH\x00\x12J\n\x17redacted_thinking_block\x18\x03 \x01(\x0b\x32\'.amplifier.module.RedactedThinkingBlockH\x00\x12:\n\x0ftool_call_block\x18\x04 \x01(\x0b\x32\x1f.amplifier.module.ToolCallBlockH\x00\x12>\n\x11tool_result_block\x18\x05 \x01(\x0b\x32!.amplifier.module.ToolResultBlockH\x00\x12\x33\n\x0bimage_block\x18\x06 \x01(\x0b\x32\x1c.amplifier.module.ImageBlockH\x00\x12;\n\x0freasoning_block\x18\x07 \x01(\x0b\x32 .amplifier.module.ReasoningBlockH\x00\x12\x30\n\nvisibility\x18\x08 \x01(\x0e\x32\x1c.amplifier.module.VisibilityB\x07\n\x05\x62lock\"B\n\x10\x43ontentBlockList\x12.\n\x06\x62locks\x18\x01 \x03(\x0b\x32\x1e.amplifier.module.ContentBlock\"\xca\x01\n\x07Message\x12$\n\x04role\x18\x01 \x01(\x0e\x32\x16.amplifier.module.Role\x12\x16\n\x0ctext_content\x18\x02 \x01(\tH\x00\x12;\n\rblock_content\x18\x03 \x01(\x0b\x32\".amplifier.module.ContentBlockListH\x00\x12\x0c\n\x04name\x18\x04 \x01(\t\x12\x14\n\x0ctool_call_id\x18\x05 \x01(\t\x12\x15\n\rmetadata_json\x18\x06 \x01(\tB\t\n\x07\x63ontent\"C\n\x0fToolCallMessage\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x16\n\x0e\x61rguments_json\x18\x03 \x01(\t\"K\n\rToolSpecProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x17\n\x0fparameters_json\x18\x03 \x01(\t\"7\n\x10JsonSchemaFormat\x12\x13\n\x0bschema_json\x18\x01 \x01(\t\x12\x0e\n\x06strict\x18\x02 \x01(\x08\"u\n\x0eResponseFormat\x12\x0e\n\x04text\x18\x01 \x01(\x08H\x00\x12\x0e\n\x04json\x18\x02 \x01(\x08H\x00\x12\x39\n\x0bjson_schema\x18\x03 \x01(\x0b\x32\".amplifier.module.JsonSchemaFormatH\x00\x42\x08\n\x06\x66ormat\"\xa3\x01\n\x05Usage\x12\x15\n\rprompt_tokens\x18\x01 \x01(\x05\x12\x19\n\x11\x63ompletion_tokens\x18\x02 \x01(\x05\x12\x14\n\x0ctotal_tokens\x18\x03 \x01(\x05\x12\x18\n\x10reasoning_tokens\x18\x04 \x01(\x05\x12\x19\n\x11\x63\x61\x63he_read_tokens\x18\x05 \x01(\x05\x12\x1d\n\x15\x63\x61\x63he_creation_tokens\x18\x06 \x01(\x05\"@\n\x0b\x44\x65gradation\x12\x11\n\trequested\x18\x01 \x01(\t\x12\x0e\n\x06\x61\x63tual\x18\x02 \x01(\t\x12\x0e\n\x06reason\x18\x03 \x01(\t\"\x81\x03\n\x0b\x43hatRequest\x12+\n\x08messages\x18\x01 \x03(\x0b\x32\x19.amplifier.module.Message\x12.\n\x05tools\x18\x02 \x03(\x0b\x32\x1f.amplifier.module.ToolSpecProto\x12\x39\n\x0fresponse_format\x18\x03 \x01(\x0b\x32 .amplifier.module.ResponseFormat\x12\x13\n\x0btemperature\x18\x04 \x01(\x01\x12\r\n\x05top_p\x18\x05 \x01(\x01\x12\x19\n\x11max_output_tokens\x18\x06 \x01(\x05\x12\x17\n\x0f\x63onversation_id\x18\x07 \x01(\t\x12\x0e\n\x06stream\x18\x08 \x01(\x08\x12\x15\n\rmetadata_json\x18\t \x01(\t\x12\r\n\x05model\x18\n \x01(\t\x12\x13\n\x0btool_choice\x18\x0b \x01(\t\x12\x0c\n\x04stop\x18\x0c \x03(\t\x12\x18\n\x10reasoning_effort\x18\r \x01(\t\x12\x0f\n\x07timeout\x18\x0e \x01(\x01\"\xe0\x01\n\x0c\x43hatResponse\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12\x35\n\ntool_calls\x18\x02 \x03(\x0b\x32!.amplifier.module.ToolCallMessage\x12&\n\x05usage\x18\x03 \x01(\x0b\x32\x17.amplifier.module.Usage\x12\x32\n\x0b\x64\x65gradation\x18\x04 \x01(\x0b\x32\x1d.amplifier.module.Degradation\x12\x15\n\rfinish_reason\x18\x05 \x01(\t\x12\x15\n\rmetadata_json\x18\x06 \x01(\t\"F\n\nToolResult\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x13\n\x0boutput_json\x18\x02 \x01(\t\x12\x12\n\nerror_json\x18\x03 \x01(\t\"\x8d\x04\n\nHookResult\x12,\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1c.amplifier.module.HookAction\x12\x11\n\tdata_json\x18\x02 \x01(\t\x12\x0e\n\x06reason\x18\x03 \x01(\t\x12\x19\n\x11\x63ontext_injection\x18\x04 \x01(\t\x12\x46\n\x16\x63ontext_injection_role\x18\x05 \x01(\x0e\x32&.amplifier.module.ContextInjectionRole\x12\x11\n\tephemeral\x18\x06 \x01(\x08\x12\x17\n\x0f\x61pproval_prompt\x18\x07 \x01(\t\x12\x18\n\x10\x61pproval_options\x18\x08 \x03(\t\x12\x18\n\x10\x61pproval_timeout\x18\t \x01(\x01\x12;\n\x10\x61pproval_default\x18\n \x01(\x0e\x32!.amplifier.module.ApprovalDefault\x12\x17\n\x0fsuppress_output\x18\x0b \x01(\x08\x12\x14\n\x0cuser_message\x18\x0c \x01(\t\x12>\n\x12user_message_level\x18\r \x01(\x0e\x32\".amplifier.module.UserMessageLevel\x12\x1b\n\x13user_message_source\x18\x0e \x01(\t\x12\"\n\x1a\x61ppend_to_last_tool_result\x18\x0f \x01(\x08\"\x8d\x01\n\tModelInfo\x12\n\n\x02id\x18\x01 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x02 \x01(\t\x12\x16\n\x0e\x63ontext_window\x18\x03 \x01(\x05\x12\x19\n\x11max_output_tokens\x18\x04 \x01(\x05\x12\x14\n\x0c\x63\x61pabilities\x18\x05 \x03(\t\x12\x15\n\rdefaults_json\x18\x06 \x01(\t\"\xb0\x01\n\x0cProviderInfo\x12\n\n\x02id\x18\x01 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x02 \x01(\t\x12\x1b\n\x13\x63redential_env_vars\x18\x03 \x03(\t\x12\x14\n\x0c\x63\x61pabilities\x18\x04 \x03(\t\x12\x15\n\rdefaults_json\x18\x05 \x01(\t\x12\x34\n\rconfig_fields\x18\x06 \x03(\x0b\x32\x1d.amplifier.module.ConfigField\"o\n\x0f\x41pprovalRequest\x12\x11\n\ttool_name\x18\x01 \x01(\t\x12\x0e\n\x06\x61\x63tion\x18\x02 \x01(\t\x12\x14\n\x0c\x64\x65tails_json\x18\x03 \x01(\t\x12\x12\n\nrisk_level\x18\x04 \x01(\t\x12\x0f\n\x07timeout\x18\x05 \x01(\x01\"F\n\x10\x41pprovalResponse\x12\x10\n\x08\x61pproved\x18\x01 \x01(\x08\x12\x0e\n\x06reason\x18\x02 \x01(\t\x12\x10\n\x08remember\x18\x03 \x01(\x08\"A\n\x12ListModelsResponse\x12+\n\x06models\x18\x01 \x03(\x0b\x32\x1b.amplifier.module.ModelInfo\"O\n\x16ParseToolCallsResponse\x12\x35\n\ntool_calls\x18\x01 \x03(\x0b\x32!.amplifier.module.ToolCallMessage\"@\n\x1aOrchestratorExecuteRequest\x12\x0e\n\x06prompt\x18\x01 \x01(\t\x12\x12\n\nsession_id\x18\x02 \x01(\t\">\n\x1bOrchestratorExecuteResponse\x12\x10\n\x08response\x18\x01 \x01(\t\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"?\n\x11\x41\x64\x64MessageRequest\x12*\n\x07message\x18\x01 \x01(\x0b\x32\x19.amplifier.module.Message\"B\n\x13GetMessagesResponse\x12+\n\x08messages\x18\x01 \x03(\x0b\x32\x19.amplifier.module.Message\"J\n\x1bGetMessagesForRequestParams\x12\x14\n\x0ctoken_budget\x18\x01 \x01(\x05\x12\x15\n\rprovider_name\x18\x02 \x01(\t\"A\n\x12SetMessagesRequest\x12+\n\x08messages\x18\x01 \x03(\x0b\x32\x19.amplifier.module.Message\"5\n\x11HookHandleRequest\x12\r\n\x05\x65vent\x18\x01 \x01(\t\x12\x11\n\tdata_json\x18\x02 \x01(\t\"d\n\x1b\x43ompleteWithProviderRequest\x12\x15\n\rprovider_name\x18\x01 \x01(\t\x12.\n\x07request\x18\x02 \x01(\x0b\x32\x1d.amplifier.module.ChatRequest\";\n\x12\x45xecuteToolRequest\x12\x11\n\ttool_name\x18\x01 \x01(\t\x12\x12\n\ninput_json\x18\x02 \x01(\t\"3\n\x0f\x45mitHookRequest\x12\r\n\x05\x65vent\x18\x01 \x01(\t\x12\x11\n\tdata_json\x18\x02 \x01(\t\"V\n\x19\x45mitHookAndCollectRequest\x12\r\n\x05\x65vent\x18\x01 \x01(\t\x12\x11\n\tdata_json\x18\x02 \x01(\t\x12\x17\n\x0ftimeout_seconds\x18\x03 \x01(\x01\"4\n\x1a\x45mitHookAndCollectResponse\x12\x16\n\x0eresponses_json\x18\x01 \x03(\t\"(\n\x12GetMessagesRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\"Y\n\x17KernelAddMessageRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12*\n\x07message\x18\x02 \x01(\x0b\x32\x19.amplifier.module.Message\"a\n\x17GetMountedModuleRequest\x12\x13\n\x0bmodule_name\x18\x01 \x01(\t\x12\x31\n\x0bmodule_type\x18\x02 \x01(\x0e\x32\x1c.amplifier.module.ModuleType\"U\n\x18GetMountedModuleResponse\x12\r\n\x05\x66ound\x18\x01 \x01(\x08\x12*\n\x04info\x18\x02 \x01(\x0b\x32\x1c.amplifier.module.ModuleInfo\"=\n\x19RegisterCapabilityRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x12\n\nvalue_json\x18\x02 \x01(\t\"$\n\x14GetCapabilityRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\":\n\x15GetCapabilityResponse\x12\r\n\x05\x66ound\x18\x01 \x01(\x08\x12\x12\n\nvalue_json\x18\x02 \x01(\t*\xbc\x01\n\nModuleType\x12\x1b\n\x17MODULE_TYPE_UNSPECIFIED\x10\x00\x12\x18\n\x14MODULE_TYPE_PROVIDER\x10\x01\x12\x14\n\x10MODULE_TYPE_TOOL\x10\x02\x12\x14\n\x10MODULE_TYPE_HOOK\x10\x03\x12\x16\n\x12MODULE_TYPE_MEMORY\x10\x04\x12\x19\n\x15MODULE_TYPE_GUARDRAIL\x10\x05\x12\x18\n\x14MODULE_TYPE_APPROVAL\x10\x06*\x82\x01\n\x0cHealthStatus\x12\x1d\n\x19HEALTH_STATUS_UNSPECIFIED\x10\x00\x12\x19\n\x15HEALTH_STATUS_SERVING\x10\x01\x12\x1d\n\x19HEALTH_STATUS_NOT_SERVING\x10\x02\x12\x19\n\x15HEALTH_STATUS_UNKNOWN\x10\x03*\xad\x01\n\x0f\x43onfigFieldType\x12!\n\x1d\x43ONFIG_FIELD_TYPE_UNSPECIFIED\x10\x00\x12\x1c\n\x18\x43ONFIG_FIELD_TYPE_STRING\x10\x01\x12\x1c\n\x18\x43ONFIG_FIELD_TYPE_NUMBER\x10\x02\x12\x1d\n\x19\x43ONFIG_FIELD_TYPE_BOOLEAN\x10\x03\x12\x1c\n\x18\x43ONFIG_FIELD_TYPE_SECRET\x10\x04*\xd8\x02\n\x11ProviderErrorType\x12#\n\x1fPROVIDER_ERROR_TYPE_UNSPECIFIED\x10\x00\x12\x1c\n\x18PROVIDER_ERROR_TYPE_AUTH\x10\x01\x12\"\n\x1ePROVIDER_ERROR_TYPE_RATE_LIMIT\x10\x02\x12&\n\"PROVIDER_ERROR_TYPE_CONTEXT_LENGTH\x10\x03\x12\'\n#PROVIDER_ERROR_TYPE_INVALID_REQUEST\x10\x04\x12&\n\"PROVIDER_ERROR_TYPE_CONTENT_FILTER\x10\x05\x12#\n\x1fPROVIDER_ERROR_TYPE_UNAVAILABLE\x10\x06\x12\x1f\n\x1bPROVIDER_ERROR_TYPE_TIMEOUT\x10\x07\x12\x1d\n\x19PROVIDER_ERROR_TYPE_OTHER\x10\x08*\x8c\x01\n\rToolErrorType\x12\x1f\n\x1bTOOL_ERROR_TYPE_UNSPECIFIED\x10\x00\x12\x1d\n\x19TOOL_ERROR_TYPE_EXECUTION\x10\x01\x12\x1e\n\x1aTOOL_ERROR_TYPE_VALIDATION\x10\x02\x12\x1b\n\x17TOOL_ERROR_TYPE_TIMEOUT\x10\x03*\x8c\x01\n\rHookErrorType\x12\x1f\n\x1bHOOK_ERROR_TYPE_UNSPECIFIED\x10\x00\x12\x1d\n\x19HOOK_ERROR_TYPE_EXECUTION\x10\x01\x12\x1e\n\x1aHOOK_ERROR_TYPE_VALIDATION\x10\x02\x12\x1b\n\x17HOOK_ERROR_TYPE_TIMEOUT\x10\x03*\x86\x01\n\x04Role\x12\x14\n\x10ROLE_UNSPECIFIED\x10\x00\x12\x0f\n\x0bROLE_SYSTEM\x10\x01\x12\r\n\tROLE_USER\x10\x02\x12\x12\n\x0eROLE_ASSISTANT\x10\x03\x12\r\n\tROLE_TOOL\x10\x04\x12\x11\n\rROLE_FUNCTION\x10\x05\x12\x12\n\x0eROLE_DEVELOPER\x10\x06*o\n\nVisibility\x12\x1a\n\x16VISIBILITY_UNSPECIFIED\x10\x00\x12\x12\n\x0eVISIBILITY_ALL\x10\x01\x12\x17\n\x13VISIBILITY_LLM_ONLY\x10\x02\x12\x18\n\x14VISIBILITY_USER_ONLY\x10\x03*\xab\x01\n\nHookAction\x12\x1b\n\x17HOOK_ACTION_UNSPECIFIED\x10\x00\x12\x18\n\x14HOOK_ACTION_CONTINUE\x10\x01\x12\x16\n\x12HOOK_ACTION_MODIFY\x10\x02\x12\x14\n\x10HOOK_ACTION_DENY\x10\x03\x12\x1e\n\x1aHOOK_ACTION_INJECT_CONTEXT\x10\x04\x12\x18\n\x14HOOK_ACTION_ASK_USER\x10\x05*\xa8\x01\n\x14\x43ontextInjectionRole\x12&\n\"CONTEXT_INJECTION_ROLE_UNSPECIFIED\x10\x00\x12!\n\x1d\x43ONTEXT_INJECTION_ROLE_SYSTEM\x10\x01\x12\x1f\n\x1b\x43ONTEXT_INJECTION_ROLE_USER\x10\x02\x12$\n CONTEXT_INJECTION_ROLE_ASSISTANT\x10\x03*l\n\x0f\x41pprovalDefault\x12 \n\x1c\x41PPROVAL_DEFAULT_UNSPECIFIED\x10\x00\x12\x1c\n\x18\x41PPROVAL_DEFAULT_APPROVE\x10\x01\x12\x19\n\x15\x41PPROVAL_DEFAULT_DENY\x10\x02*\x91\x01\n\x10UserMessageLevel\x12\"\n\x1eUSER_MESSAGE_LEVEL_UNSPECIFIED\x10\x00\x12\x1b\n\x17USER_MESSAGE_LEVEL_INFO\x10\x01\x12\x1e\n\x1aUSER_MESSAGE_LEVEL_WARNING\x10\x02\x12\x1c\n\x18USER_MESSAGE_LEVEL_ERROR\x10\x03\x32\xa5\x01\n\x0bToolService\x12>\n\x07GetSpec\x12\x17.amplifier.module.Empty\x1a\x1a.amplifier.module.ToolSpec\x12V\n\x07\x45xecute\x12$.amplifier.module.ToolExecuteRequest\x1a%.amplifier.module.ToolExecuteResponse2\x9f\x03\n\x0fProviderService\x12\x42\n\x07GetInfo\x12\x17.amplifier.module.Empty\x1a\x1e.amplifier.module.ProviderInfo\x12K\n\nListModels\x12\x17.amplifier.module.Empty\x1a$.amplifier.module.ListModelsResponse\x12I\n\x08\x43omplete\x12\x1d.amplifier.module.ChatRequest\x1a\x1e.amplifier.module.ChatResponse\x12T\n\x11\x43ompleteStreaming\x12\x1d.amplifier.module.ChatRequest\x1a\x1e.amplifier.module.ChatResponse0\x01\x12Z\n\x0eParseToolCalls\x12\x1e.amplifier.module.ChatResponse\x1a(.amplifier.module.ParseToolCallsResponse2}\n\x13OrchestratorService\x12\x66\n\x07\x45xecute\x12,.amplifier.module.OrchestratorExecuteRequest\x1a-.amplifier.module.OrchestratorExecuteResponse2\xa3\x03\n\x0e\x43ontextService\x12J\n\nAddMessage\x12#.amplifier.module.AddMessageRequest\x1a\x17.amplifier.module.Empty\x12M\n\x0bGetMessages\x12\x17.amplifier.module.Empty\x1a%.amplifier.module.GetMessagesResponse\x12m\n\x15GetMessagesForRequest\x12-.amplifier.module.GetMessagesForRequestParams\x1a%.amplifier.module.GetMessagesResponse\x12L\n\x0bSetMessages\x12$.amplifier.module.SetMessagesRequest\x1a\x17.amplifier.module.Empty\x12\x39\n\x05\x43lear\x12\x17.amplifier.module.Empty\x1a\x17.amplifier.module.Empty2Z\n\x0bHookService\x12K\n\x06Handle\x12#.amplifier.module.HookHandleRequest\x1a\x1c.amplifier.module.HookResult2k\n\x0f\x41pprovalService\x12X\n\x0fRequestApproval\x12!.amplifier.module.ApprovalRequest\x1a\".amplifier.module.ApprovalResponse2\xd0\x07\n\rKernelService\x12\x65\n\x14\x43ompleteWithProvider\x12-.amplifier.module.CompleteWithProviderRequest\x1a\x1e.amplifier.module.ChatResponse\x12p\n\x1d\x43ompleteWithProviderStreaming\x12-.amplifier.module.CompleteWithProviderRequest\x1a\x1e.amplifier.module.ChatResponse0\x01\x12Q\n\x0b\x45xecuteTool\x12$.amplifier.module.ExecuteToolRequest\x1a\x1c.amplifier.module.ToolResult\x12K\n\x08\x45mitHook\x12!.amplifier.module.EmitHookRequest\x1a\x1c.amplifier.module.HookResult\x12o\n\x12\x45mitHookAndCollect\x12+.amplifier.module.EmitHookAndCollectRequest\x1a,.amplifier.module.EmitHookAndCollectResponse\x12Z\n\x0bGetMessages\x12$.amplifier.module.GetMessagesRequest\x1a%.amplifier.module.GetMessagesResponse\x12P\n\nAddMessage\x12).amplifier.module.KernelAddMessageRequest\x1a\x17.amplifier.module.Empty\x12i\n\x10GetMountedModule\x12).amplifier.module.GetMountedModuleRequest\x1a*.amplifier.module.GetMountedModuleResponse\x12Z\n\x12RegisterCapability\x12+.amplifier.module.RegisterCapabilityRequest\x1a\x17.amplifier.module.Empty\x12`\n\rGetCapability\x12&.amplifier.module.GetCapabilityRequest\x1a\'.amplifier.module.GetCapabilityResponse2\xaf\x02\n\x0fModuleLifecycle\x12H\n\x05Mount\x12\x1e.amplifier.module.MountRequest\x1a\x1f.amplifier.module.MountResponse\x12;\n\x07\x43leanup\x12\x17.amplifier.module.Empty\x1a\x17.amplifier.module.Empty\x12M\n\x0bHealthCheck\x12\x17.amplifier.module.Empty\x1a%.amplifier.module.HealthCheckResponse\x12\x46\n\rGetModuleInfo\x12\x17.amplifier.module.Empty\x1a\x1c.amplifier.module.ModuleInfob\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -35,30 +35,30 @@ _globals['_MOUNTREQUEST_CONFIGENTRY']._serialized_options = b'8\001' _globals['_CONFIGFIELD_SHOWWHENENTRY']._loaded_options = None _globals['_CONFIGFIELD_SHOWWHENENTRY']._serialized_options = b'8\001' - _globals['_MODULETYPE']._serialized_start=6772 - _globals['_MODULETYPE']._serialized_end=6960 - _globals['_HEALTHSTATUS']._serialized_start=6963 - _globals['_HEALTHSTATUS']._serialized_end=7093 - _globals['_CONFIGFIELDTYPE']._serialized_start=7096 - _globals['_CONFIGFIELDTYPE']._serialized_end=7269 - _globals['_PROVIDERERRORTYPE']._serialized_start=7272 - _globals['_PROVIDERERRORTYPE']._serialized_end=7616 - _globals['_TOOLERRORTYPE']._serialized_start=7619 - _globals['_TOOLERRORTYPE']._serialized_end=7759 - _globals['_HOOKERRORTYPE']._serialized_start=7762 - _globals['_HOOKERRORTYPE']._serialized_end=7902 - _globals['_ROLE']._serialized_start=7905 - _globals['_ROLE']._serialized_end=8039 - _globals['_VISIBILITY']._serialized_start=8041 - _globals['_VISIBILITY']._serialized_end=8152 - _globals['_HOOKACTION']._serialized_start=8155 - _globals['_HOOKACTION']._serialized_end=8326 - _globals['_CONTEXTINJECTIONROLE']._serialized_start=8329 - _globals['_CONTEXTINJECTIONROLE']._serialized_end=8497 - _globals['_APPROVALDEFAULT']._serialized_start=8499 - _globals['_APPROVALDEFAULT']._serialized_end=8607 - _globals['_USERMESSAGELEVEL']._serialized_start=8610 - _globals['_USERMESSAGELEVEL']._serialized_end=8755 + _globals['_MODULETYPE']._serialized_start=6725 + _globals['_MODULETYPE']._serialized_end=6913 + _globals['_HEALTHSTATUS']._serialized_start=6916 + _globals['_HEALTHSTATUS']._serialized_end=7046 + _globals['_CONFIGFIELDTYPE']._serialized_start=7049 + _globals['_CONFIGFIELDTYPE']._serialized_end=7222 + _globals['_PROVIDERERRORTYPE']._serialized_start=7225 + _globals['_PROVIDERERRORTYPE']._serialized_end=7569 + _globals['_TOOLERRORTYPE']._serialized_start=7572 + _globals['_TOOLERRORTYPE']._serialized_end=7712 + _globals['_HOOKERRORTYPE']._serialized_start=7715 + _globals['_HOOKERRORTYPE']._serialized_end=7855 + _globals['_ROLE']._serialized_start=7858 + _globals['_ROLE']._serialized_end=7992 + _globals['_VISIBILITY']._serialized_start=7994 + _globals['_VISIBILITY']._serialized_end=8105 + _globals['_HOOKACTION']._serialized_start=8108 + _globals['_HOOKACTION']._serialized_end=8279 + _globals['_CONTEXTINJECTIONROLE']._serialized_start=8282 + _globals['_CONTEXTINJECTIONROLE']._serialized_end=8450 + _globals['_APPROVALDEFAULT']._serialized_start=8452 + _globals['_APPROVALDEFAULT']._serialized_end=8560 + _globals['_USERMESSAGELEVEL']._serialized_start=8563 + _globals['_USERMESSAGELEVEL']._serialized_end=8708 _globals['_EMPTY']._serialized_start=44 _globals['_EMPTY']._serialized_end=51 _globals['_TOOLSPEC']._serialized_start=53 @@ -78,121 +78,121 @@ _globals['_HEALTHCHECKRESPONSE']._serialized_start=734 _globals['_HEALTHCHECKRESPONSE']._serialized_end=820 _globals['_CONFIGFIELD']._serialized_start=823 - _globals['_CONFIGFIELD']._serialized_end=1168 - _globals['_CONFIGFIELD_SHOWWHENENTRY']._serialized_start=1121 - _globals['_CONFIGFIELD_SHOWWHENENTRY']._serialized_end=1168 - _globals['_PROVIDERERROR']._serialized_start=1171 - _globals['_PROVIDERERROR']._serialized_end=1359 - _globals['_TOOLERROR']._serialized_start=1362 - _globals['_TOOLERROR']._serialized_end=1513 - _globals['_HOOKERROR']._serialized_start=1515 - _globals['_HOOKERROR']._serialized_end=1615 - _globals['_AMPLIFIERERROR']._serialized_start=1618 - _globals['_AMPLIFIERERROR']._serialized_end=1857 - _globals['_TEXTBLOCK']._serialized_start=1859 - _globals['_TEXTBLOCK']._serialized_end=1884 - _globals['_THINKINGBLOCK']._serialized_start=1886 - _globals['_THINKINGBLOCK']._serialized_end=1955 - _globals['_REDACTEDTHINKINGBLOCK']._serialized_start=1957 - _globals['_REDACTEDTHINKINGBLOCK']._serialized_end=1994 - _globals['_TOOLCALLBLOCK']._serialized_start=1996 - _globals['_TOOLCALLBLOCK']._serialized_end=2057 - _globals['_TOOLRESULTBLOCK']._serialized_start=2059 - _globals['_TOOLRESULTBLOCK']._serialized_end=2119 - _globals['_IMAGEBLOCK']._serialized_start=2121 - _globals['_IMAGEBLOCK']._serialized_end=2188 - _globals['_REASONINGBLOCK']._serialized_start=2190 - _globals['_REASONINGBLOCK']._serialized_end=2240 - _globals['_CONTENTBLOCK']._serialized_start=2243 - _globals['_CONTENTBLOCK']._serialized_end=2740 - _globals['_CONTENTBLOCKLIST']._serialized_start=2742 - _globals['_CONTENTBLOCKLIST']._serialized_end=2808 - _globals['_MESSAGE']._serialized_start=2811 - _globals['_MESSAGE']._serialized_end=3013 - _globals['_TOOLCALLMESSAGE']._serialized_start=3015 - _globals['_TOOLCALLMESSAGE']._serialized_end=3082 - _globals['_TOOLSPECPROTO']._serialized_start=3084 - _globals['_TOOLSPECPROTO']._serialized_end=3159 - _globals['_JSONSCHEMAFORMAT']._serialized_start=3161 - _globals['_JSONSCHEMAFORMAT']._serialized_end=3216 - _globals['_RESPONSEFORMAT']._serialized_start=3218 - _globals['_RESPONSEFORMAT']._serialized_end=3335 - _globals['_USAGE']._serialized_start=3338 - _globals['_USAGE']._serialized_end=3501 - _globals['_DEGRADATION']._serialized_start=3503 - _globals['_DEGRADATION']._serialized_end=3567 - _globals['_CHATREQUEST']._serialized_start=3570 - _globals['_CHATREQUEST']._serialized_end=3980 - _globals['_CHATRESPONSE']._serialized_start=3983 - _globals['_CHATRESPONSE']._serialized_end=4207 - _globals['_TOOLRESULT']._serialized_start=4209 - _globals['_TOOLRESULT']._serialized_end=4279 - _globals['_HOOKRESULT']._serialized_start=4282 - _globals['_HOOKRESULT']._serialized_end=4807 - _globals['_MODELINFO']._serialized_start=4810 - _globals['_MODELINFO']._serialized_end=4951 - _globals['_PROVIDERINFO']._serialized_start=4954 - _globals['_PROVIDERINFO']._serialized_end=5137 - _globals['_APPROVALREQUEST']._serialized_start=5139 - _globals['_APPROVALREQUEST']._serialized_end=5250 - _globals['_APPROVALRESPONSE']._serialized_start=5252 - _globals['_APPROVALRESPONSE']._serialized_end=5322 - _globals['_LISTMODELSRESPONSE']._serialized_start=5324 - _globals['_LISTMODELSRESPONSE']._serialized_end=5389 - _globals['_PARSETOOLCALLSRESPONSE']._serialized_start=5391 - _globals['_PARSETOOLCALLSRESPONSE']._serialized_end=5470 - _globals['_ORCHESTRATOREXECUTEREQUEST']._serialized_start=5472 - _globals['_ORCHESTRATOREXECUTEREQUEST']._serialized_end=5536 - _globals['_ORCHESTRATOREXECUTERESPONSE']._serialized_start=5538 - _globals['_ORCHESTRATOREXECUTERESPONSE']._serialized_end=5600 - _globals['_ADDMESSAGEREQUEST']._serialized_start=5602 - _globals['_ADDMESSAGEREQUEST']._serialized_end=5665 - _globals['_GETMESSAGESRESPONSE']._serialized_start=5667 - _globals['_GETMESSAGESRESPONSE']._serialized_end=5733 - _globals['_GETMESSAGESFORREQUESTPARAMS']._serialized_start=5735 - _globals['_GETMESSAGESFORREQUESTPARAMS']._serialized_end=5809 - _globals['_SETMESSAGESREQUEST']._serialized_start=5811 - _globals['_SETMESSAGESREQUEST']._serialized_end=5876 - _globals['_HOOKHANDLEREQUEST']._serialized_start=5878 - _globals['_HOOKHANDLEREQUEST']._serialized_end=5931 - _globals['_COMPLETEWITHPROVIDERREQUEST']._serialized_start=5933 - _globals['_COMPLETEWITHPROVIDERREQUEST']._serialized_end=6033 - _globals['_EXECUTETOOLREQUEST']._serialized_start=6035 - _globals['_EXECUTETOOLREQUEST']._serialized_end=6094 - _globals['_EMITHOOKREQUEST']._serialized_start=6096 - _globals['_EMITHOOKREQUEST']._serialized_end=6147 - _globals['_EMITHOOKANDCOLLECTREQUEST']._serialized_start=6149 - _globals['_EMITHOOKANDCOLLECTREQUEST']._serialized_end=6235 - _globals['_EMITHOOKANDCOLLECTRESPONSE']._serialized_start=6237 - _globals['_EMITHOOKANDCOLLECTRESPONSE']._serialized_end=6289 - _globals['_GETMESSAGESREQUEST']._serialized_start=6291 - _globals['_GETMESSAGESREQUEST']._serialized_end=6331 - _globals['_KERNELADDMESSAGEREQUEST']._serialized_start=6333 - _globals['_KERNELADDMESSAGEREQUEST']._serialized_end=6422 - _globals['_GETMOUNTEDMODULEREQUEST']._serialized_start=6424 - _globals['_GETMOUNTEDMODULEREQUEST']._serialized_end=6521 - _globals['_GETMOUNTEDMODULERESPONSE']._serialized_start=6523 - _globals['_GETMOUNTEDMODULERESPONSE']._serialized_end=6608 - _globals['_REGISTERCAPABILITYREQUEST']._serialized_start=6610 - _globals['_REGISTERCAPABILITYREQUEST']._serialized_end=6671 - _globals['_GETCAPABILITYREQUEST']._serialized_start=6673 - _globals['_GETCAPABILITYREQUEST']._serialized_end=6709 - _globals['_GETCAPABILITYRESPONSE']._serialized_start=6711 - _globals['_GETCAPABILITYRESPONSE']._serialized_end=6769 - _globals['_TOOLSERVICE']._serialized_start=8758 - _globals['_TOOLSERVICE']._serialized_end=8923 - _globals['_PROVIDERSERVICE']._serialized_start=8926 - _globals['_PROVIDERSERVICE']._serialized_end=9341 - _globals['_ORCHESTRATORSERVICE']._serialized_start=9343 - _globals['_ORCHESTRATORSERVICE']._serialized_end=9468 - _globals['_CONTEXTSERVICE']._serialized_start=9471 - _globals['_CONTEXTSERVICE']._serialized_end=9890 - _globals['_HOOKSERVICE']._serialized_start=9892 - _globals['_HOOKSERVICE']._serialized_end=9982 - _globals['_APPROVALSERVICE']._serialized_start=9984 - _globals['_APPROVALSERVICE']._serialized_end=10091 - _globals['_KERNELSERVICE']._serialized_start=10094 - _globals['_KERNELSERVICE']._serialized_end=11065 - _globals['_MODULELIFECYCLE']._serialized_start=11068 - _globals['_MODULELIFECYCLE']._serialized_end=11371 + _globals['_CONFIGFIELD']._serialized_end=1153 + _globals['_CONFIGFIELD_SHOWWHENENTRY']._serialized_start=1106 + _globals['_CONFIGFIELD_SHOWWHENENTRY']._serialized_end=1153 + _globals['_PROVIDERERROR']._serialized_start=1156 + _globals['_PROVIDERERROR']._serialized_end=1344 + _globals['_TOOLERROR']._serialized_start=1347 + _globals['_TOOLERROR']._serialized_end=1498 + _globals['_HOOKERROR']._serialized_start=1500 + _globals['_HOOKERROR']._serialized_end=1600 + _globals['_AMPLIFIERERROR']._serialized_start=1603 + _globals['_AMPLIFIERERROR']._serialized_end=1842 + _globals['_TEXTBLOCK']._serialized_start=1844 + _globals['_TEXTBLOCK']._serialized_end=1869 + _globals['_THINKINGBLOCK']._serialized_start=1871 + _globals['_THINKINGBLOCK']._serialized_end=1940 + _globals['_REDACTEDTHINKINGBLOCK']._serialized_start=1942 + _globals['_REDACTEDTHINKINGBLOCK']._serialized_end=1979 + _globals['_TOOLCALLBLOCK']._serialized_start=1981 + _globals['_TOOLCALLBLOCK']._serialized_end=2042 + _globals['_TOOLRESULTBLOCK']._serialized_start=2044 + _globals['_TOOLRESULTBLOCK']._serialized_end=2104 + _globals['_IMAGEBLOCK']._serialized_start=2106 + _globals['_IMAGEBLOCK']._serialized_end=2173 + _globals['_REASONINGBLOCK']._serialized_start=2175 + _globals['_REASONINGBLOCK']._serialized_end=2225 + _globals['_CONTENTBLOCK']._serialized_start=2228 + _globals['_CONTENTBLOCK']._serialized_end=2725 + _globals['_CONTENTBLOCKLIST']._serialized_start=2727 + _globals['_CONTENTBLOCKLIST']._serialized_end=2793 + _globals['_MESSAGE']._serialized_start=2796 + _globals['_MESSAGE']._serialized_end=2998 + _globals['_TOOLCALLMESSAGE']._serialized_start=3000 + _globals['_TOOLCALLMESSAGE']._serialized_end=3067 + _globals['_TOOLSPECPROTO']._serialized_start=3069 + _globals['_TOOLSPECPROTO']._serialized_end=3144 + _globals['_JSONSCHEMAFORMAT']._serialized_start=3146 + _globals['_JSONSCHEMAFORMAT']._serialized_end=3201 + _globals['_RESPONSEFORMAT']._serialized_start=3203 + _globals['_RESPONSEFORMAT']._serialized_end=3320 + _globals['_USAGE']._serialized_start=3323 + _globals['_USAGE']._serialized_end=3486 + _globals['_DEGRADATION']._serialized_start=3488 + _globals['_DEGRADATION']._serialized_end=3552 + _globals['_CHATREQUEST']._serialized_start=3555 + _globals['_CHATREQUEST']._serialized_end=3940 + _globals['_CHATRESPONSE']._serialized_start=3943 + _globals['_CHATRESPONSE']._serialized_end=4167 + _globals['_TOOLRESULT']._serialized_start=4169 + _globals['_TOOLRESULT']._serialized_end=4239 + _globals['_HOOKRESULT']._serialized_start=4242 + _globals['_HOOKRESULT']._serialized_end=4767 + _globals['_MODELINFO']._serialized_start=4770 + _globals['_MODELINFO']._serialized_end=4911 + _globals['_PROVIDERINFO']._serialized_start=4914 + _globals['_PROVIDERINFO']._serialized_end=5090 + _globals['_APPROVALREQUEST']._serialized_start=5092 + _globals['_APPROVALREQUEST']._serialized_end=5203 + _globals['_APPROVALRESPONSE']._serialized_start=5205 + _globals['_APPROVALRESPONSE']._serialized_end=5275 + _globals['_LISTMODELSRESPONSE']._serialized_start=5277 + _globals['_LISTMODELSRESPONSE']._serialized_end=5342 + _globals['_PARSETOOLCALLSRESPONSE']._serialized_start=5344 + _globals['_PARSETOOLCALLSRESPONSE']._serialized_end=5423 + _globals['_ORCHESTRATOREXECUTEREQUEST']._serialized_start=5425 + _globals['_ORCHESTRATOREXECUTEREQUEST']._serialized_end=5489 + _globals['_ORCHESTRATOREXECUTERESPONSE']._serialized_start=5491 + _globals['_ORCHESTRATOREXECUTERESPONSE']._serialized_end=5553 + _globals['_ADDMESSAGEREQUEST']._serialized_start=5555 + _globals['_ADDMESSAGEREQUEST']._serialized_end=5618 + _globals['_GETMESSAGESRESPONSE']._serialized_start=5620 + _globals['_GETMESSAGESRESPONSE']._serialized_end=5686 + _globals['_GETMESSAGESFORREQUESTPARAMS']._serialized_start=5688 + _globals['_GETMESSAGESFORREQUESTPARAMS']._serialized_end=5762 + _globals['_SETMESSAGESREQUEST']._serialized_start=5764 + _globals['_SETMESSAGESREQUEST']._serialized_end=5829 + _globals['_HOOKHANDLEREQUEST']._serialized_start=5831 + _globals['_HOOKHANDLEREQUEST']._serialized_end=5884 + _globals['_COMPLETEWITHPROVIDERREQUEST']._serialized_start=5886 + _globals['_COMPLETEWITHPROVIDERREQUEST']._serialized_end=5986 + _globals['_EXECUTETOOLREQUEST']._serialized_start=5988 + _globals['_EXECUTETOOLREQUEST']._serialized_end=6047 + _globals['_EMITHOOKREQUEST']._serialized_start=6049 + _globals['_EMITHOOKREQUEST']._serialized_end=6100 + _globals['_EMITHOOKANDCOLLECTREQUEST']._serialized_start=6102 + _globals['_EMITHOOKANDCOLLECTREQUEST']._serialized_end=6188 + _globals['_EMITHOOKANDCOLLECTRESPONSE']._serialized_start=6190 + _globals['_EMITHOOKANDCOLLECTRESPONSE']._serialized_end=6242 + _globals['_GETMESSAGESREQUEST']._serialized_start=6244 + _globals['_GETMESSAGESREQUEST']._serialized_end=6284 + _globals['_KERNELADDMESSAGEREQUEST']._serialized_start=6286 + _globals['_KERNELADDMESSAGEREQUEST']._serialized_end=6375 + _globals['_GETMOUNTEDMODULEREQUEST']._serialized_start=6377 + _globals['_GETMOUNTEDMODULEREQUEST']._serialized_end=6474 + _globals['_GETMOUNTEDMODULERESPONSE']._serialized_start=6476 + _globals['_GETMOUNTEDMODULERESPONSE']._serialized_end=6561 + _globals['_REGISTERCAPABILITYREQUEST']._serialized_start=6563 + _globals['_REGISTERCAPABILITYREQUEST']._serialized_end=6624 + _globals['_GETCAPABILITYREQUEST']._serialized_start=6626 + _globals['_GETCAPABILITYREQUEST']._serialized_end=6662 + _globals['_GETCAPABILITYRESPONSE']._serialized_start=6664 + _globals['_GETCAPABILITYRESPONSE']._serialized_end=6722 + _globals['_TOOLSERVICE']._serialized_start=8711 + _globals['_TOOLSERVICE']._serialized_end=8876 + _globals['_PROVIDERSERVICE']._serialized_start=8879 + _globals['_PROVIDERSERVICE']._serialized_end=9294 + _globals['_ORCHESTRATORSERVICE']._serialized_start=9296 + _globals['_ORCHESTRATORSERVICE']._serialized_end=9421 + _globals['_CONTEXTSERVICE']._serialized_start=9424 + _globals['_CONTEXTSERVICE']._serialized_end=9843 + _globals['_HOOKSERVICE']._serialized_start=9845 + _globals['_HOOKSERVICE']._serialized_end=9935 + _globals['_APPROVALSERVICE']._serialized_start=9937 + _globals['_APPROVALSERVICE']._serialized_end=10044 + _globals['_KERNELSERVICE']._serialized_start=10047 + _globals['_KERNELSERVICE']._serialized_end=11023 + _globals['_MODULELIFECYCLE']._serialized_start=11026 + _globals['_MODULELIFECYCLE']._serialized_end=11329 # @@protoc_insertion_point(module_scope) diff --git a/proto/amplifier_module_pb2_grpc.py b/proto/amplifier_module_pb2_grpc.py index 9a93798..1c5c8cd 100644 --- a/proto/amplifier_module_pb2_grpc.py +++ b/proto/amplifier_module_pb2_grpc.py @@ -906,7 +906,7 @@ def __init__(self, channel): self.EmitHook = channel.unary_unary( '/amplifier.module.KernelService/EmitHook', request_serializer=amplifier__module__pb2.EmitHookRequest.SerializeToString, - response_deserializer=amplifier__module__pb2.Empty.FromString, + response_deserializer=amplifier__module__pb2.HookResult.FromString, _registered_method=True) self.EmitHookAndCollect = channel.unary_unary( '/amplifier.module.KernelService/EmitHookAndCollect', @@ -1030,7 +1030,7 @@ def add_KernelServiceServicer_to_server(servicer, server): 'EmitHook': grpc.unary_unary_rpc_method_handler( servicer.EmitHook, request_deserializer=amplifier__module__pb2.EmitHookRequest.FromString, - response_serializer=amplifier__module__pb2.Empty.SerializeToString, + response_serializer=amplifier__module__pb2.HookResult.SerializeToString, ), 'EmitHookAndCollect': grpc.unary_unary_rpc_method_handler( servicer.EmitHookAndCollect, @@ -1170,7 +1170,7 @@ def EmitHook(request, target, '/amplifier.module.KernelService/EmitHook', amplifier__module__pb2.EmitHookRequest.SerializeToString, - amplifier__module__pb2.Empty.FromString, + amplifier__module__pb2.HookResult.FromString, options, channel_credentials, insecure, From 7af64c6a9120c5266d9004cfda698a79c50d9cb5 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Fri, 6 Mar 2026 06:24:25 -0800 Subject: [PATCH 9/9] refactor: remove duplicated ALL_EVENTS list and unnecessary _engine.pyi stubs - events.py: add ALL_EVENTS to the _engine import block; delete the local 41-item list that duplicated the Rust-authoritative value - _engine.pyi: remove the module-level event constant stubs block (~41 lines) that was added as defensive coding for a transitional state; restores the file to its origin/main shape - tests: add test_all_events_from_engine.py which asserts that events.ALL_EVENTS is the exact object exported by _engine (not a locally-constructed copy) --- python/amplifier_core/_engine.pyi | 47 ---------------------------- python/amplifier_core/events.py | 45 +------------------------- tests/test_all_events_from_engine.py | 17 ++++++++++ 3 files changed, 18 insertions(+), 91 deletions(-) create mode 100644 tests/test_all_events_from_engine.py diff --git a/python/amplifier_core/_engine.pyi b/python/amplifier_core/_engine.pyi index 3417bc6..b81cc00 100644 --- a/python/amplifier_core/_engine.pyi +++ b/python/amplifier_core/_engine.pyi @@ -18,53 +18,6 @@ from typing import Any, Optional __version__: str RUST_AVAILABLE: bool -# --------------------------------------------------------------------------- -# Event constants -# --------------------------------------------------------------------------- - -SESSION_START: str -SESSION_END: str -SESSION_FORK: str -SESSION_RESUME: str -PROMPT_SUBMIT: str -PROMPT_COMPLETE: str -PLAN_START: str -PLAN_END: str -PROVIDER_REQUEST: str -PROVIDER_RESPONSE: str -PROVIDER_RETRY: str -PROVIDER_ERROR: str -PROVIDER_THROTTLE: str -PROVIDER_TOOL_SEQUENCE_REPAIRED: str -PROVIDER_RESOLVE: str -LLM_REQUEST: str -LLM_RESPONSE: str -CONTENT_BLOCK_START: str -CONTENT_BLOCK_DELTA: str -CONTENT_BLOCK_END: str -THINKING_DELTA: str -THINKING_FINAL: str -TOOL_PRE: str -TOOL_POST: str -TOOL_ERROR: str -CONTEXT_PRE_COMPACT: str -CONTEXT_POST_COMPACT: str -CONTEXT_COMPACTION: str -CONTEXT_INCLUDE: str -ORCHESTRATOR_COMPLETE: str -EXECUTION_START: str -EXECUTION_END: str -USER_NOTIFICATION: str -ARTIFACT_WRITE: str -ARTIFACT_READ: str -POLICY_VIOLATION: str -APPROVAL_REQUIRED: str -APPROVAL_GRANTED: str -APPROVAL_DENIED: str -CANCEL_REQUESTED: str -CANCEL_COMPLETED: str -ALL_EVENTS: list[str] - # --------------------------------------------------------------------------- # RustSession — wraps amplifier_core::Session # --------------------------------------------------------------------------- diff --git a/python/amplifier_core/events.py b/python/amplifier_core/events.py index d7f41b1..36d1775 100644 --- a/python/amplifier_core/events.py +++ b/python/amplifier_core/events.py @@ -60,52 +60,9 @@ # Cancellation lifecycle CANCEL_REQUESTED, CANCEL_COMPLETED, + ALL_EVENTS, ) -ALL_EVENTS: list[str] = [ - SESSION_START, - SESSION_END, - SESSION_FORK, - SESSION_RESUME, - PROMPT_SUBMIT, - PROMPT_COMPLETE, - PLAN_START, - PLAN_END, - PROVIDER_REQUEST, - PROVIDER_RESPONSE, - PROVIDER_RETRY, - PROVIDER_ERROR, - PROVIDER_THROTTLE, - PROVIDER_TOOL_SEQUENCE_REPAIRED, - PROVIDER_RESOLVE, - LLM_REQUEST, - LLM_RESPONSE, - CONTENT_BLOCK_START, - CONTENT_BLOCK_DELTA, - CONTENT_BLOCK_END, - THINKING_DELTA, - THINKING_FINAL, - TOOL_PRE, - TOOL_POST, - TOOL_ERROR, - CONTEXT_PRE_COMPACT, - CONTEXT_POST_COMPACT, - CONTEXT_COMPACTION, - CONTEXT_INCLUDE, - ORCHESTRATOR_COMPLETE, - EXECUTION_START, - EXECUTION_END, - USER_NOTIFICATION, - ARTIFACT_WRITE, - ARTIFACT_READ, - POLICY_VIOLATION, - APPROVAL_REQUIRED, - APPROVAL_GRANTED, - APPROVAL_DENIED, - CANCEL_REQUESTED, - CANCEL_COMPLETED, -] - __all__ = [ "SESSION_START", "SESSION_END", diff --git a/tests/test_all_events_from_engine.py b/tests/test_all_events_from_engine.py new file mode 100644 index 0000000..9c89ccf --- /dev/null +++ b/tests/test_all_events_from_engine.py @@ -0,0 +1,17 @@ +""" +Test that ALL_EVENTS in events.py is imported from _engine (not locally defined). + +This is a structural test: it verifies we don't have a duplicated local list +that can drift from the Rust-authoritative source. +""" + +from amplifier_core import _engine +from amplifier_core import events + + +def test_all_events_is_imported_from_engine(): + """ALL_EVENTS must be the object exported by the Rust _engine, not a local copy.""" + assert events.ALL_EVENTS is _engine.ALL_EVENTS, ( + "events.ALL_EVENTS is a local copy, not imported from _engine. " + "Remove the local list and add ALL_EVENTS to the _engine import." + )