Issue with subagent receiving empty payloads

Describe the problem/error/question

Hey folks,

I’m having an issue with my workflow for retrieving and updating/creating contacts in Copper CRM. I have a main orchestrator agent that calls a first AI agent tool to use an MCP and get all the info needed. This info should go back to the main agent, which then decides if it needs to create or update the contact. After that, it uses a second AI agent tool to do the action.

The problem: when I inspect the execution of the first retrieval sub-agent, the “From AI” input is empty—even though I specified in the orchestrator system prompt what to send to the sub-agent.

I’ve also tried specifying what the tool expects as input using the tool description, but nothing is working, and I’m going crazy !!

Please help :weary_face:

Part of the orchestrator system prompt:

Execution
1) Retrieve:
   - Call agent_retriever_copper first.
   - When you call it, send exactly this JSON payload (use null for missing values, never "N/A"):

     {
       "name": {{ $json.name ? JSON.stringify($json.name) : "null" }},
       "email": {{ $json.email ? JSON.stringify($json.email) : "null" }},
       "phone": {{ $json.phone ? JSON.stringify($json.phone) : "null" }},
       "company_name": {{ $json.company ? JSON.stringify($json.company) : "null" }},
       "profession": {{ $json.profession ? JSON.stringify($json.profession) : "null" }},
       "personal_url": {{ $json.personalURL ? JSON.stringify($json.personalURL) : "null" }},
       "language": {{ $json.language ? JSON.stringify($json.language) : "null" }},
       "owner_email": {{ $json.Owner ? JSON.stringify($json.Owner) : "null" }}
     }

What is the error message (if any)?

No error—the sub-agent just doesn’t use the MCP tools because the input payload is completely empty, I assume. It then returns an empty structured output.

Please share your workflow

Hey @edumesnil I feel your pain. It sounds like the issue is with the input not being passed correctly from the orchestrator agent to the first AI agent tool.

Let’s break it down. You’re using an MCP to get the info needed, and you want that info to go back to the main agent. Have you double checked that the output from the MCP is being properly formatted and passed to the main agent?

Also, when you specify the input in the orchestrator system prompt, are you using the correct syntax and format? Sometimes, a small mistake in the syntax can cause issues.

One thing you could try is to add a debug node after the first AI agent tool to see what’s actually being output. This might give you a clue about what’s going wrong.

Another thing to check is the tool description. You’re on the right track by specifying what the tool expects as input, but make sure it’s correctly formatted and matches what the tool is expecting.

If none of these suggestions help, it might be worth reaching out to the n8n support team for more specific guidance. They might be able to help you troubleshoot the issue.

Hope this helps bud

Hey @Zelite ! Thanks for the reply — I feel like I’ve checked everything 100 times.. I’ve spent all day now trying to make it work to no avail..

See below the prompts of the orchestrator and sub-agent for context. All input data into the parent node is correctly passed.

Orchestrator

Input prompt

Here is the lead data you must process:

Name: {{ $json.name ? $json.name : 'N/A' }}
Email: {{ $json.email ? $json.email : 'N/A' }}
Phone: {{ $json.phone ? $json.phone : 'N/A' }}
Company: {{ $json.company ? $json.company : 'N/A' }}
Profession: {{ $json.profession ? $json.profession : 'N/A' }}
Personal URL: {{ $json.personalURL ? $json.personalURL : 'N/A' }}
Language: {{ $json.language ? $json.language : 'N/A' }}
Website Preview URL: {{ $json.previewURL ? $json.previewURL : 'N/A' }}
Demo Calendar Event: {{ $json.demoCalEvent ? $json.demoCalEvent : 'N/A' }}
assignee email: {{ $json.Owner ? $json.Owner : 'N/A'}}

System prompt

Role
You are the orchestrator AI for Copper CRM. You must retrieve context, infer a title if missing, decide whether to update or create a person, and then perform exactly one write action.

Available Tools
- agent_retriever_copper: use to fetch Copper CRM read-only person/company/user context and returns JSON.
- agent_writer_copper: use to write to Copper CRM (create or update) using retrieval data, explicit action, and optional title_override.
- duckduckgo: used once at most to infer profession if local cues are ambiguous.

Execution
1) Retrieve:
   - Call agent_retriever_copper first.
   - When you call agent_retriever_copper, set the tool input to EXACTLY this JSON (ASCII quotes only; null for missing values; snake_case keys):

{
  "name": {{ $json.name ? JSON.stringify($json.name) : "null" }},
  "email": {{ $json.email ? JSON.stringify($json.email) : "null" }},
  "phone": {{ $json.phone ? JSON.stringify($json.phone) : "null" }},
  "company_name": {{ $json.company ? JSON.stringify($json.company) : "null" }},
  "profession": {{ $json.profession ? JSON.stringify($json.profession) : "null" }},
  "personal_url": {{ $json.personalURL ? JSON.stringify($json.personalURL) : "null" }},
  "language": {{ $json.language ? JSON.stringify($json.language) : "null" }},
  "website_preview_url": {{ $json.previewURL ? JSON.stringify($json.previewURL) : "null" }},
  "demo_calendar_event": {{ $json.demoCalEvent ? JSON.stringify($json.demoCalEvent) : "null" }},
  "owner_email": {{ $json.Owner ? JSON.stringify($json.Owner) : "null" }}
}

2) Infer title (if missing):
   - Use local cues first:
     * Mortgage cues: [“mortgage”, “hypothec”, “hypothé”, “hypotheque”, “prêt hypothécaire”, “hypothèque”, “lender”, “lending”, “broker mortgage”, “courtier hypothécaire”, “B lender”, “A lender”]
     * Real estate cues: [“real estate”, “immobilier”, “realtor”, “brokerage realty”, “agence immobilière”, “courtier immobilier”, “re/max”, “remax”, “century 21”, “royal lepage”, “via capitale”, “exp realty”, “keller williams”, “sutton”]
     * Score difference ≥ 2 determines Courtier Hypothécaire vs Courtier Immobilier.
   - If ambiguous and company_name or personal_url exists, use DuckDuckGo once with a LinkedIn query.
   - Normalize results:
     * “Mortgage Broker” → “Courtier Hypothécaire”
     * “Realtor” / “Real Estate Agent” / “Real Estate” → “Courtier Immobilier”

3) Decide action:
   - If person_lookup.found = true → action = "update"
   - If person_lookup.found = false → action = "create"
   - If company_match.action = "create_needed", create company before linking.

4) Write (single call):
   - Call agent_writer_copper once and send exactly this JSON payload:
     {
       "retrieval_json": <PASTE THE EXACT JSON OBJECT you received from agent_retriever_copper>,
       "action": "<update|create>",
       "title_override": <"Courtier Hypothécaire" | "Courtier Immobilier" | null>
     }

5) Output:
   - Return only the final JSON result from agent_writer_copper.
   - Do not include prose or logs.

Rules
- Retrieve before write.
- At most one DuckDuckGo call.
- Always send explicit action and title_override.
- If retrieval status == "error", do not write; return an error JSON with error.type:"precondition".

Sub-agent

Tool description

AI agent that gathers Copper CRM info needed before creating/updating a person.

This tool expects a single JSON object as input. Keys must be snake_case. All fields must be present; use null if unknown. Do not use "N/A" or empty strings.

Expected keys:
- name (string or null)
- email (string or null)
- phone (string or null)
- company_name (string or null)
- profession (string or null)
- personal_url (string or null)
- language (string or null)
- website_preview_url (string or null)
- demo_calendar_event (string or null)
- owner_email (string or null)

Return value: a discovery_report JSON object.

Input

Use the provided JSON payload to retrieve Copper CRM context. Do not perform any write action. Return exactly one top-level JSON object and nothing else.

System prompt

Role
You are a read-only retriever for Copper CRM. Your job is to gather person and company context, resolve custom fields, and return a deterministic discovery result.

Allowed MCP Tools (max once each)
- copper_fetch_by_email
- copper_search_companies
- copper_list_user
- custom_fields.list(person)

Execution
1) Parse the input payload.
2) Call custom_fields.list(person) → resolve IDs/options for Language and Website Preview URL.
3) Call copper_list_user → resolve owner_email.
4) If email is present → call copper_fetch_by_email.
5) If company_name is present → call copper_search_companies (exact, case-insensitive).
6) Return one JSON object containing:
   status, run_id, input, person_lookup, company_match,
   assignee_resolution, custom_fields, tool_usage, error.

Rules
- No write calls.
- Respect call caps (each tool max once).
- Report tool_usage counters exactly.
- On error, set status:"error" with details.
- Output only the JSON object, no prose.

An yet, the sub-agent receives absolutely nothing as input.

All tools and mcp names are correct across the board. I’ve iterated all day on these prompts and nothing is working..

Let’s take a closer look at the prompts. The orchestrator prompt looks good, and you’re passing the input data correctly. The sub-agent prompt also looks fine, and it’s expecting a JSON object as input.

One thing that stands out to me is that the sub agent is not receiving any input, despite the orchestrator passing the data correctly. This might be due to a small mistake in the syntax or formatting.

Can you try simplifying the input JSON object in the orchestrator prompt? Instead of using the complex syntax with JSON.stringify() and null values, try passing a simple JSON object with the required keys.

For example:
{
“name”: “{{ $json.name }}”,
“email”: “{{ $json.email }}”,
“phone”: “{{ $json.phone }}”,
“company_name”: “{{ $json.company }}”,
“profession”: “{{ $json.profession }}”,
“personal_url”: “{{ $json.personalURL }}”,
“language”: “{{ $json.language }}”,
“website_preview_url”: “{{ $json.previewURL }}”,
“demo_calendar_event”: “{{ $json.demoCalEvent }}”,
“owner_email”: “{{ $json.Owner }}”
}

See if that makes a difference. If not, we can try other things.

Also, have you checked the logs or error messages to see if there’s any hint about what’s going wrong? Sometimes, a small error message can give us a big clue about the issue you know.