Voice has become the feature every AI agent platform rushes to ship. The obvious move is to build it as a standalone channel, something that sits beside your web app, your CLI tool, or your Telegram bot. It feels intuitive. You see voice, you create a voice interface. But that instinct creates a brittle architecture. It duplicates work, corrupts your logs, and slowly pulls your project context out of shape.

At APC and APX, we chose a different route. Voice is not a channel. It is a mode. It sits on top of a surface rather than replacing it. Getting this distinction right is what keeps the system from drifting apart.

The Wrong Abstraction

When you treat voice as its own channel, you implicitly assume that speaking to an agent is a fundamentally different conversation from typing to one. Engineering teams respond by splitting the codebase. Suddenly there is a CLI channel and a separate voice-CLI channel. There is a web channel and a parallel voice-web channel. Each demands its own prompt variations, formatting rules, and context handling logic.

This is where the mess begins. A tweak to an agent's behavior must now be copied across multiple prompt trees. If the team forgets one surface, the experience fragments. Users get one tone over text and a slightly different personality over speech. Over time, these small inconsistencies compound into system drift. The portable context layer stops being portable because it has to account for vocal delivery in one branch and silent text in another. The abstraction leaks, and your once-unified project definition frays into a collection of channel-specific hacks.

Splitting Context from Runtime

To prevent this, we split responsibilities between two layers that stay strictly apart.

APC holds the project context. It defines the agents, the rules, and the skills that make up a project. Think of it as the stable meaning of the system. It answers the structural questions. What does this agent know? What is it allowed to do? What tools can it call? APC should remain completely agnostic about whether a reply is rendered on a screen, sent through a chat API, or piped through a speaker.

APX handles the runtime layer. It manages the surfaces you actually touch: the CLI, the web application, the desktop interface, the Telegram bot. When a user sends a request, APX chooses where and how to present the response. Deciding whether to format an answer for reading or optimize it for speaking is a runtime concern. It belongs in APX, not in APC.

This separation means a project defined in APC stays intact no matter how many surfaces APX exposes. The contract does not change. Only the presentation layer does.

How Modes Actually Work

In our implementation, surfaces like Telegram, the CLI, and the web app are channels. A channel tells you where an interaction happened. Voice is layered through channel metadata as a mode. A mode tells you how a reply should behave.

The prompt builder respects this boundary. It pulls from the project context in APC, then inspects the channel metadata. If the desktop surface is running in voice mode, the builder appends targeted instructions only at that moment. Maybe it cues the model toward shorter sentences, clearer punctuation for synthesis, or spoken number conventions. If the same desktop surface is running in text mode, those vocal instructions never enter the prompt.

The result is a single prompt tree per surface. There is no separate voice-desktop branch. There is no whisper-web variant. The modifier applies only when the runtime asks for it, and only at the last responsible moment. The core prompt stays constant.

What You Gain

This architecture pays off in three concrete ways.

Lower maintenance costs. If voice were its own channel, every surface would need a twin. You would maintain a CLI channel and a voice-CLI channel, a Telegram channel and a voice-Telegram channel, and so on. Every time you adjust a system prompt, fix a formatting bug, or refine a skill description, you would have to propagate that change across both trees. Miss one, and users notice the gap. By using a mode, you keep one prompt tree per surface. Voice becomes a conditional overlay rather than a fork in the road, so your workload stays linear as you add new ways to interact.

การบันทึก Log ที่แม่นยำ. ช่องทาง (Channels) จะบันทึกว่าการโต้ตอบเกิดขึ้นที่ไหน ส่วนโหมด (Modes) จะบันทึกว่าการตอบกลับถูกส่งมอบอย่างไร การโต้ตอบบนเดสก์ท็อปยังคงเป็นการโต้ตอบบนเดสก์ท็อป ไม่ว่าผู้ใช้จะอ่านหรือฟังก็ตาม เมื่อทีมของคุณไล่ตามบั๊กหรือตรวจสอบข้อมูลวิเคราะห์ (analytics) พวกเขาไม่จำเป็นต้องมานั่งเปรียบเทียบระหว่าง "desktop-voice" กับ "desktop-text" ราวกับว่าเป็นอินเทอร์เฟซผลิตภัณฑ์ที่ต่างกัน ตัวระบุช่องทางจะยังคงสะอาด และแฟล็กของโหมดจะวางอยู่ข้างๆ กันอย่างเป็นระเบียบในเมทาดาตา การบันทึก Log ของคุณจะมีความถูกต้อง และการดีบั๊กจะทำได้ง่าย เพราะตำแหน่งและพฤติกรรมไม่ได้ถูกนำมาปนกัน

บริบทของโปรเจกต์ที่ชัดเจน. APC เป็นตัวกำหนดข้อกำหนด (contract) โดยไม่ควรต้องสนใจว่าการตอบกลับจะเป็นการพูด การกระซิบ หรือการแสดงผลด้วยฟอนต์แบบ monospace สิ่งเหล่านี้เป็นเรื่องของ runtime การเก็บรูปแบบเสียงไว้ภายใน APX ช่วยให้เราคงความสามารถในการเคลื่อนย้าย (portability) ของ APC ไว้ได้ คุณสามารถยกคำนิยามโปรเจกต์ของ APC ไปวางในสภาพแวดล้อม runtime ใหม่ทั้งหมดได้ โดยไม่ต้องลากเอาข้อสันนิษฐานเรื่องการจัดรูปแบบเสียงหรือส่วนเกินจากการปรับแต่งเสียง (speech-optimization cruft) ติดไปด้วย ขอบเขตยังคงเดิม และความหมายของโปรเจกต์ยังคงเสถียร

ข้อพิสูจน์บนเดสก์ท็อป

เส้นทางเดสก์ท็อปของเราเองแสดงให้เห็นถึงสิ่งนี้ในการใช้งานจริงทุกวัน เดสก์ท็อปคือพื้นผิว (surface) เมื่อผู้ใช้เปิดใช้งานการพูด ระบบจะรันพื้นผิวเดสก์ท็อปเดิมนั้นในโหมดเสียง เนื่องจากเสียงอยู่ในเลเยอร์ของโหมด ช่องทางเดสก์ท็อปจึงยังคงบริบทและพฤติกรรมเดิมไว้ได้ครบถ้วน มันไม่ได้กลายเป็นผลิตภัณฑ์อื่นที่มีกฎเกณฑ์ต่างออกไป ตัวสร้างพรอมต์ (prompt builder) เพียงแค่สังเกตเห็นแฟล็กและเพิ่มคำสั่งเสียงเมื่อจำเป็นเท่านั้น เมื่อผู้ใช้สลับกลับไปเป็นข้อความ คำสั่งเหล่านั้นก็จะหายไปทั้งหมด บริบทของโปรเจกต์ที่อยู่เบื้องหลังไม่เคยเปลี่ยนไป เดสก์ท็อปก็ยังคงเป็นเดสก์ท็อปเสมอ

บทสรุปที่สำคัญ

แนวคิดหลักนั้นเรียบง่าย APC อธิบายถึงความหมายของโปรเจกต์ที่คงที่ APX อธิบายถึงการประมวลผลขณะรันไทม์ (runtime execution) เสียงเป็นเพียงตัวปรับแต่ง (modifier) บนพื้นผิว ไม่ใช่การแทนที่พื้นผิว หากคุณจัดการแบบนี้ พรอมต์ของคุณก็จะยังคงมีขนาดเล็ก การบันทึก Log ของคุณก็จะยังคงชัดเจน และ...