A program can run another program that uses the kit, such as its own command line from a page of its web UI, or a tool call run as a child. The child writes its own event log. The parent reads it, and the two logs name one trace.
Procedure#
- In the parent, put the
Traceparentofprogress.Span.TraceContextin the child’s environment, asTRACEPARENT. Give the child an event log of its own, in a file of the parent’s, by the flag or the variable that the child takes for it. - In the child, read
TRACEPARENTand parse it withprogress.ParseTraceContext. Give it to the Bus asBusOptions.Trace, or tocliprogress.Options.Trace. - In the parent, follow the log with
eventlog.NewTail. Poll it while the child runs, and once after it ended. - Feed the lines to a
eventlog.Reducer, to show the runs in a view, or to aeventlog.Forwarder, to send the events of your trace on to a sink.
dir, err := os.MkdirTemp("", "child")
if err != nil {
panic(err)
}
defer func() { _ = os.RemoveAll(dir) }()
path := filepath.Join(dir, "progress.jsonl")
// The server, in the span of the call, hands the child its trace.
server := progress.NewBus(progress.BusOptions{Program: "server"})
_, call := progress.Start(progress.WithBus(context.Background(), server), progress.KindCall, "tool call")
traceparent := call.TraceContext().Traceparent()
// The child, run with its event log in path and TRACEPARENT set.
trace, _ := progress.ParseTraceContext(traceparent, "")
f, err := os.Create(path)
if err != nil {
panic(err)
}
log := progress.NewLog(f, progress.LogOptions{})
bus := progress.NewBus(progress.BusOptions{Sinks: []progress.Sink{log}, Program: "prog", Trace: trace})
fanout.Map(progress.WithBus(context.Background(), bus), []string{"exe1", "exe2", "exe3"},
fanout.MapOptions[string]{Step: "probe the nodes"},
func(context.Context, string) (struct{}, error) { return struct{}{}, nil })
bus.Close()
if err := log.Close(); err != nil {
panic(err)
}
_ = f.Close()
// The server again; Send would send the notification on the call's
// session, and here keeps the last.
var last string
n := mcpprogress.New(mcpprogress.Options{
Program: "server",
Send: func(_ context.Context, progress, total float64, message string) error {
last = fmt.Sprintf("%v/%v %s", progress, total, message)
return nil
},
})
tail := eventlog.NewTail(path, eventlog.NewForwarder(n, server.TraceID()))
if _, err := tail.Poll(); err != nil {
panic(err)
}
if err := n.Close(context.Background()); err != nil {
panic(err)
}
call.End(nil)
server.Close()
fmt.Println(last)3/3 probe the nodes: 3/3 doneprogress/eventlog/example_test.go. CI runs it and checks its output.NOTE
The event log has version 1. Keys can be added in a release, and are never renamed or removed. A reader passes over the keys and the values that it does not know. See the event log.