M3 implementada e backlog atualizado

This commit is contained in:
Marcos Lohmann
2026-09-14 10:11:29 -03:00
parent f3efcc3414
commit c894c1604b
65 changed files with 4261 additions and 74 deletions

View File

@@ -0,0 +1,46 @@
"""Channel adapter contract (EP-12-T02, README.md §3.3, §6.13).
Channel adapters encapsulate differences between experiences (app,
proactive/push, media/campaigns, ...) so the Agent Runtime's dispatch logic
(EP-12-T01) stays channel-agnostic. `ChannelRegistry.adapter_for` fails
loudly (`UnsupportedChannelError`) for an unregistered channel -- an
NBADecision with `channel="sms"` when no SMS adapter is registered must
never be silently dropped or misdelivered to the wrong channel.
"""
from __future__ import annotations
from typing import Any, Protocol
from emcp_bus.agent_runtime.dispatcher import DispatchResult
from emcp_bus.orchestrator.nba_model import NBADecision
class UnsupportedChannelError(Exception):
pass
class ChannelAdapter(Protocol):
channel: str
async def deliver(self, nba: NBADecision, dispatch_result: DispatchResult) -> dict[str, Any]:
"""Formats `dispatch_result` for this channel's delivery mechanism
and returns whatever the channel considers a delivery receipt --
this RI's adapters return a plain dict; a production channel
(push notification, SMS gateway, ...) would return that channel's
own receipt shape instead."""
...
class ChannelRegistry:
def __init__(self, adapters: list[ChannelAdapter]) -> None:
self._adapters = {adapter.channel: adapter for adapter in adapters}
def adapter_for(self, channel: str) -> ChannelAdapter:
adapter = self._adapters.get(channel)
if adapter is None:
raise UnsupportedChannelError(
f"No channel adapter registered for {channel!r} "
f"(registered: {sorted(self._adapters)})"
)
return adapter

View File

@@ -0,0 +1,31 @@
"""Conversational channel adapter (EP-12-T02, README.md §3.3, §6.13).
The one concrete channel adapter this RI demonstrates -- formats a dispatch
result as a conversational message for the `app` channel. A production
deployment would add adapters for push/media/campaign channels the same
way, each registered under its own `channel` value in a `ChannelRegistry`,
without changing `AgentDispatcher` (EP-12-T01) at all.
"""
from __future__ import annotations
from typing import Any
from emcp_bus.agent_runtime.dispatcher import DispatchResult
from emcp_bus.orchestrator.nba_model import NBADecision
class ConversationalChannelAdapter:
channel = "app"
async def deliver(self, nba: NBADecision, dispatch_result: DispatchResult) -> dict[str, Any]:
if dispatch_result.status == "denied":
message = f"I wasn't able to complete {nba.action} right now."
else:
message = f"Done: {nba.action} completed successfully."
return {
"channel": self.channel,
"decision_id": nba.decision_id,
"message": message,
"status": dispatch_result.status,
}