<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Bo's Blog: architecture</title><link href="https://www.odux.uk/" rel="alternate"/><link href="https://www.odux.uk/tags/architecture.atom" rel="self"/><id>https://www.odux.uk/</id><updated>2026-02-07T23:00:10+00:00</updated><author><name>Bo Xu</name></author><entry><title>Core of Pi - the while loop</title><link href="https://odux.com/2026/Feb/7/pi-core-the-while-loop/#atom-tag" rel="alternate"/><published>2026-02-07T23:00:10+00:00</published><updated>2026-02-07T23:00:10+00:00</updated><id>https://odux.com/2026/Feb/7/pi-core-the-while-loop/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/badlogic/pi-mono/blob/main/packages/agent/src/agent-loop.ts"&gt;Core of Pi - the while loop&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
The core of the Pi is basically a &lt;code&gt;while&lt;/code&gt; loop, in &lt;code&gt;packages/agent/src/agent-loop.ts&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ts"&gt;    // Outer loop: continues when queued follow-up messages arrive after agent would stop
    while (true) {
      let hasMoreToolCalls = true;
      let steeringAfterTools: AgentMessage[] | null = null;

      // Inner loop: process tool calls and steering messages
      while (hasMoreToolCalls || pendingMessages.length &amp;gt; 0) {
        if (!firstTurn) {
          stream.push({ type: &amp;quot;turn_start&amp;quot; });
        } else {
          firstTurn = false;
        }

        // Process pending messages (inject before next assistant response)
        if (pendingMessages.length &amp;gt; 0) {
          for (const message of pendingMessages) {
            stream.push({ type: &amp;quot;message_start&amp;quot;, message });
            stream.push({ type: &amp;quot;message_end&amp;quot;, message });
            currentContext.messages.push(message);
            newMessages.push(message);
          }
          pendingMessages = [];
        }

        // Stream assistant response
        const message = await streamAssistantResponse(currentContext, config, signal, stream, streamFn);
        newMessages.push(message);

        if (message.stopReason === &amp;quot;error&amp;quot; || message.stopReason === &amp;quot;aborted&amp;quot;) {
          stream.push({ type: &amp;quot;turn_end&amp;quot;, message, toolResults: [] });
          stream.push({ type: &amp;quot;agent_end&amp;quot;, messages: newMessages });
          stream.end(newMessages);
          return;
        }

        // Check for tool calls
        const toolCalls = message.content.filter((c) =&amp;gt; c.type === &amp;quot;toolCall&amp;quot;);
        hasMoreToolCalls = toolCalls.length &amp;gt; 0;

        const toolResults: ToolResultMessage[] = [];
        if (hasMoreToolCalls) {
          const toolExecution = await executeToolCalls(
            currentContext.tools,
            message,
            signal,
            stream,
            config.getSteeringMessages,
          );
          toolResults.push(...toolExecution.toolResults);
          steeringAfterTools = toolExecution.steeringMessages ?? null;

          for (const result of toolResults) {
            currentContext.messages.push(result);
            newMessages.push(result);
          }
        }

        stream.push({ type: &amp;quot;turn_end&amp;quot;, message, toolResults });

        // Get steering messages after turn completes
        if (steeringAfterTools &amp;amp;&amp;amp; steeringAfterTools.length &amp;gt; 0) {
          pendingMessages = steeringAfterTools;
          steeringAfterTools = null;
        } else {
          pendingMessages = (await config.getSteeringMessages?.()) || [];
        }
      }

      // Agent would stop here. Check for follow-up messages.
      const followUpMessages = (await config.getFollowUpMessages?.()) || [];
      if (followUpMessages.length &amp;gt; 0) {
        // Set as pending so inner loop processes them
        pendingMessages = followUpMessages;
        continue;
      }

      // No more messages, exit
      break;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This loop is conceptually simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User sends messages to AI.&lt;/li&gt;
&lt;li&gt;AI decides it needs tool calls, executes them, and gets results.&lt;/li&gt;
&lt;li&gt;AI checks results; if it needs more tools, repeat.&lt;/li&gt;
&lt;li&gt;AI finishes and checks for follow-up messages; continue if present, otherwise stop.&lt;/li&gt;
&lt;/ol&gt;


    &lt;p&gt;Tags: &lt;a href="https://odux.com/tags/AI"&gt;AI&lt;/a&gt;, &lt;a href="https://odux.com/tags/programming"&gt;programming&lt;/a&gt;, &lt;a href="https://odux.com/tags/architecture"&gt;architecture&lt;/a&gt;&lt;/p&gt;



</summary><category term="AI"/><category term="programming"/><category term="architecture"/></entry><entry><title>Auth Problem Looked Bigger Than It Was</title><link href="https://odux.com/2026/Feb/5/auth-problem-looked-bigger-than-it-was/#atom-tag" rel="alternate"/><published>2026-02-05T19:37:20+00:00</published><updated>2026-02-05T19:37:20+00:00</updated><id>https://odux.com/2026/Feb/5/auth-problem-looked-bigger-than-it-was/#atom-tag</id><summary type="html">
    I spent most of this afternoon deep in the weeds designing an auth bridge between an existing cluster of servers and a new service used by the same clients base across the servers. The initial conversations went straight to the “big” answers—Cognito, full OAuth flows, external identity plumbing everywhere—and for a while it felt like the only responsible path was also the most complex one.
&lt;br/&gt;&lt;br/&gt;
Then, after nearly two hours, I realized what we really needed was a trusted issuer and a trusted verifier. We can use the existing platform to issue JWT bearer tokens from our user/client model, sign them with private keys we control, and let the new service verify them with public keys while enforcing claims like issuer, audience, scope, subject, and expiry.
&lt;br/&gt;&lt;br/&gt;
Suddenly the design felt natural: no per-request callback to the issuer, no unnecessary moving parts, and clean attribution of every service call to a known user and client for metering and audit.
&lt;br/&gt;&lt;br/&gt;
A good reminder that “production-grade” doesn’t always mean “maximal complexity”—sometimes the strongest design is the one that makes trust boundaries explicit and keeps the system understandable.
    
        &lt;p&gt;Tags: &lt;a href="https://odux.com/tags/programming"&gt;programming&lt;/a&gt;, &lt;a href="https://odux.com/tags/architecture"&gt;architecture&lt;/a&gt;, &lt;a href="https://odux.com/tags/authentication"&gt;authentication&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="programming"/><category term="architecture"/><category term="authentication"/></entry></feed>