# SPEC-002 — Agent Runtime ## 1. Overview The Agent Runtime coordinates conversational execution for agents on the OCI platform. It combines LangGraph graphs, per-conversation state, intent routing, domain agents, guardrails, judges, long-term memory, and persistence. The reusable framework library provides services and contracts consumed by agent backends; the reference `agent_template_backend` assembles these services into an executable workflow. ## 2. Implementation structure | Component | Location | Responsibility | |---|---|---| | Shared runtime | `libs/agent_framework/src/agent_framework/runtime/` | Reusable agent-execution capabilities. | | Reference workflow | `templates/agent_template_backend/app/workflows/agent_graph.py` | Graph construction and workflow-node implementations. | | Workflow state | `templates/agent_template_backend/app/state.py` | Typed definition of data exchanged between nodes. | | Agent configuration | `templates/agent_template_backend/config/agents.yaml` | Agent registry and policy/configuration file paths. | | Routing configuration | `templates/agent_template_backend/config/routing.yaml` | Router mode, fallback, threshold, and intent catalog. | | Tool configuration | `templates/agent_template_backend/config/tools.yaml` | Tool definitions used by agents. | ## 3. Execution state The workflow uses the `AgentState` definition shown below. Its `TypedDict(total=False)` declaration allows nodes to add the fields relevant to each execution stage without requiring every field to be present in each state instance. ```python from typing import Any, TypedDict class AgentState(TypedDict, total=False): tenant_id: str agent_id: str session_id: str conversation_key: str workflow_id: str agent_profile: dict[str, Any] user_text: str sanitized_input: str route: str intent: str route_decision: dict[str, Any] answer: str final_answer: str history: list[dict[str, Any]] context: dict[str, Any] guardrail_decisions: list[dict[str, Any]] judge_results: list[dict[str, Any]] next_state: str domain: str mcp_tools: list[str] mcp_results: list[dict[str, Any]] rag_results: list[dict[str, Any]] available_mcp_tools: list[str] selected_tool_call: dict[str, Any] pending_tool_call: dict[str, Any] active_transaction: dict[str, Any] last_transaction: dict[str, Any] transaction_status: str transaction_pre_validation: dict[str, Any] transaction_evidence: list[dict[str, Any]] last_transaction_evidence: dict[str, Any] relevant_transaction_evidence: list[dict[str, Any]] confirmation_required: bool confirmation_received: bool tool_policy_result: dict[str, Any] missing_parameters: list[str] supervisor_plan: dict[str, Any] supervisor_results: list[dict[str, Any]] agent_responses: list[dict[str, Any]] pending_topics: list[dict[str, Any]] handled_topics: list[dict[str, Any]] operation_results: dict[str, dict[str, Any]] multi_intent_plan: dict[str, Any] active_agent: str route_bypassed: bool continuity_signal: dict[str, Any] session_control: str session_ended: bool human_handoff_requested: bool blocked: bool supervisor_action: str supervisor_guidance: str supervisor_attempt: int supervisor_handover_reason: str output_supervisor_results: list[dict[str, Any]] output_guardrails_already_applied: bool long_term_memories: list[dict[str, Any]] long_term_memory_context: str long_term_memory_write_result: dict[str, Any] long_term_memory_subject_key: str long_term_memory_load_error: str ``` ## 4. Execution flow The workflow is compiled using `FrameworkStateGraph(AgentState)` with a checkpointer created by `create_langgraph_checkpointer(self.settings)`. ```mermaid flowchart TD START([START]) --> input_guardrails input_guardrails -->|continue| load_long_term_memory input_guardrails -->|blocked| output_guardrails load_long_term_memory --> routing_decision routing_decision --> billing_agent routing_decision --> product_agent routing_decision --> orders_agent routing_decision --> support_agent routing_decision --> handoff routing_decision --> human_handoff routing_decision --> end_session routing_decision --> supervisor_agent billing_agent --> output_supervisor product_agent --> output_supervisor orders_agent --> output_supervisor support_agent --> output_supervisor handoff --> output_supervisor human_handoff --> output_supervisor end_session --> output_supervisor supervisor_agent --> output_supervisor output_supervisor --> output_guardrails output_guardrails -->|continue| judge output_guardrails -->|blocked| persist judge --> supervisor_review supervisor_review --> persist_long_term_memory persist_long_term_memory --> persist persist --> END([END]) ``` Routing after `input_guardrails` depends on `state['blocked']`. Following `routing_decision`, the value of `state['route']` selects one of the registered nodes, defaulting to `billing_agent` when the key is absent. Routing after `output_guardrails` also checks `state['blocked']`. ## 5. Workflow nodes | Node | Responsibility | |---|---| | `input_guardrails` | Evaluates and processes input before routing. | | `load_long_term_memory` | Loads long-term memory context. | | `routing_decision` | Determines the conversation's execution destination. | | `billing_agent` | Runs the billing agent. | | `product_agent` | Runs the product and services agent. | | `orders_agent` | Runs the orders agent. | | `support_agent` | Runs the support agent. | | `handoff` | Handles the transfer path defined by the workflow. | | `human_handoff` | Handles requests for transfer to a human representative. | | `end_session` | Handles session termination. | | `supervisor_agent` | Runs the supervisor-related path. | | `output_supervisor` | Processes response supervision. | | `output_guardrails` | Applies output policies. | | `judge` | Evaluates the response. | | `supervisor_review` | Processes review after the judge. | | `persist_long_term_memory` | Performs the long-term memory write stage. | | `persist` | Performs the final workflow persistence stage. | Nodes are wrapped by the `_node` method, which executes them within `self.langgraph_telemetry.node(name, state)`. ## 6. Routing The `config/routing.yaml` file defines the main options under the `router` key: ```yaml # Configurable enterprise routing with MCP-aware intents. router: # mode can also be set through the ROUTING_MODE environment variable. # Values: router | supervisor mode: router fallback_agent: billing_agent confidence_threshold: 0.65 allow_handoff: true ``` The configuration also contains `state_policies`, which keep certain confirmation or parameter-collection stages bound to the corresponding agent, and an `intents` catalog with attributes such as `name`, `domain`, `agent`, `priority`, `mcp_tools`, `keywords`, and `examples`. The agent catalog in `config/agents.yaml` declares `telecom_contas` and `retail_orders`. Each entry points to prompt, routing, guardrail, judge, MCP server, and tool policy/configuration files, as well as domain metadata. ## 7. Agent and tool execution Domain nodes dispatch requests to the billing, product, order, and support agent instances. MCP tools are exposed to agents through tool configuration and tool-routing components. Tool results can be carried in `mcp_results`; selected tool names are stored in `mcp_tools`. State also supports `selected_tool_call`, `pending_tool_call`, `tool_policy_result`, `missing_parameters`, and transaction-evidence fields. These allow preparation, validation, confirmation, and execution stages to share information across the conversation. ## 8. Transaction state `AgentState` holds the current transaction in `active_transaction` and the latest completed transaction in `last_transaction`. The fields `transaction_status`, `transaction_pre_validation`, `transaction_evidence`, `last_transaction_evidence`, `relevant_transaction_evidence`, `confirmation_required`, and `confirmation_received` carry the information required for transactional processing. Routing configuration defines state policies for `WAITING_BILLING_CONFIRMATION`, `WAITING_PRODUCT_CONFIRMATION`, `WAITING_ORDER_CONFIRMATION`, `WAITING_SUPPORT_CONFIRMATION`, `COLLECTING_BILLING_PARAMETERS`, `COLLECTING_PRODUCT_PARAMETERS`, `COLLECTING_ORDER_PARAMETERS`, and `COLLECTING_SUPPORT_PARAMETERS`. The transactional-flow reference guide is [`docs/TRANSACTION_STATE_DEVELOPER_GUIDE.md`](../docs/TRANSACTION_STATE_DEVELOPER_GUIDE.md). ## 9. Memory and checkpoints The workflow includes two explicit long-term memory stages: `load_long_term_memory`, before routing, and `persist_long_term_memory`, before final persistence. The fields `long_term_memories`, `long_term_memory_context`, `long_term_memory_write_result`, `long_term_memory_subject_key`, and `long_term_memory_load_error` form this capability's state interface. Graph construction passes the result of `create_langgraph_checkpointer(self.settings)` to `compile`. Checkpoint storage and retrieval are handled by the configured checkpointer, retaining state continuity across interactions according to the selected backend. ## 10. Guardrails, supervision, and judges Input passes through `input_guardrails` before memory loading and routing. When input is blocked, execution goes directly to `output_guardrails`. Agent responses pass through `output_supervisor` and `output_guardrails`. An unblocked response proceeds to `judge` and `supervisor_review`; a blocked response goes to `persist`. Results are carried in `guardrail_decisions`, `output_supervisor_results`, `judge_results`, `supervisor_action`, `supervisor_guidance`, and related state fields. ## 11. Observability The workflow wraps each node in `langgraph_telemetry.node` instrumentation, associating execution with the node identity and conversation state. Framework telemetry and observability mechanisms provide traceability for decisions and execution stages. ## 12. Error handling and recovery by component ### 12.1. Failure propagation principles The runtime distinguishes failures handled within a stage, negative policy/tool results, and exceptions that prevent graph execution. Each component's contract determines whether processing continues, a response is modified, or the exception is returned to the caller. The `blocked` field represents a policy decision in state; it does not generically denote a Python exception. | Area | Representation | Execution impact | |---|---|---| | Guardrails | Decisions with `allowed`, `code`, and `reason`; supervision decisions with `action` | May stop routing, replace or sanitize the response, or allow processing to continue. | | MCP | Tool results with `ok`, `error`, `tool_name`, and source-specific execution details | The agent uses the result to control how the operation proceeds. | | Long-term memory | `long_term_memory_load_error` and `long_term_memory_write_result` fields | Read or write exceptions caught in the node do not terminate graph execution. | | RAG | Retrieval result or technical-error object | A failure may be incorporated into the agent's execution context. | | Checkpoints | Repository exceptions, integrity validation, and version recovery | Recovery may select an earlier valid checkpoint or start from a clean state; exhausted storage retries propagate an exception. | | Workflow | Exceptions raised by nodes or `graph.ainvoke` | Execution is marked as failed in telemetry and the exception is re-raised. | ### 12.2. Input, sessions, and input guardrails The `input_guardrails` node first checks `session_ended`. If the session has already ended, it sets `blocked=True`, `session_control="END_SESSION"`, `session_ended=True`, and `next_state="SESSION_ENDED"`, composes the message configured in `SESSION_ALREADY_ENDED_MESSAGE`, and records `session.message.rejected_after_end`. This prevents the message from reaching the router or domain agents. For active sessions, the pipeline evaluates guardrails and stores decisions in `guardrail_decisions`. If any decision has `allowed=False`, the node generates a safe response, sets `route="blocked"` and `intent="input_guardrail_blocked"`, clears the turn's MCP tool and result lists, and follows the output branch. `_input_guardrail_user_message` has specific messages for `COER`, `INPUT_SIZE`, `DLEX_IN`, `PINJ`, `TOX`, and `CMP`, plus a default response for other codes. Detailed reasons remain in decisions and telemetry; the runtime controls the user-facing message. Evaluation records `guardrails.input.completed`, individual decisions in guardrail telemetry, and GRL events through the observer. When input is blocked, the pre-routing memory node, router, and agent tools do not execute. The branch proceeds to `output_guardrails` and, according to graph conditions, ends in `persist`. **Implementation reference:** `templates/agent_template_backend/app/workflows/agent_graph.py` (`input_guardrails`, `_input_guardrail_user_message`, `_after_input_guardrails`). ### 12.3. Routing, classification, and continuity Routing configuration is loaded through `routing/config_loader.py`; a missing file raises `FileNotFoundError`. `EnterpriseRouter` locally handles errors from classification calls and model-output parsing. In the semantic classifier for expected inputs, a failed `llm.ainvoke` call is logged with `logger.warning`, and the function returns `(None, None)`. Subsequent routing paths can therefore operate without treating the failure as a valid classification. The flow uses `fallback_agent` and `confidence_threshold` settings, as well as state policies in `routing.yaml`. Selecting a fallback is a routing decision, not intrinsically an error code. The `route_decision`, `continuity_signal`, `pending_topics`, and `multi_intent_plan` structures preserve information for resumption and routing of related turns. Failure recovery depends on the method actually invoked: not every internal routing error is handled by the same `except` clause. Exceptions escaping the node reach the workflow failure handler in `ainvoke`. **Implementation references:** `libs/agent_framework/src/agent_framework/routing/config_loader.py`, `routing/enterprise_router.py`, `routing/continuity.py`; `templates/agent_template_backend/app/workflows/agent_graph.py` (`routing_decision`, `ainvoke`). ### 12.4. Agent execution, arguments, and MCP tools Tool preparation is handled by the reusable runtime in `runtime/agent_runtime.py`. `_prepare_mcp_call` invokes the MCP router and returns a structured preparation error when `prepare_call` provides one. If the tool router is unavailable, the runtime produces a negative result with `ok=False`, `tool_name`, and the description `MCP Tool Router indisponível`. During parameter mapping, regular-expression errors (`re.error`, `IndexError`, `ValueError`) are caught and logged as `mcp.parameter.regex_extract_failed`. For LLM-based extraction, missing model/message data and inference exceptions are recorded as `mcp.parameter.llm_extract_failed`. Argument processing and call results remain subject to tool validation and agent policies; absence of an exception alone does not establish success. MCP results may expose `ok=False` and `error`, including validation and execution failures. The implementation emits `NOC.MCP_TOOL_FAILED` along one path that aggregates negative tool results. Transaction handling, confirmations, evidence, and partial results operate on execution data including `active_transaction`, `transaction_pre_validation`, `transaction_evidence`, and `operation_results`, depending on operation type. **Implementation reference:** `libs/agent_framework/src/agent_framework/runtime/agent_runtime.py` (`_prepare_mcp_call`, parameter handling, tool execution, and result aggregation). ### 12.5. RAG and knowledge retrieval The runtime wraps knowledge-retrieval failures in a technical result containing `technical_error=True`, `technical_error_in_rag=True`, `status="error"`, and a description in `error`. This allows the agent to distinguish retrieval failure from a completed search yielding no useful evidence. The RAG service also validates preconditions: `RagService.generate_answer` raises `RuntimeError` when used without an LLM, and `kbdb_service.py` checks configuration/signatures and disallows ingestion/publication when the KBDB provider is configured exclusively for serving. During ingestion, a missing directory may raise `FileNotFoundError`, while `chunk_overlap >= chunk_size` raises `ValueError`. These initialization and administration errors must not be confused with an empty retrieval result during a conversation. **Implementation references:** `libs/agent_framework/src/agent_framework/runtime/agent_runtime.py` (technical RAG error handling); `rag/rag_service.py`, `rag/kbdb_service.py`, `rag/ingest.py`. ### 12.6. Long-term memory The `load_long_term_memory` node catches exceptions from `long_term_memory_manager.load(state)`. If loading fails, it publishes `long_term_memory.load.failed`, returns `long_term_memories=[]` and `long_term_memory_context=""`, and stores the exception text in `long_term_memory_load_error`. The conversation continues without retrieved long-term memory context for that turn. The `persist_long_term_memory` node catches exceptions from `long_term_memory_manager.persist_turn(state)`. A successful write publishes `long_term_memory.persist.completed` and stores its return value in `long_term_memory_write_result`; a failure publishes `long_term_memory.persist.failed` and returns `{"saved": 0, "error": str(exc)}` in the same field. Thus, a long-term memory persistence failure is isolated within the node and does not automatically terminate the graph with an exception. These fields concern the workflow's long-term memory and are separate from LangGraph checkpoint persistence. **Implementation reference:** `templates/agent_template_backend/app/workflows/agent_graph.py` (`load_long_term_memory`, `persist_long_term_memory`). ### 12.7. Checkpoints, retries, and integrity The graph is compiled with the checkpointer created by `create_langgraph_checkpointer(self.settings)` and uses `conversation_key` or `session_id` as `thread_id` in `ainvoke`. The repository layer in `checkpoints/checkpoint_repository.py` retries operations protected by `_with_retry`: after an exception, it computes a bounded exponential delay with jitter, records `checkpoint..retry`, and retries up to `retry_policy.max_attempts`. Once attempts are exhausted, it re-raises the last exception. On recovery, `recover_latest` scans recent records, discards those failing `is_valid_checkpoint`, and skips checkpoints with `CheckpointIntegrityError`, recording `checkpoint.recovery.skip_corrupt`. If no valid checkpoint remains after integrity failures, recovery returns `None` so execution can begin without restored state. Compaction after a write is isolated: a failure records `checkpoint.compaction.failed` and does not undo the completed write. Integrity recovery cannot guarantee retention of history if no valid checkpoint can be recovered. Storage-communication errors that exceed the retry policy follow the exception path of the calling component. **Implementation references:** `libs/agent_framework/src/agent_framework/checkpoints/checkpoint_repository.py`, `checkpoints/langgraph_saver.py`; `templates/agent_template_backend/app/workflows/agent_graph.py` (`ainvoke`). ### 12.8. Output supervision and output guardrails `output_supervisor` evaluates the response and interprets `RailAction`. For `ALLOW`, `SANITIZE`, and `OBSERVE`, it uses `decision.candidate`; for `HANDOVER`, it supplies a message referring the user to a specialist; for other actions, it uses `decision.fallback_message`. The action, guidance, and results are stored in `supervisor_action`, `supervisor_guidance`, and `output_supervisor_results`. The node sets `output_guardrails_already_applied=True`. When `output_guardrails` sees this flag, it returns the prepared response without running the pipeline again. Otherwise, it runs `guardrails.run_output`, records decisions, blocks, and GRL completion, then returns `final_answer`. The parallel rail executor (`guardrails/parallel_executor.py`) converts unexpected task failures to `RailResult`: it selects `RailAction.BLOCK` when `fail_closed` is enabled and `RailAction.OBSERVE` otherwise. This is executor policy, not a claim that all guardrails fail closed. The executor also handles cancellation and early termination when a terminal action is produced. **Implementation references:** `templates/agent_template_backend/app/workflows/agent_graph.py` (`output_supervisor`, `output_guardrails`); `libs/agent_framework/src/agent_framework/guardrails/parallel_executor.py`. ### 12.9. Judges and supervisor review The `judge` node builds evaluation context from MCP results, RAG results, and relevant transaction evidence, executes evaluation, and stores outcomes in `judge_results`. The subsequent review invokes `self.supervisor.review` and records `supervisor.review.completed`. A judge's failing score or recommendation belongs to evaluation and review; it is not, by definition, an infrastructure exception. The judge layer validates configuration and structured responses. The calibrated judge client raises `ValueError` for unsupported tasks or invalid structured output. LLM construction and loading of judge configuration can also raise `RuntimeError` or `ValueError` under the conditions checked in `judges/judge.py`. Exceptions not caught within the node propagate to `ainvoke`. **Implementation references:** `templates/agent_template_backend/app/workflows/agent_graph.py` (`judge`, `supervisor_review`); `libs/agent_framework/src/agent_framework/judges/judge.py`, `judges/calibrated/llm_client.py`. ### 12.10. Final persistence and workflow-level failure The `persist` node emits completion observability, including the IC event `AGENT_COMPLETED`. The public graph entry point is `ainvoke`, which configures the thread, starts workflow telemetry, and emits the start events `NOC.001` and `AGENT_STARTED` before calling `self.graph.ainvoke`. If `graph.ainvoke` completes, the workflow is marked completed and its final state is returned. If an exception escapes the graph, the `except Exception` handler in `ainvoke` calls `workflow_telemetry.failed("agent_workflow", exc)`, emits `NOC.005` with `error` and `exception_type`, and re-raises the same exception. This path does not automatically convert the error into a successful conversational response. Failures handled inside nodes, such as long-term memory failures, do not trigger the global `except`. Unhandled errors in routing, agent execution, checkpoint storage, or other stages may reach this boundary and cause a workflow-level failure. **Implementation reference:** `templates/agent_template_backend/app/workflows/agent_graph.py` (`persist`, `ainvoke`). ### 12.11. Operational events and tracing `_node` wraps every node execution in `self.langgraph_telemetry.node(name, state)`. Additional mechanisms use workflow spans, semantic events, and the observer for GRL, IC, and NOC. Concrete records in the flow include `session.message.rejected_after_end`, `guardrails.input.completed`, `guardrails.output.completed`, `output_supervisor.completed`, `supervisor.review.completed`, `long_term_memory.load.failed`, `long_term_memory.persist.failed`, `long_term_memory.persist.completed`, and `workflow_telemetry.failed` on workflow-level failure. Operational errors should be correlated using `conversation_key` or `session_id`, together with `tenant_id` and `agent_id` when available. A memory or tool failure event does not necessarily indicate overall workflow failure; this depends on whether the component returned a state/result or propagated an exception. **Implementation references:** `templates/agent_template_backend/app/workflows/agent_graph.py`; `libs/agent_framework/src/agent_framework/observability/`. ## 13. Integration interfaces | Interface | Runtime integration | |---|---| | LangGraph | State-graph construction, compilation, and execution. | | Domain agents | Specialized billing, product, order, and support nodes. | | MCP | Tool selection, parameter mapping, and execution by agents. | | RAG | Knowledge retrieval incorporated into agent execution. | | Guardrails | Input and output evaluation. | | Judges | Evaluation after response generation. | | Memory | Long-term information loading and writing. | | Checkpointer | Graph-state persistence. | | Observability | Per-node telemetry and execution tracing. | ## 14. Implementation references - `libs/agent_framework/src/agent_framework/runtime/` - `templates/agent_template_backend/app/state.py` - `templates/agent_template_backend/app/workflows/agent_graph.py` - `templates/agent_template_backend/config/agents.yaml` - `templates/agent_template_backend/config/routing.yaml` - `templates/agent_template_backend/config/tools.yaml` - [`SPEC-004 — MCP Gateway`](SPEC-004-MCP-Gateway.md) - [`SPEC-005 — Guardrails`](SPEC-005-Guardrails.md) - [`SPEC-007 — Observability`](SPEC-007-Observability.md) - [`SPEC-012 — Canonical Contracts`](SPEC-012-Canonical-Contracts.md)