AiAgent WASM Plugin Development
Write a resident WASM plugin on the AI agent: subscribe to TCP messages, use aia_* host functions (including sandboxed file access), and what goes into the package.
This document is for third-party developers writing a resident WASM plugin that runs on the AI agent (AiAgent) and handles TCP commands. Its runtime model is nearly identical to the controller plugin (subscription + stdin/stdout line protocol) — code migrates directly.
1. What it is, and when to write one
An AiAgent plugin is not an "AiAgent external tool":
| AiAgent plugin (this doc) | AiAgent external tool (see) | |
|---|---|---|
| Host role | A resident business plugin receiving TCP commands | A tool the LLM can call on demand |
| Loading | Distributed to AiConfig/plugins/<uuid>/ and loaded by the host | AiConfig/externaltools/<toolDir>/, called by the model |
| Entry | main() (_start), reads stdin, writes stdout | WASI command module: args JSON in, result out |
| Good for | Resident behavior, subscribing to platform events, local resources | Adding a "function" for the model |
Write one when the behavior must happen on the machine where the AiAgent runs (e.g. reading/writing that machine's hosts), triggered by platform events. If you only need another callable tool, use an external tool.
2. Relationship of the four endpoints
Plugin/
gui/<uuid>/ page plugin (iframe + tss.*)
Controller/<uuid>/ controller WASM plugin (ctl_*)
scan/<uuid>/ scan-node WASM app plugin (app_*)
aiagent/<uuid>/ this doc (aia_*)
installTargets in info.yaml lists the endpoints to install (gui, controller, scan, aiagent). Each endpoint acts on its own machine, which is why the plugin directory sandbox grants permissions per source endpoint: the same path on gui and aiagent are two independent records.
3. Package layout
Plugin/aiagent/<uuid>/
build/aiagent.wasm build output (preferred; any .wasm under build/ is also accepted)
src/ sources
info.yaml metadata (one per package, under Controller/<uuid>/)
4. Runtime model
For every TCP message the host: writes the message JSON to stdin, instantiates the module and calls _start (main()); the plugin reads stdin and writes stdout lines ([INIT] / [RESP]). handled=true in [RESP] means the plugin consumed the message. Subscriptions are declared in init (matched on Command1/Command2/CommandA; empty = wildcard).
5. Minimal project
aiacore.go holds the runtime skeleton (SetInfo / Subscribe / Capability / Handle, the aia_* imports and main); main.go holds your logic:
//go:build ignore
package main
func init() {
SetInfo("My AiAgent plugin", "1.0.0")
Subscribe("", "", "MyCommand")
Capability("tcp.handle")
Handle(func(msg Msg) bool {
if msg.CommandA != "MyCommand" { return false }
return true
})
}
6. Build
GOWORK=off GOOS=wasip1 GOARCH=wasm go build -o build/aiagent.wasm .
Two pitfalls: use
GOWORK=off; and do not pass-buildmode=c-shared(the AiAgent host drives_start). Note that scan-node app plugins are the opposite — they requirec-shared.
Let the build service/script pick the right mode for you: add //tss:buildmode command to your source's header comment; both the plugin-store build service and the local build-plugins.sh read it and build an ordinary command module. This is the "same source, different build mode per endpoint" trap: both modes compile, but only the right one loads. The build service parses the artifact's export symbols after compiling (this endpoint requires _start or plugin_handle) and fails the build on a mismatch, moving the trap to build time.
7. Host API (aia_*)
Exported by the host in the env namespace: aia_log, aia_read_file, aia_write_file, aia_register_fs_entry, aia_set_timeout. The host bumps HostApiVersion when the export set changes; declare a minimum with minAgentHostApi in info.yaml.
aia_read_file
Reads a file on the AiAgent machine (sandbox-gated). Returns JSON {"ok":bool,"data":<base64>,"size":n,"error":"..."}. Logical keys: system.hosts, system.resolv, system.tmp, user.home. Relative paths mean files inside the plugin's own directory.
aia_write_file
Writes a file on the AiAgent machine (full overwrite, same gating). Write and read bits are independent; nothing is written when denied.
aia_register_fs_entry
Registers an external path in the plugin directory sandbox (idempotent; registration is not permission).
aia_log
Structured logging: writes a line into the AiAgent log (system log and the "developer mode" live stream). Do not log heavily on hot paths.
aia_set_timeout
Requests a per-invocation timeout for this plugin (milliseconds). On the AiAgent side execution currently uses the host default, so this is a capability placeholder / intent declaration; the host enforces a hard cap on a single invocation.
8. Sandboxed file access
Reading/writing outside the plugin's own directory requires the "Plugin Store > Settings > Plugin Directory Sandbox" grant, matched on source endpoint aiagent + plugin id + normalized path + permission bits — identical to the other endpoints. Dangerous paths are rejected at registration and never allowed. Relative paths are always inside the plugin's own directory.
aia_dns_get
Reads the per-adapter DNS of the AiAgent machine.
aia_dns_set
Sets the per-adapter DNS of the AiAgent machine. DNS is not a file (registry on Windows, scutil on macOS), so it is a dedicated capability: authorization uses the plugin directory sandbox keyed by system.dns (kind=capability) with the aiagent endpoint's read/write grant; unregistered access auto-registers and is denied. Dns: [] restores an adapter to automatic (DHCP).
9. Debugging
Plugin logs go through aia_log; a failed plugin is skipped in isolation without affecting other plugins or the main flow.