Providers and agents

A provider wraps an LLM SDK behind a stable interface. An agent is a configured provider instance — a model, a system prompt, limits, and a tool set — held as a module-level singleton.

The runtime’s provider modules are thin re-exports; the implementations live in morpheus-llm so the Lambda functions share them.

# agentcore/providers/claude_service.py
from morpheus_llm.providers.claude_service import ClaudeAgentService

# agentcore/providers/base_service.py
from morpheus_llm.providers.base_service import BaseAgentService

ClaudeAgentService

Claude on Bedrock via the Strands SDK. Authenticates through the runtime’s IAM role — no API key.

ClaudeAgentService(
    config: AgentConfig,
    tools: list[Any] | Callable[[], list[Any]],
    memory_id: str | None = None,
)
Argument Purpose

config

AgentConfig — model id, system prompt, max_tokens, max_turns, optional structured_output_model

tools

A static list, or a zero-argument factory callable

memory_id

Overrides the MEMORY_ID environment variable

run() versus new_session()

Method Behaviour

run(prompt, options)

Builds a fresh Agent, calls it once, returns an AgentResult. Stateless — for single-shot prompts.

new_session(options)

Returns a fresh Agent. The Agent is directly callable; calling it repeatedly accumulates agent.messages automatically. Used by BaseWorkflow.execute_steps().

Both return a fresh Agent. ClaudeAgentService instances are container-lifetime singletons; constructing the Agent once in init would accumulate conversation history across every request the container served, leaking one caller’s conversation into another’s.

Static versus factory tools

# Static — stateless tools
svc = ClaudeAgentService(config, [add_numbers, shell])

# Factory — required when any tool holds a stateful transport
svc = ClaudeAgentService(config, lambda: [add_numbers, create_hubspot_mcp(...)])

MCPClient holds an open HTTP session and cannot be shared across agent invocations. Any tool set containing one must be a factory. See Tools.

Pre-wired agents

Module-level singletons in agentcore/providers/agents/. Constructed once at import, so no per-request setup cost beyond building the Agent.

dialler_agent

agentcore/providers/agents/dialler_agent.py:

config: AppConfig = load_config()

dialler_agent = ClaudeAgentService(
    config=AgentConfig(
        model_id=AgentModelIds.CLAUDE_SONNET_4,
        system_prompt="You are a helpful assistant that can help with dialling tasks.",
        max_tokens=1000,
        max_turns=10,
    ),
    tools=lambda: [
        editor,
        shell,
        http_request,
        python_repl,
        add_numbers,
        create_hubspot_mcp(
            client_id=config.hubspot_client_id,
            client_secret=config.hubspot_client_secret,
            refresh_token=config.hubspot_refresh_token,
        ),
    ],
)

Two things to note:

  • load_config() is called at module import, which is why main.py must call it before importing the registry. It hits the cache.

  • Credentials are passed explicitly into create_hubspot_mcp(). The library never reads os.environ itself.

This agent has shell and python_repl, which give the model arbitrary command and code execution inside the container. Acceptable for a workflow whose input is a phone number from a trusted caller; not acceptable for a workflow that ingests untrusted text. See prompt injection.

Types

@dataclass
class AgentConfig:
    model_id: str
    system_prompt: str
    max_tokens: int
    max_turns: int
    structured_output_model: type | None = None

@dataclass
class AgentResult:
    output: str
    stop_reason: str = "end_turn"          # "end_turn" | "max_turns"
    usage: dict[str, int] | None = None
    structured_output: Any = None

@dataclass
class RunOptions:
    system_prompt: str | None = None       # per-call system prompt override
    max_turns: int | None = None
    metadata: dict | None = None
    session_id: str | None = None          # required for memory
    actor_id: str | None = None            # memory namespace key

session_id and actor_id are what wire an invocation to AgentCore Memory — see AgentCore Memory.

Model ids

Always from morpheus_llm.utils.AgentModelIds:

Member Model id

CLAUDE_OPUS_4_6

us.anthropic.claude-opus-4-6-v1

CLAUDE_SONNET_4

us.anthropic.claude-sonnet-4-20250514-v1:0

CLAUDE_HAIKU_4_5

us.anthropic.claude-haiku-4-5-20251001-v1:0

CLAUDE_HAIKU_3_5

us.anthropic.claude-3-5-haiku-20241022-v1:0

CLAUDE_HAIKU_3

us.anthropic.claude-3-haiku-20240307-v1:0

NOVA_MICRO

us.amazon.nova-micro-v1:0

NOVA_LITE

us.amazon.nova-lite-v1:0

NOVA_2_LITE

us.amazon.nova-2-lite-v1:0

Do not strip the us. prefix — it selects the cross-region inference profile that newer models require.

Adding an agent

  1. Create agentcore/providers/agents/<name>_agent.py exporting a module-level singleton.

  2. Take model ids from AgentModelIds.

  3. Use a lambda for tools if any tool is stateful.

  4. Pass credentials explicitly from load_config().

  5. Reference it from a workflow — see Workflows.

# agentcore/providers/agents/research_agent.py
from agentcore.config.loader import AppConfig, load_config
from morpheus_llm.providers import ClaudeAgentService
from morpheus_llm.tools import create_tavily_mcp
from morpheus_llm.types import AgentConfig
from morpheus_llm.utils import AgentModelIds

config: AppConfig = load_config()

research_agent = ClaudeAgentService(
    config=AgentConfig(
        model_id=AgentModelIds.CLAUDE_HAIKU_4_5,
        system_prompt="You are a research assistant with real-time web access.",
        max_tokens=4096,
        max_turns=15,
    ),
    tools=lambda: [create_tavily_mcp(api_key=config.tavily_api_key)],
)

Adding a provider

BaseAgentService (in morpheus-llm) already owns tool resolution, memory session management, and the run / new_session API. A new provider implements one method:

class MyProviderService(BaseAgentService):
    def _make_agent(self, options: RunOptions | None = None) -> Any:
        return SomeAgent(
            model=...,
            system_prompt=(options.system_prompt if options and options.system_prompt
                           else self._config.system_prompt),
            tools=self._resolve_tools(),
            session_manager=self._make_session_manager(options) if options else None,
        )

The provider belongs in libs/morpheus-llm/morpheus_llm/providers/, not in agentcore/, so the Lambdas can use it too.