<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dan </title>
    <description>The latest articles on DEV Community by Dan  (@tuobi).</description>
    <link>https://dev.to/tuobi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4116962%2F13834ee0-5734-409e-b181-8659d01989d1.jpg</url>
      <title>DEV Community: Dan </title>
      <link>https://dev.to/tuobi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tuobi"/>
    <language>en</language>
    <item>
      <title>We Kept Rebuilding Aiden for Six Months: From a HID Demo to a Physical AI Agent</title>
      <dc:creator>Dan </dc:creator>
      <pubDate>Wed, 09 Sep 2026 08:34:26 +0000</pubDate>
      <link>https://dev.to/tuobi/we-kept-rebuilding-aiden-for-six-months-from-a-hid-demo-to-a-physical-ai-agent-5boc</link>
      <guid>https://dev.to/tuobi/we-kept-rebuilding-aiden-for-six-months-from-a-hid-demo-to-a-physical-ai-agent-5boc</guid>
      <description>&lt;p&gt;Six months ago, Aiden was much closer to a hardware experiment than an Agent platform.&lt;/p&gt;

&lt;p&gt;The first public version proved a simple idea:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Capture the screen of a phone.&lt;/li&gt;
&lt;li&gt;Send that image to a multimodal model.&lt;/li&gt;
&lt;li&gt;Turn the model’s decision into keyboard, pointer, or touch input.&lt;/li&gt;
&lt;li&gt;Observe the result and repeat.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That was enough for a demo.&lt;/p&gt;

&lt;p&gt;It was not enough for a system that had to survive USB reconnects, stale screenshots, context limits, interrupted voice conversations, different operating systems, and tasks that occasionally needed a human.&lt;/p&gt;

&lt;p&gt;During roughly six months of development—the public repository history begins in late April—we kept rebuilding Aiden around those failures.&lt;/p&gt;

&lt;p&gt;This post is a tour of the current architecture, but more importantly, it is a record of the assumptions we had to replace along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Aiden actually is
&lt;/h2&gt;

&lt;p&gt;Aiden is a physical AI Agent project for operating phones and computers through an external hardware control path.&lt;/p&gt;

&lt;p&gt;The current development-board implementation connects to a target device through a USB-C hub:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the target display is captured through HDMI;&lt;/li&gt;
&lt;li&gt;an RK628D or TC358743 bridge converts the video signal to CSI;&lt;/li&gt;
&lt;li&gt;a C++ service captures frames from the Linux video device;&lt;/li&gt;
&lt;li&gt;the Go Agent sends visual observations to a configured multimodal model;&lt;/li&gt;
&lt;li&gt;the model returns structured tool calls;&lt;/li&gt;
&lt;li&gt;the Agent writes keyboard, pointer, touch, or auxiliary-control reports through Linux USB HID gadget devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The basic loop looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Target display
    |
    v
HDMI -&amp;gt; RK628D or TC358743 -&amp;gt; CSI
    |
    v
/dev/video0
    |
    v
frame_service
    |
    v
Screenshot observation
    |
    v
Go Agent -&amp;gt; configured model endpoint
    |
    v
Structured tool call
    |
    v
/dev/hidg0, /dev/hidg1, /dev/hidg2
    |
    v
Target device input
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voice adds another path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Board microphone
    |
    v
audio_service
    |
    v
VAD -&amp;gt; STT or audio attachment
    |
    v
Go Agent -&amp;gt; LLM
    |
    v
TTS
    |
    v
audio_service playback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The firmware and device-side Agent Runtime are open source. The current repository includes the firmware overlay, C++ hardware services, Go Agent, OTA tooling, tests, and benchmark infrastructure.&lt;/p&gt;

&lt;p&gt;It is not the final integrated Aiden hardware product.&lt;/p&gt;

&lt;p&gt;There is another boundary worth making clear:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Agent Runtime runs on the device. Model inference does not have to.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Screenshots, audio, and text are sent to the model, STT, TTS, and search endpoints configured by the owner. Those endpoints can be hosted or self-hosted. Aiden does not require an Aiden-hosted inference backend.&lt;/p&gt;

&lt;p&gt;You can inspect the current architecture in the &lt;a href="https://aidenai.io/docs/architecture/overview/" rel="noopener noreferrer"&gt;Aiden documentation&lt;/a&gt; and the &lt;a href="https://github.com/AidenAI-IO/aiden-firmware" rel="noopener noreferrer"&gt;open-source firmware repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture we ended up with
&lt;/h2&gt;

&lt;p&gt;The current system has several distinct layers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hardware abstraction&lt;/td&gt;
&lt;td&gt;Low-level GPIO, audio, video, and HID capabilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardware services&lt;/td&gt;
&lt;td&gt;Own shared resources such as video capture and audio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-language transport&lt;/td&gt;
&lt;td&gt;Connect Go and C++ through Unix domain sockets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go Agent&lt;/td&gt;
&lt;td&gt;Model requests, tools, memory, tasks, voice, and Web APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;USB gadget layer&lt;/td&gt;
&lt;td&gt;Keyboard, pointer/touch, auxiliary control, and ECM networking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firmware integration&lt;/td&gt;
&lt;td&gt;Startup scripts, configuration, watchdogs, OTA, and image generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evaluation&lt;/td&gt;
&lt;td&gt;Environment bridges, benchmarks, traces, screenshots, and reports&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four principles now shape most of the codebase:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A hardware resource should have one owner.&lt;/li&gt;
&lt;li&gt;Go and C++ should communicate through explicit protocols rather than a shared ABI.&lt;/li&gt;
&lt;li&gt;Agent reasoning should be decoupled from hardware implementation details.&lt;/li&gt;
&lt;li&gt;Every important action should leave enough state and evidence to explain what happened.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We did not begin with all four principles. We reached them by repeatedly breaking the earlier architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refactor 1: From opening hardware everywhere to one resource owner
&lt;/h2&gt;

&lt;p&gt;Screen capture was one of the first architectural problems.&lt;/p&gt;

&lt;p&gt;A simple prototype can open &lt;code&gt;/dev/video0&lt;/code&gt;, capture a frame, and close it. That becomes unreliable when several tools or services believe they can access the same device.&lt;/p&gt;

&lt;p&gt;Multiple consumers introduce questions that the model should never have to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which process owns the capture stream?&lt;/li&gt;
&lt;li&gt;Who configures the sensor?&lt;/li&gt;
&lt;li&gt;What happens if HDMI disconnects?&lt;/li&gt;
&lt;li&gt;Which frame is fresh?&lt;/li&gt;
&lt;li&gt;Who restarts the pipeline after failure?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We moved that responsibility into &lt;code&gt;frame_service&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;frame_service&lt;/code&gt; is the single owner of the video device. Other processes request screenshots through a Unix domain socket rather than opening the hardware themselves.&lt;/p&gt;

&lt;p&gt;The audio path follows the same principle. &lt;code&gt;audio_service&lt;/code&gt; owns recording, playback, and volume state.&lt;/p&gt;

&lt;p&gt;The UDS protocol uses a JSON header with an optional binary payload. That gives us a language-neutral boundary: C++ can own the hardware while Go consumes the capability without depending on a C++ ABI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Go Agent
   |
   | screenshot request over UDS
   v
frame_service
   |
   | fresh frame
   v
/dev/video0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation also made failures easier to locate.&lt;/p&gt;

&lt;p&gt;If the Agent cannot get a screenshot, we can ask whether the failure is in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the model;&lt;/li&gt;
&lt;li&gt;the Go screenshot client;&lt;/li&gt;
&lt;li&gt;the UDS protocol;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;frame_service&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;the HDMI bridge;&lt;/li&gt;
&lt;li&gt;or the underlying video device.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before the boundary existed, all of those failures looked like “the screenshot tool failed.”&lt;/p&gt;

&lt;p&gt;The first major frame-service work appeared in &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/17" rel="noopener noreferrer"&gt;PR #17&lt;/a&gt;. Later changes moved screenshots behind a Screen Provider, changed capture to happen on demand, and improved freshness and latency in &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/519" rel="noopener noreferrer"&gt;PR #519&lt;/a&gt;, &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/521" rel="noopener noreferrer"&gt;PR #521&lt;/a&gt;, and &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/559" rel="noopener noreferrer"&gt;PR #559&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The reusable lesson was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don’t let every part of an Agent application become a hardware driver.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Refactor 2: From platform branches to explicit action semantics
&lt;/h2&gt;

&lt;p&gt;Our early HID code was full of apparently small platform decisions.&lt;/p&gt;

&lt;p&gt;Should a pointer be absolute or relative? Is a swipe a mouse movement or a touchscreen gesture? Which coordinate space does a screenshot use? Should text entry use HID, ADB, or a Phone Bridge?&lt;/p&gt;

&lt;p&gt;These decisions leaked into tool descriptions, prompts, configuration, and benchmark code.&lt;/p&gt;

&lt;p&gt;That made the Agent’s behavior depend on several sources of truth at once.&lt;/p&gt;

&lt;p&gt;We eventually separated two layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what the Agent intends to do;&lt;/li&gt;
&lt;li&gt;how the configured device performs it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The intention layer contains operations such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;click;&lt;/li&gt;
&lt;li&gt;swipe;&lt;/li&gt;
&lt;li&gt;drag;&lt;/li&gt;
&lt;li&gt;keyboard shortcut;&lt;/li&gt;
&lt;li&gt;text entry;&lt;/li&gt;
&lt;li&gt;auxiliary media or Android controls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The execution layer selects a configured HID, ADB, or HTTP input path and applies the appropriate coordinate and keyboard rules.&lt;/p&gt;

&lt;p&gt;This became the MNK Provider abstraction.&lt;/p&gt;

&lt;p&gt;We also removed reverse platform inference. The configured &lt;code&gt;device_type&lt;/code&gt; became the authority instead of asking multiple parts of the runtime to guess the target platform.&lt;/p&gt;

&lt;p&gt;That made the system less magical and more predictable.&lt;/p&gt;

&lt;p&gt;Aiden still does not have one universal input transport. HID, ADB, and HTTP remain different paths with different constraints. The abstraction unifies the Agent-facing action semantics, not the underlying operating systems.&lt;/p&gt;

&lt;p&gt;We then changed what happens after an action.&lt;/p&gt;

&lt;p&gt;A click or swipe should not be considered successful merely because writing the input report returned no error. The device may ignore the event, display another page, or route the input somewhere unexpected.&lt;/p&gt;

&lt;p&gt;The runtime began attaching post-action visual observations and touch markers so the model could reason about where the action landed and what changed afterward.&lt;/p&gt;

&lt;p&gt;A drag could also be separated into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;drag_start
    |
    v
observe the screen
    |
    v
drag_release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That lets the Agent inspect the intermediate state before committing to the release.&lt;/p&gt;

&lt;p&gt;The central input refactors are visible in &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/539" rel="noopener noreferrer"&gt;PR #539&lt;/a&gt;, &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/543" rel="noopener noreferrer"&gt;PR #543&lt;/a&gt;, &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/588" rel="noopener noreferrer"&gt;PR #588&lt;/a&gt;, and &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/595" rel="noopener noreferrer"&gt;PR #595&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A shared action interface is useful only if the platform-specific boundary underneath it remains explicit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Refactor 3: From scattered settings to a configuration control plane
&lt;/h2&gt;

&lt;p&gt;As Aiden added more model, STT, and TTS providers, configuration became a source of architectural debt.&lt;/p&gt;

&lt;p&gt;Each integration brought slightly different assumptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;provider-specific base URLs;&lt;/li&gt;
&lt;li&gt;different model identifiers;&lt;/li&gt;
&lt;li&gt;different reasoning controls;&lt;/li&gt;
&lt;li&gt;credentials that must survive unrelated edits;&lt;/li&gt;
&lt;li&gt;STT and TTS settings with their own defaults;&lt;/li&gt;
&lt;li&gt;fields understood by the runtime but not yet understood by the UI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A naive configuration page reads a file, edits the fields it knows, and writes the whole file back.&lt;/p&gt;

&lt;p&gt;That is dangerous. Unknown fields, comments, ordering, and credentials can disappear because the UI did not understand them.&lt;/p&gt;

&lt;p&gt;We gradually moved provider and configuration ownership into the Agent Runtime.&lt;/p&gt;

&lt;p&gt;The current direction includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unified model, STT, and TTS provider records;&lt;/li&gt;
&lt;li&gt;provider-owned endpoint configuration;&lt;/li&gt;
&lt;li&gt;metadata-driven validation;&lt;/li&gt;
&lt;li&gt;native Anthropic and Responses API paths;&lt;/li&gt;
&lt;li&gt;provider-aware reasoning controls;&lt;/li&gt;
&lt;li&gt;JSON Merge Patch updates;&lt;/li&gt;
&lt;li&gt;atomic TOML writes;&lt;/li&gt;
&lt;li&gt;preservation of comments, formatting, unknown fields, and write-only credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Config Web also moved toward using the Go Agent as its management API instead of maintaining a second implementation of configuration behavior.&lt;/p&gt;

&lt;p&gt;That is more than a frontend refactor.&lt;/p&gt;

&lt;p&gt;It establishes one authority for resolving, validating, and applying runtime configuration.&lt;/p&gt;

&lt;p&gt;Relevant changes include &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/484" rel="noopener noreferrer"&gt;PR #484&lt;/a&gt;, &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/504" rel="noopener noreferrer"&gt;PR #504&lt;/a&gt;, &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/542" rel="noopener noreferrer"&gt;PR #542&lt;/a&gt;, &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/551" rel="noopener noreferrer"&gt;PR #551&lt;/a&gt;, and &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/631" rel="noopener noreferrer"&gt;PR #631&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A configuration UI is part of your runtime contract, not just a form that edits a file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Refactor 4: From one growing transcript to several kinds of memory
&lt;/h2&gt;

&lt;p&gt;Our early mental model of context was straightforward: keep the conversation and send the relevant history back to the model.&lt;/p&gt;

&lt;p&gt;That stops being straightforward when the history contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user messages;&lt;/li&gt;
&lt;li&gt;assistant responses;&lt;/li&gt;
&lt;li&gt;screenshots;&lt;/li&gt;
&lt;li&gt;tool calls;&lt;/li&gt;
&lt;li&gt;large tool results;&lt;/li&gt;
&lt;li&gt;device state;&lt;/li&gt;
&lt;li&gt;runtime warnings;&lt;/li&gt;
&lt;li&gt;learned procedures;&lt;/li&gt;
&lt;li&gt;user preferences;&lt;/li&gt;
&lt;li&gt;task evidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Putting all of this into one transcript creates both token pressure and semantic confusion.&lt;/p&gt;

&lt;p&gt;We started separating the information by purpose.&lt;/p&gt;

&lt;p&gt;The current memory architecture distinguishes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;session memory&lt;/strong&gt;, which keeps the active conversation and compressed history;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;long-term memory&lt;/strong&gt;, which stores explicitly saved user facts, preferences, and rules;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;device memory&lt;/strong&gt;, which stores reusable device, application, navigation, procedure, calibration, failure, and fact knowledge;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;task episodes&lt;/strong&gt;, which preserve execution evidence for audit and background learning.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Device memory is no longer automatically injected into every prompt. The model calls the recall tool when a task materially depends on previous device knowledge.&lt;/p&gt;

&lt;p&gt;The runtime message model also distinguishes ordinary conversation from state generated by the system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;MessageRoleUser&lt;/span&gt;       &lt;span class="n"&gt;MessageRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"user"&lt;/span&gt;
    &lt;span class="n"&gt;MessageRoleAssistant&lt;/span&gt;  &lt;span class="n"&gt;MessageRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"assistant"&lt;/span&gt;
    &lt;span class="n"&gt;MessageRoleToolCall&lt;/span&gt;   &lt;span class="n"&gt;MessageRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tool_call"&lt;/span&gt;
    &lt;span class="n"&gt;MessageRoleToolResult&lt;/span&gt; &lt;span class="n"&gt;MessageRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tool_result"&lt;/span&gt;
    &lt;span class="n"&gt;MessageRoleState&lt;/span&gt;      &lt;span class="n"&gt;MessageRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"state"&lt;/span&gt;
    &lt;span class="n"&gt;MessageRoleSystem&lt;/span&gt;     &lt;span class="n"&gt;MessageRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"system"&lt;/span&gt;
    &lt;span class="n"&gt;MessageRoleNotice&lt;/span&gt;     &lt;span class="n"&gt;MessageRole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"notice"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;State&lt;/code&gt; message can describe the current device or screen environment.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;Notice&lt;/code&gt; message can carry a task result, loop-guard correction, or human-action request without pretending that the person wrote it.&lt;/p&gt;

&lt;p&gt;Large results and old context introduced another set of refactors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;large tool results can be stored as recoverable artifacts;&lt;/li&gt;
&lt;li&gt;historical tool results and state can be pruned before compaction;&lt;/li&gt;
&lt;li&gt;a context session can rotate after truncation;&lt;/li&gt;
&lt;li&gt;parent-child lineage records how the new session was created;&lt;/li&gt;
&lt;li&gt;persisted session events became the recovery source instead of redundant history mirrors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Relevant changes include &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/475" rel="noopener noreferrer"&gt;PR #475&lt;/a&gt;, &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/497" rel="noopener noreferrer"&gt;PR #497&lt;/a&gt;, &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/524" rel="noopener noreferrer"&gt;PR #524&lt;/a&gt;, &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/607" rel="noopener noreferrer"&gt;PR #607&lt;/a&gt;, &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/609" rel="noopener noreferrer"&gt;PR #609&lt;/a&gt;, and &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/618" rel="noopener noreferrer"&gt;PR #618&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The current design is documented in the &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/blob/main/docs/04-agent/memory-plane.md" rel="noopener noreferrer"&gt;Aiden memory plane&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Context management is part of Agent architecture, not cleanup performed after the architecture is finished.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Refactor 5: From voice input to foreground and background Agents
&lt;/h2&gt;

&lt;p&gt;Adding speech to an Agent looks easy when voice is treated as another input method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;speech -&amp;gt; STT -&amp;gt; Agent -&amp;gt; TTS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works until the Agent starts a multi-step device task.&lt;/p&gt;

&lt;p&gt;A device task may need to capture several screens, call tools, wait for UI changes, and recover from unexpected states. Meanwhile, the person may want to interrupt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Stop.”&lt;/li&gt;
&lt;li&gt;“Use the other account.”&lt;/li&gt;
&lt;li&gt;“What is happening?”&lt;/li&gt;
&lt;li&gt;“I’ll enter the password myself.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If conversation and execution share one loop, the conversation waits for the task. If every backend event is immediately sent to the voice model, the Agent can interrupt itself.&lt;/p&gt;

&lt;p&gt;We rebuilt realtime voice around two cooperating Agents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a foreground Realtime Agent that owns the live conversation;&lt;/li&gt;
&lt;li&gt;a backend Agent that executes device tasks;&lt;/li&gt;
&lt;li&gt;an asynchronous task queue connecting them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The task manager uses an explicit lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;StatusCreated&lt;/span&gt;    &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"created"&lt;/span&gt;
    &lt;span class="n"&gt;StatusQueued&lt;/span&gt;     &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"queued"&lt;/span&gt;
    &lt;span class="n"&gt;StatusRunning&lt;/span&gt;    &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"running"&lt;/span&gt;
    &lt;span class="n"&gt;StatusCancelling&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"cancelling"&lt;/span&gt;
    &lt;span class="n"&gt;StatusCancelled&lt;/span&gt;  &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"cancelled"&lt;/span&gt;
    &lt;span class="n"&gt;StatusFailed&lt;/span&gt;     &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"failed"&lt;/span&gt;
    &lt;span class="n"&gt;StatusCompleted&lt;/span&gt;  &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"completed"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The status describes execution state. It does not prove that the user’s intended goal was achieved.&lt;/p&gt;

&lt;p&gt;The foreground Agent can create, query, or cancel background tasks. It does not need to wait for the backend Agent to finish before continuing the conversation.&lt;/p&gt;

&lt;p&gt;Human handoff also became part of the task lifecycle.&lt;/p&gt;

&lt;p&gt;If a password, CAPTCHA, payment confirmation, permission dialog, or ambiguous decision requires a person, the backend task can publish a pending action and remain in the &lt;code&gt;running&lt;/code&gt; state.&lt;/p&gt;

&lt;p&gt;After the person finishes, the foreground Agent returns a continuation message. The manager resumes the same task identity and backend context instead of starting an unrelated task.&lt;/p&gt;

&lt;p&gt;We also added backpressure to result delivery. Terminal task updates use a 500-millisecond sliding debounce window, and the runtime injects them only when the foreground is idle.&lt;/p&gt;

&lt;p&gt;This prevents several nearby results from creating several competing spoken responses.&lt;/p&gt;

&lt;p&gt;The first full-duplex implementation landed in &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/566" rel="noopener noreferrer"&gt;PR #566&lt;/a&gt;. Realtime provider adapters and the expanded foreground tool surface followed in &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/620" rel="noopener noreferrer"&gt;PR #620&lt;/a&gt; and &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/623" rel="noopener noreferrer"&gt;PR #623&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The architecture is described in &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/blob/main/docs/04-agent/foreground-background-agents.md" rel="noopener noreferrer"&gt;Foreground and Background Agents&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A voice Agent operating a physical device needs ownership and scheduling rules, not only lower audio latency.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Refactor 6: From successful demos to saved execution evidence
&lt;/h2&gt;

&lt;p&gt;A demo video tells you that one task worked once.&lt;/p&gt;

&lt;p&gt;It usually does not tell you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which tools were called;&lt;/li&gt;
&lt;li&gt;whether the Agent used the intended input path;&lt;/li&gt;
&lt;li&gt;whether the screenshot was fresh;&lt;/li&gt;
&lt;li&gt;what the screen looked like before and after;&lt;/li&gt;
&lt;li&gt;whether the final response matched the physical outcome;&lt;/li&gt;
&lt;li&gt;whether the result can be reproduced.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We built the benchmark system around an Environment Bridge contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Runner
   |
   v
Go Agent
   |
   v
Environment Bridge
   |
   +-- physical device
   +-- MobileGym / ADB
   +-- desktop environment
   +-- custom bridge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each task, the runner can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;prepare an isolated environment;&lt;/li&gt;
&lt;li&gt;capture a pre-task screenshot;&lt;/li&gt;
&lt;li&gt;send the task to the real Agent;&lt;/li&gt;
&lt;li&gt;capture a post-task screenshot;&lt;/li&gt;
&lt;li&gt;extract the structured tool trace;&lt;/li&gt;
&lt;li&gt;run deterministic assertions;&lt;/li&gt;
&lt;li&gt;optionally evaluate the saved evidence with a judge;&lt;/li&gt;
&lt;li&gt;retain the artifacts for later analysis.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Execution and scoring are separate. A rubric or judge can change without forcing the physical task to run again.&lt;/p&gt;

&lt;p&gt;We also added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a hardware-free Docker sandbox;&lt;/li&gt;
&lt;li&gt;centralized benchmark configuration;&lt;/li&gt;
&lt;li&gt;local low-level smoke tests;&lt;/li&gt;
&lt;li&gt;macOS, Linux, and Windows desktop bridges;&lt;/li&gt;
&lt;li&gt;environment health checks;&lt;/li&gt;
&lt;li&gt;isolated Agent workers for concurrent environments;&lt;/li&gt;
&lt;li&gt;separate pre/post screenshots and structured traces.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The benchmark now asks a better question than “Did the demo look good?”&lt;/p&gt;

&lt;p&gt;It asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What did the Agent observe, what action did it take, what changed, and what evidence supports the reported result?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Relevant changes include &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/523" rel="noopener noreferrer"&gt;PR #523&lt;/a&gt;, &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/533" rel="noopener noreferrer"&gt;PR #533&lt;/a&gt;, &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/569" rel="noopener noreferrer"&gt;PR #569&lt;/a&gt;, &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/593" rel="noopener noreferrer"&gt;PR #593&lt;/a&gt;, and &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/612" rel="noopener noreferrer"&gt;PR #612&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The current flow is documented in the &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/blob/main/docs/09-benchmark/architecture.md" rel="noopener noreferrer"&gt;benchmark architecture&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some refactors made Aiden smaller
&lt;/h2&gt;

&lt;p&gt;Not every important change added a feature.&lt;/p&gt;

&lt;p&gt;We removed several ideas after they stopped matching the system we were building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reverse platform inference;&lt;/li&gt;
&lt;li&gt;automatic upfront memory injection;&lt;/li&gt;
&lt;li&gt;unused chat-history mirrors;&lt;/li&gt;
&lt;li&gt;duplicated configuration paths;&lt;/li&gt;
&lt;li&gt;obsolete script and image-diff tools;&lt;/li&gt;
&lt;li&gt;tool descriptions containing platform policy that belonged in configuration;&lt;/li&gt;
&lt;li&gt;assumptions that a completed loop implied a successful physical outcome.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Removing a tool can look like lost capability.&lt;/p&gt;

&lt;p&gt;For an Agent, it can be the opposite. Every exposed tool expands the model’s decision space. An ambiguous or obsolete tool makes the runtime harder to understand and the Agent harder to control.&lt;/p&gt;

&lt;p&gt;One example is &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/pull/601" rel="noopener noreferrer"&gt;PR #601&lt;/a&gt;, which removed script and image-diff tools while preserving post-action screen-change detection as a narrower capability.&lt;/p&gt;

&lt;p&gt;The lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A smaller, explicit tool surface can be more capable than a larger, ambiguous one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What the six months changed
&lt;/h2&gt;

&lt;p&gt;The visible result is a larger system, but the more important change is in where responsibilities now live.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Earlier assumption&lt;/th&gt;
&lt;th&gt;Current direction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Any process can capture a frame&lt;/td&gt;
&lt;td&gt;One service owns each hardware resource&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tools decide platform behavior&lt;/td&gt;
&lt;td&gt;Configuration selects an explicit provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A written HID report means success&lt;/td&gt;
&lt;td&gt;Observe and verify the resulting screen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration is a file-editing problem&lt;/td&gt;
&lt;td&gt;The Agent owns the runtime contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context is one transcript&lt;/td&gt;
&lt;td&gt;Conversation, state, memory, and evidence are separate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice is another input mode&lt;/td&gt;
&lt;td&gt;Foreground conversation and backend work are decoupled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A stopped loop means success&lt;/td&gt;
&lt;td&gt;Execution state and physical outcome are different&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A demo is enough evidence&lt;/td&gt;
&lt;td&gt;Save screenshots, traces, assertions, and reports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;More tools mean more capability&lt;/td&gt;
&lt;td&gt;Smaller boundaries can improve reliability&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these principles is unique by itself.&lt;/p&gt;

&lt;p&gt;The difficulty is applying all of them at once while a model is interacting with a real screen, an operating system, a USB stack, and hardware that can disconnect.&lt;/p&gt;

&lt;p&gt;A physical Agent is not only an LLM with tools.&lt;/p&gt;

&lt;p&gt;It is a distributed system squeezed onto a small device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the project stands
&lt;/h2&gt;

&lt;p&gt;The current Aiden development board can combine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HDMI screen observation;&lt;/li&gt;
&lt;li&gt;keyboard, pointer, touch, and auxiliary USB HID control;&lt;/li&gt;
&lt;li&gt;USB ECM networking;&lt;/li&gt;
&lt;li&gt;configurable model, STT, and TTS endpoints;&lt;/li&gt;
&lt;li&gt;voice interaction;&lt;/li&gt;
&lt;li&gt;skills and several kinds of memory;&lt;/li&gt;
&lt;li&gt;foreground and background task execution;&lt;/li&gt;
&lt;li&gt;human handoff;&lt;/li&gt;
&lt;li&gt;OTA and diagnostics;&lt;/li&gt;
&lt;li&gt;device and desktop benchmark environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are still real boundaries.&lt;/p&gt;

&lt;p&gt;The basic control path requires a target capable of video output and USB HID input. iOS requires AssistiveTouch. Optional Phone Bridge, notification, BLE, and ADB paths have their own setup requirements.&lt;/p&gt;

&lt;p&gt;Compatibility still depends on the target device, operating-system version, capture bridge, USB behavior, audio path, permissions, and configured inference endpoints.&lt;/p&gt;

&lt;p&gt;The current repository should be read as a development-board implementation and an invitation to inspect the engineering—not as a claim that every supported-looking combination has already been validated.&lt;/p&gt;

&lt;p&gt;If you are building voice Agents, GUI Agents, embedded controllers, or hardware automation systems, we would especially value reports that include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the board and firmware revision;&lt;/li&gt;
&lt;li&gt;the target device and OS version;&lt;/li&gt;
&lt;li&gt;the task prompt;&lt;/li&gt;
&lt;li&gt;the configured model endpoint;&lt;/li&gt;
&lt;li&gt;screenshots before and after;&lt;/li&gt;
&lt;li&gt;the structured tool trace;&lt;/li&gt;
&lt;li&gt;interruption or handoff behavior;&lt;/li&gt;
&lt;li&gt;what you expected and what actually happened.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can explore the project through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/AidenAI-IO/aiden-firmware" rel="noopener noreferrer"&gt;Aiden Firmware on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aidenai.io/docs/architecture/overview/" rel="noopener noreferrer"&gt;System Architecture Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/AidenAI-IO/aiden-firmware/blob/main/docs/01-getting-started/hardware.md" rel="noopener noreferrer"&gt;Hardware and Wiring Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/AidenAI-IO/aiden-firmware/tree/main/docs/04-agent" rel="noopener noreferrer"&gt;Agent Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/AidenAI-IO/aiden-firmware/tree/main/docs/09-benchmark" rel="noopener noreferrer"&gt;Benchmark Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are still refactoring Aiden.&lt;/p&gt;

&lt;p&gt;That is not because the original idea stopped working.&lt;/p&gt;

&lt;p&gt;It is because “the Agent moved the cursor” and “the system can explain what it observed, what it did, what happened next, and why it stopped” are two very different engineering milestones.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>architecture</category>
    </item>
    <item>
      <title>We Tried to Make a Voice Agent Control a Real Device. Here's What Broke</title>
      <dc:creator>Dan </dc:creator>
      <pubDate>Wed, 09 Sep 2026 07:47:45 +0000</pubDate>
      <link>https://dev.to/tuobi/we-tried-to-make-a-voice-agent-control-a-real-device-heres-what-broke-5gef</link>
      <guid>https://dev.to/tuobi/we-tried-to-make-a-voice-agent-control-a-real-device-heres-what-broke-5gef</guid>
      <description>&lt;p&gt;Most voice-agent demos have a simple loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user speaks.&lt;/li&gt;
&lt;li&gt;The model thinks.&lt;/li&gt;
&lt;li&gt;The agent replies.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That loop works well when the agent only needs to answer a question.&lt;/p&gt;

&lt;p&gt;It becomes much harder when the agent is also controlling a real device.&lt;/p&gt;

&lt;p&gt;Aiden can read a phone’s screen and operate it through an external hardware control path. A task might involve several screenshots, tool calls, taps, swipes, and waits for the device to respond.&lt;/p&gt;

&lt;p&gt;While that is happening, the person may still want to speak:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Stop.”&lt;/li&gt;
&lt;li&gt;“Actually, open the other app.”&lt;/li&gt;
&lt;li&gt;“Wait, I’ll handle this part.”&lt;/li&gt;
&lt;li&gt;“What is it doing right now?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our first instinct was to treat voice as another input method around the existing Agent loop. That turned out to be the wrong abstraction.&lt;/p&gt;

&lt;p&gt;The difficult part was not streaming audio. It was deciding who owns the task, when messages should be delivered, and how to keep two different kinds of Agent work from interfering with each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 1: Don’t make the conversation own the device task
&lt;/h2&gt;

&lt;p&gt;A long-running GUI task and a real-time conversation have different timing requirements.&lt;/p&gt;

&lt;p&gt;The device task needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inspect the screen;&lt;/li&gt;
&lt;li&gt;call tools;&lt;/li&gt;
&lt;li&gt;send input;&lt;/li&gt;
&lt;li&gt;wait for the device;&lt;/li&gt;
&lt;li&gt;inspect the new screen;&lt;/li&gt;
&lt;li&gt;continue or recover.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The voice interaction needs to respond quickly and remain available for interruptions.&lt;/p&gt;

&lt;p&gt;Putting both inside one loop created predictable problems. The conversation had to wait for device work, and interruptions arrived too late to be useful.&lt;/p&gt;

&lt;p&gt;The solution was to split the responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The foreground Realtime Agent handles listening, short conversational responses, and interruption.&lt;/li&gt;
&lt;li&gt;The backend Agent handles screen understanding, tool calls, and device actions.&lt;/li&gt;
&lt;li&gt;An asynchronous task queue connects the two.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The foreground Agent is not a second GUI operator. It is an interaction layer that can start, cancel, query, or redirect work handled by the backend.&lt;/p&gt;

&lt;p&gt;This separation also means that the existing backend execution path can be reused across different voice modes instead of implementing device control twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 2: “Completed” does not mean “Successful”
&lt;/h2&gt;

&lt;p&gt;This sounds obvious, but it matters a lot in an Agent system.&lt;/p&gt;

&lt;p&gt;A task can finish because the execution loop reached its end state. That does not necessarily mean the intended result happened.&lt;/p&gt;

&lt;p&gt;For example, a task might:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;open an app but fail to find the expected screen;&lt;/li&gt;
&lt;li&gt;send an input event but receive an unexpected result;&lt;/li&gt;
&lt;li&gt;stop after hitting a safety or loop guard;&lt;/li&gt;
&lt;li&gt;wait for a human action and never receive it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We therefore keep execution state and outcome separate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Completed&lt;/code&gt; means execution ended.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Failed&lt;/code&gt; means an abnormal condition interrupted execution.&lt;/li&gt;
&lt;li&gt;The task result explains whether the intended action was actually achieved.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without this distinction, the conversational Agent is encouraged to turn “the loop stopped” into “the task succeeded.” That is a small wording problem in a demo and a serious reliability problem in a physical system.&lt;/p&gt;

&lt;p&gt;The distinction also appears directly in the task model. Execution status, result, error, and pending human action are stored separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;StatusCreated&lt;/span&gt;    &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"created"&lt;/span&gt;
    &lt;span class="n"&gt;StatusQueued&lt;/span&gt;     &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"queued"&lt;/span&gt;
    &lt;span class="n"&gt;StatusRunning&lt;/span&gt;    &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"running"&lt;/span&gt;
    &lt;span class="n"&gt;StatusCancelling&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"cancelling"&lt;/span&gt;
    &lt;span class="n"&gt;StatusCancelled&lt;/span&gt;  &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"cancelled"&lt;/span&gt;
    &lt;span class="n"&gt;StatusFailed&lt;/span&gt;     &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"failed"&lt;/span&gt;
    &lt;span class="n"&gt;StatusCompleted&lt;/span&gt;  &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"completed"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;                &lt;span class="kt"&gt;string&lt;/span&gt;      &lt;span class="s"&gt;`json:"id"`&lt;/span&gt;
    &lt;span class="n"&gt;Prompt&lt;/span&gt;            &lt;span class="kt"&gt;string&lt;/span&gt;      &lt;span class="s"&gt;`json:"prompt"`&lt;/span&gt;
    &lt;span class="n"&gt;Status&lt;/span&gt;            &lt;span class="n"&gt;Status&lt;/span&gt;      &lt;span class="s"&gt;`json:"status"`&lt;/span&gt;
    &lt;span class="n"&gt;Result&lt;/span&gt;            &lt;span class="kt"&gt;string&lt;/span&gt;      &lt;span class="s"&gt;`json:"result,omitempty"`&lt;/span&gt;
    &lt;span class="n"&gt;Error&lt;/span&gt;             &lt;span class="kt"&gt;string&lt;/span&gt;      &lt;span class="s"&gt;`json:"error,omitempty"`&lt;/span&gt;
    &lt;span class="n"&gt;CreatedAt&lt;/span&gt;         &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;   &lt;span class="s"&gt;`json:"created_at"`&lt;/span&gt;
    &lt;span class="n"&gt;UpdatedAt&lt;/span&gt;         &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;   &lt;span class="s"&gt;`json:"updated_at"`&lt;/span&gt;
    &lt;span class="n"&gt;StartedAt&lt;/span&gt;         &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;  &lt;span class="s"&gt;`json:"started_at,omitempty"`&lt;/span&gt;
    &lt;span class="n"&gt;CompletedAt&lt;/span&gt;       &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;  &lt;span class="s"&gt;`json:"completed_at,omitempty"`&lt;/span&gt;
    &lt;span class="n"&gt;PendingUserAction&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UserAction&lt;/span&gt; &lt;span class="s"&gt;`json:"pending_user_action,omitempty"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that &lt;code&gt;StatusCompleted&lt;/code&gt; only describes the execution lifecycle. The actual outcome still has to be understood from &lt;code&gt;Result&lt;/code&gt; and the observed device state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AidenAI-IO/aiden-firmware/blob/9230b8596e45f5a865ec9f08dbda71f2f8f9a223/src/agent/internal/agenttask/manager.go#L13-L46" rel="noopener noreferrer"&gt;View the task model on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 3: Notifications need backpressure
&lt;/h2&gt;

&lt;p&gt;The backend Agent can produce several events in a short period: tool results, screen changes, task updates, or requests for human input.&lt;/p&gt;

&lt;p&gt;If every event is immediately injected into the foreground conversation, the model receives a noisy stream of updates. Worse, a background result can arrive in the middle of an answer and compete with the person’s current request.&lt;/p&gt;

&lt;p&gt;We added a notification queue with a short aggregation window. When several task results arrive close together, the runtime waits 500 milliseconds, extending the window if another result arrives, and then delivers the combined update.&lt;/p&gt;

&lt;p&gt;We also avoid injecting a background task message while the foreground Agent is actively answering.&lt;/p&gt;

&lt;p&gt;This is not only a queueing detail. It changes how the interaction feels. A real-time Agent should not sound as if it is being interrupted by its own internal logs.&lt;/p&gt;

&lt;p&gt;Here is the less glamorous—but surprisingly important—part of the implementation. Instead of forwarding each completed task immediately, the runtime resets a short debounce timer whenever another result arrives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;agentTaskNotifications&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;pendingTaskUpdates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;pendingTaskUpdates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DrainTerminalTasks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;taskUpdatesReady&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;taskDebounceTimer&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;taskDebounceTimer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewTimer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;realtimeTaskResultDebounce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;taskDebounceTimer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;taskDebounceTimer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;taskDebounceTimer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;realtimeTaskResultDebounce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;taskDebounce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;taskDebounceTimer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;realtimeTaskResultDebounce&lt;/code&gt; is currently set to 500 milliseconds. It is a small delay, but it prevents several closely spaced backend events from turning into several competing voice responses.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AidenAI-IO/aiden-firmware/blob/9230b8596e45f5a865ec9f08dbda71f2f8f9a223/src/agent/cmd/daemon/realtime_wakeup.go#L26-L33" rel="noopener noreferrer"&gt;View the debounce interval&lt;/a&gt; and &lt;a href="https://github.com/AidenAI-IO/aiden-firmware/blob/9230b8596e45f5a865ec9f08dbda71f2f8f9a223/src/agent/cmd/daemon/realtime_wakeup.go#L739-L753" rel="noopener noreferrer"&gt;the notification queue logic&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 4: Keep the two contexts separate
&lt;/h2&gt;

&lt;p&gt;The foreground and backend Agents should not share one constantly changing transcript.&lt;/p&gt;

&lt;p&gt;The backend needs detailed device state and tool history. The foreground needs enough information to explain what is happening and respond to the person. Mixing everything together makes both contexts harder to manage.&lt;/p&gt;

&lt;p&gt;Aiden uses two runtime message types to communicate across the boundary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;StateMessage&lt;/code&gt; carries current device and runtime state.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NoticeMessage&lt;/code&gt; carries events generated by the runtime, such as task results, loop-guard corrections, or requests for human action.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a new screen observation can be added as state without rewriting the system prompt. A completed backend task can be delivered as a notice without pretending that the person said it.&lt;/p&gt;

&lt;p&gt;At the model boundary, these runtime messages are converted into ordinary user-message content. The runtime still controls where the information came from and when it should be delivered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 5: Human handoff is part of the normal lifecycle
&lt;/h2&gt;

&lt;p&gt;A physical-device task will sometimes need a person.&lt;/p&gt;

&lt;p&gt;Maybe the device requires authorization. Maybe the user needs to choose an account. Maybe the Agent reaches an action that should not be automated without confirmation.&lt;/p&gt;

&lt;p&gt;Instead of treating this as an exception outside the task system, Aiden gives it an explicit lifecycle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The backend calls &lt;code&gt;request_user_action&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The foreground Agent asks the person.&lt;/li&gt;
&lt;li&gt;The foreground Agent returns the answer through &lt;code&gt;response_user_action&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The backend continues with the same task identity and device context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The task remains managed while it waits.&lt;/p&gt;

&lt;p&gt;We also keep device execution serial for now. Multiple tasks competing for one screen and one input path create ambiguous ownership: which task owns the next screenshot, or the next tap? Serial execution is less exciting than parallelism, but it keeps observation and action understandable.&lt;/p&gt;

&lt;p&gt;When the person provides the missing information, we do not create an unrelated replacement task. The existing task is returned to the queue with a continuation message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;StatusRunning&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PendingUserAction&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"agent task is not waiting for user action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;taskID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PendingUserAction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;actionNotified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nextPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userMessage&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resumeQueued&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UpdatedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;

&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"agent task queue is full"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets the workflow preserve its task identity while making the pause and resume behavior explicit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/AidenAI-IO/aiden-firmware/blob/9230b8596e45f5a865ec9f08dbda71f2f8f9a223/src/agent/internal/agenttask/manager.go#L205-L237" rel="noopener noreferrer"&gt;View the human-handoff continuation code on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What we are still validating
&lt;/h2&gt;

&lt;p&gt;This implementation is part of Aiden’s open-source development-board firmware. It is not a claim that every board, phone, operating-system version, or audio setup behaves identically.&lt;/p&gt;

&lt;p&gt;The actual experience still depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the target device and OS;&lt;/li&gt;
&lt;li&gt;the audio and trigger path;&lt;/li&gt;
&lt;li&gt;the configured model endpoint;&lt;/li&gt;
&lt;li&gt;screen-capture and input hardware;&lt;/li&gt;
&lt;li&gt;the specific task being tested.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Useful tests include speaking while audio is playing, interrupting a running task, canceling and querying task state, waiting for human input, and checking that only one device task owns the control path at a time.&lt;/p&gt;

&lt;p&gt;The full-duplex architecture is still evolving, but the main lesson has held up:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When an Agent talks to a person and operates a real device at the same time, the core problem is not just voice latency. It is ownership, scheduling, context, and honest task state.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The implementation is available in the &lt;a href="https://github.com/AidenAI-IO/aiden-firmware" rel="noopener noreferrer"&gt;Aiden firmware repository&lt;/a&gt;. The deeper architectural write-up is available on the &lt;a href="https://aidenai.io/blog/when-voice-meets-the-physical-world-inside-aidens-full-duplex-agent-architecture/" rel="noopener noreferrer"&gt;Aiden blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are building a voice Agent that can also take real-world actions, I’d be interested to hear how you handle interruptions, task ownership, and human handoff.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
