<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>NodlinGo | nodlin.com</title><link>https://nodlin.com/tag/nodlingo/</link><atom:link href="https://nodlin.com/tag/nodlingo/index.xml" rel="self" type="application/rss+xml"/><description>NodlinGo</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Sat, 15 Aug 2026 00:00:00 +0000</lastBuildDate><image><url>https://nodlin.com/media/logo.svg</url><title>NodlinGo</title><link>https://nodlin.com/tag/nodlingo/</link></image><item><title>Golang API</title><link>https://nodlin.com/strategy/golangapi/</link><pubDate>Sat, 15 Aug 2026 00:00:00 +0000</pubDate><guid>https://nodlin.com/strategy/golangapi/</guid><description>&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>External agents in Nodlin communicate with the platform over &lt;strong>NATS&lt;/strong> using &lt;strong>JSON&lt;/strong>. They register the node types they own, receive &lt;strong>actions&lt;/strong> and &lt;strong>events&lt;/strong>, and issue commands back to Nodlin (store nodes, create links, raise events, run command sequences, and more).&lt;/p>
&lt;p>To simplify that work, Nodlin provides a Go library — &lt;strong>&lt;code>nodlinGo&lt;/code>&lt;/strong> (&lt;code>github.com/johnha/nodlinGo/v2&lt;/code>) — that covers:&lt;/p>
&lt;ul>
&lt;li>Agent registration and lifecycle&lt;/li>
&lt;li>Typed node specifications (actions, events, relationships)&lt;/li>
&lt;li>Handler context for reading and updating nodes&lt;/li>
&lt;li>Forms (vueform JSON for the web client)&lt;/li>
&lt;li>Node images (SVG / PNG)&lt;/li>
&lt;li>Checkpoints, links, out-of-context command sequences&lt;/li>
&lt;li>Assignments, FYIs, activity reporting, and user notifications&lt;/li>
&lt;/ul>
&lt;p>Reference agents such as &lt;strong>FRED&lt;/strong> and &lt;strong>OpenAI&lt;/strong> show real usage of the same library.&lt;/p>
&lt;div class="flex px-4 py-3 mb-6 rounded-md bg-primary-100 dark:bg-primary-900">
&lt;span class="pr-3 pt-1 text-primary-600 dark:text-primary-300">
&lt;svg height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m11.25 11.25l.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0a9 9 0 0 1 18 0m-9-3.75h.008v.008H12z"/>&lt;/svg>
&lt;/span>
&lt;span class="dark:text-neutral-300">Other language SDKs (for example TypeScript) may be provided later. The wire protocol is JSON over NATS, so a custom client is possible — but the protocol includes &lt;strong>command IDs&lt;/strong>, &lt;strong>checkpoints&lt;/strong>, and dependency tracking so responses can be ordered safely under parallel execution. Prefer the official library unless you have a strong reason to re-implement it. See &lt;a href="https://nodlin.com/strategy/propagationofchange/">propagation of change&lt;/a>.&lt;/span>
&lt;/div>
&lt;div class="flex px-4 py-3 mb-6 rounded-md bg-yellow-100 dark:bg-yellow-900">
&lt;span class="pr-3 pt-1 text-red-400">
&lt;svg height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0zM12 15.75h.007v.008H12z"/>&lt;/svg>
&lt;/span>
&lt;span class="dark:text-neutral-300">Availability of the Go library as a public open-source module may lag the platform beta. Contact Nodlin for access, module versions, and deployment guidance for your environment.&lt;/span>
&lt;/div>
&lt;h3 id="related-documentation">Related documentation&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>
&lt;/strong> — sandboxed script agents (no external I/O)&lt;/li>
&lt;li>&lt;strong>
&lt;/strong> — form concepts (script-oriented; Go uses the same vueform model)&lt;/li>
&lt;li>&lt;strong>
&lt;/strong> — visual design for nodes&lt;/li>
&lt;li>&lt;strong>
&lt;/strong> — checkpoints, parallelism, quiescence&lt;/li>
&lt;li>&lt;strong>
&lt;/strong> — core collaboration model&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="when-to-use-an-external-go-agent">When to use an external Go agent&lt;/h2>
&lt;p>Nodlin supports several ways to define behaviour. Choose deliberately:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Approach&lt;/th>
&lt;th>Language&lt;/th>
&lt;th>Access&lt;/th>
&lt;th>Best for&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>External Go agent&lt;/strong>&lt;/td>
&lt;td>Go + &lt;code>nodlinGo&lt;/code>&lt;/td>
&lt;td>External systems over NATS/JSON&lt;/td>
&lt;td>APIs, AI, databases, multi-node synthesis, long-running work&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>External Python agent&lt;/td>
&lt;td>Python&lt;/td>
&lt;td>Same external model&lt;/td>
&lt;td>Teams standardising on Python&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Internal agent&lt;/td>
&lt;td>Go (cluster)&lt;/td>
&lt;td>Direct store / internal bus&lt;/td>
&lt;td>Shared platform primitives (workflow, notes, files)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Script package&lt;/td>
&lt;td>Starlark&lt;/td>
&lt;td>Sandboxed Nodlin API only&lt;/td>
&lt;td>Fast domain packs &lt;strong>without&lt;/strong> external I/O&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Use external Go when you need to:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Call HTTP APIs, AI providers, or internal services&lt;/li>
&lt;li>Keep secrets and network access outside the script sandbox&lt;/li>
&lt;li>Run work that may take seconds or minutes&lt;/li>
&lt;li>Own a set of node types that integrate tightly with those systems&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Prefer scripts&lt;/strong> when logic is pure graph behaviour, forms, and visuals with no external calls.&lt;/p>
&lt;p>&lt;strong>Prefer internal workflow types&lt;/strong> (task, question, option, decision, …) when the concept is already a first-class platform type users know.&lt;/p>
&lt;p>Behaviour is the same across agent kinds: typed nodes, forms, relationships, actions, and events. Only the API surface differs.&lt;/p>
&lt;hr>
&lt;h2 id="core-model-a-reactive-graph">Core model: a reactive graph&lt;/h2>
&lt;p>Nodlin is a &lt;strong>reactive knowledge graph&lt;/strong>. There is no central orchestrator that walks your business process.&lt;/p>
&lt;ol>
&lt;li>A node is stored (created or updated).&lt;/li>
&lt;li>Nodlin dispatches events to related dependents.&lt;/li>
&lt;li>Each dependent recomputes &lt;strong>its own&lt;/strong> state and may store again.&lt;/li>
&lt;li>Change cascades through topology until the graph is quiet (&lt;strong>consistency at quiescence&lt;/strong>).&lt;/li>
&lt;/ol>
&lt;p>Design implications:&lt;/p>
&lt;ul>
&lt;li>Prefer a &lt;strong>small number of meaningful types&lt;/strong> over deep custom control-flow code.&lt;/li>
&lt;li>Edge direction is &lt;strong>dependency direction&lt;/strong>: &lt;code>A → B&lt;/code> means &lt;strong>A cares about B&lt;/strong> (A receives events when B changes). Physical or causal flow is often the reverse.&lt;/li>
&lt;li>Do not invent a separate propagation engine. Relations + events &lt;strong>are&lt;/strong> the engine.&lt;/li>
&lt;li>Cycles are allowed; handlers should &lt;strong>converge&lt;/strong> (skip &lt;code>StoreNode&lt;/code> when nothing material changed).&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="architecture-overview">Architecture overview&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">┌─────────────────┐ NATS / JSON ┌──────────────────────┐
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ External Go │◄────────────────────►│ Nodlin extAgent │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ agent process │ register, actions, │ (platform bridge) │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ (nodlinGo) │ events, callbacks └──────────┬───────────┘
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└─────────────────┘ │
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ▼
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Nodlin core / graphs
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>At startup the agent:&lt;/p>
&lt;ol>
&lt;li>Connects to the agent NATS bus.&lt;/li>
&lt;li>Publishes an &lt;strong>&lt;code>ExtAgentSpec&lt;/code>&lt;/strong> (manifest of types, actions, events, relations).&lt;/li>
&lt;li>Receives an external agent id and heartbeats.&lt;/li>
&lt;li>Subscribes to action/event subjects for its handlers.&lt;/li>
&lt;li>Calls back into Nodlin for stores, links, checkpoints, command sequences, etc.&lt;/li>
&lt;/ol>
&lt;p>All handler work is framed by a &lt;strong>request context&lt;/strong> (user, graph, expanded related nodes, trace). In-context callbacks are only valid while that handler is running.&lt;/p>
&lt;hr>
&lt;h2 id="getting-started">Getting started&lt;/h2>
&lt;h3 id="runtime-configuration">Runtime configuration&lt;/h3>
&lt;p>Typical flags / environment variables (names may vary slightly by agent):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Setting&lt;/th>
&lt;th>Purpose&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>nodlin_agent_nats_server&lt;/code> / &lt;code>NODLIN_AGENT_NATS_SERVER&lt;/code>&lt;/td>
&lt;td>NATS address for external agents&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>hostname&lt;/code> / &lt;code>HOSTNAME&lt;/code>&lt;/td>
&lt;td>Instance identity (often the pod name)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>domain_agent_id&lt;/code> / &lt;code>DOMAIN_AGENT_ID&lt;/code>&lt;/td>
&lt;td>Domain agent registration id&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Agent-specific secrets&lt;/td>
&lt;td>API keys, tokens — &lt;strong>never hardcode&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="canonical-project-layout">Canonical project layout&lt;/h3>
&lt;p>Follow a clean separation (as in the FRED agent):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">~/nodlin/agents/&amp;lt;agent&amp;gt;/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;lt;agent&amp;gt;.go # main: flags, ExtAgentSpec, NewNodlinAgent, shutdown
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> go.mod # module &amp;lt;agent&amp;gt;; require nodlinGo/v2 (+ nodlinImage if used)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> Dockerfile
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> types/ # FQN constants + UDT structs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> images/ or util/ # agent and type icons
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;lt;domainpkg&amp;gt;/ # handlers, forms, external clients
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> agentTypes.go # processor + StartProcessing(agent)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> createX.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> deleteNode.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> createXForm.go
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> test/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="minimal-main-pattern">Minimal &lt;code>main&lt;/code> pattern&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">package&lt;/span> &lt;span class="nx">main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;context&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;os&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;os/signal&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;syscall&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;github.com/namsral/flag&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;github.com/rs/zerolog&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;github.com/rs/zerolog/log&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">nlGoAgent&lt;/span> &lt;span class="s">&amp;#34;github.com/johnha/nodlinGo/v2/agent&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;github.com/johnha/nodlinGo/v2/nodlinAgent&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">natsAddr&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">logLevel&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">hostname&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">domainAgentID&lt;/span> &lt;span class="kt">string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">flag&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">StringVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">natsAddr&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;nodlin_agent_nats_server&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;NATS JSON agent bus&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">flag&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">StringVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">logLevel&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;myagent_server_loglevel&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;debug&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;log level&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">flag&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">StringVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">hostname&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;hostname&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;pod/host name&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">flag&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">StringVar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">domainAgentID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;domain_agent_id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;domain agent id&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">flag&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Parse&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">level&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">zerolog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ParseLevel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">logLevel&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">level&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">zerolog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DebugLevel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">zerolog&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">SetGlobalLevel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">level&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">cancel&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithCancel&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Background&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nf">cancel&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">exitServer&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">chan&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Signal&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">signal&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Notify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">exitServer&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Interrupt&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SIGINT&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">syscall&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SIGTERM&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">processor&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">mypkg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewProcessor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="cm">/* secrets from flags/env */&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Fatal&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nf">Err&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Msg&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;init processor&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">spec&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">nodlinAgent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ExtAgentSpec&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Agent&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;myagent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Version&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;v1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Description&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Short description of the agent&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Image&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">images&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">AGENT_ICON&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Help&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Markdown help for operators and users&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Types&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="nx">nodlinAgent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ExtAgentTypeSpec&lt;/span>&lt;span class="p">{&lt;/span> &lt;span class="cm">/* … */&lt;/span> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ExecUser&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;admin&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ExecDomain&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;master&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">terminated&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">chan&lt;/span> &lt;span class="kd">struct&lt;/span>&lt;span class="p">{})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">errChan&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nb">make&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kd">chan&lt;/span> &lt;span class="kt">error&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">execAgent&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">nlGoAgent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewNodlinAgent&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">spec&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">natsAddr&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">hostname&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">domainAgentID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">errChan&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">terminated&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Error&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nf">Err&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Msg&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;register agent&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">cancel&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">processor&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">StartProcessing&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">execAgent&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">select&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="nx">agErr&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span>&lt;span class="nx">errChan&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">log&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Error&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="nf">Err&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">agErr&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">Msg&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;agent error&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">cancel&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span>&lt;span class="nx">exitServer&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">execAgent&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">execAgent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Shutdown&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">cancel&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">case&lt;/span> &lt;span class="o">&amp;lt;-&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Done&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">execAgent&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">execAgent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Shutdown&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Local development often uses &lt;code>go.work&lt;/code> or &lt;code>replace&lt;/code> directives to point at local &lt;code>nodlinGo&lt;/code> / &lt;code>nodlinImage&lt;/code> checkouts.&lt;/p>
&lt;hr>
&lt;h2 id="agent-specification">Agent specification&lt;/h2>
&lt;h3 id="extagentspec">&lt;code>ExtAgentSpec&lt;/code>&lt;/h3>
&lt;p>Top-level registration payload:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Field&lt;/th>
&lt;th>Role&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>Agent&lt;/code>&lt;/td>
&lt;td>Short name (no special characters), e.g. &lt;code>fred&lt;/code>, &lt;code>openai&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Version&lt;/code>&lt;/td>
&lt;td>Agent version string&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Description&lt;/code> / &lt;code>Help&lt;/code> / &lt;code>Image&lt;/code>&lt;/td>
&lt;td>Human-facing agent metadata&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Types&lt;/code>&lt;/td>
&lt;td>Slice of &lt;code>ExtAgentTypeSpec&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ExecUser&lt;/code> / &lt;code>ExecDomain&lt;/code>&lt;/td>
&lt;td>Execution identity (often &lt;code>admin&lt;/code> / &lt;code>master&lt;/code> for platform agents)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Validation expands short type and relation names to &lt;strong>FQNs&lt;/strong> and initialises handler codes used on the wire.&lt;/p>
&lt;h3 id="fully-qualified-names-fqn">Fully qualified names (FQN)&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Kind&lt;/th>
&lt;th>Pattern&lt;/th>
&lt;th>Example&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Type&lt;/td>
&lt;td>&lt;code>agt_&amp;lt;domain&amp;gt;_&amp;lt;user&amp;gt;_&amp;lt;agent&amp;gt;_&amp;lt;name&amp;gt;&lt;/code>&lt;/td>
&lt;td>&lt;code>agt_master_admin_fred_category&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Relation&lt;/td>
&lt;td>&lt;code>agr_&amp;lt;domain&amp;gt;_&amp;lt;user&amp;gt;_&amp;lt;agent&amp;gt;_&amp;lt;name&amp;gt;&lt;/code>&lt;/td>
&lt;td>&lt;code>agr_master_admin_fred_has_series&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Property&lt;/td>
&lt;td>&lt;code>agp_&amp;lt;domain&amp;gt;_&amp;lt;user&amp;gt;_&amp;lt;agent&amp;gt;_&amp;lt;name&amp;gt;&lt;/code>&lt;/td>
&lt;td>&lt;code>agp_master_admin_openai_title&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Define constants in a &lt;code>types&lt;/code> package — never scatter raw FQN strings through handlers.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">const&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">FredDataCategory&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;agt_master_admin_fred_category&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">FredDataSeries&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;agt_master_admin_fred_series&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">FredDataSubCategory&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;agr_master_admin_fred_has_subcategory&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">FredDataHasSeries&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">&amp;#34;agr_master_admin_fred_has_series&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;h3 id="extagenttypespec">&lt;code>ExtAgentTypeSpec&lt;/code>&lt;/h3>
&lt;p>Every type should declare:&lt;/p>
&lt;ul>
&lt;li>&lt;code>Name&lt;/code>, user-facing &lt;code>Label&lt;/code>, &lt;code>Description&lt;/code>&lt;/li>
&lt;li>&lt;strong>&lt;code>Help&lt;/code> (markdown)&lt;/strong> — short usage guide shown in the UI&lt;/li>
&lt;li>&lt;code>Icon&lt;/code> — type picker icon; reuse in form headers&lt;/li>
&lt;li>&lt;code>Actions&lt;/code> — at least &lt;strong>&lt;code>create&lt;/code>&lt;/strong> and &lt;strong>&lt;code>delete&lt;/code>&lt;/strong>&lt;/li>
&lt;li>Optional lifecycle actions (see below)&lt;/li>
&lt;li>&lt;code>Relations&lt;/code> with human labels&lt;/li>
&lt;li>&lt;code>Events&lt;/code> the type reacts to&lt;/li>
&lt;li>&lt;code>NoDuplicate: true&lt;/code> when external side-state must not be copied; or implement &lt;code>duplicated&lt;/code> to rebuild side-state after import&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">nodlinAgent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ExtAgentTypeSpec&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FredDataCategory&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Label&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;FRED Category&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Description&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Category of FRED data&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Icon&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">images&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FRED_LARGE_ICON&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Help&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;FRED organises data series into hierarchical categories.&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Actions&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">nodlinAgent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ExtAgentActionSpec&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">Name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;create&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Description&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Create category&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">HandlerFunc&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">fred&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">CreateCategory&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">Name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;delete&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Description&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Delete category&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">HandlerFunc&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">fred&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">DeleteNode&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>&lt;span class="nx">Name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FredAddCategory&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Description&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;Add subcategory&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">HandlerFunc&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">fred&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">AddCategory&lt;/span>&lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Relations&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">nodlinAgent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ExtAgentRelationshipSpec&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">OverRelation&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FredDataSubCategory&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ToType&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FredDataCategory&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Label&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;has subcategory&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">InverseLabel&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;parent category&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Weight&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;hr>
&lt;h2 id="actions">Actions&lt;/h2>
&lt;p>Handlers have a single signature:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">ActionFunc&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">nodlin&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">nodlinAgent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Nodlin&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">error&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">EventFunc&lt;/span> &lt;span class="kd">func&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">nodlin&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">nodlinAgent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Nodlin&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">error&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Return a non-nil &lt;code>error&lt;/code> to fail the operation; &lt;code>nil&lt;/code> reports success.&lt;/p>
&lt;h3 id="required-actions">Required actions&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Action&lt;/th>
&lt;th>When&lt;/th>
&lt;th>Typical work&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>create&lt;/code>&lt;/td>
&lt;td>Node is created or saved from the form&lt;/td>
&lt;td>Defaults, form, image, label/summary, &lt;code>StoreNode&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>delete&lt;/code>&lt;/td>
&lt;td>Node is deleted&lt;/td>
&lt;td>Tear down side-state; often &lt;code>nl.Delete(ctx)&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="optional-lifecycle-actions">Optional lifecycle actions&lt;/h3>
&lt;p>Declare only what you handle:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Action&lt;/th>
&lt;th>Fired when&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>assigned&lt;/code>&lt;/td>
&lt;td>Assignment created on the node&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>updatedAssignment&lt;/code>&lt;/td>
&lt;td>Assignment details changed&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>completeAssignment&lt;/code>&lt;/td>
&lt;td>Assignment completed&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>incompleteAssignment&lt;/code>&lt;/td>
&lt;td>Assignment re-opened&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>fyiUpdated&lt;/code>&lt;/td>
&lt;td>FYI created or updated&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>fyiAllAcknowledged&lt;/code>&lt;/td>
&lt;td>All FYIs acknowledged&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>fileupdate&lt;/code>&lt;/td>
&lt;td>File attached/changed&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>duplicated&lt;/code>&lt;/td>
&lt;td>Node copied (e.g. import) — rebuild side-state&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Custom action names are fine for form buttons (&lt;code>addCategory&lt;/code>, &lt;code>chartSeries&lt;/code>, …).&lt;/p>
&lt;p>An action is delivered &lt;strong>only if the type advertises it&lt;/strong>. Missing handlers are skipped (useful when an agent is offline for optional hooks).&lt;/p>
&lt;hr>
&lt;h2 id="relationships-and-events">Relationships and events&lt;/h2>
&lt;h3 id="relationships">Relationships&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">nodlinAgent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ExtAgentRelationshipSpec&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">OverRelation&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">RelHasItem&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ToType&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MyItem&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Label&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;has item&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">InverseLabel&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;item of&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// display only — does not create reverse reactivity&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">InverseRelation&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">RelItemOf&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// only if the other side must react&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Weight&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Rules that matter in practice:&lt;/p>
&lt;ul>
&lt;li>A declared relation is a &lt;strong>dependency&lt;/strong>: the owning type may re-evaluate when the related node changes. Do not add reactive edges “just in case”.&lt;/li>
&lt;li>&lt;strong>&lt;code>InverseLabel&lt;/code> is informational&lt;/strong> (menus / reverse display). It does &lt;strong>not&lt;/strong> by itself cause reverse re-execution.&lt;/li>
&lt;li>Set &lt;strong>&lt;code>InverseRelation&lt;/code>&lt;/strong> only when the &lt;strong>other&lt;/strong> node must react. For true bidirectional pairs, both types should declare the inverse so a link from either side produces both edges.&lt;/li>
&lt;li>One-way reaction is intentional when only one side depends on the other (e.g. query → observation if the observation does not need query status).&lt;/li>
&lt;li>When creating links in code, always pass a human &lt;strong>&lt;code>userLabel&lt;/code>&lt;/strong> matching the spec label — otherwise the canvas shows the raw relation FQN.&lt;/li>
&lt;/ul>
&lt;h3 id="events">Events&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">nodlinAgent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ExtAgentEventSpec&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Name&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;*&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// updated + linked + deleted&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">FromType&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MyItem&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">OverRelation&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">RelHasItem&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">HandlerFunc&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">p&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">OnItemChange&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Guidelines:&lt;/p>
&lt;ul>
&lt;li>Default to &lt;code>&amp;quot;*&amp;quot;&lt;/code> so link, unlink/delete, and data updates all recompute.&lt;/li>
&lt;li>Use a &lt;strong>named&lt;/strong> event when intermediate &lt;code>StoreNode&lt;/code>s would thrash dependents; raise it explicitly with &lt;code>nl.SendEvent(ctx, &amp;quot;documentReady&amp;quot;)&lt;/code>.&lt;/li>
&lt;li>Prefer specific names for expensive external recomputes.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="prefer-platform-constructs-before-new-types">Prefer platform constructs before new types&lt;/h2>
&lt;p>Before inventing a domain type, check whether Nodlin already models the concept.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Need&lt;/th>
&lt;th>Prefer&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Todo / assignee / work item&lt;/td>
&lt;td>&lt;strong>Assignments&lt;/strong> on a node + lifecycle actions&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Inform-only participants&lt;/td>
&lt;td>&lt;strong>FYIs&lt;/strong> + &lt;code>fyiUpdated&lt;/code> / &lt;code>fyiAllAcknowledged&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Task / question / option / decision&lt;/td>
&lt;td>Internal &lt;strong>workflow&lt;/strong> types&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Notes / comments&lt;/td>
&lt;td>Existing internal note/comment capabilities&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Example — meeting attendees:&lt;/strong> do &lt;strong>not&lt;/strong> store an &lt;code>attendees&lt;/code> array in UDT. Participants are people on &lt;strong>assignments&lt;/strong> (and informed people on &lt;strong>FYIs&lt;/strong>). The meeting node reads &lt;code>ActionList&lt;/code> / &lt;code>FYIList&lt;/code>, renders names in label/summary/image, and registers lifecycle handlers so complete/ack updates refresh the display.&lt;/p>
&lt;h3 id="assignments-and-fyis">Assignments and FYIs&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">actions&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">nl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ActionList&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// or ActionListForNode&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">fyis&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">nl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">FYIList&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// or FYIListForNode&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>The product UI already supports adding assignees, completion, calendar views, and FYI acknowledgement. Use those APIs and lifecycle hooks rather than reinventing todo lists in payload fields.&lt;/p>
&lt;h3 id="internal-workflow-types-reuse">Internal workflow types (reuse)&lt;/h3>
&lt;p>Shared building blocks users already recognise (from the workflow agent), including:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>FQN&lt;/th>
&lt;th>Role&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>agt_core_all_workflowAgent_task&lt;/code>&lt;/td>
&lt;td>Task / todo-style work&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>agt_core_all_workflowAgent_question&lt;/code>&lt;/td>
&lt;td>Question&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>agt_core_all_workflowAgent_option&lt;/code>&lt;/td>
&lt;td>Option&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>agt_core_all_workflowAgent_decision&lt;/code>&lt;/td>
&lt;td>Decision&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>agt_core_all_workflowAgent_checkpoint&lt;/code>&lt;/td>
&lt;td>Checkpoint&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>agt_core_all_workflowAgent_comment&lt;/code>&lt;/td>
&lt;td>Comment&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>When designing an external agent:&lt;/p>
&lt;ol>
&lt;li>Ask whether a workflow type already fits.&lt;/li>
&lt;li>If it almost fits, prefer extending the internal type over a parallel external twin.&lt;/li>
&lt;li>Link external domain nodes &lt;strong>to&lt;/strong> workflow nodes when composition is clearer than duplication.&lt;/li>
&lt;li>Use &lt;code>ActionForAgent&lt;/code> when creating or updating nodes owned by another agent.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="handler-context-nodlinagentnodlin">Handler context: &lt;code>*nodlinAgent.Nodlin&lt;/code>&lt;/h2>
&lt;p>Every action and event handler receives a &lt;code>Nodlin&lt;/code> value: request header, expanded node context, and command helpers.&lt;/p>
&lt;h3 id="in-context-vs-out-of-context">In-context vs out-of-context&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Context&lt;/th>
&lt;th>When&lt;/th>
&lt;th>Allowed writes&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>In-context&lt;/strong>&lt;/td>
&lt;td>Inside the handler before it returns&lt;/td>
&lt;td>&lt;code>StoreNode&lt;/code>, &lt;code>Link*&lt;/code>, &lt;code>Action&lt;/code>, &lt;code>Checkpoint&lt;/code>, &lt;code>SendEvent&lt;/code>, …&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Out-of-context&lt;/strong>&lt;/td>
&lt;td>Goroutine / worker after the handler returns&lt;/td>
&lt;td>&lt;code>ExecuteCommandSequence&lt;/code>, &lt;code>ReportNodeActivity&lt;/code>, &lt;code>SendUserNotification&lt;/code>, &lt;code>SendRequest&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>When the handler returns, the framework ACKs success and the server closes the trace. Calling &lt;code>nl.StoreNode&lt;/code> from a background goroutine after return fails with a &lt;strong>context not found&lt;/strong> style error.&lt;/p>
&lt;p>Returning &lt;strong>without&lt;/strong> &lt;code>StoreNode&lt;/code> is valid — the framework still ACKs. Unstored action payload fields are discarded.&lt;/p>
&lt;h3 id="important-methods">Important methods&lt;/h3>
&lt;p>&lt;strong>Request / identity&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>IsActionRequest&lt;/code>, &lt;code>IsEventRequest&lt;/code>, &lt;code>IsNew&lt;/code>&lt;/li>
&lt;li>&lt;code>RequestActionName&lt;/code>, &lt;code>RequestEventName&lt;/code>&lt;/li>
&lt;li>&lt;code>GetUserGraphContext&lt;/code>, &lt;code>MsgHeader&lt;/code>, &lt;code>NewID&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Read nodes&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>GetNodeWithUpdates&lt;/code> — central node with action UDT overlay (usual create/save path)&lt;/li>
&lt;li>&lt;code>GetCentralNode&lt;/code> — node before updates (&lt;code>nil&lt;/code> if new)&lt;/li>
&lt;li>&lt;code>GetRelatedActiveNodes&lt;/code> / &lt;code>GetRelatedNodes&lt;/code>&lt;/li>
&lt;li>&lt;code>GetNodeID&lt;/code>, &lt;code>GetNodeAndGraphDetailInContext&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Write&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>StoreNode&lt;/code>, &lt;code>Delete&lt;/code> / &lt;code>DeleteNode&lt;/code>, &lt;code>Restore&lt;/code> / &lt;code>RestoreNode&lt;/code>&lt;/li>
&lt;li>&lt;code>Link&lt;/code>, &lt;code>LinkFrom&lt;/code>, &lt;code>Unlink&lt;/code>, &lt;code>UnlinkFrom&lt;/code>&lt;/li>
&lt;li>&lt;code>Checkpoint&lt;/code>, &lt;code>Action&lt;/code>, &lt;code>ActionForAgent&lt;/code>, &lt;code>SendEvent&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Collaboration&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>ActionList&lt;/code>, &lt;code>ActionListForNode&lt;/code>&lt;/li>
&lt;li>&lt;code>FYIList&lt;/code>, &lt;code>FYIListForNode&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Agent-level&lt;/strong> (&lt;code>*agent.NodlinAgent&lt;/code>)&lt;/p>
&lt;ul>
&lt;li>&lt;code>ExecuteCommandSequence&lt;/code>&lt;/li>
&lt;li>&lt;code>ReportNodeActivity&lt;/code>&lt;/li>
&lt;li>&lt;code>SendUserNotification&lt;/code>&lt;/li>
&lt;li>&lt;code>Shutdown&lt;/code>, &lt;code>GetAgentDetail&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>UDT helpers&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>udt.NewNodeJsonUDTFromType(v)&lt;/code>&lt;/li>
&lt;li>&lt;code>json.Unmarshal(node.UDT, &amp;amp;v)&lt;/code>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="standard-handler-patterns">Standard handler patterns&lt;/h2>
&lt;h3 id="create">Create&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">p&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Processor&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">CreateCategory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">nl&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">nodlinAgent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Nodlin&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">error&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">cancel&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Background&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nf">cancel&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">updated&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">nl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">GetNodeWithUpdates&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">errors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">errors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">New&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;load node&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kd">var&lt;/span> &lt;span class="nx">data&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Category&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">json&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Unmarshal&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">updated&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">UDT&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">errors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">err&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">errors&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">New&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;decode udt&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// defaults + domain logic — including recompute from related nodes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">updated&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Label&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">util&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">PStr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">summary&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">buildSummary&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">updated&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Summary&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">summary&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">updated&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">NodeSizeX&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">util&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">PFloat64&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">250&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">updated&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">NodeSizeY&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">util&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">PFloat64&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">150&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">updated&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Image&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="cm">/* PNG or SVG */&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">form&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nf">createCategoryForm&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">updated&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Schema&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">form&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">udtBytes&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">udt&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewNodeJsonUDTFromType&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">updated&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">UDT&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">udtBytes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">nl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">StoreNode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">updated&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ExtAgentNode&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>&lt;strong>Important:&lt;/strong> if event handlers derive fields from related nodes, &lt;strong>&lt;code>create&lt;/code> must apply the same derivation&lt;/strong>. Otherwise user edits look stale until some other event fires.&lt;/p>
&lt;h3 id="delete">Delete&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">func&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">p&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">Processor&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">DeleteNode&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">nl&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">nodlinAgent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Nodlin&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="kt">error&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">cancel&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithTimeout&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">context&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Background&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Second&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">defer&lt;/span> &lt;span class="nf">cancel&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">nl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Delete&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;h3 id="create--checkpoint--link">Create → checkpoint → link&lt;/h3>
&lt;p>This ordered flow is extremely common for in-context child creation:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>create&lt;/strong> child (&lt;code>nl.Action&lt;/code> … &lt;code>&amp;quot;create&amp;quot;&lt;/code>)&lt;/li>
&lt;li>&lt;strong>&lt;code>Checkpoint&lt;/code>&lt;/strong> so the child exists before linking&lt;/li>
&lt;li>&lt;strong>link&lt;/strong> (&lt;code>Link&lt;/code> / &lt;code>LinkFrom&lt;/code>) with &lt;code>userLabel&lt;/code>&lt;/li>
&lt;li>optional further checkpoint&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">newID&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">util&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewID&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">nl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Action&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FredDataCategory&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;create&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">newID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">newUDT&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">nl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">Checkpoint&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="kc">nil&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nx">err&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">return&lt;/span> &lt;span class="nx">nl&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">LinkFrom&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">parentID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FredDataCategory&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">newID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FredDataCategory&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">FredDataSubCategory&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;has subcategory&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;parent category&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;div class="flex px-4 py-3 mb-6 rounded-md bg-primary-100 dark:bg-primary-900">
&lt;span class="pr-3 pt-1 text-primary-600 dark:text-primary-300">
&lt;svg height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m11.25 11.25l.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0a9 9 0 0 1 18 0m-9-3.75h.008v.008H12z"/>&lt;/svg>
&lt;/span>
&lt;span class="dark:text-neutral-300">Internally, Nodlin has a combined &lt;strong>create-and-link&lt;/strong> path (&lt;code>NL_CLIENT_NODE_AND_LINK_ACTION&lt;/code>). That convenience is &lt;strong>not currently exposed&lt;/strong> on the external Go API. External agents must use explicit create → checkpoint → link (in-context) or the equivalent multi-checkpoint &lt;strong>command sequence&lt;/strong> (out-of-context).&lt;/span>
&lt;/div>
&lt;hr>
&lt;h2 id="long-running-external-work">Long-running external work&lt;/h2>
&lt;p>Handlers should finish quickly (sub-second target after scheduling work). External HTTP or AI can take minutes and will trip action timeouts and NATS heartbeats if done inline.&lt;/p>
&lt;p>&lt;strong>Pattern (OpenAI-style):&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>In the handler: validate, mark &lt;code>InProgress&lt;/code>, build form/image, &lt;code>StoreNode&lt;/code>, enqueue work, &lt;strong>return&lt;/strong>.&lt;/li>
&lt;li>Capture before return: message header, user/graph context, node and graph ids.&lt;/li>
&lt;li>Background worker:
&lt;ul>
&lt;li>call the external API&lt;/li>
&lt;li>&lt;code>ReportNodeActivity&lt;/code> periodically so the UI pulses&lt;/li>
&lt;li>notify the user on start/failure/success&lt;/li>
&lt;li>apply results with &lt;strong>&lt;code>ExecuteCommandSequence&lt;/code>&lt;/strong> (new out-of-context stream)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Never call &lt;code>nl.StoreNode&lt;/code> from the background after the original handler returned.&lt;/li>
&lt;/ol>
&lt;h3 id="command-sequences">Command sequences&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">seq&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">createCP&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">nodlinRequest&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewCommandRequestSequence&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">correlationID&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">shortid&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">MustGenerate&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">actionMsg&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">nodlinCommands&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ExtNodeActionMsg&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">NodeID&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">newID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">NodeVersion&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">shortid&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">MustGenerate&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">NodeType&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MyItem&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">Action&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;create&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">UDT&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">itemUDT&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">GraphID&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">graphID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">GraphDomain&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">domain&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">actionMsg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithCommandID&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">shortid&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">MustGenerate&lt;/span>&lt;span class="p">()).&lt;/span>&lt;span class="nf">WithCorrelationID&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">correlationID&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">actionMsg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithMessageHeader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">domainAgentId&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">extAgentId&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">remoteAgentId&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">hostname&lt;/span>&lt;span class="p">).&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">AsUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">userID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">userDomain&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">ForGraph&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">graphID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">domain&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">createCP&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">AddRequest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">actionMsg&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">linkCP&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">createCP&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NextCheckpoint&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">linkMsg&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">nodlinCommands&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ExtNodeLinkMsg&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Operation&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">nodlinAgent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">NL_LINK_CREATE&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">FromNodeID&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">parentID&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">FromNodeType&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MyCategory&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ToNodeID&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">newID&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">ToNodeType&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">MyItem&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">OverRelation&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">types&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">RelHasItem&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">UserLabel&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s">&amp;#34;has item&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Weight&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="mi">10&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">linkMsg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithCommandID&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">shortid&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">MustGenerate&lt;/span>&lt;span class="p">()).&lt;/span>&lt;span class="nf">WithCorrelationID&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">correlationID&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">linkMsg&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">WithMessageHeader&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">domainAgentId&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">extAgentId&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">remoteAgentId&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">hostname&lt;/span>&lt;span class="p">).&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">AsUser&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">userID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">userDomain&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nf">ForGraph&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">graphID&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">domain&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">linkCP&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">AddRequest&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">linkMsg&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">_&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">agent&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ExecuteCommandSequence&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="nx">seq&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="cm">/* max duration */&lt;/span> &lt;span class="kc">nil&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Checkpoint ordering: &lt;strong>create nodes → next checkpoint → links → optional status update&lt;/strong>. Stamp every command with header, user, graph, command id, and shared correlation id.&lt;/p>
&lt;p>If two sequential handlers might start the same background job for one node, guard with a module-level set/mutex.&lt;/p>
&lt;hr>
&lt;h2 id="forms-vueform">Forms (vueform)&lt;/h2>
&lt;p>External Go agents build the same vueform JSON schemas the web client understands. Concepts match the
; the Go builder lives in &lt;code>github.com/johnha/nodlinGo/v2/vueform&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">form&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">vueform&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewForm&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">view&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">form&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">AddGroup&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;mainView&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">header&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">view&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">AddGroup&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;_header&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">vueform&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SchemaOpts&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Align&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">vueform&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;right&amp;#34;&lt;/span>&lt;span class="p">)})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">col1&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">header&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">AddColumn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;_h1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">col2&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">header&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">AddColumn&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;_h2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">9&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">col1&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">AddImage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;_icon&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nb">string&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">images&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ICON&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="nx">vueform&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">ImageOpts&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Width&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">vueform&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;48&amp;#34;&lt;/span>&lt;span class="p">)})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">col2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">AddStatic&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;_title&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;h3&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">vueform&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">StaticOpts&lt;/span>&lt;span class="p">{&lt;/span>&lt;span class="nx">Content&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">util&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">PStr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Category&amp;#34;&lt;/span>&lt;span class="p">)})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">view&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">AddTextInput&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">vueform&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">SchemaOpts&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Label&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">vueform&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Name&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">Info&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nx">vueform&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">String&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Short display name for this category&amp;#34;&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">})&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// expandable editor for long descriptions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// buttons with Action + Payload for custom actions&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">schemaBytes&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">form&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">JsonForm&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="c1">// wraps as {&amp;#34;vueform&amp;#34;: ...}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">updated&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Schema&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="nx">schema&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">NodeJsonSchema&lt;/span>&lt;span class="p">)(&lt;/span>&lt;span class="nx">schemaBytes&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Rules:&lt;/p>
&lt;ul>
&lt;li>Every editable field needs &lt;strong>&lt;code>Info&lt;/code>&lt;/strong> help (meaning, units, ranges).&lt;/li>
&lt;li>Tabs that accept input need a &lt;strong>unique save button id&lt;/strong> when multiple saves exist.&lt;/li>
&lt;li>Action buttons: &lt;code>ButtonOpts{Action, Payload}&lt;/code> map into UDT fields on the next action.&lt;/li>
&lt;li>Prefer progressive disclosure (expand/add buttons) over dumping huge child lists as always-visible nodes.&lt;/li>
&lt;/ul>
&lt;p>Common builders: &lt;code>AddGroup&lt;/code>, &lt;code>AddColumn&lt;/code>, &lt;code>AddTextInput&lt;/code>, &lt;code>AddTextArea&lt;/code>, &lt;code>AddEditor&lt;/code>, &lt;code>AddButton&lt;/code>, &lt;code>AddSlider&lt;/code>, &lt;code>AddRadioGroup&lt;/code>, &lt;code>AddSelect&lt;/code>, &lt;code>AddCheckbox&lt;/code>, &lt;code>AddStatic&lt;/code>, &lt;code>AddImage&lt;/code>, &lt;code>AddSpace&lt;/code>, &lt;code>AddDivider&lt;/code>, optional tabs via &lt;code>form.AddTab&lt;/code>.&lt;/p>
&lt;hr>
&lt;h2 id="node-display-and-images">Node display and images&lt;/h2>
&lt;p>Users scan the canvas by &lt;strong>label&lt;/strong>, &lt;strong>icon&lt;/strong>, &lt;strong>summary&lt;/strong>, and &lt;strong>edge labels&lt;/strong>. Space is small — the node image should communicate status at a glance so users need not open the form.&lt;/p>
&lt;p>&lt;strong>Always set on store:&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>Label&lt;/code>&lt;/li>
&lt;li>&lt;code>Summary&lt;/code> (markdown: description and key metrics; not only a repeat of the label)&lt;/li>
&lt;li>&lt;code>NodeSizeX&lt;/code> / &lt;code>NodeSizeY&lt;/code> (common ~250×150–350×200)&lt;/li>
&lt;li>Runtime &lt;code>Image&lt;/code>&lt;/li>
&lt;li>Type-level &lt;code>Icon&lt;/code> and markdown &lt;code>Help&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="prefer-templated-svg-for-bespoke-cards">Prefer templated SVG for bespoke cards&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Approach&lt;/th>
&lt;th>When&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>SVG template&lt;/strong> + &lt;code>image.NewNodlinSVGImage&lt;/code>&lt;/td>
&lt;td>Status colours, multi-line text, designer-owned layout&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>PNG via &lt;code>nodlinImage&lt;/code> text box&lt;/td>
&lt;td>Simple bordered label + icon chrome&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">const&lt;/span> &lt;span class="nx">cardSVG&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s">`&amp;lt;svg xmlns=&amp;#34;http://www.w3.org/2000/svg&amp;#34; width=&amp;#34;300&amp;#34; height=&amp;#34;160&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &amp;lt;rect width=&amp;#34;100%&amp;#34; height=&amp;#34;100%&amp;#34; fill=&amp;#34;{$BG}&amp;#34; rx=&amp;#34;12&amp;#34;/&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &amp;lt;text x=&amp;#34;16&amp;#34; y=&amp;#34;40&amp;#34; font-size=&amp;#34;18&amp;#34; fill=&amp;#34;#fff&amp;#34;&amp;gt;{$LABEL}&amp;lt;/text&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> &amp;lt;text x=&amp;#34;16&amp;#34; y=&amp;#34;70&amp;#34; font-size=&amp;#34;14&amp;#34; fill=&amp;#34;#eee&amp;#34;&amp;gt;{$STATUS}&amp;lt;/text&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&amp;lt;/svg&amp;gt;`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">svg&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">strings&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewReplacer&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;{$BG}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">bg&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;{$LABEL}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">html&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">EscapeString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">label&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;{$STATUS}&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">status&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">).&lt;/span>&lt;span class="nf">Replace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">cardSVG&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">node&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Image&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">image&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">NewNodlinSVGImage&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">svg&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Keep templates as &lt;strong>complete SVG documents&lt;/strong> with placeholders — not ad-hoc concatenation of path fragments — so designers can round-trip through tools such as BoxySVG. Encode scannable signals (status colour, type mark, short label, counts).&lt;/p>
&lt;hr>
&lt;h2 id="design-checklist">Design checklist&lt;/h2>
&lt;p>Before coding types, answer:&lt;/p>
&lt;ol>
&lt;li>What decisions does the user make on the canvas vs in a form?&lt;/li>
&lt;li>What must recompute when something else changes? (Only those get reactive edges.)&lt;/li>
&lt;li>Can collaboration be assignments/FYIs instead of new nodes/fields?&lt;/li>
&lt;li>Can an internal workflow type cover this instead of a new type?&lt;/li>
&lt;li>Can two concepts be fields on one node without losing search/link value?&lt;/li>
&lt;li>Are edges labeled in language the user understands?&lt;/li>
&lt;li>Is fan-out bounded, or do we need expand-on-demand actions?&lt;/li>
&lt;li>If there is a cycle, will handlers converge at quiescence?&lt;/li>
&lt;/ol>
&lt;h3 id="implementation-checklist">Implementation checklist&lt;/h3>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Agent name, version, ExecUser/ExecDomain set&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Each type has icon, label, markdown Help, create, delete&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Considered assignments/FYIs and internal workflow types before new types&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Constants for all type/relation FQNs&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Forms with field Info, header icon, unique save/action button ids&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Labels, summaries, scannable images set on store&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Event subscriptions match true dependency edges only&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> InverseRelation only where reverse reactivity is required&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> create → checkpoint → link (or command-sequence equivalent)&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Long I/O offloaded; command sequences for out-of-context writes&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Activity reporting + user notifications for multi-second jobs&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Secrets from env/flags&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Agent builds and registers cleanly in your environment&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="common-failure-modes">Common failure modes&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Symptom&lt;/th>
&lt;th>Likely cause&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Action timeout&lt;/td>
&lt;td>External I/O awaited inside the handler&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Context not found on store&lt;/td>
&lt;td>Background used &lt;code>nl.StoreNode&lt;/code> after handler return&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Edge shows FQN&lt;/td>
&lt;td>Missing &lt;code>UserLabel&lt;/code> on link&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Dependent never updates&lt;/td>
&lt;td>Wrong edge direction, missing event, or missing &lt;code>InverseRelation&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Stale derived fields after save&lt;/td>
&lt;td>&lt;code>create&lt;/code> did not recompute like the event handler&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Agent dies under load&lt;/td>
&lt;td>Blocking work without returning; heartbeat starvation&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Registration fails&lt;/td>
&lt;td>Missing hostname / NATS / domain agent id, or invalid spec&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="example-agents">Example agents&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Example&lt;/th>
&lt;th>What to copy&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>FRED&lt;/strong>&lt;/td>
&lt;td>Clean scaffold, REST expand-on-demand, forms with action payloads, simple images&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>OpenAI&lt;/strong>&lt;/td>
&lt;td>Background workers, &lt;code>ReportNodeActivity&lt;/code>, multi-node command sequences, named reactive events&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Study those implementations alongside this guide when building a new agent.&lt;/p>
&lt;hr>
&lt;h2 id="package-map-quick">Package map (quick)&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Import path&lt;/th>
&lt;th>Role&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>github.com/johnha/nodlinGo/v2/agent&lt;/code>&lt;/td>
&lt;td>&lt;code>NewNodlinAgent&lt;/code>, command sequences, activity, notifications&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>github.com/johnha/nodlinGo/v2/nodlinAgent&lt;/code>&lt;/td>
&lt;td>Specs + &lt;code>Nodlin&lt;/code> handler context&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>github.com/johnha/nodlinGo/v2/nodlinTypes&lt;/code>&lt;/td>
&lt;td>Wire types, user/graph context&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>github.com/johnha/nodlinGo/v2/nodlinCommands&lt;/code>&lt;/td>
&lt;td>Out-of-context command messages&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>github.com/johnha/nodlinGo/v2/nodlinRequest&lt;/code>&lt;/td>
&lt;td>Command sequences and checkpoints&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>github.com/johnha/nodlinGo/v2/node/udt&lt;/code>&lt;/td>
&lt;td>UDT encode/decode&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>github.com/johnha/nodlinGo/v2/vueform&lt;/code>&lt;/td>
&lt;td>Form builder&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>github.com/johnha/nodlinGo/v2/image&lt;/code>&lt;/td>
&lt;td>PNG/SVG node images&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>github.com/johnha/nodlinGo/v2/util&lt;/code>&lt;/td>
&lt;td>IDs and pointer helpers&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>github.com/johnha/nodlinImage/...&lt;/code>&lt;/td>
&lt;td>Text-box icon images (optional)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>External Go agents let Nodlin reach the outside world without abandoning the reactive graph model. Register types, react with focused handlers, store scannable nodes, and keep long work out of the request path. Prefer platform assignments, FYIs, and workflow types before inventing parallel concepts — and treat every relation as a deliberate dependency, not decoration.&lt;/p></description></item></channel></rss>