Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,32 @@ The package provides the `agentex` CLI with these main commands:
`SendEventParams`, `CancelTaskParams`, `RPC_SYNC_METHODS`,
`PARAMS_MODEL_BY_METHOD`
- `json_rpc.py` - `JSONRPCRequest`, `JSONRPCResponse`, `JSONRPCError`
- `/src/agentex/config/` - **Canonical** location for deployment/agent
configuration models (manifest shapes). Depends only on `pydantic`, so it is
safe to import from a slim REST-only install.
- `agent_config.py`, `build_config.py`, `deployment_config.py`,
`local_development_config.py`, `environment_config.py`, `agent_manifest.py`
(model classes only), plus their model deps `credentials.py` and
`agent_configs.py`
- yaml loaders / build machinery (`load_environments_config*`,
`load_agent_manifest`, `build_context_manager`, `BuildContextManager`) stay
in `agentex.lib.sdk.config.*` so these models stay slim-safe
- `/src/agentex/lib/` - Custom library code (not modified by code generator)
- `/cli/` - Command-line interface implementation
- `/core/` - Core services, adapters, and temporal workflows
- `/sdk/` - SDK utilities and FastACP implementation
- `config/` - manifest loaders + Docker build machinery (`agent_manifest`'s
`load_agent_manifest`/`build_context_manager`/`BuildContextManager`,
`validation`, `project_config`) plus **back-compat shims** for the model
classes now canonical under `agentex.config.*`
- `/types/` - Custom type definitions
- `acp.py`, `json_rpc.py` - **back-compat shims** re-exporting from
`agentex.protocol.*`. Existing `from agentex.lib.types.{acp,json_rpc}
import ...` keeps working; new code should import from the canonical
`agentex.protocol.*` paths.
- Other modules (`tracing`, `agent_card`, `credentials`, `fastacp`,
`llm_messages`, `converters`, etc.) stay here — they have heavier
transitive deps (temporal, openai-agents, model_utils/yaml) and
aren't slim-safe.
`agentex.protocol.*`. `credentials.py`, `agent_configs.py` - shims
re-exporting from `agentex.config.*`. Existing `from agentex.lib...`
imports keep working; new code should import from the canonical paths.
- Other modules (`tracing`, `agent_card`, `fastacp`, `llm_messages`,
`converters`, etc.) stay here — they have heavier transitive deps
(temporal, openai-agents, model_utils/yaml) and aren't slim-safe.
- `/utils/` - Utility functions
- `/examples/` - Example implementations and tutorials
- `/tests/` - Test suites
Expand Down
14 changes: 14 additions & 0 deletions src/agentex/config/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Deployment & agent configuration shapes for Agentex.
The modules under `agentex.config.*` are the typed manifest/deployment
configuration models (agent, build, deployment, environment, local-dev) plus
their leaf model deps (credentials, temporal). They depend only on pydantic,
so they are safe to import from a slim REST-only install without the ADK
runtime.
For back-compat, the same classes are re-exported from their historical
locations under `agentex.lib.sdk.config.*` and
`agentex.lib.types.{agent_configs,credentials}`. The yaml-loading helpers
(`load_environments_config*`) stay in `agentex.lib.sdk.config.environment_config`
so the promoted models remain slim-safe.
"""
7 changes: 7 additions & 0 deletions src/agentex/config/_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from pydantic import BaseModel, ConfigDict


class ConfigBaseModel(BaseModel):
# Preserves the config the former agentex.lib.utils.model_utils.BaseModel
# applied; deployment_config's `global` alias relies on populate_by_name.
model_config = ConfigDict(from_attributes=True, populate_by_name=True)
64 changes: 64 additions & 0 deletions src/agentex/config/agent_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from __future__ import annotations

from typing import Any, Literal

from pydantic import Field

from agentex.config._base import ConfigBaseModel
from agentex.config.credentials import CredentialMapping
from agentex.config.agent_configs import TemporalConfig, TemporalWorkflowConfig


class AgentConfig(ConfigBaseModel):
name: str = Field(
...,
description="The name of the agent.",
pattern=r"^[a-z0-9-]+$",
)
acp_type: Literal["sync", "async", "agentic"] = Field(..., description="The type of agent.")
agent_input_type: Literal["text", "json"] | None = Field(
default=None,
description="The type of input the agent accepts."
)
description: str = Field(..., description="The description of the agent.")
env: dict[str, str] | None = Field(
default=None, description="Environment variables to set directly in the agent deployment"
)
credentials: list[CredentialMapping | dict[str, Any]] | None = Field(
default=None,
description="List of credential mappings to mount to the agent deployment. Supports both legacy format and new typed credentials.",
)
temporal: TemporalConfig | None = Field(
default=None, description="Temporal workflow configuration for this agent"
)

def is_temporal_agent(self) -> bool:
"""Check if this agent uses Temporal workflows"""
# Check temporal config with enabled flag
if self.temporal and self.temporal.enabled:
return True
return False

def get_temporal_workflow_config(self) -> TemporalWorkflowConfig | None:
"""Get temporal workflow configuration, checking both new and legacy formats"""
# Check new workflows list first
if self.temporal and self.temporal.enabled and self.temporal.workflows:
return self.temporal.workflows[0] # Return first workflow for backward compatibility

# Check legacy single workflow
if self.temporal and self.temporal.enabled and self.temporal.workflow:
return self.temporal.workflow

return None

def get_temporal_workflows(self) -> list[TemporalWorkflowConfig]:
"""Get all temporal workflow configurations"""
# Check new workflows list first
if self.temporal and self.temporal.enabled and self.temporal.workflows:
return self.temporal.workflows

# Check legacy single workflow
if self.temporal and self.temporal.enabled and self.temporal.workflow:
return [self.temporal.workflow]

return []
87 changes: 87 additions & 0 deletions src/agentex/config/agent_configs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from __future__ import annotations

from pydantic import Field, BaseModel, field_validator, model_validator


class TemporalWorkflowConfig(BaseModel):
"""
Configuration for the temporal workflow that defines the agent.

Attributes:
name: The name of the temporal workflow that defines the agent.
queue_name: The name of the temporal queue to send tasks to.
"""

name: str = Field(
..., description="The name of the temporal workflow that defines the agent."
)
queue_name: str = Field(
..., description="The name of the temporal queue to send tasks to."
)


# TODO: Remove this class when we remove the agentex agents create
class TemporalWorkerConfig(BaseModel):
"""
Configuration for temporal worker deployment

Attributes:
image: The image to use for the temporal worker
workflow: The temporal workflow configuration
"""

image: str | None = Field(
default=None, description="Image to use for the temporal worker"
)
workflow: TemporalWorkflowConfig | None = Field(
default=None,
description="Configuration for the temporal workflow that defines the agent. Only required for agents that leverage Temporal.",
)


class TemporalConfig(BaseModel):
"""
Simplified temporal configuration for agents

Attributes:
enabled: Whether this agent uses Temporal workflows
workflow: The temporal workflow configuration
workflows: The list of temporal workflow configurations
health_check_port: Port for temporal worker health check endpoint
"""

enabled: bool = Field(
default=False, description="Whether this agent uses Temporal workflows"
)
workflow: TemporalWorkflowConfig | None = Field(
default=None,
description="Temporal workflow configuration. Required when enabled=True. (deprecated: use workflows instead)",
)
workflows: list[TemporalWorkflowConfig] | None = Field(
default=None,
description="List of temporal workflow configurations. Used when enabled=true.",
)
health_check_port: int | None = Field(
default=None,
description="Port for temporal worker health check endpoint. Defaults to 80 if not specified.",
)

@field_validator("workflows")
@classmethod
def validate_workflows_not_empty(cls, v):
"""Ensure workflows list is not empty when provided"""
if v is not None and len(v) == 0:
raise ValueError("workflows list cannot be empty when provided")
return v

@model_validator(mode="after")
def validate_temporal_config_when_enabled(self):
"""Validate that workflow configuration exists when enabled=true"""
if self.enabled:
# Must have either workflow (legacy) or workflows (new)
if not self.workflow and (not self.workflows or len(self.workflows) == 0):
raise ValueError(
"When temporal.enabled=true, either 'workflow' or 'workflows' must be provided and non-empty"
)

return self
24 changes: 24 additions & 0 deletions src/agentex/config/agent_manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from __future__ import annotations

from pydantic import Field

from agentex.config._base import ConfigBaseModel
from agentex.config.agent_config import AgentConfig
from agentex.config.build_config import BuildConfig
from agentex.config.deployment_config import DeploymentConfig
from agentex.config.local_development_config import LocalDevelopmentConfig


class AgentManifest(ConfigBaseModel):
"""
Represents a manifest file that describes how to build and deploy an agent.
"""

build: BuildConfig
agent: AgentConfig
local_development: LocalDevelopmentConfig | None = Field(
default=None, description="Configuration for local development"
)
deployment: DeploymentConfig | None = Field(
default=None, description="Deployment configuration for the agent"
)
37 changes: 37 additions & 0 deletions src/agentex/config/build_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from __future__ import annotations

from pydantic import Field

from agentex.config._base import ConfigBaseModel


class BuildContext(ConfigBaseModel):
"""
Represents the context in which the Docker image should be built.
"""

root: str = Field(
...,
description="The root directory of the build context. Should be specified relative to the location of the "
"build config file.",
)
include_paths: list[str] = Field(
default_factory=list,
description="The paths to include in the build context. Should be specified relative to the root directory.",
)
dockerfile: str = Field(
...,
description="The path to the Dockerfile. Should be specified relative to the root directory.",
)
dockerignore: str | None = Field(
None,
description="The path to the .dockerignore file. Should be specified relative to the root directory.",
)


class BuildConfig(ConfigBaseModel):
"""
Represents a configuration for building the action as a Docker image.
"""

context: BuildContext
34 changes: 34 additions & 0 deletions src/agentex/config/credentials.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from pydantic import Field, BaseModel


class CredentialMapping(BaseModel):
"""Maps a Kubernetes secret to an environment variable in the agent container.

This allows agents to securely access credentials stored in Kubernetes secrets
by mapping them to environment variables. For example, you can map a secret
containing an API key to an environment variable that your agent code expects.

Example:
A mapping of {"env_var_name": "OPENAI_API_KEY",
"secret_name": "ai-credentials",
"secret_key": "openai-key"}
will make the value from the "openai-key" field in the "ai-credentials"
Kubernetes secret available to the agent as OPENAI_API_KEY environment variable.

Attributes:
env_var_name: The name of the environment variable that will be available to the agent
secret_name: The name of the Kubernetes secret containing the credential
secret_key: The key within the Kubernetes secret that contains the credential value
"""

env_var_name: str = Field(
...,
description="Name of the environment variable that will be available to the agent",
)
secret_name: str = Field(
..., description="Name of the Kubernetes secret containing the credential"
)
secret_key: str = Field(
...,
description="Key within the Kubernetes secret that contains the credential value",
)
Loading
Loading