Taking a task
Accept work from another agent in Go, report progress and send the results back.
You are the delegate: your program listens, and other agents hand it work. The shape mirrors the CLI guide: listen, handle each message in its thread, say what you are doing, send the results back.
1. Listen
p, err := awp.New(awp.Options{
Dir: "/var/lib/builder",
Name: "builder@ci",
Listen: []string{"tailcat"}, // reachable from anywhere; "tcp:127.0.0.1:7000" for local use
})
if err != nil {
log.Fatal(err)
}
defer p.Close()
addr, _ := p.TailcatAddress()
fmt.Println("address:", addr) // share it with whoever delegates to youA tailcat listener takes a few seconds; TailcatAddress says why it is missing until then. The address is a bearer secret for reaching hello, so share it with care.
2. Handle each message in its thread
for ev := range p.Events(ctx) {
m, ok := ev.(awp.Message)
if !ok {
continue
}
go handle(p, m)
}
func handle(p *awp.Peer, m awp.Message) {
p.SetState(m.Peer, m.Thread, awp.StateWorking, "running the suite")
result, logPath, err := run(m) // your work
if err != nil {
p.SetState(m.Peer, m.Thread, awp.StateFailed, err.Error())
return
}
p.Send(awp.Draft{To: m.Peer, Thread: m.Thread, ReplyTo: m.ID, Text: result, Files: []string{logPath}})
p.SetState(m.Peer, m.Thread, awp.StateDone, "")
}The message was stored and acked before the event reached you; a crash after that loses nothing, and History has it. m.Parts carries the data and code parts; m.Text() joins the text.
3. Ask when you need to
p.SetState(m.Peer, m.Thread, awp.StateWaiting, "which database?")
p.Send(awp.Draft{To: m.Peer, Thread: m.Thread, Text: "Staging or production database?"})Set waiting every time you ask, so the delegator (and its user) can tell a question from progress. The answer arrives as a Message in the same thread; go back to working.
4. Requests for capabilities
A message may carry a request: a data part of a vnd.awp type, such as application/vnd.awp.exec+json. The engine checks it against the sender’s grants before you see it:
for _, r := range m.Requests {
if r.Allowed {
// r.Cap is "exec", "fs:read", "fs:write" or "admin"; act on m.Parts[r.Part].Data.
}
// A denied request was answered with err forbidden already.
}Options.Policy.Serve lets the engine fulfil exec, fs:read and fs:write by itself, confined to Policy.Root; see Grants.
5. Files that arrive
Attachments come as Blob events, with the file already written under the state directory:
case awp.Blob:
fmt.Printf("%s (%d bytes) at %s, thread %s\n", e.Name, e.Size, e.Path, e.Thread)Blobs larger than Options.BlobLimit (50 MiB) are refused with err blob_refused; the sender’s Blob never fires and it sees a PeerError.