cd /news/artificial-intelligence/building-a-multi-agent-ai-for-compan… · home topics artificial-intelligence article
[ARTICLE · art-76900] src=dev.to ↗ pub= topic=artificial-intelligence verified=true sentiment=· neutral

Building a Multi-Agent AI for Company LinkedIn Pages — Part 2: Building the Orchestrator Agent

A developer building a multi-agent AI system for company LinkedIn pages details the creation of an Orchestrator Agent that manages communication and execution order among independent agents. The agent classifies topics and returns structured JSON output, with preprocessing functions like strip_json_fences() to handle malformed responses from smaller local models.

read4 min views1 publishedJul 28, 2026

In the previous article, I broke down the problem into independent responsibilities instead of expecting one agent to do everything.

If every agent is independent of eachother, how do they communicate with eachother? Who decides what runs next?

That's where the Orchestrator Agent acts like a traffic controller, managing how agents pass context to eachother and maintaining order in which they run.

linkedin-agent/
├── agents/
│   ├── orchestrator.py     
│   ├── research.py          
│   ├── examples.py          
│   ├── critic.py            
│   ├── brief.py             
│   ├── hook.py              
│   ├── draft.py             
│   └── writing_critic.py    
├── core/
│   ├── llm.py               
│   ├── models.py            
│   └── utils.py            
├── data/
│   └── chroma/
├── main.py                 
├── api.py                   
├── requirements.txt        
└── .env

Each agent has it's own file. The core folder has:

The Orchestrator Agent has one job: understand the topic, classify it, decide the best content angle, and return structured output for the Research Agent.

For every llm call we are making we need a SYSYTEM_PROMPT ie the prompt we write to define how the agent has to work and a user_message which has all context from previous agents.

So, lets start by writing a SYSTEM_PROMPT for the Orchestrator Agent.

SYSTEM_PROMPT = """
You are the orchestrator agent for WanderMesh.

Your job is to analyze a topic and return:

- domain
- content_type
- angle
- reasoning

Rules:
- Return only valid JSON.
- Choose one domain from a predefined list.
- Choose one content type.
- Choose one content angle.
- Explain your reasoning briefly.
"""

I've shortened the prompt for readability. The production version also includes domain-specific categories, output constraints, and a few-shot example to improve classification consistency.

Rather than asking the model an open-ended question, I constrained its output to a fixed structure. That made the downstream pipeline far more predictable.

One issue I ran into early was that the model didn't always return valid JSON. Sometimes it wrapped the response in markdown code fences, sometimes it produced trailing commas, and occasionally it returned smart quotes instead of standard JSON quotes.

Since every downstream agent expected valid JSON, I needed a preprocessing step before parsing the response.

While testing with smaller local models like gemma2:2b, I found they occasionally ignored formatting instructions and returned malformed JSON despite being explicitly asked for valid JSON.

To help fix the JSON format I wrote the strip_json_fences() function.

If you're still getting comfortable with Python, I highly recommend CS50 Python. I learned a lot by reading the notes first and then implementing everything myself.

def strip_json_fences(raw:str) -> str:
    raw = raw.strip()
    if raw.startswith("```

json"):
        raw = raw.removeprefix("

``` json").strip()
    elif raw.startswith("```

"):
        raw = raw.removeprefix("

```").strip()
    if raw.endswith("```

"):
        raw = raw.removesuffix("

```").strip()
    raw = re.sub(r',\s*]', ']', raw)
    raw = re.sub(r',\s*}', '}', raw)
    raw = raw.replace('\u201c', '\\"').replace('\u201d', '\\"')
    raw = raw.replace('\u2018', "'").replace('\u2019', "'")
    return raw

Even valid JSON isn't enough. The model might forget a field, rename one, or return an unexpected structure. Before handing the output to the next agent, I wanted to validate that it matched exactly what the pipeline expected.

Further reading: If you're new to Pydantic, the official documentation on Models and Fields is an excellent place to understand the validation features used here.

Now lets create a Pydantic Object for the Orchestrator Agent. For now, I'm only validating the structure, not restricting the possible values. That keeps the orchestrator flexible while the rest of the pipeline is still evolving.

class OrchestratorOutput(BaseModel):
    domain: str
    angle: str
    reasoning: str
    content_type: str

Now lets write the main function for classifying topic. Before writing the code lets fundamentally breakdown what this function should do.

Topic
   │
   ▼
clean_topic()
   │
   ▼
call_llm()
   │
   ▼
strip_json_fences()
   │
   ▼
json.loads()
   │
   ▼
Pydantic Validation
   │
   ▼
OrchestratorOutput

It should take topic as input and return Pydantic Object as output for Research Agent to use.

The topic should first be cleaned using the clean_topic(), removing extra spaces if any and checking if the user hasn't entered an empty string.

Then we pass the cleaned topic and SYSTEM_PROMPT to the call_llm() function and then remove json fences from it using strip_json_fences() function to return valid JSON.

Finally, the JSON is parsed and validated. If anything goes wrong, the function returns None instead of passing invalid data further down the pipeline.

As a standard practice we parse JSON and validate the parsed JSON in try and except blocks to handle Errors if something breaks.

def classify_topic(topic:str) -> OrchestratorOutput|None:
    cleaned_topic = clean_topic(topic)
    raw_response = call_llm(SYSTEM_PROMPT, cleaned_topic)
    refined_response = strip_json_fences(raw_response)
    try:
        parsed = json.loads(refined_response)
    except json.JSONDecodeError:
        print("LLM did not return valid JSON:")
        print(raw_response)
        return None
    try:
        return OrchestratorOutput.model_validate(parsed)
    except ValidationError as e:
        print("LLM returned JSON but it doesn't match expected structure:")
        print(e)
        return None

At this point, the orchestrator can consistently classify a topic and return structured output that every downstream agent can rely on.

In the next article, I'll build the Research Agent, the component responsible for gathering factual information before any content is generated.

Github Repo: https://github.com/Manav-N4/linkedin-agent#linkedin-agent

── more in #artificial-intelligence 4 stories · sorted by recency
── more on @wandermesh 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/building-a-multi-age…] indexed:0 read:4min 2026-07-28 ·