# How to use Skills with structured ouput?

**URL:** <https://forum.langchain.com/t/how-to-use-skills-with-structured-ouput/3066>\
**Category:** Deep Agents\
**Created:** [March 3, 2026, 7:11am UTC](https://forum.langchain.com/t/how-to-use-skills-with-structured-ouput/3066 "2026-03-03T07:11:44Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![SeccoMaracuja](https://yyz1.discourse-cdn.com/flex007/user_avatar/forum.langchain.com/seccomaracuja/32/2254_2.png) [@SeccoMaracuja](https://forum.langchain.com/u/SeccoMaracuja)\
**Post date:** [March 3, 2026, 7:11am UTC](https://forum.langchain.com/t/how-to-use-skills-with-structured-ouput/3066/1 "2026-03-03T07:11:44Z")

</div>

My deep agent has five Skills. Each Skill produces a specific output format. For example, sometimes a table, sometimes plain text, and sometimes a structured list defined within the Skill’s markdown.

However, after a Skill is executed, the agent reformats the response and destroys the original output style.

I want the agent to return the Skill output **exactly 1:1** , without modifying or reformatting it in any way.

How can I achieve this?

Below is an example Skill:

> You are a Senior Technical Architect.
> 
> Given a high-level request, output a table with these columns:
> 
> | Section | Description | Tasks / Notes |
> 
> Sections to cover:
> 
> 1. Business Objective
> 2. Success Metrics
> 3. System Impact
> 4. Data Tasks
> 5. Risks
> 6. Phased Plan
> 
> Be concise and use bullet points in the Tasks / Notes column.

---

<div class="post-metadata">

**Author:** ![pawel-twardziak](https://yyz1.discourse-cdn.com/flex007/user_avatar/forum.langchain.com/pawel-twardziak/32/960_2.png) [@pawel-twardziak](https://forum.langchain.com/u/pawel-twardziak)\
**Post date:** [March 3, 2026, 8:51am UTC](https://forum.langchain.com/t/how-to-use-skills-with-structured-ouput/3066/2 "2026-03-03T08:51:47Z")

</div>

hi @SeccoMaracuja

The root cause is that Skills are documentation-based, not execution-based - the agent reads the skill’s instructions and then generates its own response, which is where the reformatting happens.

Skills in Deep Agents follow a progressive disclosure pattern ([source: `deepagents/middleware/skills.py`, line 561-600](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/deepagents/middleware/skills.py)):

1. the `SkillsMiddleware` injects skill names and descriptions into the system prompt
2. When the agent recognizes a skill applies, it reads the full `SKILL.md` file via the `read_file` tool
3. the agent follows the skill’s instructions and generates its own response
4. here is no post-processing or output interception by the skills system

This means the agent’s LLM is free to rephrase, summarize, or reformat the output - which is exactly the problem you’re experiencing.

### solution 1: `response_format` with a pydantic schema

The most reliable approach is to use the `response_format` parameter on `create_deep_agent` to enforce a structured output schema. This way, the agent’s final response must conform to your defined structure.

```python
from pydantic import BaseModel, Field
from deepagents import create_deep_agent
from langchain.agents.structured_output import ToolStrategy

# Define schemas for each skill output type
class ArchitectureTable(BaseModel):
    """Structured architecture analysis output."""
    business_objective: str = Field(description="Business objective section with description and tasks")
    success_metrics: str = Field(description="Success metrics section with description and tasks")
    system_impact: str = Field(description="System impact section with description and tasks")
    data_tasks: str = Field(description="Data tasks section with description and tasks")
    risks: str = Field(description="Risks section with description and tasks")
    phased_plan: str = Field(description="Phased plan section with description and tasks")

agent = create_deep_agent(
    skills=["/skills/"],
    response_format=ToolStrategy(schema=ArchitectureTable),
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "Analyze the architecture for project X"}]
})

# Access the validated structured output
structured = result["structured_response"]
print(structured.business_objective)
print(structured.phased_plan)

```

The `response_format` parameter is passed directly through to the underlying `create_agent` call ([source: `deepagents/graph.py`, line 91 and 314](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/deepagents/graph.py)).

Docs reference: [Deep Agents Customization](https://docs.langchain.com/oss/python/deepagents/customization)

### solution 2: string field to preserve raw output

If you want to preserve the skill’s output exactly as-is (e.g. a markdown table), use a single string field in your schema:

```python
from pydantic import BaseModel, Field
from deepagents import create_deep_agent
from langchain.agents.structured_output import ToolStrategy

class SkillOutput(BaseModel):
    """Preserves the skill output verbatim."""
    skill_name: str = Field(description="Name of the skill that was executed")
    raw_output: str = Field(
        description=(
            "The EXACT output produced by following the skill instructions. "
            "Do NOT modify, summarize, or reformat this content in any way. "
            "Preserve all original formatting including tables, bullet points, "
            "and whitespace exactly as generated."
        )
    )

agent = create_deep_agent(
    skills=["/skills/"],
    response_format=ToolStrategy(schema=SkillOutput),
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "Run the architecture analysis for project X"}]
})

# The raw_output field preserves the original formatting
print(result["structured_response"].raw_output)

```

The key here is the detailed field description - it acts as an instruction to the LLM to preserve the format verbatim.

### solution 3: custom ystem prompt to prevent reformatting

If you don’t need structured output but simply want the agent to stop reformatting, add explicit instructions in the `system_prompt`:

```python
from deepagents import create_deep_agent

SYSTEM_PROMPT = """
CRITICAL OUTPUT RULE: When you execute a Skill, you MUST return the Skill's
output EXACTLY as produced - character for character. Do NOT:
- Rephrase or summarize the output
- Change the formatting (tables, lists, bullet points)
- Add your own commentary around the output
- Merge or restructure sections

Simply output the Skill result as-is. If the Skill produces a markdown table,
return that exact markdown table. If it produces bullet points, return those
exact bullet points.
"""

agent = create_deep_agent(
    system_prompt=SYSTEM_PROMPT,
    skills=["/skills/"],
)

```

This approach relies on prompt engineering and is less reliable than structured output, but it’s the simplest change.

### solution 4: middleware for dynamic per-skill output handling

For the most flexibility (5 skills with different formats), you can write custom middleware that dynamically selects the response format based on which skill was activated:

```python
from langchain.agents.middleware import AgentMiddleware, ModelRequest, ModelResponse
from langchain.agents.structured_output import ToolStrategy
from pydantic import BaseModel, Field

class TableOutput(BaseModel):
    table_markdown: str = Field(description="Complete markdown table output, preserved exactly")

class ListOutput(BaseModel):
    list_content: str = Field(description="Complete structured list output, preserved exactly")

class PlainTextOutput(BaseModel):
    text: str = Field(description="Complete plain text output, preserved exactly")

SKILL_FORMAT_MAP = {
    "architecture-analysis": ToolStrategy(schema=TableOutput),
    "requirements-list": ToolStrategy(schema=ListOutput),
    "summary-writer": ToolStrategy(schema=PlainTextOutput),
}

class SkillOutputMiddleware(AgentMiddleware):
      def wrap_model_call(self, request, handler):                                                                       
          messages = request.messages
          matched_format = None                                                                                          
          for msg in reversed(messages):
              content = getattr(msg, "content", "")
              if isinstance(content, str):
                  for skill_name, fmt in SKILL_FORMAT_MAP.items():
                      if skill_name in content:
                          matched_format = fmt
                          break
              if matched_format:
                  break
          if matched_format:
              request = request.override(response_format=matched_format)
          return handler(request)

 agent = create_deep_agent(
     skills=["/skills/"],
     middleware=[SkillOutputMiddleware()],
     response_format=ToolStrategy(schema=PlainTextOutput), # base format; middleware overrides
 )

```

### which one to use?

| Approach | Reliability | Complexity | Best For |
| --- | --- | --- | --- |
| **Solution 1** : Pydantic schema per skill type | High | Medium | When you need typed, validated output |
| **Solution 2** : Single `raw_output` string field | High | Low | Preserving exact formatting for all skills |
| **Solution 3** : System prompt only | Medium | Very Low | Quick fix, prototype stage |
| **Solution 4** : Custom middleware | Very High | High | Multiple skills with different schemas |

`structured_response` is excluded from subagent output ([source: `deepagents/middleware/subagents.py`, line 121-128](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/deepagents/middleware/subagents.py)). If your skills run inside subagents, the structured response won’t propagate to the parent. You’d need to include the data explicitly in the `ToolMessage`.

`ToolStrategy` vs `ProviderStrategy`: `ToolStrategy` works with any model that supports tool calling. `ProviderStrategy` uses the provider’s native structured output API (e.g., OpenAI’s JSON mode). Both are imported from `langchain.agents.structured_output`.

**Skills don’t own the output** - they are pure documentation injected into the system prompt. The agent reads them and generates its own response. This is by design (progressive disclosure pattern). The `response_format` parameter is the official mechanism to control output structure.

Links:

- [Deep Agents source: `graph.py` - `create_deep_agent` signature](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/deepagents/graph.py)
- [Deep Agents source: `middleware/skills.py` - Skills system prompt template](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/deepagents/middleware/skills.py)
- [Deep Agents source: `middleware/subagents.py` - structured\_response exclusion](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/deepagents/middleware/subagents.py)
- [Deep Agents integration test: `test_response_format_tool_strategy`](https://github.com/langchain-ai/deepagents/blob/main/libs/deepagents/tests/integration_tests/test_deepagents.py)
- [Deep Agents Customization Docs](https://docs.langchain.com/oss/python/deepagents/customization)
- [LangChain Structured Output Docs](https://docs.langchain.com/oss/python/langchain/structured-output)

---

<div class="post-metadata">

**Author:** ![SeccoMaracuja](https://yyz1.discourse-cdn.com/flex007/user_avatar/forum.langchain.com/seccomaracuja/32/2254_2.png) [@SeccoMaracuja](https://forum.langchain.com/u/SeccoMaracuja)\
**Post date:** [March 3, 2026, 9:21am UTC](https://forum.langchain.com/t/how-to-use-skills-with-structured-ouput/3066/3 "2026-03-03T09:21:55Z")

</div>

Thank you Pawel! I think middleware for dynamic per-skill output handling is the key. I will try it out!

---

<div class="post-metadata">

**Author:** ![pawel-twardziak](https://yyz1.discourse-cdn.com/flex007/user_avatar/forum.langchain.com/pawel-twardziak/32/960_2.png) [@pawel-twardziak](https://forum.langchain.com/u/pawel-twardziak)\
**Post date:** [March 3, 2026, 9:37am UTC](https://forum.langchain.com/t/how-to-use-skills-with-structured-ouput/3066/4 "2026-03-03T09:37:17Z")

</div>

hi @SeccoMaracuja LangChain is really powerful and mighty with its middlewares 🚀 Fingers crossed 🤞
