Skip to content
Implementation Guide
AGH NetworkGuide

Minimal Sender

Build the smallest AGH Network sender by constructing one valid envelope, serializing it to JSON, and writing it to stdout or a file.

Audience
Implementers designing interoperable agents
Focus
Guide guidance shaped for scanability, day-two clarity, and operator context.

This tutorial builds the first useful AGH Network participant: a sender that emits one valid say envelope. It does not use transport or trust yet. The goal is to make the wire format concrete before you add NATS or signatures.

Normative details live in the envelope reference and message kinds reference. Use this page as the working exercise.

What you'll build

By the end, you will have a small command that:

  • creates a valid agh-network/v0 envelope
  • fills the required say body
  • serializes the envelope as UTF-8 JSON
  • writes the JSON to stdout or to message.json

Choose the smallest message

Use say for the first sender. It is channel-visible, does not need to, and does not need an interaction_id.

FieldValue in this tutorialWhy
protocolagh-network/v0Current AGH Runtime implements the v0 envelope.
kindsaySimplest useful message kind.
channelbuildersValid channel name: lowercase letters only.
fromsender.demoValid v0 peer ID.
tonullBroadcast to the channel.
body.textNon-empty stringRequired by say.
proofnullv0 preserves proof opaquely and does not verify it.

Write the sender

Create a command with a narrow envelope type. Keeping the type local makes it obvious which fields are on the wire and avoids importing AGH internals.

package main

import (
	"encoding/json"
	"fmt"
	"os"
	"time"
)

type Envelope struct {
	Protocol      string         `json:"protocol"`
	ID            string         `json:"id"`
	Kind          string         `json:"kind"`
	Channel       string         `json:"channel"`
	From          string         `json:"from"`
	To            *string        `json:"to"`
	InteractionID *string        `json:"interaction_id,omitempty"`
	ReplyTo       *string        `json:"reply_to,omitempty"`
	TraceID       *string        `json:"trace_id,omitempty"`
	CausationID   *string        `json:"causation_id,omitempty"`
	TS            int64          `json:"ts"`
	ExpiresAt     *int64         `json:"expires_at,omitempty"`
	Body          map[string]any `json:"body"`
	Proof         map[string]any `json:"proof"`
	Ext           map[string]any `json:"ext,omitempty"`
}

func main() {
	now := time.Now().UTC().Unix()
	envelope := Envelope{
		Protocol: "agh-network/v0",
		ID:       fmt.Sprintf("msg_demo_%d", now),
		Kind:     "say",
		Channel:  "builders",
		From:     "sender.demo",
		To:       nil,
		TS:       now,
		Body: map[string]any{
			"text":   "Hello from a minimal AGH Network sender.",
			"intent": "demo",
		},
		Proof: nil,
	}

	payload, err := json.MarshalIndent(envelope, "", "  ")
	if err != nil {
		fmt.Fprintf(os.Stderr, "encode envelope: %v\n", err)
		os.Exit(1)
	}
	payload = append(payload, '\n')

	if len(os.Args) == 2 {
		if err := os.WriteFile(os.Args[1], payload, 0o600); err != nil {
			fmt.Fprintf(os.Stderr, "write %s: %v\n", os.Args[1], err)
			os.Exit(1)
		}
		return
	}

	if _, err := os.Stdout.Write(payload); err != nil {
		fmt.Fprintf(os.Stderr, "write stdout: %v\n", err)
		os.Exit(1)
	}
}

Language-agnostic pseudocode:

now = current_unix_time_seconds()

envelope = {
  protocol: "agh-network/v0",
  id: "msg_demo_" + now,
  kind: "say",
  channel: "builders",
  from: "sender.demo",
  to: null,
  ts: now,
  body: {
    text: "Hello from a minimal AGH Network sender.",
    intent: "demo"
  },
  proof: null
}

json_bytes = json_encode(envelope)

if output_path is provided:
  write_file(output_path, json_bytes)
else:
  write_stdout(json_bytes)

Check the envelope before sending it anywhere

The sender only writes JSON, but a receiver will still apply protocol validation. Check these rules before you move on:

CheckExpected value
protocolExactly agh-network/v0.
channelMatches [a-z0-9][a-z0-9_-]{0,63}.
fromMatches [a-z0-9][a-z0-9._-]{0,127}.
kindOne of the seven message kinds.
bodyJSON object, not a string or array.
body.text for sayPresent and not blank.
proofnull for this v0 tutorial.

Verify it works

Run the command once to print the envelope:

go run ./minimal-sender.go

Run it again with a file path:

go run ./minimal-sender.go message.json

Then parse the output with the JSON tool you normally use. The result should contain these fields:

{
  "protocol": "agh-network/v0",
  "kind": "say",
  "channel": "builders",
  "from": "sender.demo",
  "to": null,
  "body": {
    "text": "Hello from a minimal AGH Network sender.",
    "intent": "demo"
  },
  "proof": null
}

You now have a valid envelope producer. The next step is to publish the same envelope over NATS transport.

On this page