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
1 change: 1 addition & 0 deletions .codegraph
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,18 @@ After the translator is complete and all existing `teams:` configs translate suc
#### Scenario: TeamConfig still parseable from YAML
- **WHEN** a YAML config with `teams:` section is loaded
- **THEN** `TeamConfig` SHALL parse successfully and be translatable to `GraphConfig`

### Requirement: Agent connections translated to graph edges
The translator SHALL convert agent `connections:` configuration to `GraphEdgeConfig` objects. Only `NodeConnectionConfig` entries (connections to other agents) SHALL produce edges. `FileConnectionConfig` and `CallableConnectionConfig` entries SHALL be skipped, as they write to external sinks and do not represent edges between graph steps.

#### Scenario: NodeConnectionConfig translated to edge
- **WHEN** an agent has a `NodeConnectionConfig(name="reviewer")` connection
- **THEN** the translator SHALL produce a `GraphEdgeConfig` with `from` set to the source agent name and `to` set to `"reviewer"`

#### Scenario: FileConnectionConfig skipped
- **WHEN** an agent has a `FileConnectionConfig(path="logs/messages.txt")` connection
- **THEN** the translator SHALL NOT produce a `GraphEdgeConfig` for this connection

#### Scenario: CallableConnectionConfig skipped
- **WHEN** an agent has a `CallableConnectionConfig(callable="builtins:print")` connection
- **THEN** the translator SHALL NOT produce a `GraphEdgeConfig` for this connection
18 changes: 9 additions & 9 deletions openspec/changes/thin-wrapper-refactor/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,15 @@

## 4. Phase 4: Team Cleanup

- [ ] 4.1 Create `src/agentpool_config/graph_translation.py` module
- [ ] 4.2 Implement `translate_team_to_graph()` for sequential teams — `members` → chained steps with implicit edges
- [ ] 4.3 Implement `translate_team_to_graph()` for parallel teams — `members` → Fork/Join edges
- [ ] 4.4 Map `shared_prompt` to step-level prompt in `GraphStepConfig`
- [ ] 4.5 Map `member_timeout` to step-level timeout
- [ ] 4.6 Map `member_prompt_templates` to per-step prompt templates
- [ ] 4.7 Map `member_retry_attempts` and `member_retry_delay` (document dropped fields if no GraphConfig equivalent — see Open Question 8)
- [ ] 4.8 Integrate translator into config loading — auto-translate when `teams:` present, `graph:` absent
- [ ] 4.9 Write tests for translator covering all `TeamConfig` field combinations
- [x] 4.1 Create `src/agentpool_config/graph_translation.py` module
- [x] 4.2 Implement `translate_team_to_graph()` for sequential teams — `members` → chained steps with implicit edges
- [x] 4.3 Implement `translate_team_to_graph()` for parallel teams — `members` → Fork/Join edges
- [x] 4.4 Map `shared_prompt` to step-level prompt in `GraphStepConfig`
- [x] 4.5 Map `member_timeout` to step-level timeout
- [x] 4.6 Map `member_prompt_templates` to per-step prompt templates
- [x] 4.7 Map `member_retry_attempts` and `member_retry_delay` (document dropped fields if no GraphConfig equivalent — see Open Question 8)
- [x] 4.8 Integrate translator into config loading — auto-translate when `teams:` present, `graph:` absent
- [x] 4.9 Write tests for translator covering all `TeamConfig` field combinations
- [ ] 4.10 Test translator against all `teams:` YAML configs in `site/examples/`
- [ ] 4.11 Remove `Team` class from `src/agentpool/delegation/team.py`
- [ ] 4.12 Remove `TeamRun` class from `src/agentpool/delegation/teamrun.py`
Expand Down
40 changes: 31 additions & 9 deletions src/agentpool/models/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
from agentpool_config.compaction import CompactionConfig
from agentpool_config.context import ConfigContextManager
from agentpool_config.converters import ConversionConfig
from agentpool_config.graph_config import GraphConfig
from agentpool_config.graph_translation import translate_config_to_graph
from agentpool_config.mcp_server import BaseMCPServerConfig, MCPServerConfig
from agentpool_config.observability import ObservabilityConfig
from agentpool_config.output_types import StructuredResponseConfig
Expand Down Expand Up @@ -441,6 +443,14 @@ class AgentsManifest(Schema):
Excluded from serialization.
"""

graph: GraphConfig | None = Field(default=None)
"""Graph configuration for agent workflow definitions.

When set, defines the execution topology directly. When absent,
the manifest validator auto-translates from ``teams:`` and
``connections:`` sections.
"""

model_config = ConfigDict(
extra="allow",
json_schema_extra={
Expand Down Expand Up @@ -774,15 +784,7 @@ def get_output_type(self, agent_name: str) -> type[Any] | None:

@model_validator(mode="after")
def _populate_node_names(self) -> Self:
"""Populate ``name`` on agent/team configs from their dict key.

When agents or teams are defined in YAML, the dict key (e.g.
``worker:``) is the canonical identifier, but ``config.name``
stays ``None`` because ``NodeConfig`` is frozen and the field
defaults to ``None``. This validator back-fills ``name`` so
that ``Agent.from_config()`` and graph step IDs use the correct
value instead of falling back to ``"native_agent"``.
"""
"""Populate ``name`` on agent/team configs from their dict key."""
for name, config in self.agents.items():
if config.name is None:
self.agents[name] = config.model_copy(update={"name": name})
Expand All @@ -792,6 +794,26 @@ def _populate_node_names(self) -> Self:
self.teams[name] = team_cfg.model_copy(update={"name": name})
return self

@model_validator(mode="after")
def _auto_translate_teams_to_graph(self) -> Self:
"""Auto-translate ``teams:`` and ``connections:`` to ``graph:``.

When a ``graph:`` section is already provided, it takes precedence
and no translation occurs. Otherwise, teams and agent connections
are translated to a unified ``GraphConfig``.
"""
if self.graph is not None:
return self
all_nodes = self.nodes
translated = translate_config_to_graph(
agents=all_nodes,
teams=self.teams or None,
existing_graph=None,
)
if translated is not None:
self.graph = translated
return self

@model_validator(mode="after")
def validate_extra_fields(self) -> Self:
"""Validate and warn about unknown extra fields.
Expand Down
25 changes: 24 additions & 1 deletion src/agentpool_config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from agentpool_config.forward_targets import ForwardingTarget
from agentpool_config.session import SessionQuery
from agentpool_config.session_pool import ACPConfig, OpenCodeConfig, SessionPoolConfig
from agentpool_config.teams import TeamConfig
from agentpool_config.teams import TeamConfig, TeamMemberConfig
from agentpool_config.durable import CheckpointConfig, DeferredToolConfig
from agentpool_config.mcp_server import (
BaseMCPServerConfig,
Expand All @@ -38,6 +38,19 @@
HooksConfig,
PromptHookConfig,
)
from agentpool_config.graph_config import (
GraphConfig,
GraphEdgeConfig,
GraphJoinConfig,
GraphStepConfig,
)
from agentpool_config.graph_translation import (
build_steps_from_agents,
translate_config_to_graph,
translate_connections_to_edges,
translate_team_to_graph,
translate_teams_to_graphs,
)
from agentpool_config.toolsets import ToolsetConfig
from agentpool_config.skills import SkillsConfig, DEFAULT_SKILLS_PATHS
from agentpool_config.skill_commands import SkillSlashConfig, SkillCommandConfig
Expand Down Expand Up @@ -86,6 +99,10 @@
"DeferredToolConfig",
"EventHandlerConfig",
"ForwardingTarget",
"GraphConfig",
"GraphEdgeConfig",
"GraphJoinConfig",
"GraphStepConfig",
"HookConfig",
"HooksConfig",
"MCPServerConfig",
Expand All @@ -103,12 +120,18 @@
"StdoutEventHandlerConfig",
"StreamableHTTPMCPServerConfig",
"TeamConfig",
"TeamMemberConfig",
"ToolConfig",
"ToolsetConfig",
"build_steps_from_agents",
"find_project_config",
"get_global_config_dir",
"get_global_config_path",
"resolve_config",
"resolve_config_for_server",
"resolve_handler_configs",
"translate_config_to_graph",
"translate_connections_to_edges",
"translate_team_to_graph",
"translate_teams_to_graphs",
]
32 changes: 31 additions & 1 deletion src/agentpool_config/graph_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,17 @@


class GraphStepConfig(Schema):
"""Configuration for a single step (node) in a graph."""
"""Configuration for a single step (node) in a graph.

When translated from legacy ``teams:`` YAML, the following fields carry
per-member team configuration that has no native ``graph:`` equivalent:

- ``shared_prompt``: team-level shared prompt injected into this step
- ``prompt_template``: Jinja2 template for per-member prompt rendering
- ``member_timeout``: maximum seconds this step may run before cancellation
- ``member_retry_attempts``: number of retry attempts on failure
- ``member_retry_delay``: delay between retry attempts in seconds
"""

model_config = ConfigDict(
populate_by_name=True,
Expand All @@ -38,6 +48,26 @@ class GraphStepConfig(Schema):
mcp_servers: list[str | MCPServerConfig] = Field(default_factory=list)
"""MCP servers available to this step."""

shared_prompt: str | None = Field(default=None, title="Shared prompt")
"""Optional shared prompt injected into this step (from team config)."""

prompt_template: str | None = Field(default=None, title="Jinja2 prompt template")
"""Optional Jinja2 template for per-step prompt rendering (from team member config)."""

member_timeout: float | None = Field(default=None, title="Per-step timeout (seconds)")
"""Maximum seconds this step may run before being cancelled.

When set, steps that exceed this deadline are cancelled and their
errors are recorded. Other steps that finish in time are not affected.
``None`` (default) means no timeout.
"""

member_retry_attempts: int = Field(default=0, title="Retry attempts")
"""Number of retry attempts on step failure (0 = no retries)."""

member_retry_delay: float = Field(default=0.0, title="Retry delay (seconds)")
"""Delay between retry attempts in seconds."""


class GraphJoinConfig(Schema):
"""Configuration for an explicit join node in a graph."""
Expand Down
Loading