Go

Go Reference

The Go handler SDK lives in github.com/firetiger-oss/pugmark/ipc. It plays the same role as the Python SDK, wiring your code to the runtime’s stdin/stdout streams and the object server, but it reads the way Go usually does: the log is an iterator of objects, and you decode each one into your own structs rather than into a dynamically typed value.

go get github.com/firetiger-oss/pugmark

Minimal handler

The runtime feeds the whole log in on every invocation. Walk it, decide the one event that comes next, and append it. ipc.DefaultSession returns a session wired to the process’s standard streams.

package main

import (
	"encoding/json"
	"strings"

	"github.com/firetiger-oss/pugmark/ipc"
)

type Event struct {
	Kind string `json:"kind"`
	Text string `json:"text"`
}

func main() {
	s := ipc.DefaultSession()

	var last *Event
	for obj, err := range s.Read() {
		if err != nil {
			panic(err)
		}
		if obj.ContentType() != "application/json" {
			continue // skip lifecycle and pause records
		}
		var ev Event
		body := obj.Body()
		json.NewDecoder(body).Decode(&ev)
		body.Close()
		last = &ev
	}

	switch {
	case last != nil && last.Kind == "input":
		data, _ := json.Marshal(Event{Kind: "echo", Text: strings.ToUpper(last.Text)})
		s.Write(ipc.LocalObject(&ipc.Output{Data: data, ContentType: "application/json"}))
	default:
		s.Pause("nothing to echo")
	}
}

Build it and run it under the CLI the same way as any other handler:

go build -o /tmp/handler ./...
pugmark start
pugmark push <<<'{"kind":"input","text":"hello"}'
pugmark run --once -- /tmp/handler

Reading the log

s.Read yields the full session log in order, including any entries inherited from a parent session, as a sequence of objects paired with an error. Range over it once per invocation and fold each object into whatever you need to track. There is no built-in JSON helper; an object’s body is an io.ReadCloser, and you decode it into your own types.

for obj, err := range s.Read() {
	if err != nil {
		return err
	}
	var ev Event
	body := obj.Body()
	defer body.Close()
	json.NewDecoder(body).Decode(&ev)
	fold(&state, ev)
}

When a workflow fills named slots rather than appending a running log, s.Load fetches the most recent object written under a given name, and s.State ranges over one object per unique name.

Writing outputs

An output is an ipc.Output describing the body and its metadata; ipc.LocalObject turns one into the object that s.Write appends to the log.

data, _ := json.Marshal(Event{Kind: "echo", Text: "HELLO"})
s.Write(ipc.LocalObject(&ipc.Output{
	Data:        data,
	ContentType: "application/json",
	Name:        "reply",
}))

To reference a body that already lives in remote storage instead of sending it inline, use ipc.RemoteOutput(uri). The runtime fetches the bytes, hashes them, and stores them under the content-addressable layout for you.

Object methods

Every object the runtime hands you, and every one you construct, satisfies the ipc.Object interface:

MethodReturns
Body()an io.ReadCloser over the bytes (decompressed for you)
ContentType()the MIME type
Name()the name the writer set, if any
Metadata()the map[string]string of user metadata
SchemaURL()the associated schema URL, if any
Event()the lifecycle tag (push, start, …) carried on the record
Fork()whether the object opens a child session
Session()the target session ID for a cross-session write

Control flow

s.Pause("waiting for the next message")  // suspend until something new arrives
s.Sleep(time.Minute, "rate-limited")     // suspend for a bounded duration

Events

Each invocation carries one lifecycle event. Most handlers ignore it, since the log alone decides the next move, but s.Events exposes it when you want to branch on a fresh start versus a resume:

for event, err := range s.Events() {
	if err != nil {
		return err
	}
	switch e := event.(type) {
	case ipc.StartEvent: // a new root session
	case ipc.ForkEvent:  // forked from a parent
	case ipc.WakeEvent:  // resumed from pause or sleep
	case ipc.PushEvent:  // an object arrived; e is itself the Object
		_ = e
	}
}

The package also exposes ipc.Read, ipc.Write, ipc.Pause, and the rest as top-level functions that operate on the default session, so a small handler can skip the s := ipc.DefaultSession() line entirely. See the handler protocol for the exact semantics of each event and record.

The lower-level library

Everything the CLI does is also available as a plain Go library in the top-level github.com/firetiger-oss/pugmark package, for tools that want to read and write sessions directly without running the handler loop. That covers starting and listing sessions, storing and loading snapshots, and scanning objects. The full surface lives on the same reference linked at the top of this page.