Design topics as mini-agents that avoid duplicate messages

Note

This article describes features and behavior of topics with a conversational trigger in the standard harness. Learn how to access standard features in Access standard agents and agent flows.

This article focuses on design best practices to avoid duplicate messages. Duplicate messages come from context gaps, so design starts with understanding how context flows. Refer to the diagram in Context distribution in the standard harness to understand how the orchestration layer shares context with each component.

In the standard harness, the planner calls topics the same way it calls tools and agents. It reads each topic's description to decide when to use the topic, generates the topic's inputs from its active context and from the user, and reads the topic's outputs when the topic finishes. A topic with a clear description, well-defined inputs, and well-defined outputs behaves like a mini-agent in the plan: the orchestration layer gathers what the topic needs, the topic runs its logic, it returns what it produced, and the orchestration layer formats and communicates the answer to the user.

For the user, a mini-agent feels conversational. The user can talk naturally as the topic collects the inputs it needs, ask follow-up questions, and get rich responses back from the agent.

Tip

In the standard harness, keep deterministic logic inside the topic and leave user communication to the orchestration layer. Collect the values the topic needs as inputs before it runs, and return what it produced as outputs after it runs.

Name and describe the topic so the orchestration layer can route to it

The orchestration layer uses the topic name and description to route requests to the topic. Write both for the orchestration layer. Give the topic a clear, specific name that describes what it does.

Write the description in two parts. First, explain when to use the topic. Second, briefly explain what to do based on the topic's outputs, including how the orchestration layer should route requests and handle results after the topic runs. Don't describe the topic's internal screens or steps.

For example, use the following description for a topic that answers account balance requests and reports back with an answered output:

This topic handles account balance requests. 
If its answered output is true, the user has already received their response and it should not be answered again.

Collect inputs before the topic runs

Give the topic an input for each value it needs. The orchestration layer can gather those values from its active context, and from the user in a conversational way, before the topic runs. An input is delivered into a variable that the topic's logic then uses.

Use the input name, description, entity, and validation settings to help the orchestration layer fill an input accurately:

  • The input name tells the orchestration layer what's being collected, and it's used to form the question if the orchestration layer has to ask the user for the value. Name it for the value, not the mechanism. For example, name an input The user's request about... rather than OData filter, so the orchestration layer doesn't ask a user to write a query.

  • The input description is a prompt to the orchestration layer, not a label for the user. Use it to tell the orchestration layer how to interpret, constrain, or transform the value before the topic receives it. The orchestration layer can fill an input from the conversation, from an earlier output, or from user profile data. It can choose from a set of values, apply some constraints, and write queries based on schema info.

    Input descriptions can even instruct the orchestration layer to build a value in a specific format. For example, a topic that filters a list can take an input whose description tells the orchestration layer how to construct the filter from the user's request, including the available fields, the query syntax, and a few examples.

  • Entities set the allowed type and range for an input, so only valid values reach the topic's logic.

  • Advanced validation and conditional logic, including Power Fx, act as deterministic checks. They can prevent an input from being filled, or prevent the topic from acting, when a condition isn't met.

Deterministic input checks are as reliable as code, so business rules and compliance constraints are respected even when the rest of the plan is generated.

Keep the logic and the guardrails in the topic

Keep the topic's deterministic work inside the topic: the steps it runs, the calculations it makes, and the rules it enforces. The maker exercises control and applies crucial business logic that runs the same way every time, like a tool.

Return results as outputs, not messages to the user

When the topic finishes, return what it produced as outputs so the orchestration layer can use it and decide how to respond. Prefer this approach over having the topic message the user directly. A topic that writes to the user while the orchestration layer also answers is a common source of duplicate messages—the orchestration layer doesn't know that the topic already responded.

Important

A topic that returns no outputs is a red flag. If a topic answered the user, collected a value, or showed a card but returns nothing, the orchestration layer can't see what happened and might answer the same request again. This behavior is the most common cause of duplicate messages from topics.

The following tested outputs are strongly recommended as reliable patterns for context and communication. They apply whether the topic answers the user directly or returns all information to the orchestration layer to answer.

Output Description How to use
answered True if the user has already received a satisfactory answer to their request within this topic. Set it to true in the topic once it answers or shows the result. Refer to the example top-level instruction that follows, which ensures that the orchestration layer treats that part of the request as answered and doesn't repeat it.
choiceReceived True if the user has already made their selection within this topic. Set it to true once the user makes a selection, such as selecting a card button. The orchestration layer doesn't re-ask the question.
balanceValue The value the topic retrieved and already provided to the user. Set it to the important data the topic retrieved, and name the output for that data. The orchestration layer reuses it from context instead of fetching it again.
messageSummary A short summary of what was already shown to the user, to keep in context. Set it when a message carries information the plan needs later. The orchestration layer stays aware of what the user was told and doesn't repeat or contradict it.

Outputs alone are adequate for older models. Newer models also need a top-level instruction that tells the orchestration layer to check the outputs before answering.

This sample top-level instruction is a tested working example. Edit and customize it as needed.

Whenever any topic or agent is called, always look for the 'answered' boolean output before deciding what to reply. Topics and agents have their own channel of communication with the user. If 'answered' is true, always assume that the request has been answered appropriately using at least one of the output variables, and check which ones based on the output description. Do not give an awkward acknowledgement of the answered content. Only provide the unanswered outputs, and continue the conversation naturally with the next step.

The term channel doesn't refer to an integration channel. It's a prompting device that tells the orchestration layer the user might have already seen the answer through another component. Depending on the agent's model, it can be more effective to place a similar instruction in the agent's own instructions. Learn more in Design a robust top-level instruction to avoid repeated messages.

If the topic must show something the orchestration layer can't reproduce, such as an Adaptive Card, let the topic show it and return an answered-state output.

Tip

Learn more about duplicate messages and answered-state outputs in Design best practices to avoid duplicate messages. Learn how context moves between the orchestration layer and a topic in Context distribution in the standard harness.

Collect answers when a topic still needs user input

Some topics must ask a question or show a card, for example, to collect a choice with buttons. This design approach is valid. Keep in mind that an open question or card needs to be resolved when the user changes course before answering.

Before adding a question node, consider whether the value can be collected as an input instead. When keeping a question node, handle the case where the user asks for something else while the question is still open. Learn more in An open question or card returns after a different request was handled.

Example: Prevent an Adaptive Card selection from being re-asked

A topic asks the user to pick a category with an Adaptive Card:

Which category is your issue?

[Billing] [Technical] [Account]

The orchestration layer receives the question text through the conversation history, but not the fact that the card was shown or a button was selected by the user. After the user selects a button, the orchestration layer might re-ask the same question in plain text.

Design the topic to report its action to avoid this issue:

Output Type What to set it to
answered True/False True when the topic already showed the answer or prompt to the user.
choiceReceived True/False True when the user already made a choice selection.
selectedCategory Text Whenever a choice is received, this output contains the category the user chose.

Add an instruction to the topic description so the orchestration layer knows what a successful run means. Rely on the top-level instruction in Return results as outputs, not messages to the user so a newer model checks these outputs before asking again.

Best practices for topics in the standard harness

  • Give the topic a clear, specific name and a description that says when to use it (and optionally what to do after it runs).
  • Add an input for each value the topic needs, and write the input description as a prompt to the orchestration layer.
  • Name inputs for the value they hold, since the name forms the question in case the orchestration layer has to ask the user.
  • Keep deterministic logic and guardrails, such as entities, validation, and Power Fx, inside the topic.
  • Avoid messaging the user directly within the topic. Use a message node, question node, or Adaptive Card only when necessary.
  • Return results as outputs, including an answered-state output.