Go
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:
| Method | Returns |
|---|---|
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.