Skip to content
Open
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
25 changes: 23 additions & 2 deletions src/conductor/engine/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ class WorkflowContext:
workflow_inputs: dict[str, Any] = field(default_factory=dict)
"""Inputs provided at workflow start."""

workflow_dir: str = ""
"""Directory containing the workflow YAML file (resolved absolute path).
Available in templates as ``{{ workflow.dir }}``."""

workflow_file: str = ""
"""Absolute path to the workflow YAML file.
Available in templates as ``{{ workflow.file }}``."""

workflow_name: str = ""
"""Name of the workflow from the YAML config.
Available in templates as ``{{ workflow.name }}``."""

agent_outputs: dict[str, dict[str, Any]] = field(default_factory=dict)
"""Outputs from executed agents, keyed by agent name."""

Expand Down Expand Up @@ -161,11 +173,20 @@ def build_for_agent(
Raises:
KeyError: If explicit mode is used and a required (non-optional) input is missing.
"""
# Build workflow metadata available in all modes
workflow_meta: dict[str, Any] = {}
if self.workflow_dir:
workflow_meta["dir"] = self.workflow_dir
if self.workflow_file:
workflow_meta["file"] = self.workflow_file
if self.workflow_name:
workflow_meta["name"] = self.workflow_name

# For explicit mode, start with empty workflow inputs
# For other modes, include all workflow inputs
if mode == "explicit":
ctx: dict[str, Any] = {
"workflow": {"input": {}},
"workflow": {"input": {}, **workflow_meta},
"context": {
"iteration": self.current_iteration,
"history": self.execution_history.copy(),
Expand All @@ -176,7 +197,7 @@ def build_for_agent(
self._add_explicit_input(ctx, input_ref)
else:
ctx = {
"workflow": {"input": self.workflow_inputs.copy()},
"workflow": {"input": self.workflow_inputs.copy(), **workflow_meta},
"context": {
"iteration": self.current_iteration,
"history": self.execution_history.copy(),
Expand Down
6 changes: 5 additions & 1 deletion src/conductor/engine/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,11 @@ def __init__(
self.config = config
self.skip_gates = skip_gates
self.workflow_path = workflow_path
self.context = WorkflowContext()
self.context = WorkflowContext(
workflow_dir=str(Path(workflow_path).resolve().parent) if workflow_path else "",
workflow_file=str(Path(workflow_path).resolve()) if workflow_path else "",
workflow_name=config.workflow.name,
)
self.renderer = TemplateRenderer()
self.router = Router()
self.limits = LimitEnforcer(
Expand Down
47 changes: 47 additions & 0 deletions tests/test_engine/test_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ def test_init_default_values(self) -> None:
assert ctx.agent_outputs == {}
assert ctx.current_iteration == 0
assert ctx.execution_history == []
assert ctx.workflow_dir == ""
assert ctx.workflow_file == ""
assert ctx.workflow_name == ""

def test_set_workflow_inputs(self) -> None:
"""Test setting workflow inputs."""
Expand Down Expand Up @@ -151,6 +154,50 @@ def test_last_only_mode_empty_history(self) -> None:
assert "context" in agent_ctx


class TestWorkflowContextMetadata:
"""Tests for workflow metadata (dir, file, name) in context."""

def test_workflow_dir_file_name_in_accumulate_context(self) -> None:
"""Test workflow.dir, workflow.file, workflow.name available in accumulate mode."""
ctx = WorkflowContext(
workflow_dir="/home/user/workflows",
workflow_file="/home/user/workflows/main.yaml",
workflow_name="my-workflow",
)
ctx.set_workflow_inputs({"key": "val"})

agent_ctx = ctx.build_for_agent("agent", [], mode="accumulate")

assert agent_ctx["workflow"]["dir"] == "/home/user/workflows"
assert agent_ctx["workflow"]["file"] == "/home/user/workflows/main.yaml"
assert agent_ctx["workflow"]["name"] == "my-workflow"
assert agent_ctx["workflow"]["input"] == {"key": "val"}

def test_workflow_metadata_in_explicit_mode(self) -> None:
"""Test workflow.dir/file/name available in explicit mode (not filtered)."""
ctx = WorkflowContext(
workflow_dir="/registry/twig",
workflow_file="/registry/twig/sdlc.yaml",
workflow_name="twig-sdlc",
)

agent_ctx = ctx.build_for_agent("agent", [], mode="explicit")

assert agent_ctx["workflow"]["dir"] == "/registry/twig"
assert agent_ctx["workflow"]["file"] == "/registry/twig/sdlc.yaml"
assert agent_ctx["workflow"]["name"] == "twig-sdlc"

def test_empty_metadata_omitted(self) -> None:
"""Test that empty workflow metadata fields are not included."""
ctx = WorkflowContext()

agent_ctx = ctx.build_for_agent("agent", [], mode="accumulate")

assert "dir" not in agent_ctx["workflow"]
assert "file" not in agent_ctx["workflow"]
assert "name" not in agent_ctx["workflow"]


class TestWorkflowContextExplicitMode:
"""Tests for explicit context mode."""

Expand Down
Loading