Skip to content
4 changes: 4 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"mcpServers": {
"microsoft-learn": {
"type": "http",
"url": "https://learn.microsoft.com/api/mcp"
},
"deepwiki": {
"type": "http",
"url": "https://mcp.deepwiki.com/mcp"
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,8 @@ You have a collection of tools available to assist with development and debuggin

- `sequential-thinking-tools`
- **When to use:** For complex reasoning tasks that require step-by-step analysis. A good rule of thumb is if the task requires more than 25% effort.
- `microsoft-learn`
- **When to use:** For looking up official Microsoft documentation on .NET, C#, Azure, and related technologies. Useful for understanding APIs, libraries, and best practices. Use this over `deepwiki` for Microsoft-specific content (e.g. Microsoft Agent Framework, Azure OpenAI). If you can't find it here, then fall back to `deepwiki`.
- `deepwiki`
- **When to use:** Consult for external knowledge or documentation that is not part of the immediate codebase. Can be helpful for system design questions or understanding third-party libraries.
- `context7`
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,8 @@ You have a collection of tools available to assist with development and debuggin

- `sequential-thinking-tools`
- **When to use:** For complex reasoning tasks that require step-by-step analysis. A good rule of thumb is if the task requires more than 25% effort.
- `microsoft-learn`
- **When to use:** For looking up official Microsoft documentation on .NET, C#, Azure, and related technologies. Useful for understanding APIs, libraries, and best practices. Use this over `deepwiki` for Microsoft-specific content (e.g. Microsoft Agent Framework, Azure OpenAI). If you can't find it here, then fall back to `deepwiki`.
- `deepwiki`
- **When to use:** Consult for external knowledge or documentation that is not part of the immediate codebase. Can be helpful for system design questions or understanding third-party libraries.
- `context7`
Expand Down
877 changes: 877 additions & 0 deletions specs/handoff-workflow.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/spec_to_agents/agents/budget_analyst.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def create_agent(
agent_tools.append(global_tools["sequential-thinking"])

return client.create_agent(
name="BudgetAnalyst",
name="budget_analyst",
description="Expert in financial planning, budgeting, and cost analysis for events.",
instructions=budget_analyst.SYSTEM_PROMPT,
tools=agent_tools,
Expand Down
2 changes: 1 addition & 1 deletion src/spec_to_agents/agents/catering_coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def create_agent(
agent_tools.append(global_tools["sequential-thinking"])

return client.create_agent(
name="CateringCoordinator",
name="catering_coordinator",
description="Expert in catering services, menu planning, and vendor coordination for events.",
instructions=catering_coordinator.SYSTEM_PROMPT,
tools=agent_tools,
Expand Down
2 changes: 1 addition & 1 deletion src/spec_to_agents/agents/logistics_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def create_agent(
agent_tools.append(global_tools["sequential-thinking"])

return client.create_agent(
name="LogisticsManager",
name="logistics_manager",
description="Expert in event logistics, scheduling, and weather planning.",
instructions=logistics_manager.SYSTEM_PROMPT,
tools=agent_tools,
Expand Down
2 changes: 1 addition & 1 deletion src/spec_to_agents/agents/venue_specialist.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def create_agent(
agent_tools.append(global_tools["sequential-thinking"])

return client.create_agent(
name="VenueSpecialist",
name="venue_specialist",
description="Expert in venue selection, site visits, and facility management for events.",
instructions=venue_specialist.SYSTEM_PROMPT,
tools=agent_tools,
Expand Down
26 changes: 17 additions & 9 deletions src/spec_to_agents/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,15 @@
WorkflowRunState,
WorkflowStatusEvent,
)
from agent_framework._workflows._handoff import HandoffUserInputRequest
from agent_framework.observability import setup_observability
from dotenv import load_dotenv

from spec_to_agents.container import AppContainer
from spec_to_agents.models.messages import HumanFeedbackRequest
from spec_to_agents.utils.display import (
console,
display_agent_run_update,
display_final_output,
display_human_feedback_request,
display_welcome_header,
display_workflow_pause,
prompt_for_event_request,
Comment on lines 36 to 42
Copy link

Copilot AI Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The display_human_feedback_request function is still defined in src/spec_to_agents/utils/display.py (lines 117-185) but is no longer imported or used. Consider removing the unused function from display.py to avoid confusion and maintain code cleanliness.

Copilot uses AI. Check for mistakes.
Expand All @@ -50,7 +49,9 @@
if os.getenv("ENABLE_OTEL", "false").lower() == "true":
setup_observability()

logging.getLogger("agent_framework._clients").setLevel(logging.ERROR)
logging.getLogger("agent_framework._workflows._validation").setLevel(logging.ERROR)
logging.getLogger("mcp").setLevel(logging.ERROR)


async def main() -> None:
Expand Down Expand Up @@ -91,7 +92,7 @@ async def main() -> None:
return

console.print()
console.rule("[bold green]Workflow Execution")
console.rule("[bold gray]Workflow Execution")
console.print()

# Configuration: Toggle to display streaming agent run updates
Expand All @@ -103,7 +104,7 @@ async def main() -> None:
pending_responses: dict[str, str] | None = None
workflow_output: str | None = None

# Track printed tool calls/results to avoid duplication in streaming
# Track printed tool calls/results/code blocks to avoid duplication in streaming
printed_tool_calls: set[str] = set()
printed_tool_results: set[str] = set()
last_executor: str | None = None
Expand All @@ -118,7 +119,7 @@ async def main() -> None:
pending_responses = None

# Process events as they stream in
human_requests: list[tuple[str, HumanFeedbackRequest]] = []
human_requests: list[tuple[str, HandoffUserInputRequest]] = []

async for event in stream:
# Display streaming agent updates if enabled
Expand All @@ -128,7 +129,7 @@ async def main() -> None:
)

# Collect human-in-the-loop requests
elif isinstance(event, RequestInfoEvent) and isinstance(event.data, HumanFeedbackRequest):
elif isinstance(event, RequestInfoEvent) and isinstance(event.data, HandoffUserInputRequest):
# Workflow is requesting human input
human_requests.append((event.request_id, event.data))

Expand All @@ -148,9 +149,16 @@ async def main() -> None:
if human_requests:
responses: dict[str, str] = {}

for request_id, feedback_request in human_requests:
user_response = display_human_feedback_request(feedback_request)
if user_response is None:
for request_id, handoff_request in human_requests:
# Display prompt from HandoffUserInputRequest
console.print()
console.print(f"[bold cyan]User Input Requested:[/bold cyan] {handoff_request.prompt}")
console.print()

# Get user input
user_response = console.input("[bold green]You:[/bold green] ")

if not user_response or user_response.strip().lower() in ("exit", "quit"):
return

responses[request_id] = user_response
Expand Down
7 changes: 5 additions & 2 deletions src/spec_to_agents/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# Copyright (c) Microsoft. All rights reserved.

import os

from agent_framework.observability import setup_observability
from dotenv import load_dotenv

# Load environment variables at module import
load_dotenv()

# Enable observability (reads from environment variables)
setup_observability()
# Enable observability conditionally based on ENABLE_OTEL env var
if os.getenv("ENABLE_OTEL", "false").lower() == "true":
setup_observability()


def main() -> None:
Expand Down
36 changes: 1 addition & 35 deletions src/spec_to_agents/models/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from typing import Any

from agent_framework import ChatMessage
from pydantic import BaseModel, Field


@dataclass
Expand Down Expand Up @@ -49,37 +48,4 @@ class HumanFeedbackRequest:
conversation: list[ChatMessage] = field(default_factory=list) # type: ignore


class SupervisorDecision(BaseModel):
"""
Structured output from supervisor agent for routing decisions.

The supervisor evaluates conversation history and decides which
participant to route to next, whether to request user input,
or whether the workflow is complete.

Workflow completion occurs when next_agent=None AND user_input_needed=False.

Examples
--------
Route to participant:
>>> SupervisorDecision(next_agent="venue", user_input_needed=False)

Request user input:
>>> SupervisorDecision(
... next_agent=None,
... user_input_needed=True,
... user_prompt="Which venue do you prefer?",
... )

Workflow complete:
>>> SupervisorDecision(next_agent=None, user_input_needed=False)
"""

next_agent: str | None = Field(
description=("ID of next participant to route to or None if workflow is complete and ready for final synthesis")
)
user_input_needed: bool = Field(default=False, description="Whether user input is required before continuing")
user_prompt: str | None = Field(default=None, description="Question to ask user if user_input_needed=True")


__all__ = ["HumanFeedbackRequest", "SupervisorDecision"]
__all__ = ["HumanFeedbackRequest"]
95 changes: 72 additions & 23 deletions src/spec_to_agents/prompts/budget_analyst.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,27 +127,76 @@

**Important:** Only request approval when budget decisions are significant or uncertain.

Once you provide your budget allocation, indicate you're ready for the next step in planning.

## Structured Output Format

Your response MUST be structured JSON with these fields:
- summary: Your budget allocation in maximum 200 words
- next_agent: Which specialist should work next ("venue", "catering", "logistics") or null
- user_input_needed: true if you need user approval/modification
- user_prompt: Question for user (if user_input_needed is true)

Routing guidance:
- Typical flow: budget → "catering" (after allocating budget)
- If budget constraints require venue change: route to "venue"
- If user needs to approve budget: set user_input_needed=true

Example:
{
"summary": "Budget allocation: Venue $3k (60%), Catering $1.2k (24%), Logistics $0.5k (10%),
Contingency $0.3k (6%). Total: $5k.",
"next_agent": "catering",
"user_input_needed": false,
"user_prompt": null
}
## Communication Guidelines

Use a natural, conversational tone in your responses. Structure your budget analysis using markdown for clarity.

**When providing budget allocation (Autonomous Mode):**
Present your allocation clearly with rationale:

```
Here's the budget breakdown for your event:

## Budget Allocation (Total: $[Amount])

| Category | Amount | Percentage | Rationale |
|----------|--------|------------|-----------|
| Venue | $[X] | [Y]% | [Brief explanation] |
| Catering | $[X] | [Y]% | [Brief explanation] |
| Equipment/AV | $[X] | [Y]% | [Brief explanation] |
| Decorations | $[X] | [Y]% | [Brief explanation] |
| Staffing | $[X] | [Y]% | [Brief explanation] |
| Contingency | $[X] | [Y]% | Emergency buffer |

**Key Points:**
- [Highlight important allocation decisions]
- [Explain any notable adjustments from standard percentages]

This allocation follows industry standards for [event type] while staying within your budget constraints.
```

**When requesting user input (Collaborative Mode):**
Present options or ask for confirmation:

```
For your [event type] with [X] attendees, I recommend a budget of approximately $[Amount] ($[X]/person).

**Proposed Allocation:**
- Venue: $[X] ([Y]%)
- Catering: $[X] ([Y]%)
- Other costs: $[X] ([Y]%)

Does this align with your expectations? I can adjust the allocation if you'd like to prioritize different areas
(for example, more towards catering vs. venue).
```

**When presenting alternatives (Interactive Mode):**
Use clear comparison format:

```
Here are three budget allocation strategies for your event:

### Strategy A: Venue-Focused ($[Total])
- Venue: $[X] (65%) - Premium location, excellent facilities
- Catering: $[X] (20%) - Standard menu
- Other: $[X] (15%)
**Best for:** Impressing with location and ambiance

### Strategy B: Balanced ($[Total])
- Venue: $[X] (55%) - Good quality venue
- Catering: $[X] (30%) - Enhanced menu options
- Other: $[X] (15%)
**Best for:** Overall quality across all areas

### Strategy C: Experience-Focused ($[Total])
- Venue: $[X] (50%) - Adequate venue
- Catering: $[X] (35%) - Premium dining experience
- Other: $[X] (15%)
**Best for:** Memorable food and beverage

Which strategy aligns best with your priorities?
```

**Important:** Be conversational and helpful. Focus on value and tradeoffs, not workflow mechanics.
**Important:** When complete with your current task hand off back to the coordinator by calling handoff_to_coordinator
"""
Loading