<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Portia AI</title>
    <description>The latest articles on DEV Community by Portia AI (@portia-ai).</description>
    <link>https://dev.to/portia-ai</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F10819%2F83e85f18-809a-4ab1-9670-38e4128a53a2.png</url>
      <title>DEV Community: Portia AI</title>
      <link>https://dev.to/portia-ai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/portia-ai"/>
    <language>en</language>
    <item>
      <title>Building agents with Controlled Autonomy using our new PlanBuilder interface</title>
      <dc:creator>Robbie Heywood</dc:creator>
      <pubDate>Wed, 10 Sep 2025 14:23:42 +0000</pubDate>
      <link>https://dev.to/portia-ai/building-agents-with-controlled-autonomy-using-our-new-planbuilder-interface-1oc1</link>
      <guid>https://dev.to/portia-ai/building-agents-with-controlled-autonomy-using-our-new-planbuilder-interface-1oc1</guid>
      <description>&lt;p&gt;Balancing autonomy and reliability is a key challenge faced by teams building agents (and getting it right is &lt;a href="https://fortune.com/2025/08/18/mit-report-95-percent-generative-ai-pilots-at-companies-failing-cfo/" rel="noopener noreferrer"&gt;notoriously difficult!&lt;/a&gt;). At Portia, we’ve built many production-ready agents with our design partners and today we’re excited to share our solution: &lt;strong&gt;Controlled Autonomy&lt;/strong&gt;. Controlled autonomy is the ability to control the level of autonomy of an agent at each step of an agentic plan. We implement this using our newly reshaped PlanBuilder interface to build agentic systems, and today we’re excited to be releasing it into our open-source SDK. We believe it’s a simple, elegant interface (without the boilerplate of many agentic frameworks) that is the best way to create powerful and reliable agentic systems - we can’t wait to see what you build with it!&lt;/p&gt;

&lt;p&gt;If you’re building agents, we’d love to hear from you! Check out our &lt;a href="https://github.com/portiaAI/portia-sdk-python" rel="noopener noreferrer"&gt;open-source SDK&lt;/a&gt; and let us know what you’re building on &lt;a href="https://discord.com/invite/DvAJz9ffaR" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;. We also love to see people getting involved with contributions in the repo - if you’d like to get started with this, check out our &lt;a href="https://github.com/portiaAI/portia-sdk-python/issues" rel="noopener noreferrer"&gt;open issues&lt;/a&gt; and let us know if you’d like to take one on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Straight into an example
&lt;/h2&gt;

&lt;p&gt;Our PlanBuilder interface is designed to feel intuitive and we find agents built with it are easy to follow, so let’s dive straight into an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;portia&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PlanBuilderV2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StepOutput&lt;/span&gt;

&lt;span class="n"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;PlanBuilderV2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Run this plan to process a refund request.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;refund_info&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Info of the customer refund request&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke_tool_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;step_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;read_refund_policy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file_reader_tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filename&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./refund_policy.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;single_tool_agent_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;step_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;read_refund_request&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Find the refund request email from &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;customer_email_address&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portia:google:gmail:search_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;llm_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;step_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llm_refund_review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Review the refund request against the refund policy. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
             &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Decide if the refund should be approved or rejected. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
             &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Return the decision in the format: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;APPROVED&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; or &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;REJECTED&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;StepOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;read_refund_policy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;StepOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;read_refund_request&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
        &lt;span class="n"&gt;output_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;RefundDecision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;function_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;record_refund_decision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;refund_decision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StepOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llm_refund_review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;react_agent_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Find the payment that the customer would like refunded.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portia:mcp:mcp.stripe.com:list_customers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portia:mcp:mcp.stripe.com:list_payment_intents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;StepOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;read_refund_request&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Full example includes more steps to actually process the refund etc.
&lt;/span&gt;    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above is a modified extract from our Stripe refund agent (full example &lt;a href="https://github.com/portiaAI/portia-agent-examples/blob/main/refund-agent-mcp/refund_agent_with_builder.py" rel="noopener noreferrer"&gt;here&lt;/a&gt;), setting up an agent that acts as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read in our company’s refund policy:&lt;/strong&gt; this uses a simple &lt;code&gt;invoke_tool_step&lt;/code&gt;, which means that the tool is directly invoked with the args specified with no LLM involvement. These steps are great when you need to use a tool (often to retrieve data) but don’t need the flexibility of an LLM to call the tool because the args you want to use are fixed (this generally makes them very fast too!).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read in the refund request from an email:&lt;/strong&gt; for this step, we want to flexibly find the email in the inbox based on the refund info that is passed into the agent. To do this, we use a &lt;code&gt;single_tool_agent&lt;/code&gt;, which is an LLM that calls a single tool once in order to achieve its task. In this case, the agent creates the inbox search query based on the refund info passed in to find the refund email.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Judge the refund request against the refund policy:&lt;/strong&gt; the &lt;code&gt;llm_step&lt;/code&gt; is relatively self-explanatory here - it uses your configured LLM to judge whether we should provide the refund based on the request and the policy. We use the &lt;code&gt;StepOutput&lt;/code&gt; object to feed in the results from the previous steps, and the &lt;code&gt;output_schema&lt;/code&gt; field allows us to return the decision as a &lt;a href="https://docs.pydantic.dev/latest/" rel="noopener noreferrer"&gt;pydantic&lt;/a&gt; object rather than as text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Record the refund decision:&lt;/strong&gt; we have a python function we use to record the decisions made - we can call this easily with a &lt;code&gt;function_step&lt;/code&gt; which allows directly calling python functions as part of the plan run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find the payment in Stripe:&lt;/strong&gt; finding a payment in Stripe requires using several tools from Stripe’s remote MCP server (which is &lt;a href="https://docs.portialabs.ai/portia-tools/remote-mcp/stripe" rel="noopener noreferrer"&gt;easily enabled in your Portia account&lt;/a&gt;). Therefore, we set up a &lt;a href="https://www.promptingguide.ai/techniques/react" rel="noopener noreferrer"&gt;ReAct&lt;/a&gt; agent with the required tools and it can intelligently chain the required Stripe tools together in order to find the payment. As a bonus, Portia uses MCP Auth by default so these tool calls will be fully authenticated.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Controlled Autonomy
&lt;/h2&gt;

&lt;p&gt;As demonstrated in the above example, the power of &lt;code&gt;PlanBuilderV2&lt;/code&gt; comes from the fact you can easily connect and combine different step types, depending on your situation and requirements. This allows you to control the amount of autonomy your system has at each point in its execution, with some steps (e.g. &lt;code&gt;react_agent_step&lt;/code&gt;) making use of language models with high autonomy while others are carefully controlled and constrained (e.g. &lt;code&gt;invoke_tool_step&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0kla772rncam4irxkmdu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0kla772rncam4irxkmdu.png" alt="Autonomy of PlanBuilder steps" width="716" height="223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From our experience, it is this ‘&lt;strong&gt;controlled autonomy&lt;/strong&gt;’ that is the key to getting agents to execute reliably, which allows us to move from exciting prototypes into real, production agents. Often, prototypes are built with ‘full autonomy’, giving something like a ReAct agent access to all tools and letting it loose on a task. This approach is possible with our plan builder and can work well in some situations, but in other situations (particularly for more complex tasks) it can lead to agents that are unreliable. We’ve found that tasks often need to be broken down and structured into manageable sub-tasks, with the autonomy for each sub-task controlled, for them to be done reliably. For example, we often see research and retrieval steps in a system being done with high autonomy ReAct agent steps because they generally use read-only tools that don’t affect other systems. Then, when it comes to the agent taking actions, these steps are done with zero or low autonomy so they can be done in a more controlled manner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple Control structures
&lt;/h2&gt;

&lt;p&gt;Extending the above example, our &lt;code&gt;PlanBuilderV2&lt;/code&gt; also provides familiar control structures that you can use when breaking down tasks for your agentic system. This gives you full control to ensure that the task is approached in a reliable way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Conditional steps (if, else if, else)
&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;if_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;review&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;review&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;REJECTED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llm_review_decision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StepOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llm_refund_review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;function_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;handle_rejected_refund&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proposed_refund&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StepOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proposed_refund&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Loops - here we use .loop(over=...), but there are also alternatives for
#         .loop(while=...) and .loop(do_while=...)
&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;over&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;StepOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;step_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Loop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;function_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StepOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Loop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end_loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fun fact: We went with &lt;code&gt;.if_()&lt;/code&gt; rather than &lt;code&gt;.if()&lt;/code&gt; (note the underscore) because &lt;code&gt;if&lt;/code&gt; is a restricted keyword in python&lt;/p&gt;

&lt;h2&gt;
  
  
  Human - Agent interface
&lt;/h2&gt;

&lt;p&gt;Another aspect that is vital towards getting an agent into production is the ability to seamlessly pass control between agents and humans. While we build trust in agentic systems, there are often key steps that require verification or input from humans. Our PlanBuilder interface allows both to be handled easily, using Portia’s &lt;a href="https://docs.portialabs.ai/understand-clarifications" rel="noopener noreferrer"&gt;clarification system&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Ensure a human approves any refunds our agent gives out
&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Are you happy to proceed with the following proposed refund: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;StepOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;proposed_refund&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Allow your end user to provide input into how the agent runs
&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How would you like your refund?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Return to purchase card&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gift card&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Controlling your agent with code
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;function_step&lt;/code&gt; demonstrated earlier is a key addition to &lt;code&gt;PlanBuilderV2&lt;/code&gt;. In many agentic systems, all tool and function calls go through a language model, which can be slow and also can reduce reliability. With &lt;code&gt;function_step&lt;/code&gt;, the function is called with the provided &lt;code&gt;args&lt;/code&gt; at that point in the chain with full reliability. We’ve seen several use-case for this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guardrails:&lt;/strong&gt; where deterministic, reliable code checks are used to verify agent behaviour (see example below)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data manipulation:&lt;/strong&gt; when you want to do a simple data transformation in order to link tools together, but you don’t want to pay the latency penalty of an extra LLM call to do the transformation, you can instead do the transformation in code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plug in existing functions:&lt;/strong&gt; when you’ve already got the functionality you need in code, you can use a function_step to easily plug that into your agent.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Add a guardrail to prevent our agent giving our large refunds
&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;function_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;step_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reject_payments_above_limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;function&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;reject_payments_above_limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proposed_refund&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StepOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proposed_refund&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payment_limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What’s next?
&lt;/h2&gt;

&lt;p&gt;We’ve really enjoyed building agents with &lt;code&gt;PlanBuilderV2&lt;/code&gt; and are excited to share it more widely. We find that it complements our planning agent nicely: our planning agent can be used to dynamically create plans from natural language when that is needed for your use-case, while the plan builder can be used if you want to more carefully control the steps your agentic system takes with code. &lt;/p&gt;

&lt;p&gt;We’ve also got more features coming up over the next few weeks that will continue to make the plan builder interface even more powerful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parallelism:&lt;/strong&gt; run steps in parallel with &lt;code&gt;.parallel()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic caching:&lt;/strong&gt; add &lt;code&gt;cache=True&lt;/code&gt; to steps to automatically cache results - this is a game-changer when you want to iterate on later steps in a plan without having to fully re-run the plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step error handler:&lt;/strong&gt; specify &lt;code&gt;.on_error()&lt;/code&gt; after a step to attach an error handler to it, &lt;code&gt;.retry()&lt;/code&gt; to allow retries of steps or use &lt;code&gt;exit_step()&lt;/code&gt; to gracefully exit a plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linked plans:&lt;/strong&gt; link plans together by referring to outputs from previous plan runs.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;PlanBuilderV2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Run this plan to process a refund request.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. Run subsequent steps in parallel
&lt;/span&gt;    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke_tool_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file_reader_tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filename&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./refund_policy.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="c1"&gt;# 2. Add automatic caching to a step
&lt;/span&gt;        &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# 3. Add error handling to a step
&lt;/span&gt;    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;react_agent_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="c1"&gt;# 4. Link plans together by referring to outputs from a previous run
&lt;/span&gt;        &lt;span class="c1"&gt;# Here, we could have a previous agent that determines which       customer refunds to process
&lt;/span&gt;        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Read the refund request from my inbox from &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;PlanRunOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previous_run&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portia:google:gmail:search_email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Resume series execution
&lt;/span&gt;    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;series&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shout out to &lt;a href="https://github.com/gaurava05" rel="noopener noreferrer"&gt;gaurava05&lt;/a&gt; for adding &lt;code&gt;ExitStep&lt;/code&gt; as an open-source contribution in &lt;a href="https://github.com/portiaAI/portia-sdk-python/pull/742" rel="noopener noreferrer"&gt;this PR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So give our new PlanBuilder a try and let us know how you get on - we can’t wait to see what you build! 🚀&lt;/p&gt;

&lt;p&gt;For more details on &lt;code&gt;PlanBuilderV2&lt;/code&gt;, check out our &lt;a href="https://docs.portialabs.ai/build-plan" rel="noopener noreferrer"&gt;docs&lt;/a&gt;, our &lt;a href="https://github.com/portiaAI/portia-sdk-python/blob/main/example_builder.py" rel="noopener noreferrer"&gt;example plan&lt;/a&gt; or the &lt;a href="https://github.com/portiaAI/portia-agent-examples/blob/main/refund-agent-mcp/refund_agent_with_builder.py" rel="noopener noreferrer"&gt;full stripe refund example&lt;/a&gt;. You can also join our &lt;a href="https://discord.com/invite/DvAJz9ffaR" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; to hear future updates. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>From Hackathon Idea to Life-Saving Workflow: The Story of the DCRCA Agent</title>
      <dc:creator>Vincenzo Bianco</dc:creator>
      <pubDate>Mon, 08 Sep 2025 09:23:38 +0000</pubDate>
      <link>https://dev.to/portia-ai/from-hackathon-idea-to-life-saving-workflow-the-story-of-the-dcrca-agent-54cc</link>
      <guid>https://dev.to/portia-ai/from-hackathon-idea-to-life-saving-workflow-the-story-of-the-dcrca-agent-54cc</guid>
      <description>&lt;h3&gt;
  
  
  The AI AgentHack Hackathon
&lt;/h3&gt;

&lt;p&gt;Last week, we ran AI AgentHack, a hackathon where more than 3,000 developers built creative agentic projects using &lt;a href="https://github.com/portiaAI/portia-sdk-python" rel="noopener noreferrer"&gt;Portia&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Picking winners wasn’t easy, but one project stood out: Team Dark Mode’s DCRCA Agent (Disaster Chaos Response Coordination AI).&lt;/p&gt;

&lt;p&gt;The DCRCA Agent helps emergency teams cut through the noise. It scans live news and social feeds, pulls out the key details, and maps emergencies by priority so responders know exactly where to act first.&lt;/p&gt;

&lt;p&gt;We were extremely impressed to see this cool Portia use case and, more importantly, an application with great potential for societal impact!&lt;/p&gt;

&lt;p&gt;Below is a deep dive into how the team built this using Portia.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhk5f7q6sr2r8ikey58i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frhk5f7q6sr2r8ikey58i.png" alt=" " width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How The DCRCA Agent Is Built
&lt;/h3&gt;

&lt;p&gt;The DCRCA Agent is wired together with &lt;strong&gt;PlanBuilderV2&lt;/strong&gt;, where the workflow is laid out step by step: pulling raw data from news feeds, parsing and prioritizing it with LLM steps, routing through a human approval checkpoint, and finally dispatching updates over email and Slack. Each stage has clear inputs and outputs, making the whole flow transparent.&lt;/p&gt;

&lt;p&gt;A key design choice was the &lt;strong&gt;separation between reasoning and tool calls&lt;/strong&gt;. Reasoning tasks (like structuring raw data or scoring emergencies) live inside &lt;code&gt;.llm_step()&lt;/code&gt;, while external services such as Google Search and Gmail are called through &lt;code&gt;.invoke_tool_step()&lt;/code&gt;. This separation keeps debugging and maintenance straightforward.&lt;/p&gt;

&lt;p&gt;They also used &lt;strong&gt;custom Python functions for oversight&lt;/strong&gt; with &lt;code&gt;.function_step()&lt;/code&gt;. These functions handled approval checks and message formatting, showing how Portia makes human-in-the-loop workflows natural instead of forcing full automation.&lt;/p&gt;

&lt;p&gt;Finally, because every step exposes &lt;strong&gt;structured outputs at runtime&lt;/strong&gt;, the agent can surface both intermediate results (like “Slack message sent ✅”) and the overall summary of actions — giving the team visibility into exactly what happened.&lt;/p&gt;

&lt;p&gt;We’re thankful to Team Dark Mode and all other hackathon participants for helping us prove that Portia isn’t just for tinkering—it can drive real, high‑stakes workflows. By combining off‑the‑shelf tools, LLM reasoning and human oversight in a single plan, they built something useful and understandable. &lt;/p&gt;

&lt;p&gt;It’s exciting to imagine what other novel agentic ideas the community will bring to life next!&lt;/p&gt;

&lt;p&gt;If you want to try building your own agentic workflow, check out our &lt;a href="https://github.com/portiaAI/portia-sdk-python" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>5 tools we wish were on the Awesome AI Tools list</title>
      <dc:creator>Mounir Mouawad</dc:creator>
      <pubDate>Fri, 15 Aug 2025 12:15:07 +0000</pubDate>
      <link>https://dev.to/portia-ai/5-tools-we-wish-were-on-the-awesome-ai-tools-list-576m</link>
      <guid>https://dev.to/portia-ai/5-tools-we-wish-were-on-the-awesome-ai-tools-list-576m</guid>
      <description>&lt;p&gt;We’re big fans of the Awesome AI tools &lt;a href="https://github.com/mahseema/awesome-ai-tools?tab=readme-ov-file" rel="noopener noreferrer"&gt;list&lt;/a&gt; and we all use it to discover new AI tools over at &lt;a href="https://github.com/portiaAI/portia-sdk-python" rel="noopener noreferrer"&gt;Portia AI&lt;/a&gt;. My latest and favourite find is &lt;a href="http://getmerlin.in" rel="noopener noreferrer"&gt;Merlin&lt;/a&gt;: A Chrome extension that allows me to ask “how to” questions on any app rather than flipping over to ChatGPT or Claude to ask.&lt;/p&gt;

&lt;p&gt;Here are five tools we use a lot and wish were on the Awesome AI Tools list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://textual.textualize.io/" rel="noopener noreferrer"&gt;Textual&lt;/a&gt; – We love spicing up our terminal interface for using the Portia SDK and even non-technical customers love it when I run demos from the terminal now. We all have our favourite terminal flavour of it – I made mine with Atari retro vibes 🕹️holler if you’re using Portia and want the code for it!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://mistral.ai/solutions/document-ai" rel="noopener noreferrer"&gt;Mistral OCR&lt;/a&gt; – We think it’s the best balance of cost, speed and performance for OCR on the market right now. We also admittedly have soft spot for our neighbours across the English Channel over in La France 🥐.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://visily.ai/" rel="noopener noreferrer"&gt;Visily&lt;/a&gt; – Figma for non-designers, it’s my go-to when brainstorming early UX mocks with front-end engineers and UX designers. I especially love the ability to turn any screenshot into a wireframe because I can bring inspirations to life with some tweaks super quickly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/souzatharsis/podcastfy" rel="noopener noreferrer"&gt;Podcastfy&lt;/a&gt; – I can’t say for sure why they skipped the “i” in their name but we love that they are an open source and equally powerful alternative to NotebookLM. One of our engineers built a bite-sized AI news podcast that I listen to during my commute daily. You can recreate it &lt;a href="https://github.com/portiaAI/portia-agent-examples/tree/main/ai-research-agent" rel="noopener noreferrer"&gt;here&lt;/a&gt; using Portia SDK or get the daily podcast on our &lt;a href="https://discord.gg/DvAJz9ffaR" rel="noopener noreferrer"&gt;Discord server&lt;/a&gt;’s &lt;strong&gt;#ai-news&lt;/strong&gt; channel.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://openrouter.ai/" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt; - We love OpenRouter because it allows you to easily try out new models and load balance between models. We actually got an open source &lt;a href="https://github.com/portiaAI/portia-sdk-python/pull/640" rel="noopener noreferrer"&gt;contribution&lt;/a&gt; for this one recently, so we should be supporting it ❤️&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>ai</category>
    </item>
    <item>
      <title>Introducing SteelThread: Evals &amp; Observability for Reliable Agents</title>
      <dc:creator>Vincenzo Bianco</dc:creator>
      <pubDate>Thu, 14 Aug 2025 15:44:11 +0000</pubDate>
      <link>https://dev.to/portia-ai/introducing-steelthread-evals-observability-for-reliable-agents-1bc7</link>
      <guid>https://dev.to/portia-ai/introducing-steelthread-evals-observability-for-reliable-agents-1bc7</guid>
      <description>&lt;p&gt;We’ve spent a lot of time internally running evals for our own agents. If you care about reliability in agentic systems, you know why this matters — models drift, prompts change, third party MCP tools get updated. A small change in one place can cause unexpected behavior somewhere else.&lt;/p&gt;

&lt;p&gt;That’s why we’re excited to share something we’ve been using ourselves for months: SteelThread, our evaluation framework built on top of Portia Cloud.&lt;/p&gt;

&lt;p&gt;You can try if for free on &lt;a href="https://app.portialabs.ai/dashboard" rel="noopener noreferrer"&gt;Portia!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While building our own automations on top of Portia, we realised it was an absolute joy to run evals with owing to two of its core features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First, every agent run is captured in a structured state object called a &lt;code&gt;PlanRunState&lt;/code&gt; — steps, tool calls, arguments, outputs. That makes very targeted evaluators trivial to write, be it deterministic or LLM-as-Judge ones e.g. you can count plan steps, validate the behaviour of a specific tool, review the tone in final summary etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Second, we use Portia Cloud to store our agent runs. Whenever we manage to produce a multi-agent plan outcome that is desirable (or undesirable) e.g. during agent development, we can take the inputs and outputs of that agent run (query, plan, plan run) and instantly turn them into an Eval dataset. Since we built SteelThread, we haven’t actually needed to manually curate and build eval datasets from scratch anymore.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before SteelThread, we still felt the pain that many teams do. Creating and maintaining curated datasets was tedious. Balancing deterministic checks with LLM-as-judge evals was tricky. And running evals against real APIs often meant dealing with authentication, rate limits, or unintended side effects — so we’d spend hours stubbing tools just to test safely.&lt;/p&gt;

&lt;p&gt;SteelThread wraps all of this into a single workflow inside Portia Cloud. It gives you two ways to keep your agents in check: Streams, which spot changes in behavior in real time, and Evals which let you run regression tests against a ground truth dataset. Both Streams and Evals allow you to combine deterministic and LLM-as-judge evaluators. You can write your own evaluators but SteelThread comes with a generous helping of off-the-shelf ones for you to use as well.&lt;/p&gt;

&lt;p&gt;Here is an example flow where we add a production agent run to an Eval dataset.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/a7hDo0l_bqw"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Observability and evals are essential for building reliable agentic systems, and SteelThread just makes them easier. Paired with the Portia development SDK, it’s a powerful combo: build structured, debuggable agents, monitor them in production, and turn any incident into a regression test instantly.&lt;/p&gt;

&lt;p&gt;If you want to try it, head over to &lt;a href="https://app.portialabs.ai/dashboard" rel="noopener noreferrer"&gt;Portia Dashboard&lt;/a&gt; or check out our &lt;a href="https://github.com/portiaAI/steel_thread" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>programming</category>
    </item>
    <item>
      <title>Portia AI: Initial Thoughts on GPT-5</title>
      <dc:creator>Robbie Heywood</dc:creator>
      <pubDate>Mon, 11 Aug 2025 14:57:35 +0000</pubDate>
      <link>https://dev.to/portia-ai/portia-ai-initial-thoughts-on-gpt-5-4ik</link>
      <guid>https://dev.to/portia-ai/portia-ai-initial-thoughts-on-gpt-5-4ik</guid>
      <description>&lt;p&gt;At Portia AI, we’ve been playing around with GPT-5 since it was released a few days ago and we’re excited to announce it will be available to SDK users in tomorrow’s SDK release 🎉&lt;/p&gt;

&lt;p&gt;After playing with it for a bit, it definitely feels an incremental improvement rather than a step-change (despite my LinkedIn feed being full of people pronouncing it ‘game-changing!). To pick out some specific aspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Equivalent Accuracy&lt;/strong&gt;: on our benchmarks, GPT5’s performance is equal to the existing top model, so this is an incremental improvement (if any).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handles complex tools&lt;/strong&gt;: GPT-5 is definitely keener to use tools. We’re still playing around with this, but it does seem like it can handle (and prefers) broader, more complex tools. This is exciting - it should make it easier to build more powerful agents, but also means a re-think of the tools you’re using.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow&lt;/strong&gt;: With the default parameters, the model is seriously slow - generally 5-10x slower across each of our benchmarks. This makes tuning the new &lt;code&gt;reasoning_effort&lt;/code&gt; and &lt;code&gt;verbosity&lt;/code&gt; parameters important.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I actually miss the model picker!&lt;/strong&gt; With the model picker gone, you’re left to rely on the fuzzier world of natural language (and the new &lt;code&gt;reasoning_effort&lt;/code&gt; and &lt;code&gt;verbosity&lt;/code&gt; parameters) to control the model. This is tricky enough that OpenAI have released a new &lt;a href="https://cookbook.openai.com/examples/gpt-5/gpt-5_prompting_guide" rel="noopener noreferrer"&gt;prompt guide&lt;/a&gt; and &lt;a href="https://platform.openai.com/chat/edit?models=gpt-5&amp;amp;optimize=true" rel="noopener noreferrer"&gt;prompt optimiser&lt;/a&gt;. I think there will be real changes when there are models that you don’t feel you need to control in this way - but GPT-5 isn’t there yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solid pricing&lt;/strong&gt;: While it is a little more token-hungry on our benchmarks (10-20% more tokens in our benchmarks), at half the price of GPT-4o / 4.1 / o3, it is a good price for the level of intelligence (a &lt;a href="https://www.latent.space/p/gpt5-router" rel="noopener noreferrer"&gt;great article&lt;/a&gt; on this from Latent Space).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reasonable context window&lt;/strong&gt;: At 256k tokens, the context window is fine - but we’ve had several use-cases that use GPT-4.1 / Gemini’s 1m token windows, so we’d been hoping for more...&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coding&lt;/strong&gt;: In Cursor, I’ve found GPT-5 a bit difficult to work with - it’s slow and often over-thinks problems. I’ve moved back to claude-4, though I do use GPT-5 when looking to one-shot something rather than working with the model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are also two aspects that we haven’t dug into yet, but I’m really looking forward to putting them through their paces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool Preambles&lt;/strong&gt;: GPT 5 has been trained to give progress updates in ‘tool preamble’ messages. It’s often really important to keep the user informed as an agent progresses, which can be difficult if the model is being used as a black box. I haven’t seen much talk about this as a feature, but I think it has the potential to be incredibly useful for agent builders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replanning&lt;/strong&gt;: In the past, we’ve got ourselves stuck in loops (particularly with OpenAI models) where the model keeps trying the same thing even when it doesn’t work. GPT-5 is supposed to handle these cases that require a replan much better - it’ll be interesting to dive into this more and see if that’s the case.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a summary, this is still an incremental improvement (if any). It’s sad to see it &lt;a href="https://kieranhealy.org/blog/archives/2025/08/07/blueberry-hill/" rel="noopener noreferrer"&gt;still can’t count the letters in various fruit&lt;/a&gt; and I’m still mostly using claude-4 in cursor.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How I Built an AI Agent That Turns Daily AI News Into a Commute-Sized Podcast</title>
      <dc:creator>Robbie Heywood</dc:creator>
      <pubDate>Fri, 01 Aug 2025 14:54:27 +0000</pubDate>
      <link>https://dev.to/portia-ai/how-i-built-an-ai-agent-that-turns-daily-ai-news-into-a-commute-sized-podcast-44pg</link>
      <guid>https://dev.to/portia-ai/how-i-built-an-ai-agent-that-turns-daily-ai-news-into-a-commute-sized-podcast-44pg</guid>
      <description>&lt;p&gt;The AI landscape moves at breakneck speed. New models, research papers, funding announcements, and product launches happen daily. As someone working in AI, staying current isn't just helpful—it's essential. But when you're heads-down building features and shipping products, it's tough to find the time to stay on top of all the latest developments.&lt;/p&gt;

&lt;p&gt;That's exactly the challenge we faced at Portia AI. The solution? An AI agent that helps us make the most of the 5-minute stroll our team makes each afternoon to Kings Cross on their way home.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52iisrhxbg07vq5bnwk4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52iisrhxbg07vq5bnwk4.jpg" alt="Alt text" width="800" height="451"&gt;&lt;/a&gt;&lt;br&gt;I’m sure Harry would have spent his commute back from Kings cross listening to our AI podcast too...
  &lt;/p&gt;

&lt;h2&gt;
  
  
  Building AI News Into a Routine
&lt;/h2&gt;

&lt;p&gt;Working in AI means being subscribed to information from multiple sources. The traditional approach of manually checking news sites, Reddit, Twitter, and newsletters was tedious and time-consuming, while important developments could take time to circulate through the team.&lt;/p&gt;

&lt;p&gt;During one of our regular work hack sessions, inspired by NotebookLM's podcast feature, I decided to tackle this problem by building an AI agent that creates daily short AI news podcasts. Here's how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Subscribes to multiple AI news sources throughout the day&lt;/li&gt;
&lt;li&gt;Identifies the most significant developments&lt;/li&gt;
&lt;li&gt;Synthesizes the information into a concise narrative&lt;/li&gt;
&lt;li&gt;Generates a 2-3 minute podcast episode using the fantastic &lt;a href="https://github.com/souzatharsis/podcastfy" rel="noopener noreferrer"&gt;Podcastfy&lt;/a&gt; library&lt;/li&gt;
&lt;li&gt;Provides curated links for deeper investigation&lt;/li&gt;
&lt;li&gt;Shares the podcast and links on Slack and Discord&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We run the agent in the afternoon, so the podcast is available before people's evening commute. This timing allows people to easily integrate the updates into their daily routine. We've also found that the curated links are particularly valuable when there's a topic that's especially relevant to someone, allowing them to dig deeper into the details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Involved
&lt;/h2&gt;

&lt;p&gt;We know that staying abreast of the latest developments is a difficulty lots of teams face, so we've made these news snippets available on our public &lt;a href="https://discord.com/invite/DvAJz9ffaR" rel="noopener noreferrer"&gt;Discord server&lt;/a&gt;. Come and check it out if it sounds like something that could be useful.&lt;/p&gt;

&lt;p&gt;The code is open sourced in our &lt;a href="https://github.com/portiaAI/portia-agent-examples" rel="noopener noreferrer"&gt;agent examples repo&lt;/a&gt; if you're keen to see exactly how it works or build something similar for your own team. I think it’s a nice example of how Portia’s open-source &lt;a href="https://github.com/portiaAI/portia-sdk-python" rel="noopener noreferrer"&gt;agent SDK&lt;/a&gt; makes agents incredibly easy to build. With the agent framework handling much of the complex orchestration between services and APIs, the code ends up being not much more than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;plan_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;Task specification&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DefaultToolRegistry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;PodcastTool&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;span class="n"&gt;portia&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Portia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;portia&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan_prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hope this helps others stay on top of the fast-moving AI world! Enjoy!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>hackathon</category>
    </item>
    <item>
      <title>Building AI Agents: Choose your fighter</title>
      <dc:creator>Katerina</dc:creator>
      <pubDate>Wed, 30 Jul 2025 20:32:48 +0000</pubDate>
      <link>https://dev.to/portia-ai/building-ai-agents-choose-your-fighter-47mm</link>
      <guid>https://dev.to/portia-ai/building-ai-agents-choose-your-fighter-47mm</guid>
      <description>&lt;p&gt;&lt;em&gt;If you want to build agents with Portia AI you can &lt;a href="https://github.com/portiaAI/portia-sdk-python" rel="noopener noreferrer"&gt;try our SDK for free on Github&lt;/a&gt;. Stars welcome!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are a lot of AI Agent builders out there considering the term ‘AI Agent SDKs’ indexed at 0 on Google Trends only 6 months ago. &lt;/p&gt;

&lt;p&gt;At first glance, it’s hard to tell them apart and figure out which will be right for your use case. Everyone uses the same words to describe what they’ve created in an attempt to make the products seem ‘right’ for as many people as possible.&lt;/p&gt;

&lt;p&gt;After a few weeks of researching the market at Portia AI, I realised it’d be useful to write something high level about the different Agent Builders out there and  how you can choose between them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F324xjwa4ert8p3tq5bj7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F324xjwa4ert8p3tq5bj7.png" alt="A cartoon in the style of the street fighter game with two characters fighting in a high tech future" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  For when you want to automate something quickly and without writing much code
&lt;/h2&gt;

&lt;p&gt;In this bucket are n8n, MindStudio and now CrewAI. These products are focussed on business users as well as engineers. They’re designed to be easy to use and can be set up using visual workflows instead of code. They trade off simplicity against control so they’re less flexible and you don’t have the same fine grained options as you would get with a code first product. Great for prototyping and trying things out, or if you’ve got a very simple production use case. if you were building a KYC agent for a bank you would want something where you could build in more oversight. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.mindstudio.ai/" rel="noopener noreferrer"&gt;MindStudio&lt;/a&gt;&lt;/strong&gt; is designed for getting AI agents up and running using templates and without code. It’s useful when you want to move quickly and is integrated with the major LLM providers. It also has built in tools you can use to equip your agents without much effort. It’s most useful for straightforward AI workflows you can prototype in a few minutes, for example an agent that summarises what it sees on the page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://n8n.io/" rel="noopener noreferrer"&gt;n8n&lt;/a&gt;&lt;/strong&gt; feels more like a Zapier. It’s most powerful when used for connecting things up to automate your existing business process. The visual builder is the entry point though it can be extended with code. It supports a very large library of LLMs, databases, APIs and tools out of the box which means you don’t need to build your own integrations for  your current processes. It’s most useful for the kind of simple automations you would have used Zapier for previously, for example, send me an email when someone submits our lead form and add a record to our CRM. lt’s not well set up for long running or complex autonomous tasks which need multi step reasoning or memory management. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.crewai.com/" rel="noopener noreferrer"&gt;Crew AI&lt;/a&gt;&lt;/strong&gt; used to be known for their developer SDK product but they’ve now built tooling for no-code workflows too. They combine a visual, no and low code interface with a developer experience. You can create multi-agent systems, using a lead AI to figure out what’s needed and in what order, then delegate to its ‘crew’ of specialist agents. This could be unpredictable as you’re relying on the lead agent to keep everything under control. They’ve now introduced the concept of ‘flows’ to help offset that. You can define the exact steps the agents need to take and in what order using conditional logic, loops and state management. You lose some of the autonomy and flexibility but it increases the reliability and makes your agents easier to audit.  I’m interested to see how they prioritise changes to the no code experience and solutions driven approach against building out the developer product. Their website now emphasises the former. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  For when you’re looking for more control over your agents for critical use cases
&lt;/h2&gt;

&lt;p&gt;These are opinionated libraries or SDKs for programmers to use to build their agents. They’re code first frameworks without a no code option. It’s more work to get started than the out of the box and modular solutions  but you’ll gain fine grained control and transparency. If you’re building multi-agent systems then this is the level of oversight and control you’ll want. If you’re not then you could  give one of the solutions above a try first before graduating onto one of these SDKs if they don’t give oversight you need. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.langchain.com/" rel="noopener noreferrer"&gt;LangChain / LangGraph&lt;/a&gt;:&lt;/strong&gt; LangChain gives you building blocks, think prompts, memory, and tool wrappers, so you can roll your own agent from scratch. It’s one of the most popular tools at this level of abstraction as it supports basically every model, tool, and database you might want to use. LangChain uses an architecture that lets you chain LLM calls and let the model decide what to do next. It’s very adaptable but as with any AI agent, dialling up autonomy means you lose some of the control. It can be hard to debug where things went off the rails. LangGraph, is built on top of LangChain, introduces a more declarative, stateful graph architecture. You define the flow of agent steps explicitly, gaining better visibility and control. It also supports human checkpoints, so you can pause for manual approval. It’s open source and free to try.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.portialabs.ai/" rel="noopener noreferrer"&gt;Portia AI&lt;/a&gt;:&lt;/strong&gt; Also open source and developer focussed. It’s our product and we built it to be a production‑grade agent orchestration framework. It’s more opinionated than the other tools mentioned in this section as we build in how wt think a production agent should be put together. You can go back and forth with a planning agent to create a structured plan. It then breaks it into  multi‑step workflows and the plan is then immutable. The execution agents then carry out the plan. You can also add in ‘escalation points’ out of the box, where the agent needs to check with a human if certain conditions are hit. There’s also built-in authentication,  integrated tools like Slack, GitHub, Zendesk, Google and 1000+ tools you can access via MCP servers. It’s aimed at industries where predictability, auditability, and security matter and where you want guidance on how a production grade agent should work. It’s higher level than Lang Graph and Pydantic. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://ai.pydantic.dev/" rel="noopener noreferrer"&gt;Pydantic AI&lt;/a&gt;:&lt;/strong&gt; Pydantic give you the tools to put a production-grade agent together how you want but it’s complex. It’s built for engineers who have strong opinions about how they want their agent architecture to look. You define agents as Pydantic models with structured input and output types, system prompts, and function‑calling tools. It's model‑agnostic and integrates tightly with Pydantic Logfire for real‑time debugging and observability. Agents are reusable, type‑safe, and can use dependency injection for context/tools. Streaming responses are validated continuously for correctness. You still design the workflow, prompt templates, tool logic, and orchestration yourself. However the framework abstracts away validation, structure, and runtime safety, making it easier to build production‑grade agents in Python. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  For when you want to lean into the platforms you already use and wire everything up
&lt;/h2&gt;

&lt;p&gt;These frameworks provide scaffolding for creating AI agents but their main benefit is for if you’re already deep in the Google, OpenAI and Amazon ecosystem and don’t want to add a new provider to your stack.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://openai.github.io/openai-agents-python/" rel="noopener noreferrer"&gt;OpenAI Agent SDK&lt;/a&gt;&lt;/strong&gt;: it gives you scaffolding by creating some lightweight abstractions like the agent loop, tool registration, handoffs to other agents, and tracing. It’s then up to you to create the agents, define the tools and guardrails to put this into production. The built in traceability is super useful and the SDK is interoperable with any LLM under the hood. This is a good option if you want something lightweight and code first for building your first agent. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://google.github.io/adk-docs/" rel="noopener noreferrer"&gt;Google’s ADK&lt;/a&gt;&lt;/strong&gt; (agent developer kit) is another open-source, code-first toolkit. It helps developers build agentic systems that use dynamic reasoning or enforce some structure. It provides different built in agent types (loop, parallel, LLM driven) for you to design your work flows around based on how much control  you need. It has built in tools to Google services if you’re automating things within that ecosystem. The memory handling out of the box is also super helpful. You’ll still need to figure out the agent logic, prompts and connection everything up. But it’s flexible and abstracts out some of the more complex areas of agent design. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://aws.amazon.com/bedrock/" rel="noopener noreferrer"&gt;Amazon Bedrock &lt;/a&gt;&lt;/strong&gt;is another production‑ready agent toolkit. It has a  visual builder and modular services like Memory, Gateway, and Code Interpreter. You can connect agents to your APIs, Lambda functions, and knowledge bases that already run on Amazon out of the box. It also now supports memory retention without you having to implement it yourself. Like with the Google ADK, you’ll still need to design the agent logic, orchestration, prompts, and integration flow but Amazon AgentCore helps manage key infrastructure: session isolation, observability, identity, and tool access. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Which one should I pick?
&lt;/h2&gt;

&lt;p&gt;As you can tell from this post, there’s no ‘best’ product out there. You need to figure out the trade-offs you want to make when building your agentic systems. Here are some questions that can help you make that choice: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who’s building the agents, developers or another business function?&lt;/li&gt;
&lt;li&gt;How much control do you need over your agent? &lt;/li&gt;
&lt;li&gt;Do you need one agent or many? &lt;/li&gt;
&lt;li&gt;Are you creating something quick to test or a crucial piece of infrastructure?&lt;/li&gt;
&lt;li&gt;Do you have strong opinions about the way agents should be structured or are you looking for guidance? &lt;/li&gt;
&lt;li&gt;How much do you want or need to build yourself?&lt;/li&gt;
&lt;li&gt;How much are you locked into a particular vendor?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;New frameworks and tools are coming out all the time so we expect to see these tools start to look more similar over time. Also as we’ve seen with the launch of the MCP standard and A2A emerging, things are likely going to get more interoperable over time, not less as standards are established. The frameworks aren’t mutually exclusive and you can choose the right approach based on where you are in your development lifecycle and your answers to the questions above. The no code tools can deliver quick prototypes but are limited. If you need flexibility and control then the mid-level opinionated frameworks are a strong choice. If you have a big developer team and a very custom set up then starting with the base provider SDKs could give you the fine grained control you need. Good luck!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>programming</category>
    </item>
    <item>
      <title>Build an AI Agent And Win 💸</title>
      <dc:creator>Zevi Reinitz</dc:creator>
      <pubDate>Fri, 18 Jul 2025 20:23:30 +0000</pubDate>
      <link>https://dev.to/portia-ai/build-an-ai-agent-and-win-3p3c</link>
      <guid>https://dev.to/portia-ai/build-an-ai-agent-and-win-3p3c</guid>
      <description>&lt;p&gt;“Everyone’s talking about AI agents. But what can you &lt;em&gt;actually&lt;/em&gt; build?”&lt;/p&gt;

&lt;p&gt;We (the team at &lt;a href="https://github.com/portiaAI/portia-sdk-python" rel="noopener noreferrer"&gt;Portia AI&lt;/a&gt;) keep hearing this — so we’re turning the question back to the community... and we're offering $$$ to the people who can come up with the best answer!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Announcing The "Agents Showdown"&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;For the next 4 weeks, we're taking submissions for our first ever online hackathon. Join us for a chance to earn £500&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;💸 Glory awaits!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvpsffzecg5s3nxtcuyu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvpsffzecg5s3nxtcuyu.png" alt="Portia Hackathon" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Overview&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Portia AI is an open source SDK that wants to stand out because it helps AI agents pre-express their planned response to a prompt, share their progress during execution, and solicit human input under defined conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉🏼 We want to build some cool examples that leverage our differentiators and add them to our &lt;a href="https://github.com/portiaAI/portia-agent-examples" rel="noopener noreferrer"&gt;examples repo&lt;/a&gt; on Github.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Bounty&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The best submission will win a £500 bounty and will be featured on our social channels.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Judging Criteria&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Submission should be very current i.e. leverages the latest emerging technologies in AI (MCP, A2A etc).

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; Two Portia agents coordinating their plan runs with each other / kicking off other Portia agents.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Demonstrates Portia’s strong suit e.g. dynamic planning with reinforcement (user-led learning) and / or human-agent interaction (hooks and clarifications).

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; Use hooks to handle profanity, PII leaks, prompt injections and more.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Touches on regulated spaces where mistakes due to agents going off the rails are very costly e.g. healthcare, finance, legal, insurance&lt;/li&gt;

&lt;li&gt;Quality of submission (demo video, readme, code quality)&lt;/li&gt;

&lt;li&gt;Submission should use solely (or predominantly) the Portia framework&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Eligibility and How To Enter&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Comment on this issue when you start working on the hackathon to let us know you're on it&lt;/li&gt;
&lt;li&gt;Submit your project as a comment on &lt;a href="https://github.com/portiaAI/portia-sdk-python/issues/576" rel="noopener noreferrer"&gt;this Github issue&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Provide a link to a private repo shared with &lt;a href="https://github.com/orgs/portiaAI/people/emmaportia" rel="noopener noreferrer"&gt;emmaportia&lt;/a&gt; and &lt;a href="https://github.com/orgs/portiaAI/people/mounir-portia" rel="noopener noreferrer"&gt;Momo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Include a high-resolution demo video explaining how your project works&lt;/li&gt;

&lt;li&gt;Write up a Readme including:

&lt;ul&gt;
&lt;li&gt;Key package dependencies&lt;/li&gt;
&lt;li&gt;Setup steps&lt;/li&gt;
&lt;li&gt;Running instructions&lt;/li&gt;
&lt;li&gt;Overview of key components in the code&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Follow our &lt;a href="https://github.com/portiaAI/portia-sdk-python/blob/main/CONTRIBUTING.md#how-to-contribute" rel="noopener noreferrer"&gt;contribution guidelines&lt;/a&gt; in particular around linting&lt;/li&gt;

&lt;li&gt;Star &lt;a href="https://github.com/portiaAI/portia-sdk-python?tab=readme-ov-file" rel="noopener noreferrer"&gt;our GitHub repo&lt;/a&gt; 😇&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Developer Resources&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Get immersed in our SDK and give us a star ⭐️ (&lt;a href="https://github.com/portiaAI/portia-sdk-python" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Head over to our docs (&lt;a href="//docs.portialabs.ai"&gt;docs.portialabs.ai&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Join the conversation on Discord (&lt;a href="https://discord.gg/DvAJz9ffaR" rel="noopener noreferrer"&gt;https://discord.gg/DvAJz9ffaR&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Sign Up to &lt;a href="https://app.portialabs.ai/dashboard" rel="noopener noreferrer"&gt;Portia cloud&lt;/a&gt; and access 1000+ cloud and MCP tools with built-in auth out of the box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgcuh2m4t06za5gq7pc7t.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgcuh2m4t06za5gq7pc7t.gif" alt="GIF" width="600" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>hackathon</category>
      <category>mcp</category>
      <category>devchallenge</category>
    </item>
    <item>
      <title>Code vs LLM in a simple planning poker agent example</title>
      <dc:creator>Mounir Mouawad</dc:creator>
      <pubDate>Wed, 09 Jul 2025 17:00:33 +0000</pubDate>
      <link>https://dev.to/portia-ai/code-vs-llm-in-a-simple-planning-poker-agent-example-5dg0</link>
      <guid>https://dev.to/portia-ai/code-vs-llm-in-a-simple-planning-poker-agent-example-5dg0</guid>
      <description>&lt;p&gt;If you're building AI agents, chances are you often had to consider how much logic you want to handle through the LLM versus through traditional code. I wanted to share my experience with it this morning as a conversation starter and get your thoughts! &lt;/p&gt;

&lt;h2&gt;
  
  
  What I wanted the agent to do
&lt;/h2&gt;

&lt;p&gt;I normally spend a ton of time gathering feedback from our users. In a previous life I would put those insights into tickets in Linear and spend a ton of mental cycles trying to size the return on effort to inform our prioritisation. In this bold new world of AI, I figured I would instead write up a &lt;a href="https://en.wikipedia.org/wiki/Planning_poker" rel="noopener noreferrer"&gt;planning poker&lt;/a&gt; agent to help me do t-shirt sizing of some of those tickets in Linear. Built on the Portia SDK, the agent would:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetch relevant linear tickets using the remote MCP server for Linear, which is one of &lt;a href="https://www.portialabs.ai/tools" rel="noopener noreferrer"&gt;1000s of tools&lt;/a&gt; we have with built-in auth.&lt;/li&gt;
&lt;li&gt;Simulate sizing estimates from multiple developer personas and get to a consensus for each ticket's effort sizing. Here I wanted to create a ticket estimator tool using a subclass of our LLM tool that would return estimates as &lt;a href="https://docs.portialabs.ai/inputs-outputs#llm-tool-outputs" rel="noopener noreferrer"&gt;structured outputs&lt;/a&gt;. The tool would take a &lt;code&gt;context.md&lt;/code&gt; file where I keep a summary of the architecture and core abstractions that make up the Portia SDK so it can help the LLM with effort sizing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As it turns out, I had asked one of our devs (we'll call him Ethan) to do this and forgotten! So we both wrote this thing up at the same time except...I relied quite heavily on the LLM to handle the task while he relied way more heavily on code. Let's unpack how our approches compared.&lt;/p&gt;

&lt;h2&gt;
  
  
  How each of us built it
&lt;/h2&gt;

&lt;p&gt;Full code in our agent examples repo &lt;a href="https://github.com/portiaAI/portia-agent-examples/tree/main/planning-poker" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧠 &lt;strong&gt;LLM-heavy&lt;/strong&gt;: I relied on a robust prompt and the &lt;a href="https://docs.portialabs.ai/generate-plan" rel="noopener noreferrer"&gt;Portia planning agent&lt;/a&gt; to figure out the entire set of steps that need to be taken, that is fetch and filter tickets from Linear, then get estimates for ticket sizes from each developer persona and average them out. Essentially I relied on the LLM itself to 1) index and aggregate the sizing estimates by Linear ticket id and persona, 2) figure out how many tool call iterations (a.k.a. "unrolling") to make to handle all ticket id and persona combinations. Here's the code snippet where the magic happens:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Get tickets from Linear and estimate the size of the tickets
&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Async Portia&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get the tickets i&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m working on from Linear with a limit of 3 on the tool call. Then filter specifically for those regarding the &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; project.
    For each combination of the tickets above and the following personas, estimate the size of the ticket.
    &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;personas&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

    Return the estimates in a list of PlanningPokerEstimate objects, with estimate sizes averaged across the personas for each ticket.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="n"&gt;estimates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;portia&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;structured_output_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PlanningPokerEstimateList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;final_output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;estimates&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;🧑🏻‍💻 &lt;strong&gt;Code-heavy&lt;/strong&gt;: Ethan on the other hand figured that we don't really need to rely on the LLM, neither for planning nor for indexing / aggregating / iterating on estimates. Instead he used Portia's declarative &lt;code&gt;PlanBuilder&lt;/code&gt; &lt;a href="https://docs.portialabs.ai/generate-plan#build-a-plan-manually" rel="noopener noreferrer"&gt;interface&lt;/a&gt; to enumerate the steps and tool calls needed. He fetched the tickets using a first Portia plan run into &lt;code&gt;LinearTicket&lt;/code&gt; objects using &lt;a href="https://docs.portialabs.ai/inputs-outputs#plan-structured-outputs" rel="noopener noreferrer"&gt;structured outputs&lt;/a&gt;. To generate sizing estimates, he then iterated with conventional code over each developer persona and over each ticket element in the list returned from the previous plan run. Each iteration called the ticket estimator tool in a single step Portia plan run. Here's a code snippet containing both the ticket fetching plan run and the ticket sizing iterations:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Fetch Linear tickets
&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Async SDK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Get the tickets i&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m working on from linear regarding the &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; project&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PlanBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;structured_output_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;LinearTicketList&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; and only call the tool with a limit of 3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portia:mcp:mcp.linear.app:list_my_issues&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Filter the tickets to only include specifically the ones related to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llm_tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plan_run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;portia&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tickets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plan_run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;final_output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tickets&lt;/span&gt;

&lt;span class="c1"&gt;# Iterate over tickets and persona to generate estimates
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ticket&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;estimates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;estimate_plan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PlanBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estimate the size of the ticket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;structured_output_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PlanningPokerEstimate&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Estimate the size of the ticket: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ticket_estimator_tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;persona&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;personas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;persona&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
        &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tool_context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;estimate_tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;
        &lt;span class="n"&gt;portia&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;estimate_tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;overwrite&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;estimate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;portia&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;estimate_plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;estimate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;PlanRunState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COMPLETE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;estimates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;estimate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;final_output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What we learned
&lt;/h2&gt;

&lt;p&gt;Let's compare both approaches side by side and draw some conclusions. I hooked up Langsmith to Portia for &lt;a href="https://docs.portialabs.ai/agent-observability" rel="noopener noreferrer"&gt;observability&lt;/a&gt; so I could obtain the metrics shown below.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;LLM-heavy&lt;/th&gt;
&lt;th&gt;Code-heavy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Effort&lt;/td&gt;
&lt;td&gt;Lowest&lt;/td&gt;
&lt;td&gt;Highest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total tokens&lt;/td&gt;
&lt;td&gt;70k&lt;/td&gt;
&lt;td&gt;30k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;$0.12&lt;/td&gt;
&lt;td&gt;$0.06&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency [P99]&lt;/td&gt;
&lt;td&gt;28.95s&lt;/td&gt;
&lt;td&gt;9.70s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So what conclusions can we draw from this exercise?&lt;br&gt;
💡 &lt;strong&gt;Reliability:&lt;/strong&gt; You can trust your Portia agents to figure out the right sequence of steps and to unroll (iterate on) the tool calls correctly so that definitely simplifies development, kinda like a form of vibe coding...but much like vibe coding it does take a bit of 'LLM-whispering' (a.k.a. prompt engineering) and using the right underlying model. For plan runs with heavy iteration expectations in particular, you will need robust eval sets in place to keep tabs on reliability lest you aim for a Mona Lisa and end up with a Picasso.&lt;br&gt;
👣 &lt;strong&gt;Traceability:&lt;/strong&gt; Relying on the LLM to handle planning and execution to the extent I did does make tracing particularly easy. One single &lt;code&gt;PlanRunState&lt;/code&gt; &lt;a href="https://docs.portialabs.ai/store-retrieve-plan-runs" rel="noopener noreferrer"&gt;instance&lt;/a&gt; in the Portia dashboard showed me the entirety of the work done by the underlying subagents. This also makes revisiting the output of the plan run easier of course. Ethan on the other hand ended up with numerous plan runs, which makes auditing and / or debugging harder.&lt;br&gt;
💸 &lt;strong&gt;Cost:&lt;/strong&gt; As you'd expect the LLM-heavy method is slower and costlier. Ultimately we're still processing the same amount of context presumably (same number of tickets and estimations) but the overhead of passing along a growing context window across all execution agents during the plan run means that the LLM-heavy method is inevitably slower and costlier. You're also opening yourself up to the stochasticity of LLMs when code could do the trick.&lt;/p&gt;

&lt;h2&gt;
  
  
  A parting thought
&lt;/h2&gt;

&lt;p&gt;One aspect I don't consider in the comparison above is autonomy. Because the task is neatly scoped in this example (planning poker agent = fetch and filter tickets + estimate per persona + summarise consensus) you can make the argument that at production scale one should restrict LLM usage only to the tasks that traditional code can't handle as easily (e.g. natural language processing). BUT where inputs from the environment change or the scope of the task is fluid, the LLM-heavy approach truly thrives. I'll try and tease that more obviously in a subsequent post. &lt;br&gt;
👉🏼 &lt;strong&gt;If you're interested please shout in the comments down below!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  About Portia
&lt;/h2&gt;

&lt;p&gt;Portia AI is an open-source framework for building predictable, stateful, authenticated agentic workflows.&lt;/p&gt;

&lt;p&gt;We allow developers to have as much or as little oversight as they’d like over their multi-agent deployments and we are obsessively focused on production readiness.&lt;/p&gt;

&lt;p&gt;We invite you to play around with our &lt;a href="https://github.com/portiaAI/portia-sdk-python" rel="noopener noreferrer"&gt;SDK&lt;/a&gt;, break things, and tell us how you're getting on in &lt;a href="https://discord.gg/DvAJz9ffaR" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>llm</category>
      <category>mcp</category>
    </item>
    <item>
      <title>3 Issues That Remote MCP Developers Should Avoid</title>
      <dc:creator>Zevi Reinitz</dc:creator>
      <pubDate>Tue, 08 Jul 2025 16:48:22 +0000</pubDate>
      <link>https://dev.to/portia-ai/3-issues-that-remote-mcp-developers-should-avoid-226o</link>
      <guid>https://dev.to/portia-ai/3-issues-that-remote-mcp-developers-should-avoid-226o</guid>
      <description>&lt;p&gt;Remote MCP servers are only just starting to take off. As more platforms roll out support for the Model Context Protocol (MCP), we're seeing rapid growth in developer experimentation — and equally, we're seeing many of the common pitfalls emerge for teams building MCP servers for the first time.&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://github.com/portiaAI/portia-sdk-python" rel="noopener noreferrer"&gt;Portia&lt;/a&gt;, we’ve tested a broad range of remote MCP servers — from major providers like Asana, Atlassian, Intercom and Stripe, to emerging integrations like Fulcra, Globalping and Invideo. In the process, we’ve seen a few recurring issues that make MCP servers harder to use, slower to adopt, and more brittle in production.&lt;/p&gt;

&lt;p&gt;[&lt;em&gt;For some quick context - Portia is the framework that enables developers to build safe, reliable AI agents. We'd be thrilled to have you &lt;a href="https://github.com/portiaAI/portia-sdk-python" rel="noopener noreferrer"&gt;check out our SDK&lt;/a&gt; and give it a GitHub star!&lt;/em&gt;⭐ 🙏]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here are 3 mistakes we’d recommend every MCP developer avoid.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7ezjzq9jy8a7a5kmjrn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7ezjzq9jy8a7a5kmjrn.gif" alt="be safe" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. OAuth redirects only work on localhost
&lt;/h2&gt;

&lt;p&gt;Several MCP servers we tested work fine locally, but break when used in real-world staging or production environments. A common cause: OAuth redirect URIs are hardcoded to allow only &lt;code&gt;localhost&lt;/code&gt;. While this might be convenient during initial development, it blocks testing in any remote environment.&lt;/p&gt;

&lt;p&gt;For example, we couldn’t add Atlassian because they rejected valid redirect URIs during authorization flows. This makes it nearly impossible for integrators to properly test your server in their deployment pipelines.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Best practice:&lt;/strong&gt; Always allow multiple redirect URIs for OAuth clients, including both local and remote environments. Ideally, make this configurable per client.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Missing &lt;code&gt;.well-known&lt;/code&gt; OAuth metadata or misconfigured tool discovery
&lt;/h2&gt;

&lt;p&gt;The MCP spec depends on being able to automatically discover OAuth configuration using the &lt;code&gt;.well-known/oauth-authorization-server&lt;/code&gt; endpoint. Several servers — including PostHog, Semgrep and Invideo — either failed to serve this file or required tokens to access the tools/list endpoint, which prevents tool discovery.&lt;/p&gt;

&lt;p&gt;Without a valid &lt;code&gt;.well-known&lt;/code&gt; file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client libraries can't automatically configure OAuth.&lt;/li&gt;
&lt;li&gt;MCP agents can't discover your tool easily.&lt;/li&gt;
&lt;li&gt;Developer experience suffers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ &lt;strong&gt;Best practice:&lt;/strong&gt; Make sure your &lt;code&gt;.well-known/oauth-authorization-server&lt;/code&gt; is publicly accessible, standards-compliant, and returns the necessary OAuth metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Unreliable server availability
&lt;/h2&gt;

&lt;p&gt;MCP servers are expected to handle dynamic discovery, token exchange, and tool invocation — and this requires a fairly high level of reliability. In our testing, we encountered servers that were down for maintenance (Asana), intermittently unavailable (Plaid, Neon), or failed authorization flows with unhelpful errors (Fulcra).&lt;/p&gt;

&lt;p&gt;In an agentic world, unreliability doesn’t just block a single API call — it can break entire task chains and workflows.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Best practice:&lt;/strong&gt; Invest early in uptime monitoring, clear error messages, and comprehensive OAuth error handling. MCP servers should fail gracefully and predictably.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Good News: Testing is Easy With Portia
&lt;/h2&gt;

&lt;p&gt;If you’re building a new remote MCP server, testing your implementation doesn’t need to be painful. Portia makes it simple to integrate, validate, and experiment with new MCP servers directly inside your agentic app — often in just 3 clicks.&lt;/p&gt;

&lt;p&gt;As the MCP ecosystem matures, we're excited to help developers ship more reliable, agent-friendly integrations that just work — across staging, production, and every environment in between.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Bit More About Portia
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.portialabs.ai/" rel="noopener noreferrer"&gt;Portia AI&lt;/a&gt; is an open-source framework for building predictable, stateful, authenticated agentic workflows. &lt;/p&gt;

&lt;p&gt;We allow developers to have as much or as little oversight as they’d like over their multi-agent deployments and we are obsessively focused on production readiness. &lt;/p&gt;

&lt;p&gt;We invite you to &lt;a href="https://github.com/portiaAI/portia-sdk-python" rel="noopener noreferrer"&gt;play around with our SDK&lt;/a&gt;, break things, and tell us how you're getting on in &lt;a href="https://discord.gg/DvAJz9ffaR" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>rag</category>
      <category>llm</category>
    </item>
    <item>
      <title>Steel Thread, Evals and building reliable agents.</title>
      <dc:creator>Tom Stuart</dc:creator>
      <pubDate>Wed, 04 Jun 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/portia-ai/steel-thread-evals-and-building-reliable-agents-2ld6</link>
      <guid>https://dev.to/portia-ai/steel-thread-evals-and-building-reliable-agents-2ld6</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;At Portia we spend a lot of time thinking about what it means to make agents reliable and production worthy. Lots of people find it easy to make agents for a proof of concept but much harder to get them into production. It takes a real focus on production readiness and a suite of features to do so (lots of which are available in our SDK as we’ve talked about in previous blog posts):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User Led Learning for reliable planning&lt;/li&gt;
&lt;li&gt;Agent Memory for large data sets&lt;/li&gt;
&lt;li&gt;Human in the loop clarifications to let agents raise questions back to humans&lt;/li&gt;
&lt;li&gt;Separate planning and execution phases for constrained execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But today we want to focus on the meta question of how we know that these features help improve the reliability of agents built on top of them by talking about evals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evals - What Are They Anyway?
&lt;/h2&gt;

&lt;p&gt;“Evals” is shorthand for evaluations. They’re how we turn the vague question of “is this agent doing the right thing?” into something we can actually measure and repeat.&lt;/p&gt;

&lt;p&gt;A good eval works a lot like a unit test or integration test. You give it inputs, run them through the system, and check whether the outputs match what you expected.&lt;/p&gt;

&lt;p&gt;Where evals differ from integration or unit tests is in that we are operating in a probabilistic world. LLMs are non-deterministic and thus so is any software built on top of them. Therefore we need to adjust our approach from always asserting something to trying it multiple times and checking the correctness as a percentage.&lt;/p&gt;

&lt;p&gt;Consider an example of some software that gets the weather:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Integration test:

GET /weather?location=LONDON 

response.status_code = 200


Eval Approach:

Whats the weather in London Today?

The weather in London is 20C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An integration test is generally easy to write because we can make a request to the software and write concrete assertions about the response. With an agent this is much harder. The response is natural language and the format can vary from call to call. Not only that but with agents we are usually dealing with side effects like an email being sent correctly that aren't contained in the response.&lt;/p&gt;

&lt;p&gt;Like unit and integrations tests, we think about evals as existing on a spectrum:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At one end, you have low-level checks. These might confirm that a specific tool was called (a weather tool in the example above), or that a plan has the right structure.&lt;/li&gt;
&lt;li&gt;At the other end, you have full end-to-end tests. These look at whether the agent made the right business decision, even if it found a creative way to get there.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each type of eval gives you different insight. Low level evals tell you if the system is behaving as expected. High level evals help you understand whether it’s making good decisions for the right reasons.&lt;/p&gt;

&lt;p&gt;Evals aren’t necessarily for catching bugs though they certainly can help with this. They’re for asking big performance questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happens if we change LLM provider from OpenAI to Gemini?&lt;/li&gt;
&lt;li&gt;Does performance improve if we change the structure or even tone of our prompts?&lt;/li&gt;
&lt;li&gt;How does changing the interface of a tool affect the agent's performance?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Steel Thread
&lt;/h2&gt;

&lt;p&gt;To make evals easier, we built Steel Thread, our internal eval framework, now available to partners using the Portia SDK. Steel Thread is a lightweight Python library designed to help you write, run, and analyze evals for your agent workflows.&lt;/p&gt;

&lt;p&gt;It focuses on being:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simple&lt;/strong&gt; : Define your test cases in Python or JSON. Each test specifies inputs, expected outputs, and what success looks like.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast&lt;/strong&gt; : Built-in concurrency and retries mean you can scale up eval runs without overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible&lt;/strong&gt; : You can write low-level metrics that look at fine-grained behavior, or high-level E2E runners that test business outcomes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visualizable&lt;/strong&gt; : Steel Thread can push results to external layers like LangSmith, making it easy to explore where things are failing and why.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We use it ourselves to test the Portia SDK and the agents built with it. It’s designed for both internal development workflows and user-facing evaluation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: Writing Your First Eval
&lt;/h2&gt;

&lt;p&gt;Let’s walk through a simplified example of using Steel Thread to evaluate an agent. Say you have an agent that makes a decision based on a set of inputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;steel_thread.runner&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;E2ERunner&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;steel_thread.types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;EvalOutput&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyAgent&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;  &lt;span class="c1"&gt;# type: ignore
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;input_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;EvalOutput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

          &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChatCompletion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a smart home assistant tasked with identifying what actions to take.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;input_text&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="p"&gt;],&lt;/span&gt;
              &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;output_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;choices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;turn on&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;output_text&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;light&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;output_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lights_on&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;play&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;output_text&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jazz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;output_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;play_music_jazz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
              &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You want to test whether it produces the expected outcome given specific inputs. To do this you define a set of test cases, much like you would with normal software testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;steel_thread.e2e&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;E2EEval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EvalOutput&lt;/span&gt;

&lt;span class="n"&gt;test_cases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;E2EEval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;u1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;turn on the lights&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;EvalOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lights_on&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;final_error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;E2EEval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;u2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;play jazz music&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;EvalOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;play_music_jazz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;final_error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To integrate this with Steel Thread, we define a runner. The runner is a simple class that knows how to call your agent with the inputs from each test case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExampleAgentRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;E2ERunner&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;EvalOutput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;my_agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;input_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;EvalOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;final_error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;EvalOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;final_error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is simple enough, up to this point. This is where the magic comes in. We ask steel thread to handle updating the dataset and then to run the evals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;dataset_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;My Eval Set&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="nf"&gt;update_dataset_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataset_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eval_dataset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;run_eval_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;dataset_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;ExampleAgentRunner&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nc"&gt;EvalOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;reps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;upload_results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_concurrency&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;extra_metrics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Steel Thread will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Concurrently run your agent against the defined test case(s) using the runner according to your concurrency config.&lt;/li&gt;
&lt;li&gt;Score the outputs using a set of default metrics and/or the extra metrics you think are important for your use case.&lt;/li&gt;
&lt;li&gt;Upload the results to your visualization layer of choice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb537us2wdw5psjon3nos.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb537us2wdw5psjon3nos.png" alt="A visualization of eval data" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Extra Metrics
&lt;/h2&gt;

&lt;p&gt;In addition to checking whether the agent returns the correct output, sometimes we care about how the agent answers, for example, how concise or verbose its responses.&lt;/p&gt;

&lt;p&gt;Steel Thread makes it easy to define your own custom metrics by subclassing the Metric base class. Here's a simple example of a metric that gives higher scores to shorter outputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;steel_thread.metrics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Metric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EvaluationResult&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;steel_thread.types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Example&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShortOutputPreferred&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Metric&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Reward shorter outputs (under 20 tokens) with a score of 1.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;evaluate_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Example&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;evaluator_run_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;EvaluationResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
        &lt;span class="n"&gt;word_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;EvaluationResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;short_output_preferred&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;word_count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How We Use Steel Thread
&lt;/h2&gt;

&lt;p&gt;At Portia, Steel Thread is a core part of how we build and validate agentic systems. We use it to continuously measure reliability, correctness, and regressions across different layers of our platform. A few examples:&lt;/p&gt;

&lt;h3&gt;
  
  
  End-to-End Use Case Testing
&lt;/h3&gt;

&lt;p&gt;We write end-to-end evals for common user journeys whether it's reviewing a customer case, escalating a risk, or generating a compliance summary. These tests help ensure that agents behave as expected across realistic, high-level workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent Comparison and A/B Testing
&lt;/h3&gt;

&lt;p&gt;We frequently use our evals to have data driven discussions about feature changes. For example, does introducing a new section of context to the planning prompt affect the performance of our existing evals.&lt;/p&gt;

&lt;h3&gt;
  
  
  Low-Level Tool Call Verification
&lt;/h3&gt;

&lt;p&gt;We use low-level execution or planning evals to verify that tools are being called with the correct parameters. This is especially useful when agent behavior is compositional or dynamically generated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stress-Testing Planning as Tool Count Grows
&lt;/h3&gt;

&lt;p&gt;As our library of tools grows, planning gets harder. We use evals to track how well our agents plan under increasing complexity, including how accurate, efficient, and deterministic they remain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why All This Matters
&lt;/h2&gt;

&lt;p&gt;It’s easy to demo something impressive with LLMs. But to deploy an agent in a real-world system with users, data, and risk you need confidence that it will behave consistently and safely.&lt;/p&gt;

&lt;p&gt;That’s what evals give you: a feedback loop that allows you to iteratively improve the accuracy and reliability of your agents. You wouldn’t ship software to production without tests and you shouldn’t ship agents to production without evals.&lt;/p&gt;

&lt;p&gt;As agents become more complex, the teams that win will be the ones who can reason about behavior, not just improvise it. We hope Steel Thread helps you do that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Access
&lt;/h2&gt;

&lt;p&gt;Steel Thread is available today to our design partners - get in contact with us at &lt;a href="mailto:hello@portialabs.ai"&gt;hello@portialabs.ai&lt;/a&gt; if you're interested!&lt;/p&gt;

&lt;h2&gt;
  
  
  Join the conversation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Like this article?&lt;/strong&gt; – &lt;a href="https://github.com/portiaAI/portia-sdk-python" rel="noopener noreferrer"&gt;Give us a ⭐ on GitHub&lt;/a&gt;. It really helps!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browse our website and try our (modest) playground at &lt;a href="http://www.portialabs.ai/" rel="noopener noreferrer"&gt;www.portialabs.ai&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Head over to our docs at &lt;a href="https://docs.portialabs.ai/" rel="noopener noreferrer"&gt;docs.portialabs.ai&lt;/a&gt; or get immersed in our &lt;a href="https://github.com/portiaAI/portia-sdk-python/tree/main/portia/open_source_tools" rel="noopener noreferrer"&gt;SDK&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Join the conversation on our &lt;a href="https://discord.gg/DvAJz9ffaR" rel="noopener noreferrer"&gt;Discord channel&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Watch us embarrass ourselves on our &lt;a href="https://www.youtube.com/@PortiaAI" rel="noopener noreferrer"&gt;YouTube channel&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Follow us on &lt;a href="https://www.producthunt.com/posts/portia-ai" rel="noopener noreferrer"&gt;Product Hunt&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>portia</category>
      <category>evals</category>
      <category>reliability</category>
    </item>
    <item>
      <title>Design Highlight: Handling data at scale with Portia multi-agent systems</title>
      <dc:creator>Portia AI</dc:creator>
      <pubDate>Thu, 22 May 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/portia-ai/design-highlight-handling-data-at-scale-with-portia-multi-agent-systems-7nb</link>
      <guid>https://dev.to/portia-ai/design-highlight-handling-data-at-scale-with-portia-multi-agent-systems-7nb</guid>
      <description>&lt;p&gt;At Portia, we love building in public. Our &lt;a href="https://github.com/portiaAI/portia-sdk-python" rel="noopener noreferrer"&gt;agent framework is open-source&lt;/a&gt; and we want to involve our community in key design decisions. Recently, we’ve been focussing on improving how agents handle production data at scale in Portia. This has sparked some exciting design discussions that we wanted to share in this blog post. If you find these discussions interesting, we’d love you to be involved in future discussions! Just get in contact (details in block below) - we can’t wait to hear from you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calling All Devs
&lt;/h3&gt;

&lt;p&gt;We’d love to hear from you on the design decisions we’re making 💪 Check out the &lt;a href="https://github.com/portiaAI/portia-sdk-python/discussions/449" rel="noopener noreferrer"&gt;discussion thread&lt;/a&gt; for this blog post to have your say. If you want to join our wider community too (or just fancy saying hi!), head on over to our &lt;a href="https://discord.gg/DvAJz9ffaR" rel="noopener noreferrer"&gt;discord&lt;/a&gt;, our &lt;a href="https://www.reddit.com/r/PortiaAI/" rel="noopener noreferrer"&gt;reddit community&lt;/a&gt;, or our &lt;a href="https://github.com/portiaAI/portia-sdk-python" rel="noopener noreferrer"&gt;repo on GitHub&lt;/a&gt; (Give us a ⭐ while you’re there!).&lt;/p&gt;

&lt;p&gt;If you’re new to Portia, we’re building a multi-agent framework that’s designed to enable people to run agents reliably in production. Efficiently handling large and complex data sources is one of the key aspects of this, along with agent permissions, observability and reliability. We’ve seen numerous agent prototypes that work well on small datasets in restricted scenarios, but then start to fall over when faced with the scale and complexity of production data. We want to make sure this doesn’t happen when agents are built with Portia. In this blog post, we’ll explore the design decisions we’ve made to enable this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real agents handling data at scale&lt;a href="https://blog.portialabs.ai/multi-agent-data-at-scale#real-agents-handling-data-at-scale" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;As with all good design discussions, we work backwards from real life use-cases that we’re looking to enable / improve. We’re working with many agent builders and below are a selection of the exciting use-cases we’ve seen that require efficiently processing large data sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A debugging agent that can process many large server log files along with other debug information to diagnose issues.&lt;/li&gt;
&lt;li&gt;A research agent that can process many documents and search results over time as it conducts research into a particular company or person.&lt;/li&gt;
&lt;li&gt;A finance assistant capable of researching over a company’s financial data in a mixture of sheets and docs to answer questions - for example, “from this week’s sales data, identify the top 3 selling products and how their sales are split by Geography”&lt;/li&gt;
&lt;li&gt;A personal assistant capable of having long-running interactions with the user, including taking actions such as scheduling events and sending emails, adapting to their preferences over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In order to handle each of these use-cases well, our agents need to handle data correctly across complex, multi-step plans without being thrown by large documents or making repetitive mistakes. However, we were finding that these agent builders were hitting a couple of key issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Plan run state overload:&lt;/strong&gt; Our execution agent has access to the full state of the plan run. When it runs a tool, it stores the output of the tool run into that state for future use. Over time though, if tools were producing large amounts of data, this state could get very large and congested. As this was passed into the LLM, this would then reduce the accuracy with which the execution agent could retrieve the correct information for each step from the plan run state:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example&lt;/strong&gt; : A debugging agent might download and then analyse the logs from 10 different servers and analyse each of them. It might then move on to another task, but the logs from each of those 10 servers would still be in its plan run state, distracting from other useful information when processing future steps.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Tool calling with large inputs:&lt;/strong&gt; Our execution agent calls a language model to produce the arguments for calling each tool. However, when we wanted to call a tool with a large argument (e.g. &amp;gt;1k tokens), we would either hit the output token limits of the model or we would hit latencies that would make the system incredibly slow.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; A finance agent might want to read in a large spreadsheet and then pass its contents into a processing tool to extract the key data it needs. We saw occasions where just generating the args for the processing tool took more than 5 minutes because it needed to print out the full contents of the spreadsheet!&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;We needed to fix these two issues, so our agent builders could stop wrestling with context windows and focus on shipping features.&lt;/p&gt;

&lt;h2&gt;
  
  
  An aside on long-context models&lt;a href="https://blog.portialabs.ai/multi-agent-data-at-scale#an-aside-on-long-context-models" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Before diving into potential solutions, let’s explain why we thought this is a problem worth solving even with the vast context windows seen from the latest models (e.g. &lt;a href="https://ai.meta.com/blog/llama-4-multimodal-intelligence/" rel="noopener noreferrer"&gt;Llama4 has a 10m token window&lt;/a&gt; while &lt;a href="https://openai.com/index/gpt-4-1/" rel="noopener noreferrer"&gt;GPT 4.1 has a 1m context window&lt;/a&gt;). These models have certainly changed the equation - before their arrival, we hit context window limits a lot more than we currently do. However, using these models with large data sources is still difficult and problematic for multiple reasons:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;th&gt;SOTA models boast strong accuracy scores in needle-in-a-haystack tests, but &lt;em&gt;real&lt;/em&gt; scenarios are more complex, requiring reasoning over and connecting different pieces of information in the context, and models get much weaker at this when the context is large.&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Filling GPT 4.1’s context window will cost you $2 of processing for every LLM call. Agentic systems typically make many LLM calls, so $2 can quickly make your system very expensive!&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;As the token number increases, so does the latency, particularly for output tokens. &lt;a href="https://platform.openai.com/docs/guides/latency-optimization/3-use-fewer-input-tokens#generate-fewer-tokens" rel="noopener noreferrer"&gt;OpenAI states&lt;/a&gt; that while doubling input tokens increases latency 1-5%, doubling output tokens &lt;em&gt;doubles&lt;/em&gt; output latency. When compounded with the fact that agentic systems make many LLM calls, this can make systems very sluggish.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Failure Modes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Interestingly, language models fail in different ways when the context length gets large. There’s a great study on this from &lt;a href="https://www.databricks.com/blog/long-context-rag-performance-llms" rel="noopener noreferrer"&gt;databricks&lt;/a&gt;. This adds instability to the system because the prompts you’ve been iterating on in low data scenarios suddenly don’t work as you expected in production.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Preventing our plan run state becoming overloaded&lt;a href="https://blog.portialabs.ai/multi-agent-data-at-scale#preventing-our-plan-run-state-becoming-overloaded" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Given the above, we can’t just rely on long-context models and need to handle the issue with overloading our plan run state within our framework. To solve this, we needed to reduce the size of the context used by our execution agent, and we did this as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, we introduced agent memory. For large outputs (the default is above 1k tokens) we store the full value in agent memory with just a reference in the plan run state. This prevents previous large outputs from clogging up the plan run state when they are no longer needed.

&lt;ul&gt;
&lt;li&gt;You can configure where your agent stores memories through our &lt;code&gt;storage_class&lt;/code&gt; configuration option (see our &lt;a href="https://docs.portialabs.ai/manage-config#manage-storage-options" rel="noopener noreferrer"&gt;docs&lt;/a&gt; for more details). If you choose Portia cloud, you’ll be able to view the memories in our dashboard:&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fflaesuf76t874rk0u9ks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fflaesuf76t874rk0u9ks.png" alt="A screenshot of the Portia dashboard, showing memories from previous plan runs." width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Our planner selects inputs for each step of our plan. If one of these inputs is in agent memory, we fetch the value from memory, as we know it is specifically needed for this step. This allows the execution agent to fully utilise the large values in agent memory when needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2obsl1fnu4m1thc93652.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2obsl1fnu4m1thc93652.png" alt="A sequence diagram, showing how agent memory fills up over multiple steps" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can check out the code for this feature &lt;a href="https://github.com/portiaAI/portia-sdk-python/pull/319" rel="noopener noreferrer"&gt;in this PR&lt;/a&gt; and the docs are &lt;a href="https://docs.portialabs.ai/agent-memory" rel="noopener noreferrer"&gt;here&lt;/a&gt;. For our first implementation of agent memory, we decided to only allow pulling the full value from agent memory, rather than indexing the values in a vector database (or other form of database) and allowing queries based on that. A key reason for this (as well as wanting to keep our initial implementation as simple as possible) is that the way memories need to be queried is very task dependent. There are times when a semantic similarity search of memories is required (e.g. a debugging agent looking for similar errors among log files), while other times require filtering on exact values (e.g. a debugging agent looking for logs between two timestamps from a particular service), a projection of the values (e.g. a finance assistant just taking several columns from a spreadsheet) or access to the full value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our future vision - a memory agent&lt;a href="https://blog.portialabs.ai/multi-agent-data-at-scale#our-future-vision---a-memory-agent" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Ultimately, we’ll want to support all these patterns, but doing this efficiently requires indexing and querying the memories intelligently based on the task. We believe that this will be best done by a separate memory agent - an agent within our multi-agent system that indexes and queries agent memories so that the required pieces can be retrieved for the task and passed to the execution agent. This clearly adds complexity to the system though! So we wanted to see how our agent builders use agent memory before jumping to conclusions on the best way to index and query the memories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tell us what you think!
&lt;/h3&gt;

&lt;p&gt;We’d love to hear what you thought of the decision to not automatically ingest agent memories into a vector database. Is it something you’d like to see? Get involved in the &lt;a href="https://github.com/portiaAI/portia-sdk-python/discussions/449" rel="noopener noreferrer"&gt;Github discussion&lt;/a&gt; and let us know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solving Tool calling with large inputs&lt;a href="https://blog.portialabs.ai/multi-agent-data-at-scale#solving-tool-calling-with-large-inputs" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Our introduction of agent memory meant that the execution agent was managing the context it sent to the language model much better. However, we were still facing the issue, mentioned above, where the language model struggled to produce large arguments for tools when needed. In order to solve this, we provided the language model with the ability to use templates to input variables. When calling the language model, the execution agent would outline in the prompt that, if the language model simply wanted to use a value from agent memory verbatim, it didn’t have to copy the value out - it could just put, for example &lt;code&gt;{{$large_memory_value}}&lt;/code&gt;. We then extended the execution agent to retrieve $large_memory_value from agent memory when this was done and template the value in, so that the tool received the full value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkjt1yr5rej1zcr2u97bb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkjt1yr5rej1zcr2u97bb.png" alt="A diagram showing how templating works when providing large memory values to tools." width="724" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can check out the code for this feature in this PR: &lt;a href="https://github.com/portiaAI/portia-sdk-python/pull/369" rel="noopener noreferrer"&gt;Add ability for execution agent to template large outputs&lt;/a&gt;. Interestingly, after a bit of initial tuning, we found that the language model was able to determine correctly whether it should template a variable or not. This has led to a massive improvement in latency and cost of our agents calling tools with large data sources. For example, a personal assistant use-case that involved analysing a large spreadsheet reduced in time from 3-5 minutes to &amp;lt;10s.&lt;/p&gt;

&lt;h3&gt;
  
  
  What do you think?
&lt;/h3&gt;

&lt;p&gt;What do you think of allowing language models to template variables rather than copy them out fully? Do you have a better approach for this? Get involved in the &lt;a href="https://github.com/portiaAI/portia-sdk-python/discussions/449" rel="noopener noreferrer"&gt;GitHub discussion&lt;/a&gt; and let us know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going forwards&lt;a href="https://blog.portialabs.ai/multi-agent-data-at-scale#going-forwards" rel="noopener noreferrer"&gt;​&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We believe this work sets a great foundation for building multi-agent systems with Portia that handle data at scale, and we’ve got an exciting roadmap of features to keep making this even better:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pre-ingest knowledge / memories: we want to allow kicking off our agents with knowledge and memories already loaded, rather than requiring the agent to fetch all the information needed as part of the run&lt;/li&gt;
&lt;li&gt;Improved pagination handling: we want to allow our execution agent to more efficiently use paginated APIs&lt;/li&gt;
&lt;li&gt;Memory agent: as mentioned above, we’re excited about the possibilities opened up by a separate memory agent. Once we’ve got a good idea of how agent memory is being used in its current form, we’d love to start discussions on how this new agent might fit into our system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’re really looking forward to finding out what these new large data capabilities will unlock for agent builders working on Portia!&lt;/p&gt;

&lt;h3&gt;
  
  
  What do &lt;em&gt;you&lt;/em&gt; think?
&lt;/h3&gt;

&lt;p&gt;Hopefully you enjoyed this blog post. If you did (or even if you didn’t!), we’d love to hear from you. Did you agree with the design decisions we are taking? Do you think we should take a different approach? If you’ve got thoughts and ideas, we’d love to hear about them in the &lt;a href="https://github.com/portiaAI/portia-sdk-python/discussions/449" rel="noopener noreferrer"&gt;GitHub discussion&lt;/a&gt; associated with this post. And we love chatting about code even more, so if you’ve got an idea, fork our repo and we’d love to review the code 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  Join the conversation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Like this article?&lt;/strong&gt; – &lt;a href="https://github.com/portiaAI/portia-sdk-python" rel="noopener noreferrer"&gt;Give us a ⭐ on GitHub&lt;/a&gt;. It really helps!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browse our website and try our (modest) playground at &lt;a href="http://www.portialabs.ai/" rel="noopener noreferrer"&gt;www.portialabs.ai&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Head over to our docs at &lt;a href="https://docs.portialabs.ai/" rel="noopener noreferrer"&gt;docs.portialabs.ai&lt;/a&gt; or get immersed in our &lt;a href="https://github.com/portiaAI/portia-sdk-python/tree/main/portia/open_source_tools" rel="noopener noreferrer"&gt;SDK&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Join the conversation on our &lt;a href="https://discord.gg/DvAJz9ffaR" rel="noopener noreferrer"&gt;Discord channel&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Watch us embarrass ourselves on our &lt;a href="https://www.youtube.com/@PortiaAI" rel="noopener noreferrer"&gt;YouTube channel&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Follow us on &lt;a href="https://www.producthunt.com/posts/portia-ai" rel="noopener noreferrer"&gt;Product Hunt&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>portia</category>
      <category>memory</category>
      <category>design</category>
    </item>
  </channel>
</rss>
