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. 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 https://cs50.harvard.edu/python/ . I learned a lot by reading the notes first and then implementing everything myself. php 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 https://pydantic.dev/docs/validation/latest/concepts/models/ and Fields https://pydantic.dev/docs/validation/latest/concepts/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. php 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 https://github.com/Manav-N4/linkedin-agent linkedin-agent