Omnistack
Research

How it is put together

Three parts do the work — a multi-agent architecture that scopes who may touch what, an appos scaffolding that refuses to guess, and a hardware toolset that reads the board rather than the log file. Each section below ends with what we measured and what broke.

研究

它是怎么搭起来的

真正干活的是三部分:一套划清"谁能碰什么"的多 agent 架构、一套拒绝猜测的 appos 脚手架,以及一套读板子而不是读日志的硬件工具集。 每一节最后都写着我们测到了什么、以及什么东西坏过。

The multi-agent architecture

Omnistack does not give every agent the whole toolbox. Each one is granted the tools its stage needs and denied the rest, so the boundary is expressed in configuration rather than in a paragraph of prompt asking nicely. Five primary agents ship today, and they map one-to-one onto the stages on the detail page.

Design

system-architect

Produces the framework — scenarios, devices, apps and the contracts between them — as documents plus a diagram derived from them. It has no code editor at all: every artifact goes through a publishing tool that refuses to write a stale one.

Build

app-generator

Builds and modifies an .appk and verifies it in the simulator. It settles simple requirements itself; there is no prerequisite stage to hand a request back from.

Deploy

nx5-deploy

Puts an already-built package onto a real board from a clean state. The tool that can take down a release is deliberately not on its list.

Verify

agent-test

Writes and runs click cases against the panel and reports what came back. No shell, no file writes — the plan is an argument, because the tool already accepts it.

What scoping is actually for

A permission boundary that is only written in a prompt is not a boundary. Three findings from running all five agents against a real instance shaped the way they are configured.

A denied tool and a missing tool read the same to a model.
  • An agent that is refused a tool does not report “I am not allowed to do that” — it reports “that tool does not exist”, and then quietly falls back to typing the command by hand somewhere else.
  • So every grant an agent legitimately needs is spelled out, even where a global default would reach it. Losing one stray rule does not degrade behaviour; it makes the agent look like it lost its tools.
A permission prompt nobody is watching for never comes back.
  • Measured on an unattended run: an agent reached for the shell, the request sat unanswered for 295 seconds with no child process on the host and no output, and the agent had no way to tell it was stuck.
  • The fix was not a timeout. It was to notice that this agent had no legitimate shell use — the one command it must never improvise is a person's job by rule — and deny the tool outright. A wrong move now fails immediately and visibly instead of hanging.
Session history is not evidence.
  • A first pass at evaluating these agents ran every prompt into one session, and a model quoted an error string left over from an earlier turn as its own tool list. The same agent, given a clean session, reported its tools correctly.
  • We mention it because the failure is quiet: nothing errors, the answer is simply confidently wrong, and it reads like the thing under test is broken.

多 Agent 架构

Omnistack 不给每个 agent 一整 toolbox。每一个只被授予它那一阶段需要的工具,其余的显式拒绝—— 边界写在配置里,而不是写在提示词里求它守规矩。今天有五个 primary agent, 与技术细节页上的阶段一一对应。

设计

system-architect

产出框架——场景、设备、应用,以及它们之间的契约——形式是文档加一张由文档派生的图。 它完全没有代码编辑器:所有产物都走一个发布工具,该工具拒绝写出过期的那一份。

构建

app-generator

构建和修改 .appk 并在模拟器里验证。简单需求它自己拍板, 没有前置阶段可以把需求再交回去。

部署

nx5-deploy

把已经构建好的包从干净状态送上真板。能拆掉一个 release 的工具,刻意不在它的清单上。

验证

agent-test

对着面板写并跑点击用例,报回来的是什么。没有 shell、没有文件写入—— 用例计划是一个参数,因为工具本来就收这个。

权限划分到底是为什么

只写在提示词里的权限边界不是边界。把五个 agent 对着真机跑一遍,得到三个结论, 它们塑造了现在的配置方式。

被拒的工具和没有的工具,在模型看来没有区别。
  • 一个 agent 被拒绝了某工具,它不会说"我不被允许这么做"——它会说"没有这个工具", 然后悄悄退回到别处手敲命令。
  • 所以每个 agent 真正需要的授权都写死,哪怕本来有个全局默认能覆盖到。 少掉一条并不会降级行为,只会让 agent 看起来像丢了工具。
没人盯着的权限请求,永远不会回来。
  • 实测:一次无人值守的运行里,agent 去摸 shell,请求无人应答地挂了 295 秒, 宿主机上没有子进程、没有输出,而 agent 根本不知道自己卡住了。
  • 修法不是加超时,而是发现这个 agent 本来就没有正当的 shell 用途—— 按规则它绝不能自己发起的那个命令是人的活——于是直接拒绝。错的动作现在会立刻、明确地失败, 而不是挂住。
会话历史不是证据。
  • 第一次评估这五个 agent 时,所有 prompt 都灌进了同一个会话, 某个模型把上一轮遗留的一段报错文本当成了自己的工具清单报出来。 同一个 agent 在干净会话里报告的工具清单完全正确。
  • 之所以要提,是因为这种失败是安静的:不会报错,答案只是自信地错, 而且看起来像是被测的那个东西坏了。

The appos scaffolding

Between a signed .appk and a running board there is one command, and a chain of builders that refuse to guess at anything they were not given. The design rule is the same everywhere: a value that cannot be derived is named, and the tool stops rather than inventing it.

PieceWhat it does
apposFive-layer runtime. An application is a signed, data-only package — no native code, no scripts, no device nodes. Crossing a layer is refused at build time.
build_nx5_base_kit.pyAssembles the base Kit every Release is built from. The component sources stay outside; this stamps each manifest from its own contents and derives the recorded digests rather than typing them.
build_arm_release.pyCross-builds the runtime, and refuses a release profile it has not qualified.
nx5_deploy_app.pyOne command from a signed package to a running release, in either application mode.
appos_slint_mcp.pyReads and drives the panel's MCP endpoint for the board UI tests.

Each refuses rather than defaults: an absent board identity, an absent base Kit and an absent cross-toolchain prefix are each reported by name, with the variable that would supply them.

What a sweep of the whole catalogue showed

One pass deployed 22 applications to the board in turn and read the result back over the device. Twenty-one came up with every process and socket flag set. One did not.

22applications deployed in one sweep
21fully green on the first attempt
1failed — and we know why
546 sworst full deploy, AOT mode, cold toolchain

Measured on a physical PLC NX5, each application deployed and checked in sequence against the same board.

The one that failed was not an application defect, and the message said it was.
  • Three separate tools — the package builder, the image planner and the simulator — rejected a perfectly valid eleven-locale package with localization.supported_locales must contain 1 through 8 locales.
  • All three binaries had been built before the limit moved from 8 to 16. The source was correct; the artefacts were stale, and they stated the old rule with total confidence.
  • Reading the source could not have found it. The date on the artefact could.
What we changed because of it
  • Every tool that ships with the template now records the commit it was built from next to the artefact, so a refusal can be traced to a rule rather than mistaken for one.
  • The gate compares commits, not file timestamps: a freshly reset working tree makes every file look fresh, and that produced a false staleness warning on a tree with none.
  • A templated project now inherits full history rather than a shallow clone, because a shallow one ships a governance check that cannot pass and cannot be fixed by editing source.

appos 脚手架

从一个签名的 .appk 到板子上跑起来之间只有一条命令,中间是一串拒绝猜测的构建器。 设计规则到处都一样:推不出来的值就点名报出来,工具停下来,而不是自己编一个。

组件做什么
appos五层运行时。应用是签名、纯数据的包——不含原生代码、不含脚本、不含设备节点。 跨层在构建期就被拒绝。
build_nx5_base_kit.py装配每个 Release 都基于其上的基 Kit。 组件源码留在外面,它只把每个 manifest 按自身内容盖章,并把记录下来的摘要算出来而不是手打。
build_arm_release.py交叉编译运行时,并且拒绝它没有认证过的 release profile。
nx5_deploy_app.py从签名包到跑起来的 release,一条命令,两种应用模式都支持。
appos_slint_mcp.py通过面板的 MCP 端点读取和驱动界面,供板上 UI 测试使用。

每一个都拒绝而不是兜底:缺失的板子身份、缺失的基 Kit、缺失的交叉工具链前缀, 都会被点名报出,并附上本该提供它的那个环境变量。

全量目录扫描的结果

一次跑完把 22 个应用依次部署到板子上,再通过设备把结果读回来。21 个所有进程和套接字标志全部置起, 有一个没有。

22一次扫描里部署的应用数
21第一次就全绿
1失败——而且我们知道为什么
546 s最慢的一次完整部署,AOT 模式,冷工具链

实测于一块真实的 PLC NX5,各应用依次部署并在同一块板子上检查。

失败的那个不是应用的缺陷,而报错说它是。
  • 三个不同的工具——打包器、镜像规划器和模拟器——用一个完全合法的十一语言包拒绝了构建, 理由是 localization.supported_locales must contain 1 through 8 locales。
  • 这三个二进制都构建于上限从 8 改成 16 之前。源码是对的,产物是旧的, 而它们把旧规则说得斩钉截铁。
  • 读源码永远发现不了。看产物的日期可以。
因为这件事我们改了什么
  • 随模板分发的每个工具,现在把"构建自哪个 commit"记录在产物旁边, 于是一条拒绝可以被追溯到一条规则,而不会被误当成规则的缺陷。
  • 这道门比较的是 commit 而不是文件时间戳:刚 reset 过的树里每个文件时间都很新, 那会在一棵根本没有陈旧问题的树上报出假的陈旧警告。
  • 从模板新建的工程现在带完整历史而不是浅克隆, 因为浅克隆会带一个既不可能通过、也不可能靠改源码修好的治理检查。

The hardware toolset

The target is a PLC NX5 indoor unit. Reaching it is unglamorous work — a serial console that needs its line endings and translation mode set correctly, a read-only release over NFS, and a device that does not answer the questions you would expect it to.

Console

The serial link is not optional

On this board adb shell is unreliable, which is why the UI tests create an adb forward instead — the forward works where the shell does not. The console also needs -icrnl and carriage-return-plus-newline line endings; without them the board echoes kernel log lines back as if they were commands.

Storage

The release is read-only NFS

A deployed release is mounted read-only over NFS from the host, and the deploy checks that it is. A managed container cannot always claim the kernel's NFS ports on the same host, so the target can declare that an external server already exports the staging root and use it as-is.

The eight checks a deploy reports

Every deploy ends by reading the board back. A green deploy is eight booleans, and they are reported individually rather than as a single verdict.

CheckWhat it means
supervisorThe process that owns the runtime is up.
system_serviceThe privileged owner of anything live is up.
shellThe application runtime started.
system_service_socketIts socket accepts.
platform_service_socketThe platform service's socket accepts.
shell_first_frameThe runtime presented a first frame.
active_releaseThe release it says is active is the one mounted.
release_is_read_only_nfsIt is mounted read-only over NFS.

All eight true proves the runtime started. It does not prove the application works — that is what the board UI tests are for, and it is a separate claim.

Two application modes, and the ceiling between them

data-app and aot-app are different runtimes, not a build setting. In data mode the interpreter for the package lives inside the Shell's address space, so the package's size lands directly on a 64 MB board's resident set. A package that fits in one may not fit in the other, and the failure is a kill rather than an error message.

The indoor unit packagedata-appaot-app
ResultKilled — resident set exhaustedRuns; all eight checks true
Resident set on the board42,296 kB before the kill18,392 kB running
Full deploy, warm toolchain26 s546 s

The same signed package, deployed through the same tool, to the same board.

AOT costs about twenty times the time and buys back more than half the memory. That is the trade, stated in numbers rather than argued: iterate in data mode, qualify in AOT.

One tuning result worth keeping

The AOT runtime was built with one codegen unit, which serialises the compile. Relaxing only that — keeping LTO on — made it 1.6× faster for 2.3% more bytes, and the board's resident set did not get worse.

540 scodegen-units = 1
338 scodegen-units = 16
+2.3%binary size
no worseresident set on the board

Re-measured on the same machine with LTO held on, for the largest package in the catalogue. Earlier measurements relaxed LTO as well, which is why every row had looked slower and larger.

硬件工具集

目标是一台 PLC NX5 室内机。连上它不是什么光彩的工作——一条要正确设置行尾和转换模式的串口控制台、 一份走 NFS 的只读 release,以及一台不会回答你预期那些问题的设备。

控制台

串口链路不是可选项

在这块板子上 adb shell 不可靠,所以 UI 测试改成建 adb forward——shell 不行的地方 forward 行。控制台还需要 -icrnl 和回车加换行的行尾;不设的话板子会把 kernel 日志回显成命令执行。

存储

release 是只读 NFS

部署好的 release 从宿主机以只读方式挂载,部署检查会验证这一点。 同一台宿主机上,托管容器不一定能抢到内核的 NFS 端口, 所以 target 可以声明"已有一个外部服务器导出了 staging 根"并直接用它。

一次部署要报八项检查

每次部署都以读回板子结束。全绿是八个布尔值,而且逐项上报,而不是给一个笼统的结论。

检查它意味着什么
supervisor持有运行时的那个进程起来了。
system_service任何存活状态的特权归属者起来了。
shell应用运行时启动了。
system_service_socket它的套接字可接受连接。
platform_service_socket平台服务的套接字可接受连接。
shell_first_frame运行时给出了第一帧。
active_release它自称活跃的那个 release 正是挂载着的那个。
release_is_read_only_nfs它以只读 NFS 挂载。

八项全为真只证明运行时起来了,不证明应用好用——那是板上 UI 测试的事, 而且那是另一个结论。

两种应用模式,以及它们之间的天花板

data-app 和 aot-app 是两套不同的运行时,不是一个编译开关。 data 模式下包的解释器活在 Shell 自己的地址空间里,所以包的大小直接压在 64 MB 的板子常驻集上。 一个在一种模式下装得下的包,换一种可能装不下,而失败形式是被杀掉,不是一句报错。

室内机那个包data-appaot-app
结果被杀——常驻集耗尽跑起来,八项全绿
板上常驻集被杀前 42,296 kB运行中 18,392 kB
完整部署,热工具链26 秒546 秒

同一个签名包、同一个工具、同一块板子。

AOT 大约要多花二十倍的时间,换回一半以上的内存。这就是这笔交易,用数字写出来而不是靠争辩: 在 data 模式里迭代,在 AOT 模式里做认证。

一个值得留下的调优结果

AOT 运行时原本按一个 codegen unit 构建,那会把编译串行化。只放松这一项——LTO 保持不变—— 快了 1.6×,二进制大了 2.3%,而板上的常驻集没有变差。

540 秒codegen-units = 1
338 秒codegen-units = 16
+2.3%二进制大小
没有变差板上常驻集

在同一台机器上、保持 LTO 开启、对目录里最大的包重新测量。 更早那批测量同时放松了 LTO,这就是为什么每一行看上去都更慢更大。

What a green result means

Most embedded tooling can tell you a build succeeded. Omnistack also tells you what it did not establish, because a claim you cannot bound is not a claim you can ship on.

A green board test establishes:
  • one uniquely identified on-screen control received a single synthetic click
  • the application's view model changed, and the change is what the step asserted
  • the software frame recorded beside that assertion is the same observation that satisfied it
  • what was not clicked, and therefore not tested
It does not establish:
  • the physical LCD, the framebuffer, or the touch digitiser
  • that every glyph a tree reports is legible on the glass
  • anything about behaviour that was never exercised
278 msper click, end to end, frame capture included
100consecutive clicks, no failures
0lingering connections after a run
75elements in a typical panel's component tree

Measured on a physical PLC NX5 board running a deployed application.

一次全绿意味着什么

大多数嵌入式工具只能告诉你构建成功了。Omnistack 还会告诉你它没有证明什么—— 因为一个划不出边界的结论,不是一个能拿去交付的结论。

一次通过的板上测试证明:
  • 一个被唯一识别的屏幕控件,收到了一次合成点击
  • 应用的视图模型发生了改变,而且正是这一步断言的那个改变
  • 和该断言取自同一次观测的软件帧,就是证据本身
  • 哪些控件没有被点,因此没有被测
它没有证明:
  • 物理 LCD、framebuffer、触摸数字化仪本身
  • 组件树里报告的每一个字形,在玻璃上都读得出来
  • 任何没有被实际操作过的行为
278 ms每次点击的端到端耗时,含帧捕获
100 次连续点击,无失败
0跑完后残留的连接
75典型面板组件树的元素数

数据来自一块真实 PLC NX5 板子上运行的已部署应用。

Targets

PLC indoor unit

NX5

European two-line indoor unit. Armel target, cross-compiled and deployed over a signed release.

Bedside / call

NX1 Cedar

Bedside call unit, sharing one AppOS with the room door and declaring a disjoint capability set.

Simulation

Desktop simulator

Runs the real application package before any hardware is involved, including touch and frame assertions.

Handheld

Android

Flutter targets built, installed and screenshotted on device when the deliverable is a handset app.

Figures on this page come from measurements taken on physical hardware. What was never tested is not claimed here.

支持的平台

PLC 室内机

NX5

欧洲两线室内机。armel 目标,交叉编译,通过签名 Release 部署。

床旁 / 呼叫

NX1 Cedar

床旁呼叫单元,与病房门共用同一套 AppOS,并声明互不相交的能力集。

模拟

桌面模拟器

在碰任何硬件之前运行真实的应用包,支持触摸与帧断言。

手持

Android

交付物是手机应用时,用 Flutter 构建、装机并在真机上截图核对。