Skip to content

Commit 43abffc

Browse files
refactor(config): promote deployment-config models to agentex.config.*
Promotes the deployment/agent configuration model classes out of the heavy ADK (agentex.lib.sdk.config) into a new slim-shipped, hand-authored namespace agentex.config.*, so REST-only consumers can import them from the slim agentex-client wheel without installing the ADK runtime. Deployment-models analog of #371 (which did this for protocol types). - New agentex.config.{agent_config,build_config,deployment_config, local_development_config,environment_config,agent_manifest} plus model deps {credentials,agent_configs}. ConfigBaseModel inlines the ConfigDict the former model_utils.BaseModel provided (from_attributes/populate_by_name), folding in DeploymentConfig/InjectedSecretsValues' nested validate_by_name. - environment_config: model classes promoted; from_yaml/yaml loading stays in lib as the load_environments_config free function (keeps models slim-safe). - agent_manifest: AgentManifest model promoted (egp-api-backend validates manifest dicts with it); from_yaml -> load_agent_manifest, context_manager -> build_context_manager, plus BuildContextManager stay in lib. - Back-compat shims left at the old agentex.lib.sdk.config.* and agentex.lib.types.{credentials,agent_configs} paths. - Internal lib consumers + tests repointed to the canonical paths. - Modernize the promoted agent_configs/local_development_config validators to @field_validator so the new public surface emits no pydantic-v1 deprecation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent bbfb22e commit 43abffc

31 files changed

Lines changed: 952 additions & 721 deletions

CLAUDE.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,19 +63,32 @@ The package provides the `agentex` CLI with these main commands:
6363
`SendEventParams`, `CancelTaskParams`, `RPC_SYNC_METHODS`,
6464
`PARAMS_MODEL_BY_METHOD`
6565
- `json_rpc.py` - `JSONRPCRequest`, `JSONRPCResponse`, `JSONRPCError`
66+
- `/src/agentex/config/` - **Canonical** location for deployment/agent
67+
configuration models (manifest shapes). Depends only on `pydantic`, so it is
68+
safe to import from a slim REST-only install.
69+
- `agent_config.py`, `build_config.py`, `deployment_config.py`,
70+
`local_development_config.py`, `environment_config.py`, `agent_manifest.py`
71+
(model classes only), plus their model deps `credentials.py` and
72+
`agent_configs.py`
73+
- yaml loaders / build machinery (`load_environments_config*`,
74+
`load_agent_manifest`, `build_context_manager`, `BuildContextManager`) stay
75+
in `agentex.lib.sdk.config.*` so these models stay slim-safe
6676
- `/src/agentex/lib/` - Custom library code (not modified by code generator)
6777
- `/cli/` - Command-line interface implementation
6878
- `/core/` - Core services, adapters, and temporal workflows
6979
- `/sdk/` - SDK utilities and FastACP implementation
80+
- `config/` - manifest loaders + Docker build machinery (`agent_manifest`'s
81+
`load_agent_manifest`/`build_context_manager`/`BuildContextManager`,
82+
`validation`, `project_config`) plus **back-compat shims** for the model
83+
classes now canonical under `agentex.config.*`
7084
- `/types/` - Custom type definitions
7185
- `acp.py`, `json_rpc.py` - **back-compat shims** re-exporting from
72-
`agentex.protocol.*`. Existing `from agentex.lib.types.{acp,json_rpc}
73-
import ...` keeps working; new code should import from the canonical
74-
`agentex.protocol.*` paths.
75-
- Other modules (`tracing`, `agent_card`, `credentials`, `fastacp`,
76-
`llm_messages`, `converters`, etc.) stay here — they have heavier
77-
transitive deps (temporal, openai-agents, model_utils/yaml) and
78-
aren't slim-safe.
86+
`agentex.protocol.*`. `credentials.py`, `agent_configs.py` - shims
87+
re-exporting from `agentex.config.*`. Existing `from agentex.lib...`
88+
imports keep working; new code should import from the canonical paths.
89+
- Other modules (`tracing`, `agent_card`, `fastacp`, `llm_messages`,
90+
`converters`, etc.) stay here — they have heavier transitive deps
91+
(temporal, openai-agents, model_utils/yaml) and aren't slim-safe.
7992
- `/utils/` - Utility functions
8093
- `/examples/` - Example implementations and tutorials
8194
- `/tests/` - Test suites

src/agentex/config/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""Deployment & agent configuration shapes for Agentex.
2+
3+
The modules under `agentex.config.*` are the typed manifest/deployment
4+
configuration models (agent, build, deployment, environment, local-dev) plus
5+
their leaf model deps (credentials, temporal). They depend only on pydantic,
6+
so they are safe to import from a slim REST-only install without the ADK
7+
runtime.
8+
9+
For back-compat, the same classes are re-exported from their historical
10+
locations under `agentex.lib.sdk.config.*` and
11+
`agentex.lib.types.{agent_configs,credentials}`. The yaml-loading helpers
12+
(`load_environments_config*`) stay in `agentex.lib.sdk.config.environment_config`
13+
so the promoted models remain slim-safe.
14+
"""

src/agentex/config/_base.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from pydantic import BaseModel, ConfigDict
2+
3+
4+
class ConfigBaseModel(BaseModel):
5+
# Preserves the config the former agentex.lib.utils.model_utils.BaseModel
6+
# applied; deployment_config's `global` alias relies on populate_by_name.
7+
model_config = ConfigDict(from_attributes=True, populate_by_name=True)

src/agentex/config/agent_config.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from __future__ import annotations
2+
3+
from typing import Any, Literal
4+
5+
from pydantic import Field
6+
7+
from agentex.config._base import ConfigBaseModel
8+
from agentex.config.credentials import CredentialMapping
9+
from agentex.config.agent_configs import TemporalConfig, TemporalWorkflowConfig
10+
11+
12+
class AgentConfig(ConfigBaseModel):
13+
name: str = Field(
14+
...,
15+
description="The name of the agent.",
16+
pattern=r"^[a-z0-9-]+$",
17+
)
18+
acp_type: Literal["sync", "async", "agentic"] = Field(..., description="The type of agent.")
19+
agent_input_type: Literal["text", "json"] | None = Field(
20+
default=None,
21+
description="The type of input the agent accepts."
22+
)
23+
description: str = Field(..., description="The description of the agent.")
24+
env: dict[str, str] | None = Field(
25+
default=None, description="Environment variables to set directly in the agent deployment"
26+
)
27+
credentials: list[CredentialMapping | dict[str, Any]] | None = Field(
28+
default=None,
29+
description="List of credential mappings to mount to the agent deployment. Supports both legacy format and new typed credentials.",
30+
)
31+
temporal: TemporalConfig | None = Field(
32+
default=None, description="Temporal workflow configuration for this agent"
33+
)
34+
35+
def is_temporal_agent(self) -> bool:
36+
"""Check if this agent uses Temporal workflows"""
37+
# Check temporal config with enabled flag
38+
if self.temporal and self.temporal.enabled:
39+
return True
40+
return False
41+
42+
def get_temporal_workflow_config(self) -> TemporalWorkflowConfig | None:
43+
"""Get temporal workflow configuration, checking both new and legacy formats"""
44+
# Check new workflows list first
45+
if self.temporal and self.temporal.enabled and self.temporal.workflows:
46+
return self.temporal.workflows[0] # Return first workflow for backward compatibility
47+
48+
# Check legacy single workflow
49+
if self.temporal and self.temporal.enabled and self.temporal.workflow:
50+
return self.temporal.workflow
51+
52+
return None
53+
54+
def get_temporal_workflows(self) -> list[TemporalWorkflowConfig]:
55+
"""Get all temporal workflow configurations"""
56+
# Check new workflows list first
57+
if self.temporal and self.temporal.enabled and self.temporal.workflows:
58+
return self.temporal.workflows
59+
60+
# Check legacy single workflow
61+
if self.temporal and self.temporal.enabled and self.temporal.workflow:
62+
return [self.temporal.workflow]
63+
64+
return []
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from __future__ import annotations
2+
3+
from pydantic import Field, BaseModel, field_validator, model_validator
4+
5+
6+
class TemporalWorkflowConfig(BaseModel):
7+
"""
8+
Configuration for the temporal workflow that defines the agent.
9+
10+
Attributes:
11+
name: The name of the temporal workflow that defines the agent.
12+
queue_name: The name of the temporal queue to send tasks to.
13+
"""
14+
15+
name: str = Field(
16+
..., description="The name of the temporal workflow that defines the agent."
17+
)
18+
queue_name: str = Field(
19+
..., description="The name of the temporal queue to send tasks to."
20+
)
21+
22+
23+
# TODO: Remove this class when we remove the agentex agents create
24+
class TemporalWorkerConfig(BaseModel):
25+
"""
26+
Configuration for temporal worker deployment
27+
28+
Attributes:
29+
image: The image to use for the temporal worker
30+
workflow: The temporal workflow configuration
31+
"""
32+
33+
image: str | None = Field(
34+
default=None, description="Image to use for the temporal worker"
35+
)
36+
workflow: TemporalWorkflowConfig | None = Field(
37+
default=None,
38+
description="Configuration for the temporal workflow that defines the agent. Only required for agents that leverage Temporal.",
39+
)
40+
41+
42+
class TemporalConfig(BaseModel):
43+
"""
44+
Simplified temporal configuration for agents
45+
46+
Attributes:
47+
enabled: Whether this agent uses Temporal workflows
48+
workflow: The temporal workflow configuration
49+
workflows: The list of temporal workflow configurations
50+
health_check_port: Port for temporal worker health check endpoint
51+
"""
52+
53+
enabled: bool = Field(
54+
default=False, description="Whether this agent uses Temporal workflows"
55+
)
56+
workflow: TemporalWorkflowConfig | None = Field(
57+
default=None,
58+
description="Temporal workflow configuration. Required when enabled=True. (deprecated: use workflows instead)",
59+
)
60+
workflows: list[TemporalWorkflowConfig] | None = Field(
61+
default=None,
62+
description="List of temporal workflow configurations. Used when enabled=true.",
63+
)
64+
health_check_port: int | None = Field(
65+
default=None,
66+
description="Port for temporal worker health check endpoint. Defaults to 80 if not specified.",
67+
)
68+
69+
@field_validator("workflows")
70+
@classmethod
71+
def validate_workflows_not_empty(cls, v):
72+
"""Ensure workflows list is not empty when provided"""
73+
if v is not None and len(v) == 0:
74+
raise ValueError("workflows list cannot be empty when provided")
75+
return v
76+
77+
@model_validator(mode="after")
78+
def validate_temporal_config_when_enabled(self):
79+
"""Validate that workflow configuration exists when enabled=true"""
80+
if self.enabled:
81+
# Must have either workflow (legacy) or workflows (new)
82+
if not self.workflow and (not self.workflows or len(self.workflows) == 0):
83+
raise ValueError(
84+
"When temporal.enabled=true, either 'workflow' or 'workflows' must be provided and non-empty"
85+
)
86+
87+
return self
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from __future__ import annotations
2+
3+
from pydantic import Field
4+
5+
from agentex.config._base import ConfigBaseModel
6+
from agentex.config.agent_config import AgentConfig
7+
from agentex.config.build_config import BuildConfig
8+
from agentex.config.deployment_config import DeploymentConfig
9+
from agentex.config.local_development_config import LocalDevelopmentConfig
10+
11+
12+
class AgentManifest(ConfigBaseModel):
13+
"""
14+
Represents a manifest file that describes how to build and deploy an agent.
15+
"""
16+
17+
build: BuildConfig
18+
agent: AgentConfig
19+
local_development: LocalDevelopmentConfig | None = Field(
20+
default=None, description="Configuration for local development"
21+
)
22+
deployment: DeploymentConfig | None = Field(
23+
default=None, description="Deployment configuration for the agent"
24+
)

src/agentex/config/build_config.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from __future__ import annotations
2+
3+
from pydantic import Field
4+
5+
from agentex.config._base import ConfigBaseModel
6+
7+
8+
class BuildContext(ConfigBaseModel):
9+
"""
10+
Represents the context in which the Docker image should be built.
11+
"""
12+
13+
root: str = Field(
14+
...,
15+
description="The root directory of the build context. Should be specified relative to the location of the "
16+
"build config file.",
17+
)
18+
include_paths: list[str] = Field(
19+
default_factory=list,
20+
description="The paths to include in the build context. Should be specified relative to the root directory.",
21+
)
22+
dockerfile: str = Field(
23+
...,
24+
description="The path to the Dockerfile. Should be specified relative to the root directory.",
25+
)
26+
dockerignore: str | None = Field(
27+
None,
28+
description="The path to the .dockerignore file. Should be specified relative to the root directory.",
29+
)
30+
31+
32+
class BuildConfig(ConfigBaseModel):
33+
"""
34+
Represents a configuration for building the action as a Docker image.
35+
"""
36+
37+
context: BuildContext

src/agentex/config/credentials.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from pydantic import Field, BaseModel
2+
3+
4+
class CredentialMapping(BaseModel):
5+
"""Maps a Kubernetes secret to an environment variable in the agent container.
6+
7+
This allows agents to securely access credentials stored in Kubernetes secrets
8+
by mapping them to environment variables. For example, you can map a secret
9+
containing an API key to an environment variable that your agent code expects.
10+
11+
Example:
12+
A mapping of {"env_var_name": "OPENAI_API_KEY",
13+
"secret_name": "ai-credentials",
14+
"secret_key": "openai-key"}
15+
will make the value from the "openai-key" field in the "ai-credentials"
16+
Kubernetes secret available to the agent as OPENAI_API_KEY environment variable.
17+
18+
Attributes:
19+
env_var_name: The name of the environment variable that will be available to the agent
20+
secret_name: The name of the Kubernetes secret containing the credential
21+
secret_key: The key within the Kubernetes secret that contains the credential value
22+
"""
23+
24+
env_var_name: str = Field(
25+
...,
26+
description="Name of the environment variable that will be available to the agent",
27+
)
28+
secret_name: str = Field(
29+
..., description="Name of the Kubernetes secret containing the credential"
30+
)
31+
secret_key: str = Field(
32+
...,
33+
description="Key within the Kubernetes secret that contains the credential value",
34+
)

0 commit comments

Comments
 (0)