Lab · Give a Pico a Nervous System
In this lab you will author one artefact per semantic concern of the Nervous System — discovery, presence, observation, event, command, delegation, result, authorization, and memory classification — and validate every artefact as local YAML.
The scenario is a small two-Pico interaction that mirrors the runtime Hello Two Picos lab without requiring a running cluster:
pico-aliceobserves soil moisture and, when it drops, delegates an investigation mission topico-bob.pico-bobaccepts the delegation, produces a result, and both Picos treat the underlying messages according to identity, authorization, and memory rules.
Every step in this lab operates on local YAML only. No step provisions or connects to EMQX, MQTT, or any other broker. The transport mapping in Step 9 is a labeled, optional, non-runnable annex — nothing in it executes.
Setup
mkdir -p scratch/nervousStep 1 — Discovery advertisement
Create scratch/nervous/discovery.yaml. This is what a Pico advertises about itself to the transport, provider-neutrally.
# scratch/nervous/discovery.yaml
pico:
identity:
id: pico-alice
kind: garden-detective
capabilities:
- garden.moisture.read
- garden.watering.request
protocols:
- pico-agent-transport # provider-neutral
status:
state: onlineStep 2 — Presence
Create scratch/nervous/presence.yaml. Presence is a distinct message type from discovery: discovery is what a Pico offers, presence is whether it is reachable right now.
# scratch/nervous/presence.yaml
message:
type: presence
source: pico-alice
timestamp: 2026-01-01T00:00:00Z
payload:
state: onlineStep 3 — Observation
Create scratch/nervous/observation.yaml. Observations are sensor-shaped data; they are not events until a Pico interprets them.
# scratch/nervous/observation.yaml
message:
type: observation
source: pico-alice
subject: garden/soil
timestamp: 2026-01-01T00:00:01Z
payload:
moisture: 32
unit: percentStep 4 — Event
Create scratch/nervous/event.yaml. An event is a state transition other participants may react to.
# scratch/nervous/event.yaml
message:
type: event
source: pico-alice
subject: garden/soil/dry
timestamp: 2026-01-01T00:00:02Z
payload:
threshold: 40
observed: 32Step 5 — Command
Create scratch/nervous/command.yaml. A command names a capability on a target, never a broker topic.
# scratch/nervous/command.yaml
message:
type: command
source: pico-alice
target: pico-bob
subject: garden.watering.request # provider-neutral capability
correlation: cmd-001
payload:
duration_s: 60Step 6 — Delegation and Result
Create scratch/nervous/delegation.yaml and scratch/nervous/result.yaml. A delegation is a mission handed to another Pico; the result closes it via the same correlation value.
# scratch/nervous/delegation.yaml
message:
type: delegation
source: pico-alice
target: pico-bob
subject: mission.investigate_dry_zone
correlation: mission-001# scratch/nervous/result.yaml
message:
type: result
source: pico-bob
subject: mission.investigate_dry_zone
correlation: mission-001
payload:
status: completed
evidence-reference: memory/evidence.yamlStep 8 — Memory classification
Create scratch/nervous/memory.yaml. Not every delivered message becomes long-term memory; the Pico classifies each one.
# scratch/nervous/memory.yaml
classifications:
- message: observation.yaml
classification: transient # discarded after evaluation
- message: event.yaml
classification: observe # short-lived working state
- message: delegation.yaml
classification: mission # kept for the duration of the mission
- message: result.yaml
classification: evidence # retained as verifiable memoryStep 9 — Validate all nine artefacts
Every file is local YAML. No broker is used. These are Nervous System governance and message artefacts, not Hello Pico Rules, so the check uses python3 to confirm each file is well-formed YAML with the expected shape. pico parse only accepts the flat Rule shape (id/kind/value) taught in Part 1.
python3 - <<'PY'
import sys, yaml, pathlib
root = pathlib.Path("scratch/nervous")
names = ("discovery","presence","observation","event","command",
"delegation","result","authorization","memory")
required_types = {"presence","observation","event","command","delegation","result"}
seen_types = set()
failed = False
for name in names:
path = root / f"{name}.yaml"
try:
doc = yaml.safe_load(path.read_text())
except yaml.YAMLError as exc:
print(f"FAIL: {path} — {exc}", file=sys.stderr); failed = True; continue
if not isinstance(doc, dict) or not doc:
print(f"FAIL: {path} — expected a non-empty YAML mapping", file=sys.stderr)
failed = True; continue
if "message" in doc and isinstance(doc["message"], dict):
seen_types.add(doc["message"].get("type"))
print(f"OK: {name}.yaml parses")
missing = required_types - seen_types
if missing:
print(f"FAIL: missing semantic message types: {sorted(missing)}", file=sys.stderr)
failed = True
sys.exit(1 if failed else 0)
PYStep 10 — Optional annex · MQTT/EMQX topic mapping (non-runnable)
This annex illustrates how a semantic Nervous System message could be carried by MQTT/EMQX. It is not executed, no broker is contacted, and it is not a prerequisite for completing the lab. No credentials, tokens, or EMQX account are needed.
# scratch/nervous/mqtt-mapping.yaml (illustrative only — do not run)
transport: mqtt
mappings:
event: picos/{source}/event/{subject} # QoS 1, retained: false
observation: picos/{source}/observation/{subject} # QoS 0
command: picos/{target}/command/{subject} # QoS 1
delegation: picos/{target}/mission/{subject} # QoS 1, correlation required
result: picos/{source}/result/{subject} # QoS 1, correlation required
presence: picos/{source}/presence # QoS 1, retained: trueSemantic messages in Steps 2–6 do not change. Swapping the transport means swapping this mapping file only. For a deeper walk-through of the adapter boundary — including a mock contract check that runs on local YAML — see the optional, non-runnable follow-up page: EMQX/MQTT as a transport adapter.
Step 11 — Reflect
Answer, using your files:
- Which file expresses what capabilities the Pico offers (discovery)?
- Which file expresses whether the Pico is reachable right now (presence)?
- Why do the
command,delegation, andresultfiles name a capability or mission, and not an MQTT topic? - Why must the
correlationon theresultmatch the one on thedelegation? - Which file expresses broker-side authentication and which expresses Open Engineering authorization, and why must they stay separate?
- Which message becomes long-term memory, and why does an
observationtypically not?
What you will produce
Nine local YAML artefacts — one per semantic concern of the Nervous System — plus an optional, non-runnable MQTT/EMQX mapping annex. Together they show event-driven communication as governed communication with clear separation between discovery, presence, message semantics, identity, authorization, and memory.
Next
Continue with the Summary.