<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Projects · djuleayo</title>
    <link>https://c46f8f3d.personalpage-ahl.pages.dev/projects/</link>
    <description>Things I build, and the ideas behind them.</description>
    <generator>Hugo</generator>
    <language>en</language>
    <atom:link href="https://c46f8f3d.personalpage-ahl.pages.dev/projects/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>Trust Based Auth</title>
      <link>https://c46f8f3d.personalpage-ahl.pages.dev/projects/trust-based-auth/</link>
      <guid>https://c46f8f3d.personalpage-ahl.pages.dev/projects/trust-based-auth/</guid>
      <pubDate>Tue, 03 Nov 2026 00:00:00 UTC</pubDate>
      <description>&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Trust based auth removes sign-up, passwords, and repeat verification.&amp;lt;/strong&amp;gt; Successful interactions earn direct access. Telephony connects calls to web intent, and SMS activates a remembered browser session. Returning users act directly while the server checks current permission.&amp;lt;/p&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram&amp;#34; aria-labelledby=&amp;#34;trust-journey-caption&amp;#34;&amp;gt;
  &amp;lt;div class=&amp;#34;project-diagram-body&amp;#34;&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;First interaction &amp;lt;span&amp;gt;Establish trust and a browser session&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;ol class=&amp;#34;project-flow project-flow-four&amp;#34;&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Start in the app&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Intent saved, no sign-up&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Confirm by phone&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Call linked to web intent&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;flow-positive&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;Earn trust&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Successful fulfillment confirmed&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Activate browser&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;SMS code establishes session&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
    &amp;lt;div class=&amp;#34;journey-divider&amp;#34; aria-hidden=&amp;#34;true&amp;#34;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;Return visits &amp;lt;span&amp;gt;Less work for the user; permission still checked&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;ol class=&amp;#34;project-flow project-flow-three&amp;#34;&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Return&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Browser remembers the session&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Check permission&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Server checks current trust&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;flow-positive&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;Act directly&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;No repeat call or SMS code&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;figcaption id=&amp;#34;trust-journey-caption&amp;#34;&amp;gt;Earned trust removes repeat steps while an active session and current permission allow direct access.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;h2 id=&amp;#34;friction-removal-by-design&amp;#34;&amp;gt;
  Friction removal by design
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#friction-removal-by-design&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;The app lets people express intent before asking them to establish an identity. Their work is already captured when the first call confirms the interaction, and successful fulfillment earns a shorter path next time.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Trust carries forward across visits. SMS activation establishes the browser session; it does not interrupt every subsequent action. A new browser or an expired session needs activation again.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;telephony-integration&amp;#34;&amp;gt;
  Telephony integration
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#telephony-integration&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;The phone call arrives with context.&amp;lt;/strong&amp;gt; An Asterisk/GSM gateway sends call events to the web application, where the caller&amp;amp;rsquo;s phone number is connected to saved intent. An operator can see what the person has already selected. If there are multiple candidate carts, a short cart code resolves the match.&amp;lt;/p&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram&amp;#34; aria-labelledby=&amp;#34;telephony-integration-caption&amp;#34;&amp;gt;
  &amp;lt;div class=&amp;#34;project-diagram-body&amp;#34;&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;Inbound call &amp;lt;span&amp;gt;Bring the user&amp;#39;s web context into the conversation&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;ol class=&amp;#34;project-flow project-flow-three&amp;#34;&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;User calls&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;From their phone&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Asterisk/GSM gateway&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Authenticated call webhook&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;flow-positive&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;Web application&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Match caller to saved intent&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
    &amp;lt;div class=&amp;#34;journey-divider&amp;#34; aria-hidden=&amp;#34;true&amp;#34;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;Outbound SMS &amp;lt;span&amp;gt;Connect the phone to a remembered browser session&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;ol class=&amp;#34;project-flow project-flow-three&amp;#34;&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Web application&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Queue activation code&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;GSM gateway&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Deliver SMS&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;flow-positive&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;User&amp;#39;s phone&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Code activates browser session&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;figcaption id=&amp;#34;telephony-integration-caption&amp;#34;&amp;gt;Calls carry context into the app; SMS connects the user back to it.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;p&amp;gt;The same integration carries SMS activation back to the user&amp;amp;rsquo;s phone. Calls, saved web intent, and session activation become parts of one flow, reducing repeated explanations and data entry.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;trust-follows-outcomes&amp;#34;&amp;gt;
  Trust follows outcomes
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#trust-follows-outcomes&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Fulfilled commitments unlock direct access. Recorded no-shows restrict it.&amp;lt;/strong&amp;gt; An operator confirms the outcome; the server updates the user&amp;amp;rsquo;s trust state.&amp;lt;/p&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram&amp;#34; aria-labelledby=&amp;#34;trust-outcomes-caption&amp;#34;&amp;gt;
  &amp;lt;div class=&amp;#34;project-diagram-body&amp;#34;&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;Trust updates &amp;lt;span&amp;gt;Real outcomes determine permission&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;div class=&amp;#34;trust-root&amp;#34;&amp;gt;Operator confirms the outcome&amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;#34;trust-branches&amp;#34;&amp;gt;
      &amp;lt;div class=&amp;#34;trust-branch&amp;#34;&amp;gt;
        &amp;lt;p class=&amp;#34;trust-event&amp;#34;&amp;gt;Fulfillment confirmed&amp;lt;/p&amp;gt;
        &amp;lt;div class=&amp;#34;trust-result flow-positive&amp;#34;&amp;gt;
          &amp;lt;strong&amp;gt;TRUSTED&amp;lt;/strong&amp;gt;
          &amp;lt;span&amp;gt;Direct access allowed&amp;lt;/span&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div class=&amp;#34;trust-branch&amp;#34;&amp;gt;
        &amp;lt;p class=&amp;#34;trust-event&amp;#34;&amp;gt;No-show recorded&amp;lt;/p&amp;gt;
        &amp;lt;div class=&amp;#34;trust-result flow-restricted&amp;#34;&amp;gt;
          &amp;lt;strong&amp;gt;NOT_TRUSTED&amp;lt;/strong&amp;gt;
          &amp;lt;span&amp;gt;Direct checkout blocked&amp;lt;/span&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;p class=&amp;#34;trust-check&amp;#34;&amp;gt;Every direct checkout checks the current trust state and the phone-linked session.&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;figcaption id=&amp;#34;trust-outcomes-caption&amp;#34;&amp;gt;Operator-confirmed outcomes update trust. An existing browser session cannot bypass the permission check.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;p&amp;gt;New phone numbers start as &amp;lt;code&amp;gt;UNKNOWN&amp;lt;/code&amp;gt;. A confirmed fulfillment changes the user to &amp;lt;code&amp;gt;TRUSTED&amp;lt;/code&amp;gt;; a recorded no-show changes them to &amp;lt;code&amp;gt;NOT_TRUSTED&amp;lt;/code&amp;gt;. The current implementation uses completed orders as the evidence for these decisions.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;The model also supports &amp;lt;code&amp;gt;MEDIUM&amp;lt;/code&amp;gt;, which permits direct orders with the possibility of operator review. The outcome handler currently assigns the trusted and restricted states; it does not automatically assign this intermediate state.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;keep-permission-current&amp;#34;&amp;gt;
  Keep permission current
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#keep-permission-current&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;A remembered browser still needs current permission.&amp;lt;/strong&amp;gt; SMS activation establishes access to a phone-linked session. The outcome history determines whether that user is eligible to act directly.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;At every direct checkout, the server checks session validity, the matching phone number, and the user&amp;amp;rsquo;s current trust state. A restriction takes effect even if the browser still has a valid session.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Session tokens are hashed on the server and carried in an HttpOnly cookie. Activation codes expire and have limited attempts. Trust changes record the previous state, new state, related order, and responsible operator.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;implementation&amp;#34;&amp;gt;
  Implementation
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#implementation&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;Built with &amp;lt;strong&amp;gt;Next.js, TypeScript, Prisma, and SQLite&amp;lt;/strong&amp;gt;, with &amp;lt;strong&amp;gt;Asterisk/GSM&amp;lt;/strong&amp;gt; for calls and SMS. The operator workspace brings call context, saved carts, fulfillment, and trust decisions together.&amp;lt;/p&amp;gt;
</description>
      <content:encoded>&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Trust based auth removes sign-up, passwords, and repeat verification.&amp;lt;/strong&amp;gt; Successful interactions earn direct access. Telephony connects calls to web intent, and SMS activates a remembered browser session. Returning users act directly while the server checks current permission.&amp;lt;/p&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram&amp;#34; aria-labelledby=&amp;#34;trust-journey-caption&amp;#34;&amp;gt;
  &amp;lt;div class=&amp;#34;project-diagram-body&amp;#34;&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;First interaction &amp;lt;span&amp;gt;Establish trust and a browser session&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;ol class=&amp;#34;project-flow project-flow-four&amp;#34;&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Start in the app&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Intent saved, no sign-up&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Confirm by phone&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Call linked to web intent&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;flow-positive&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;Earn trust&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Successful fulfillment confirmed&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Activate browser&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;SMS code establishes session&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
    &amp;lt;div class=&amp;#34;journey-divider&amp;#34; aria-hidden=&amp;#34;true&amp;#34;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;Return visits &amp;lt;span&amp;gt;Less work for the user; permission still checked&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;ol class=&amp;#34;project-flow project-flow-three&amp;#34;&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Return&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Browser remembers the session&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Check permission&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Server checks current trust&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;flow-positive&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;Act directly&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;No repeat call or SMS code&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;figcaption id=&amp;#34;trust-journey-caption&amp;#34;&amp;gt;Earned trust removes repeat steps while an active session and current permission allow direct access.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;h2 id=&amp;#34;friction-removal-by-design&amp;#34;&amp;gt;
  Friction removal by design
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#friction-removal-by-design&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;The app lets people express intent before asking them to establish an identity. Their work is already captured when the first call confirms the interaction, and successful fulfillment earns a shorter path next time.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Trust carries forward across visits. SMS activation establishes the browser session; it does not interrupt every subsequent action. A new browser or an expired session needs activation again.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;telephony-integration&amp;#34;&amp;gt;
  Telephony integration
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#telephony-integration&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;The phone call arrives with context.&amp;lt;/strong&amp;gt; An Asterisk/GSM gateway sends call events to the web application, where the caller&amp;amp;rsquo;s phone number is connected to saved intent. An operator can see what the person has already selected. If there are multiple candidate carts, a short cart code resolves the match.&amp;lt;/p&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram&amp;#34; aria-labelledby=&amp;#34;telephony-integration-caption&amp;#34;&amp;gt;
  &amp;lt;div class=&amp;#34;project-diagram-body&amp;#34;&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;Inbound call &amp;lt;span&amp;gt;Bring the user&amp;#39;s web context into the conversation&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;ol class=&amp;#34;project-flow project-flow-three&amp;#34;&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;User calls&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;From their phone&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Asterisk/GSM gateway&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Authenticated call webhook&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;flow-positive&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;Web application&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Match caller to saved intent&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
    &amp;lt;div class=&amp;#34;journey-divider&amp;#34; aria-hidden=&amp;#34;true&amp;#34;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;Outbound SMS &amp;lt;span&amp;gt;Connect the phone to a remembered browser session&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;ol class=&amp;#34;project-flow project-flow-three&amp;#34;&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Web application&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Queue activation code&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;GSM gateway&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Deliver SMS&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;flow-positive&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;User&amp;#39;s phone&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Code activates browser session&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;figcaption id=&amp;#34;telephony-integration-caption&amp;#34;&amp;gt;Calls carry context into the app; SMS connects the user back to it.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;p&amp;gt;The same integration carries SMS activation back to the user&amp;amp;rsquo;s phone. Calls, saved web intent, and session activation become parts of one flow, reducing repeated explanations and data entry.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;trust-follows-outcomes&amp;#34;&amp;gt;
  Trust follows outcomes
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#trust-follows-outcomes&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Fulfilled commitments unlock direct access. Recorded no-shows restrict it.&amp;lt;/strong&amp;gt; An operator confirms the outcome; the server updates the user&amp;amp;rsquo;s trust state.&amp;lt;/p&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram&amp;#34; aria-labelledby=&amp;#34;trust-outcomes-caption&amp;#34;&amp;gt;
  &amp;lt;div class=&amp;#34;project-diagram-body&amp;#34;&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;Trust updates &amp;lt;span&amp;gt;Real outcomes determine permission&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;div class=&amp;#34;trust-root&amp;#34;&amp;gt;Operator confirms the outcome&amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;#34;trust-branches&amp;#34;&amp;gt;
      &amp;lt;div class=&amp;#34;trust-branch&amp;#34;&amp;gt;
        &amp;lt;p class=&amp;#34;trust-event&amp;#34;&amp;gt;Fulfillment confirmed&amp;lt;/p&amp;gt;
        &amp;lt;div class=&amp;#34;trust-result flow-positive&amp;#34;&amp;gt;
          &amp;lt;strong&amp;gt;TRUSTED&amp;lt;/strong&amp;gt;
          &amp;lt;span&amp;gt;Direct access allowed&amp;lt;/span&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div class=&amp;#34;trust-branch&amp;#34;&amp;gt;
        &amp;lt;p class=&amp;#34;trust-event&amp;#34;&amp;gt;No-show recorded&amp;lt;/p&amp;gt;
        &amp;lt;div class=&amp;#34;trust-result flow-restricted&amp;#34;&amp;gt;
          &amp;lt;strong&amp;gt;NOT_TRUSTED&amp;lt;/strong&amp;gt;
          &amp;lt;span&amp;gt;Direct checkout blocked&amp;lt;/span&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;p class=&amp;#34;trust-check&amp;#34;&amp;gt;Every direct checkout checks the current trust state and the phone-linked session.&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;figcaption id=&amp;#34;trust-outcomes-caption&amp;#34;&amp;gt;Operator-confirmed outcomes update trust. An existing browser session cannot bypass the permission check.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;p&amp;gt;New phone numbers start as &amp;lt;code&amp;gt;UNKNOWN&amp;lt;/code&amp;gt;. A confirmed fulfillment changes the user to &amp;lt;code&amp;gt;TRUSTED&amp;lt;/code&amp;gt;; a recorded no-show changes them to &amp;lt;code&amp;gt;NOT_TRUSTED&amp;lt;/code&amp;gt;. The current implementation uses completed orders as the evidence for these decisions.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;The model also supports &amp;lt;code&amp;gt;MEDIUM&amp;lt;/code&amp;gt;, which permits direct orders with the possibility of operator review. The outcome handler currently assigns the trusted and restricted states; it does not automatically assign this intermediate state.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;keep-permission-current&amp;#34;&amp;gt;
  Keep permission current
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#keep-permission-current&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;A remembered browser still needs current permission.&amp;lt;/strong&amp;gt; SMS activation establishes access to a phone-linked session. The outcome history determines whether that user is eligible to act directly.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;At every direct checkout, the server checks session validity, the matching phone number, and the user&amp;amp;rsquo;s current trust state. A restriction takes effect even if the browser still has a valid session.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Session tokens are hashed on the server and carried in an HttpOnly cookie. Activation codes expire and have limited attempts. Trust changes record the previous state, new state, related order, and responsible operator.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;implementation&amp;#34;&amp;gt;
  Implementation
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#implementation&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;Built with &amp;lt;strong&amp;gt;Next.js, TypeScript, Prisma, and SQLite&amp;lt;/strong&amp;gt;, with &amp;lt;strong&amp;gt;Asterisk/GSM&amp;lt;/strong&amp;gt; for calls and SMS. The operator workspace brings call context, saved carts, fulfillment, and trust decisions together.&amp;lt;/p&amp;gt;
</content:encoded>
    </item><item>
      <title>Agentic Voice System</title>
      <link>https://c46f8f3d.personalpage-ahl.pages.dev/projects/agentic-voice-agent/</link>
      <guid>https://c46f8f3d.personalpage-ahl.pages.dev/projects/agentic-voice-agent/</guid>
      <pubDate>Tue, 03 Nov 2026 00:00:00 UTC</pubDate>
      <description>&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;LLM voice agents that handle real calls, use your business systems, and follow workflows you can test and control.&amp;lt;/strong&amp;gt; Callers can ask questions, correct information, and interrupt. The system interprets their meaning, keeps accepted task state, and carries the work forward.&amp;lt;/p&amp;gt;
&amp;lt;nav class=&amp;#34;project-jumps&amp;#34; aria-label=&amp;#34;Explore this project&amp;#34;&amp;gt;
  &amp;lt;a href=&amp;#34;#a-call-that-stays-on-task&amp;#34;&amp;gt;Call example&amp;lt;/a&amp;gt;
  &amp;lt;a href=&amp;#34;#write-the-workflow-with-ordinary-awaits&amp;#34;&amp;gt;Authoring&amp;lt;/a&amp;gt;
  &amp;lt;a href=&amp;#34;#test-models-and-control-performance&amp;#34;&amp;gt;Testing &amp;amp;amp; performance&amp;lt;/a&amp;gt;
  &amp;lt;a href=&amp;#34;#the-architecture-behind-the-behavior&amp;#34;&amp;gt;Architecture&amp;lt;/a&amp;gt;
  &amp;lt;a href=&amp;#34;#telephony-and-infrastructure&amp;#34;&amp;gt;Infrastructure&amp;lt;/a&amp;gt;
  &amp;lt;a href=&amp;#34;#rsi-trace-driven-improvement&amp;#34;&amp;gt;RSI loop&amp;lt;/a&amp;gt;
&amp;lt;/nav&amp;gt;

&amp;lt;h2 id=&amp;#34;a-call-that-stays-on-task&amp;#34;&amp;gt;
  A call that stays on task
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#a-call-that-stays-on-task&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram voice-call&amp;#34; aria-labelledby=&amp;#34;voice-agent-call-caption&amp;#34;&amp;gt;
  &amp;lt;div class=&amp;#34;project-diagram-body&amp;#34;&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;A question becomes a detour, then the task continues &amp;lt;span&amp;gt;Illustrative call and execution&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;ol class=&amp;#34;call-steps&amp;#34; role=&amp;#34;list&amp;#34;&amp;gt;
      &amp;lt;li&amp;gt;
        &amp;lt;div&amp;gt;&amp;lt;strong&amp;gt;Ask&amp;lt;/strong&amp;gt;&amp;lt;p&amp;gt;The agent requests the caller&amp;#39;s first name.&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;p class=&amp;#34;call-execution&amp;#34;&amp;gt;&amp;lt;code&amp;gt;collectGivenName(...)&amp;lt;/code&amp;gt; waits for an accepted answer.&amp;lt;/p&amp;gt;
      &amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;
        &amp;lt;div&amp;gt;&amp;lt;strong&amp;gt;Explain&amp;lt;/strong&amp;gt;&amp;lt;p&amp;gt;Caller: “Why do you need my name?” The agent explains its declared purpose.&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;p class=&amp;#34;call-execution&amp;#34;&amp;gt;Navigation visits the explanation goal; collection stays pending.&amp;lt;/p&amp;gt;
      &amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;
        &amp;lt;div&amp;gt;&amp;lt;strong&amp;gt;Resume&amp;lt;/strong&amp;gt;&amp;lt;p&amp;gt;The caller supplies a name. The agent accepts the validated answer and continues the task.&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;p class=&amp;#34;call-execution&amp;#34;&amp;gt;The collector returns. The parent resumes and invokes &amp;lt;code&amp;gt;inform(...)&amp;lt;/code&amp;gt;, which checks its remaining requirements.&amp;lt;/p&amp;gt;
      &amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;figcaption id=&amp;#34;voice-agent-call-caption&amp;#34;&amp;gt;A child goal can handle several turns while the parent&amp;#39;s await remains suspended.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;h2 id=&amp;#34;write-the-workflow-with-ordinary-awaits&amp;#34;&amp;gt;
  Write the workflow with ordinary awaits
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#write-the-workflow-with-ordinary-awaits&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;code&amp;gt;defineGoal&amp;lt;/code&amp;gt; realizes a controlled state machine through ordinary async functions.&amp;lt;/strong&amp;gt; The runtime keeps a logical goal stack alongside the language&amp;amp;rsquo;s async call chain. Each registered goal invocation adds a frame: a child can handle several conversational turns while its parent waits at &amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt;. An accepted result lets the parent resume.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Illustrative composition; goal definitions and business schemas are omitted:&amp;lt;/p&amp;gt;
&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ts&amp;#34; data-lang=&amp;#34;ts&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; handleCall &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; defineGoal(callDefinition, &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;async&amp;lt;/span&amp;gt; (input, ctx) &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;await&amp;lt;/span&amp;gt; greet(input.introduction, ctx);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;await&amp;lt;/span&amp;gt; introduce(input.introduction, ctx);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;if&amp;lt;/span&amp;gt; (&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;!&amp;lt;/span&amp;gt;ctx.state.values.identity&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;?&amp;lt;/span&amp;gt;.givenName) {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;await&amp;lt;/span&amp;gt; collectGivenName(input.collection, ctx);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  }
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;await&amp;lt;/span&amp;gt; inform(input.account, ctx);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;return&amp;lt;/span&amp;gt; callEnd({ ending&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a5d6ff&amp;#34;&amp;gt;&amp;amp;#34;achieved&amp;amp;#34;&amp;lt;/span&amp;gt; }, ctx);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;});
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;The interface uses control flow developers already know.&amp;lt;/strong&amp;gt; Calls, &amp;lt;code&amp;gt;if&amp;lt;/code&amp;gt;, loops, &amp;lt;code&amp;gt;try&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;catch&amp;lt;/code&amp;gt;, and returns express the workflow; goal frames add input/output validation, requirements, cancellation, and traces. Using the language&amp;amp;rsquo;s existing constructs reduces onboarding and everyday authoring friction from learning a state library&amp;amp;rsquo;s separate vocabulary. The graph visualizes goals and recorded transitions.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;test-models-and-control-performance&amp;#34;&amp;gt;
  Test models and control performance
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#test-models-and-control-performance&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Model adapters are replaceable; the workflow stays in application code.&amp;lt;/strong&amp;gt; Goals, validation rules, and allowed transitions keep their contracts as models and decision formulations change.&amp;lt;/p&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Test the workflow:&amp;lt;/strong&amp;gt; supply known decisions and values as fixtures, then check transitions, prerequisites, and interruption handling without live inference.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Measure the model:&amp;lt;/strong&amp;gt; run a fixed corpus of caller evidence and expected decisions through a candidate provider, retaining failures as well as successes.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Control the trade-off:&amp;lt;/strong&amp;gt; compare decision quality and request latency on the same cases before changing a binding. Phase boundaries help locate whether a regression came from routing, interpretation, extraction, or generation.&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;aside class=&amp;#34;voice-performance&amp;#34; aria-label=&amp;#34;Archived navigation result&amp;#34;&amp;gt;
  &amp;lt;p class=&amp;#34;voice-performance-score&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;93.4%&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;navigation accuracy&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
  &amp;lt;p class=&amp;#34;voice-performance-context&amp;#34;&amp;gt;One recorded example: H2O-Lightning 4B on previously observed synthetic navigation cases, October 2026. The corpus makes decision quality measurable as the setup changes.&amp;lt;/p&amp;gt;
&amp;lt;/aside&amp;gt;

&amp;lt;h2 id=&amp;#34;the-architecture-behind-the-behavior&amp;#34;&amp;gt;
  The architecture behind the behavior
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#the-architecture-behind-the-behavior&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Meaning comes before flow control.&amp;lt;/strong&amp;gt; “Why do you need my name?” calls for an explanation; “I won&amp;amp;rsquo;t give my name” calls for the declared refusal policy. Both mention the same field. Semantic interpretation establishes which behavior applies, and code controls the allowed transition.&amp;lt;/p&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram&amp;#34; aria-labelledby=&amp;#34;voice-agent-phases-caption&amp;#34;&amp;gt;
  &amp;lt;div class=&amp;#34;project-diagram-body&amp;#34;&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;Four phases &amp;lt;span&amp;gt;Route · interpret · extract · respond&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;ol class=&amp;#34;project-flow project-flow-four&amp;#34; role=&amp;#34;list&amp;#34;&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Navigation&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Choose a permitted goal&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Regression&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Select a declared semantic case&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Reduction&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Extract a provisional value&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Generation&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Produce a scoped reply&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-note&amp;#34;&amp;gt;Code validates candidates and controls acceptance. Branches without data skip reduction; successful collection can skip generation.&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;figcaption id=&amp;#34;voice-agent-phases-caption&amp;#34;&amp;gt;Four responsibilities, each with a limited output contract and invoked when needed.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;p&amp;gt;Navigation and regression can use specialised finite-choice decision models. This implementation supports a local H2O model and an OpenAI navigation binding; arbitrary extraction and new free-form replies use a generative LLM. Authored templates can be delivered directly.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Prepared turns remain provisional until accepted; cancellation preserves the suspended task. Speech delivery is tracked separately, so an interrupted message remains pending and changed facts can invalidate stale speech.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;telephony-and-infrastructure&amp;#34;&amp;gt;
  Telephony and infrastructure
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#telephony-and-infrastructure&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;SIP telephony connects inbound and outbound calls to the agent.&amp;lt;/strong&amp;gt; LiveKit carries the media; isolated call processes coordinate speech providers, decisions, and business services. PostgreSQL and MinIO retain history and traces, with a dashboard for live and recorded execution.&amp;lt;/p&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram voice-infrastructure&amp;#34; aria-labelledby=&amp;#34;voice-agent-system-caption&amp;#34;&amp;gt;
  &amp;lt;div class=&amp;#34;project-diagram-body&amp;#34;&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;Media path and call execution&amp;lt;/p&amp;gt;
    &amp;lt;ol class=&amp;#34;project-flow project-flow-three project-flow-bidirectional&amp;#34; role=&amp;#34;list&amp;#34;&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Phone network&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Inbound and outbound SIP / PBX&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;LiveKit &#43; SIP bridge&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Rooms, media, and Redis coordination&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;flow-positive&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;Call worker&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Isolated process for each admitted call&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
    &amp;lt;div class=&amp;#34;journey-divider&amp;#34; aria-hidden=&amp;#34;true&amp;#34;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;Services around the worker&amp;lt;/p&amp;gt;
    &amp;lt;ul class=&amp;#34;infra-services&amp;#34; role=&amp;#34;list&amp;#34;&amp;gt;
      &amp;lt;li class=&amp;#34;project-node&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;Speech providers&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Recognition and synthesis&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;project-node&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;Decision models &#43; LLM&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Local GPU gateway in dev; provider APIs&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;project-node&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;PostgreSQL &#43; MinIO&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;History, checkpoints, audio, and traces&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;project-node&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;Operations&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Next.js dashboard; Prometheus / Grafana&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;figcaption id=&amp;#34;voice-agent-system-caption&amp;#34;&amp;gt;Two-way media connects the caller and agent; the worker uses models and business services and records execution.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;p&amp;gt;Docker deployments separate dev, stage, and production data while sharing telephony infrastructure. The development decision model can run behind an authenticated GPU gateway and SSH tunnel; Prometheus and Grafana provide host and container monitoring.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;rsi-trace-driven-improvement&amp;#34;&amp;gt;
  RSI: trace-driven improvement
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#rsi-trace-driven-improvement&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;RSI turns test calls into reviewable workflow improvements.&amp;lt;/strong&amp;gt; A larger model exercises the agent, inspects the event trace and &amp;lt;code&amp;gt;defineGoal&amp;lt;/code&amp;gt; code, and opens a merge request with a fix and a reproducing regression case.&amp;lt;/p&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram voice-rsi&amp;#34; aria-labelledby=&amp;#34;voice-agent-rsi-caption&amp;#34;&amp;gt;
  &amp;lt;div class=&amp;#34;project-diagram-body&amp;#34;&amp;gt;
    &amp;lt;ol class=&amp;#34;project-flow project-flow-four&amp;#34; role=&amp;#34;list&amp;#34;&amp;gt;
      &amp;lt;li class=&amp;#34;flow-positive&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;Make test calls&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Larger model acts as caller and produces traces&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Inspect the trace&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Larger model diagnoses goals and transitions&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Open an MR&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Flow changes, trace evidence, and a regression case&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Retest the candidate&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Corpus, replay, and further model-driven calls&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;figcaption id=&amp;#34;voice-agent-rsi-caption&amp;#34;&amp;gt;Corpus and replay checks support review of the candidate. Further calls exercise the revised workflow and feed the next iteration.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

</description>
      <content:encoded>&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;LLM voice agents that handle real calls, use your business systems, and follow workflows you can test and control.&amp;lt;/strong&amp;gt; Callers can ask questions, correct information, and interrupt. The system interprets their meaning, keeps accepted task state, and carries the work forward.&amp;lt;/p&amp;gt;
&amp;lt;nav class=&amp;#34;project-jumps&amp;#34; aria-label=&amp;#34;Explore this project&amp;#34;&amp;gt;
  &amp;lt;a href=&amp;#34;#a-call-that-stays-on-task&amp;#34;&amp;gt;Call example&amp;lt;/a&amp;gt;
  &amp;lt;a href=&amp;#34;#write-the-workflow-with-ordinary-awaits&amp;#34;&amp;gt;Authoring&amp;lt;/a&amp;gt;
  &amp;lt;a href=&amp;#34;#test-models-and-control-performance&amp;#34;&amp;gt;Testing &amp;amp;amp; performance&amp;lt;/a&amp;gt;
  &amp;lt;a href=&amp;#34;#the-architecture-behind-the-behavior&amp;#34;&amp;gt;Architecture&amp;lt;/a&amp;gt;
  &amp;lt;a href=&amp;#34;#telephony-and-infrastructure&amp;#34;&amp;gt;Infrastructure&amp;lt;/a&amp;gt;
  &amp;lt;a href=&amp;#34;#rsi-trace-driven-improvement&amp;#34;&amp;gt;RSI loop&amp;lt;/a&amp;gt;
&amp;lt;/nav&amp;gt;

&amp;lt;h2 id=&amp;#34;a-call-that-stays-on-task&amp;#34;&amp;gt;
  A call that stays on task
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#a-call-that-stays-on-task&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram voice-call&amp;#34; aria-labelledby=&amp;#34;voice-agent-call-caption&amp;#34;&amp;gt;
  &amp;lt;div class=&amp;#34;project-diagram-body&amp;#34;&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;A question becomes a detour, then the task continues &amp;lt;span&amp;gt;Illustrative call and execution&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;ol class=&amp;#34;call-steps&amp;#34; role=&amp;#34;list&amp;#34;&amp;gt;
      &amp;lt;li&amp;gt;
        &amp;lt;div&amp;gt;&amp;lt;strong&amp;gt;Ask&amp;lt;/strong&amp;gt;&amp;lt;p&amp;gt;The agent requests the caller&amp;#39;s first name.&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;p class=&amp;#34;call-execution&amp;#34;&amp;gt;&amp;lt;code&amp;gt;collectGivenName(...)&amp;lt;/code&amp;gt; waits for an accepted answer.&amp;lt;/p&amp;gt;
      &amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;
        &amp;lt;div&amp;gt;&amp;lt;strong&amp;gt;Explain&amp;lt;/strong&amp;gt;&amp;lt;p&amp;gt;Caller: “Why do you need my name?” The agent explains its declared purpose.&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;p class=&amp;#34;call-execution&amp;#34;&amp;gt;Navigation visits the explanation goal; collection stays pending.&amp;lt;/p&amp;gt;
      &amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;
        &amp;lt;div&amp;gt;&amp;lt;strong&amp;gt;Resume&amp;lt;/strong&amp;gt;&amp;lt;p&amp;gt;The caller supplies a name. The agent accepts the validated answer and continues the task.&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;p class=&amp;#34;call-execution&amp;#34;&amp;gt;The collector returns. The parent resumes and invokes &amp;lt;code&amp;gt;inform(...)&amp;lt;/code&amp;gt;, which checks its remaining requirements.&amp;lt;/p&amp;gt;
      &amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;figcaption id=&amp;#34;voice-agent-call-caption&amp;#34;&amp;gt;A child goal can handle several turns while the parent&amp;#39;s await remains suspended.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;h2 id=&amp;#34;write-the-workflow-with-ordinary-awaits&amp;#34;&amp;gt;
  Write the workflow with ordinary awaits
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#write-the-workflow-with-ordinary-awaits&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;&amp;lt;code&amp;gt;defineGoal&amp;lt;/code&amp;gt; realizes a controlled state machine through ordinary async functions.&amp;lt;/strong&amp;gt; The runtime keeps a logical goal stack alongside the language&amp;amp;rsquo;s async call chain. Each registered goal invocation adds a frame: a child can handle several conversational turns while its parent waits at &amp;lt;code&amp;gt;await&amp;lt;/code&amp;gt;. An accepted result lets the parent resume.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Illustrative composition; goal definitions and business schemas are omitted:&amp;lt;/p&amp;gt;
&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ts&amp;#34; data-lang=&amp;#34;ts&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; handleCall &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; defineGoal(callDefinition, &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;async&amp;lt;/span&amp;gt; (input, ctx) &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;amp;gt;&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;await&amp;lt;/span&amp;gt; greet(input.introduction, ctx);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;await&amp;lt;/span&amp;gt; introduce(input.introduction, ctx);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;if&amp;lt;/span&amp;gt; (&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;!&amp;lt;/span&amp;gt;ctx.state.values.identity&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;?&amp;lt;/span&amp;gt;.givenName) {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;await&amp;lt;/span&amp;gt; collectGivenName(input.collection, ctx);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  }
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;await&amp;lt;/span&amp;gt; inform(input.account, ctx);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;return&amp;lt;/span&amp;gt; callEnd({ ending&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a5d6ff&amp;#34;&amp;gt;&amp;amp;#34;achieved&amp;amp;#34;&amp;lt;/span&amp;gt; }, ctx);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;});
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;The interface uses control flow developers already know.&amp;lt;/strong&amp;gt; Calls, &amp;lt;code&amp;gt;if&amp;lt;/code&amp;gt;, loops, &amp;lt;code&amp;gt;try&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;catch&amp;lt;/code&amp;gt;, and returns express the workflow; goal frames add input/output validation, requirements, cancellation, and traces. Using the language&amp;amp;rsquo;s existing constructs reduces onboarding and everyday authoring friction from learning a state library&amp;amp;rsquo;s separate vocabulary. The graph visualizes goals and recorded transitions.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;test-models-and-control-performance&amp;#34;&amp;gt;
  Test models and control performance
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#test-models-and-control-performance&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Model adapters are replaceable; the workflow stays in application code.&amp;lt;/strong&amp;gt; Goals, validation rules, and allowed transitions keep their contracts as models and decision formulations change.&amp;lt;/p&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Test the workflow:&amp;lt;/strong&amp;gt; supply known decisions and values as fixtures, then check transitions, prerequisites, and interruption handling without live inference.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Measure the model:&amp;lt;/strong&amp;gt; run a fixed corpus of caller evidence and expected decisions through a candidate provider, retaining failures as well as successes.&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Control the trade-off:&amp;lt;/strong&amp;gt; compare decision quality and request latency on the same cases before changing a binding. Phase boundaries help locate whether a regression came from routing, interpretation, extraction, or generation.&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;aside class=&amp;#34;voice-performance&amp;#34; aria-label=&amp;#34;Archived navigation result&amp;#34;&amp;gt;
  &amp;lt;p class=&amp;#34;voice-performance-score&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;93.4%&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;navigation accuracy&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
  &amp;lt;p class=&amp;#34;voice-performance-context&amp;#34;&amp;gt;One recorded example: H2O-Lightning 4B on previously observed synthetic navigation cases, October 2026. The corpus makes decision quality measurable as the setup changes.&amp;lt;/p&amp;gt;
&amp;lt;/aside&amp;gt;

&amp;lt;h2 id=&amp;#34;the-architecture-behind-the-behavior&amp;#34;&amp;gt;
  The architecture behind the behavior
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#the-architecture-behind-the-behavior&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Meaning comes before flow control.&amp;lt;/strong&amp;gt; “Why do you need my name?” calls for an explanation; “I won&amp;amp;rsquo;t give my name” calls for the declared refusal policy. Both mention the same field. Semantic interpretation establishes which behavior applies, and code controls the allowed transition.&amp;lt;/p&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram&amp;#34; aria-labelledby=&amp;#34;voice-agent-phases-caption&amp;#34;&amp;gt;
  &amp;lt;div class=&amp;#34;project-diagram-body&amp;#34;&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;Four phases &amp;lt;span&amp;gt;Route · interpret · extract · respond&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;ol class=&amp;#34;project-flow project-flow-four&amp;#34; role=&amp;#34;list&amp;#34;&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Navigation&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Choose a permitted goal&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Regression&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Select a declared semantic case&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Reduction&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Extract a provisional value&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Generation&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Produce a scoped reply&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-note&amp;#34;&amp;gt;Code validates candidates and controls acceptance. Branches without data skip reduction; successful collection can skip generation.&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;figcaption id=&amp;#34;voice-agent-phases-caption&amp;#34;&amp;gt;Four responsibilities, each with a limited output contract and invoked when needed.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;p&amp;gt;Navigation and regression can use specialised finite-choice decision models. This implementation supports a local H2O model and an OpenAI navigation binding; arbitrary extraction and new free-form replies use a generative LLM. Authored templates can be delivered directly.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Prepared turns remain provisional until accepted; cancellation preserves the suspended task. Speech delivery is tracked separately, so an interrupted message remains pending and changed facts can invalidate stale speech.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;telephony-and-infrastructure&amp;#34;&amp;gt;
  Telephony and infrastructure
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#telephony-and-infrastructure&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;SIP telephony connects inbound and outbound calls to the agent.&amp;lt;/strong&amp;gt; LiveKit carries the media; isolated call processes coordinate speech providers, decisions, and business services. PostgreSQL and MinIO retain history and traces, with a dashboard for live and recorded execution.&amp;lt;/p&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram voice-infrastructure&amp;#34; aria-labelledby=&amp;#34;voice-agent-system-caption&amp;#34;&amp;gt;
  &amp;lt;div class=&amp;#34;project-diagram-body&amp;#34;&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;Media path and call execution&amp;lt;/p&amp;gt;
    &amp;lt;ol class=&amp;#34;project-flow project-flow-three project-flow-bidirectional&amp;#34; role=&amp;#34;list&amp;#34;&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Phone network&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Inbound and outbound SIP / PBX&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;LiveKit &#43; SIP bridge&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Rooms, media, and Redis coordination&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;flow-positive&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;Call worker&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Isolated process for each admitted call&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
    &amp;lt;div class=&amp;#34;journey-divider&amp;#34; aria-hidden=&amp;#34;true&amp;#34;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;Services around the worker&amp;lt;/p&amp;gt;
    &amp;lt;ul class=&amp;#34;infra-services&amp;#34; role=&amp;#34;list&amp;#34;&amp;gt;
      &amp;lt;li class=&amp;#34;project-node&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;Speech providers&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Recognition and synthesis&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;project-node&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;Decision models &#43; LLM&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Local GPU gateway in dev; provider APIs&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;project-node&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;PostgreSQL &#43; MinIO&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;History, checkpoints, audio, and traces&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;project-node&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;Operations&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Next.js dashboard; Prometheus / Grafana&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;figcaption id=&amp;#34;voice-agent-system-caption&amp;#34;&amp;gt;Two-way media connects the caller and agent; the worker uses models and business services and records execution.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;p&amp;gt;Docker deployments separate dev, stage, and production data while sharing telephony infrastructure. The development decision model can run behind an authenticated GPU gateway and SSH tunnel; Prometheus and Grafana provide host and container monitoring.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;rsi-trace-driven-improvement&amp;#34;&amp;gt;
  RSI: trace-driven improvement
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#rsi-trace-driven-improvement&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;RSI turns test calls into reviewable workflow improvements.&amp;lt;/strong&amp;gt; A larger model exercises the agent, inspects the event trace and &amp;lt;code&amp;gt;defineGoal&amp;lt;/code&amp;gt; code, and opens a merge request with a fix and a reproducing regression case.&amp;lt;/p&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram voice-rsi&amp;#34; aria-labelledby=&amp;#34;voice-agent-rsi-caption&amp;#34;&amp;gt;
  &amp;lt;div class=&amp;#34;project-diagram-body&amp;#34;&amp;gt;
    &amp;lt;ol class=&amp;#34;project-flow project-flow-four&amp;#34; role=&amp;#34;list&amp;#34;&amp;gt;
      &amp;lt;li class=&amp;#34;flow-positive&amp;#34;&amp;gt;&amp;lt;strong&amp;gt;Make test calls&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Larger model acts as caller and produces traces&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Inspect the trace&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Larger model diagnoses goals and transitions&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Open an MR&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Flow changes, trace evidence, and a regression case&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;strong&amp;gt;Retest the candidate&amp;lt;/strong&amp;gt;&amp;lt;span&amp;gt;Corpus, replay, and further model-driven calls&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ol&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;figcaption id=&amp;#34;voice-agent-rsi-caption&amp;#34;&amp;gt;Corpus and replay checks support review of the candidate. Further calls exercise the revised workflow and feed the next iteration.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

</content:encoded>
    </item><item>
      <title>Apispects</title>
      <link>https://c46f8f3d.personalpage-ahl.pages.dev/projects/apispects/</link>
      <guid>https://c46f8f3d.personalpage-ahl.pages.dev/projects/apispects/</guid>
      <pubDate>Tue, 03 Nov 2026 00:00:00 UTC</pubDate>
      <description>&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Reduce contract drift between backend and frontend with one shared API contract.&amp;lt;/strong&amp;gt; Apispects derives an Express router and typed client from the same TypeScript declaration, including request schemas, success responses, and business errors. Teams get editor feedback as the interface changes, with fewer definitions and integration conventions to maintain separately.&amp;lt;/p&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram api-contract&amp;#34; aria-labelledby=&amp;#34;apispects-contract-caption&amp;#34;&amp;gt;
  &amp;lt;div class=&amp;#34;project-diagram-body&amp;#34;&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;One contract connects both sides &amp;lt;span&amp;gt;Routes · Inputs · Responses · Business errors&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;div class=&amp;#34;project-node flow-positive contract-source&amp;#34;&amp;gt;
      &amp;lt;strong&amp;gt;Shared TypeScript contract&amp;lt;/strong&amp;gt;
      &amp;lt;span&amp;gt;Zod schemas &#43; &amp;lt;code&amp;gt;ApiSpec&amp;lt;/code&amp;gt;&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;ul class=&amp;#34;contract-consumers&amp;#34; role=&amp;#34;list&amp;#34;&amp;gt;
      &amp;lt;li class=&amp;#34;project-node&amp;#34;&amp;gt;
        &amp;lt;strong&amp;gt;Backend&amp;lt;/strong&amp;gt;
        &amp;lt;span&amp;gt;Express router and typed handlers&amp;lt;/span&amp;gt;
      &amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;project-node&amp;#34;&amp;gt;
        &amp;lt;strong&amp;gt;Frontend&amp;lt;/strong&amp;gt;
        &amp;lt;span&amp;gt;Typed calls, results, and business errors&amp;lt;/span&amp;gt;
      &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;figcaption id=&amp;#34;apispects-contract-caption&amp;#34;&amp;gt;Both interfaces derive from the same contract version. Teams keep ownership of their business logic and user experience.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;h2 id=&amp;#34;a-contribution-that-crosses-team-boundaries&amp;#34;&amp;gt;
  A contribution that crosses team boundaries
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#a-contribution-that-crosses-team-boundaries&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Standardize the integration work teams repeat.&amp;lt;/strong&amp;gt; Backend teams own handlers and business rules; frontend teams own the user experience. A shared contract gives both a consistent way to define endpoints, validate data, call services, and handle expected failures.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;A new endpoint follows the same path from declaration to handler to client call. Improvements to that path can benefit every team using it. Producers and consumers review changes against a common contract, reducing repeated decisions about payload shapes, endpoint wiring, and error formats.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;feedback-while-you-code&amp;#34;&amp;gt;
  Feedback while you code
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#feedback-while-you-code&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;The contract becomes the developer interface.&amp;lt;/strong&amp;gt; Autocomplete exposes available operations, accepted inputs, returned data, and declared business errors. Type inference carries changes into controller arguments and client calls when consumers compile against the updated contract.&amp;lt;/p&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram api-editor-demo&amp;#34; aria-labelledby=&amp;#34;apispects-demo-caption&amp;#34;&amp;gt;
  &amp;lt;a href=&amp;#34;/tsapispec.demo.gif&amp;#34; aria-label=&amp;#34;Open the TypeScript editor demo at full size&amp;#34;&amp;gt;
    &amp;lt;img src=&amp;#34;/tsapispec.demo.gif&amp;#34; width=&amp;#34;878&amp;#34; height=&amp;#34;520&amp;#34; loading=&amp;#34;lazy&amp;#34; decoding=&amp;#34;async&amp;#34; alt=&amp;#34;Editor demonstration of schema-driven autocomplete and type checking in a TypeScript controller.&amp;#34;&amp;gt;
  &amp;lt;/a&amp;gt;
  &amp;lt;figcaption id=&amp;#34;apispects-demo-caption&amp;#34;&amp;gt;Declared schemas supply controller types and immediate editor feedback. Open the recording to view it at full size.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;p&amp;gt;There is no separate client generation command or generated client file to refresh. TypeScript derives the interfaces; &amp;lt;code&amp;gt;makeApi&amp;lt;/code&amp;gt; constructs the router and client functions from the declaration.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;define-once-use-on-both-sides&amp;#34;&amp;gt;
  Define once, use on both sides
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#define-once-use-on-both-sides&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;Illustrative composition; the shared contract, server setup, and client setup belong in separate modules. Imports and business logic are omitted.&amp;lt;/p&amp;gt;
&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ts&amp;#34; data-lang=&amp;#34;ts&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;// Shared contract
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; spec &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  accounts&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    create_post&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      bodySchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.object&amp;lt;/span&amp;gt;({ name: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.string&amp;lt;/span&amp;gt;() }),
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      responseSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.object&amp;lt;/span&amp;gt;({ id: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.string&amp;lt;/span&amp;gt;() }),
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      cbErrorSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.literal&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#a5d6ff&amp;#34;&amp;gt;&amp;amp;#34;Name already taken&amp;amp;#34;&amp;lt;/span&amp;gt;),
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    },
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  },
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;} &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;as&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; satisfies ApiSpec;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;// Server: typed handler and Express router
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;makeController(spec.accounts.create_post, createAccount);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; { router } &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; makeApi(spec, {}, express.Router);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;// Client: typed calls and outcomes
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; { apiClient } &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; makeApi(spec, { baseUrl: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;apiOrigin&amp;lt;/span&amp;gt; });
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; result &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;await&amp;lt;/span&amp;gt; apiClient.accounts.create_post({
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  body&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; { name&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a5d6ff&amp;#34;&amp;gt;&amp;amp;#34;Ada&amp;amp;#34;&amp;lt;/span&amp;gt; },
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;});
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Business errors belong to the contract too.&amp;lt;/strong&amp;gt; The handler can return the declared &amp;lt;code&amp;gt;&amp;amp;quot;Name already taken&amp;amp;quot;&amp;lt;/code&amp;gt; case. The client receives it through the common error envelope with its payload type preserved. Consumers can handle expected failures using the interface they already share with the backend.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;check-the-boundary-at-runtime&amp;#34;&amp;gt;
  Check the boundary at runtime
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#check-the-boundary-at-runtime&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;The server validates incoming request fields against their schemas. The client parses successful response data against the response schema; development mode also checks controller output before sending it. These checks complement the types used during development.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Shared contract versions remain part of rollout coordination when clients and services deploy independently. The design reduces drift between maintained definitions and exposes incompatible data at the checked boundaries.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Built with &amp;lt;strong&amp;gt;TypeScript, Zod, Express, and fetch&amp;lt;/strong&amp;gt;. Explore the &amp;lt;a href=&amp;#34;https://github.com/apispects/core&amp;#34;  class=&amp;#34;external-link&amp;#34; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noopener&amp;#34;&amp;gt;source on GitHub&amp;lt;/a&amp;gt; or the &amp;lt;a href=&amp;#34;/posts/ts-api-spec/&amp;#34; &amp;gt;earlier TypeScript walkthrough&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;
</description>
      <content:encoded>&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Reduce contract drift between backend and frontend with one shared API contract.&amp;lt;/strong&amp;gt; Apispects derives an Express router and typed client from the same TypeScript declaration, including request schemas, success responses, and business errors. Teams get editor feedback as the interface changes, with fewer definitions and integration conventions to maintain separately.&amp;lt;/p&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram api-contract&amp;#34; aria-labelledby=&amp;#34;apispects-contract-caption&amp;#34;&amp;gt;
  &amp;lt;div class=&amp;#34;project-diagram-body&amp;#34;&amp;gt;
    &amp;lt;p class=&amp;#34;diagram-label&amp;#34;&amp;gt;One contract connects both sides &amp;lt;span&amp;gt;Routes · Inputs · Responses · Business errors&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;div class=&amp;#34;project-node flow-positive contract-source&amp;#34;&amp;gt;
      &amp;lt;strong&amp;gt;Shared TypeScript contract&amp;lt;/strong&amp;gt;
      &amp;lt;span&amp;gt;Zod schemas &#43; &amp;lt;code&amp;gt;ApiSpec&amp;lt;/code&amp;gt;&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;ul class=&amp;#34;contract-consumers&amp;#34; role=&amp;#34;list&amp;#34;&amp;gt;
      &amp;lt;li class=&amp;#34;project-node&amp;#34;&amp;gt;
        &amp;lt;strong&amp;gt;Backend&amp;lt;/strong&amp;gt;
        &amp;lt;span&amp;gt;Express router and typed handlers&amp;lt;/span&amp;gt;
      &amp;lt;/li&amp;gt;
      &amp;lt;li class=&amp;#34;project-node&amp;#34;&amp;gt;
        &amp;lt;strong&amp;gt;Frontend&amp;lt;/strong&amp;gt;
        &amp;lt;span&amp;gt;Typed calls, results, and business errors&amp;lt;/span&amp;gt;
      &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;figcaption id=&amp;#34;apispects-contract-caption&amp;#34;&amp;gt;Both interfaces derive from the same contract version. Teams keep ownership of their business logic and user experience.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;h2 id=&amp;#34;a-contribution-that-crosses-team-boundaries&amp;#34;&amp;gt;
  A contribution that crosses team boundaries
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#a-contribution-that-crosses-team-boundaries&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Standardize the integration work teams repeat.&amp;lt;/strong&amp;gt; Backend teams own handlers and business rules; frontend teams own the user experience. A shared contract gives both a consistent way to define endpoints, validate data, call services, and handle expected failures.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;A new endpoint follows the same path from declaration to handler to client call. Improvements to that path can benefit every team using it. Producers and consumers review changes against a common contract, reducing repeated decisions about payload shapes, endpoint wiring, and error formats.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;feedback-while-you-code&amp;#34;&amp;gt;
  Feedback while you code
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#feedback-while-you-code&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;The contract becomes the developer interface.&amp;lt;/strong&amp;gt; Autocomplete exposes available operations, accepted inputs, returned data, and declared business errors. Type inference carries changes into controller arguments and client calls when consumers compile against the updated contract.&amp;lt;/p&amp;gt;
&amp;lt;figure class=&amp;#34;project-diagram api-editor-demo&amp;#34; aria-labelledby=&amp;#34;apispects-demo-caption&amp;#34;&amp;gt;
  &amp;lt;a href=&amp;#34;/tsapispec.demo.gif&amp;#34; aria-label=&amp;#34;Open the TypeScript editor demo at full size&amp;#34;&amp;gt;
    &amp;lt;img src=&amp;#34;/tsapispec.demo.gif&amp;#34; width=&amp;#34;878&amp;#34; height=&amp;#34;520&amp;#34; loading=&amp;#34;lazy&amp;#34; decoding=&amp;#34;async&amp;#34; alt=&amp;#34;Editor demonstration of schema-driven autocomplete and type checking in a TypeScript controller.&amp;#34;&amp;gt;
  &amp;lt;/a&amp;gt;
  &amp;lt;figcaption id=&amp;#34;apispects-demo-caption&amp;#34;&amp;gt;Declared schemas supply controller types and immediate editor feedback. Open the recording to view it at full size.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;

&amp;lt;p&amp;gt;There is no separate client generation command or generated client file to refresh. TypeScript derives the interfaces; &amp;lt;code&amp;gt;makeApi&amp;lt;/code&amp;gt; constructs the router and client functions from the declaration.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;define-once-use-on-both-sides&amp;#34;&amp;gt;
  Define once, use on both sides
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#define-once-use-on-both-sides&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;Illustrative composition; the shared contract, server setup, and client setup belong in separate modules. Imports and business logic are omitted.&amp;lt;/p&amp;gt;
&amp;lt;div class=&amp;#34;highlight&amp;#34;&amp;gt;&amp;lt;pre tabindex=&amp;#34;0&amp;#34; style=&amp;#34;color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&amp;#34;&amp;gt;&amp;lt;code class=&amp;#34;language-ts&amp;#34; data-lang=&amp;#34;ts&amp;#34;&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;// Shared contract
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; spec &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  accounts&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    create_post&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; {
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      bodySchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.object&amp;lt;/span&amp;gt;({ name: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.string&amp;lt;/span&amp;gt;() }),
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      responseSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.object&amp;lt;/span&amp;gt;({ id: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.string&amp;lt;/span&amp;gt;() }),
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;      cbErrorSchema: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;z.literal&amp;lt;/span&amp;gt;(&amp;lt;span style=&amp;#34;color:#a5d6ff&amp;#34;&amp;gt;&amp;amp;#34;Name already taken&amp;amp;#34;&amp;lt;/span&amp;gt;),
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;    },
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  },
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;} &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;as&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; satisfies ApiSpec;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;// Server: typed handler and Express router
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;makeController(spec.accounts.create_post, createAccount);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; { router } &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; makeApi(spec, {}, express.Router);
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;// Client: typed calls and outcomes
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#8b949e;font-style:italic&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; { apiClient } &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; makeApi(spec, { baseUrl: &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;apiOrigin&amp;lt;/span&amp;gt; });
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;&amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;const&amp;lt;/span&amp;gt; result &amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;=&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#ff7b72&amp;#34;&amp;gt;await&amp;lt;/span&amp;gt; apiClient.accounts.create_post({
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;  body&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; { name&amp;lt;span style=&amp;#34;color:#ff7b72;font-weight:bold&amp;#34;&amp;gt;:&amp;lt;/span&amp;gt; &amp;lt;span style=&amp;#34;color:#a5d6ff&amp;#34;&amp;gt;&amp;amp;#34;Ada&amp;amp;#34;&amp;lt;/span&amp;gt; },
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;span style=&amp;#34;display:flex;&amp;#34;&amp;gt;&amp;lt;span&amp;gt;});
&amp;lt;/span&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Business errors belong to the contract too.&amp;lt;/strong&amp;gt; The handler can return the declared &amp;lt;code&amp;gt;&amp;amp;quot;Name already taken&amp;amp;quot;&amp;lt;/code&amp;gt; case. The client receives it through the common error envelope with its payload type preserved. Consumers can handle expected failures using the interface they already share with the backend.&amp;lt;/p&amp;gt;
&amp;lt;h2 id=&amp;#34;check-the-boundary-at-runtime&amp;#34;&amp;gt;
  Check the boundary at runtime
  &amp;lt;a class=&amp;#34;heading-link&amp;#34; href=&amp;#34;#check-the-boundary-at-runtime&amp;#34;&amp;gt;
    &amp;lt;i class=&amp;#34;fa-solid fa-link&amp;#34; aria-hidden=&amp;#34;true&amp;#34; title=&amp;#34;Link to heading&amp;#34;&amp;gt;&amp;lt;/i&amp;gt;
    &amp;lt;span class=&amp;#34;sr-only&amp;#34;&amp;gt;Link to heading&amp;lt;/span&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;The server validates incoming request fields against their schemas. The client parses successful response data against the response schema; development mode also checks controller output before sending it. These checks complement the types used during development.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Shared contract versions remain part of rollout coordination when clients and services deploy independently. The design reduces drift between maintained definitions and exposes incompatible data at the checked boundaries.&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Built with &amp;lt;strong&amp;gt;TypeScript, Zod, Express, and fetch&amp;lt;/strong&amp;gt;. Explore the &amp;lt;a href=&amp;#34;https://github.com/apispects/core&amp;#34;  class=&amp;#34;external-link&amp;#34; target=&amp;#34;_blank&amp;#34; rel=&amp;#34;noopener&amp;#34;&amp;gt;source on GitHub&amp;lt;/a&amp;gt; or the &amp;lt;a href=&amp;#34;/posts/ts-api-spec/&amp;#34; &amp;gt;earlier TypeScript walkthrough&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;
</content:encoded>
    </item>
  </channel>
</rss>
