improve send message api

This commit is contained in:
Levi Neely 2026-02-17 18:20:01 +01:00
parent a069a98ccc
commit ff2bd372c1
17 changed files with 175 additions and 151 deletions

View File

@ -126,7 +126,7 @@ Use existing `APPROVAL_REQUEST` and `APPROVAL_RESPONSE` message types:
cat > /tmp/approval.json <<EOF
{"to":"human","type":"APPROVAL_REQUEST","subject":"OAuth implementation","body":"Implemented OAuth login. Added 3 files, 200 LOC. All tests pass. Please review and approve."}
EOF
9p write agent/dev-123/outbox/msg-$(date +%s).json < /tmp/approval.json
9p write agent/dev-123/mail < /tmp/approval.json
# Bot waits (stays idle, checks inbox for response)
```
@ -144,7 +144,7 @@ External daemon monitors special "human" inbox or approval queue:
cat > /tmp/response.json <<EOF
{"to":"dev-123","type":"APPROVAL_RESPONSE","subject":"Approved","body":"LGTM - OAuth implementation looks good. Proceed with deployment."}
EOF
9p write agent/human/outbox/msg-$(date +%s).json < /tmp/response.json
9p write agent/human/mail < /tmp/response.json
```
### Bot Behavior

View File

@ -381,7 +381,7 @@ EOF
cat > /tmp/msg.json <<'EOF'
{"to":"$PEER_ID","type":"REVIEW_REQUEST","subject":"Review","body":"Please review staged changes"}
EOF
9p write agent/$ID/outbox/msg-$(date +%s).json < /tmp/msg.json
9p write agent/$ID/mail < /tmp/msg.json
```
See `workflows/DevReview` and `workflows/Planning` for complete examples.

View File

@ -64,5 +64,5 @@ To send a message to another agent or user:
cat > /tmp/msg.json <<'ENDMSG'
{"to":"recipient-id","type":"MESSAGE_TYPE","subject":"Subject","body":"Message body"}
ENDMSG
9p write agent/YOUR_ID/outbox/msg-$(date +%s).json < /tmp/msg.json
9p write agent/YOUR_ID/mail < /tmp/msg.json
```

View File

@ -359,7 +359,7 @@ func sendPrompt(id, prompt string) error {
if !isConnected() {
return fmt.Errorf("not connected to anvilsrv")
}
// Create message JSON
msg := map[string]interface{}{
"to": id,
@ -371,23 +371,20 @@ func sendPrompt(id, prompt string) error {
if err != nil {
return fmt.Errorf("failed to marshal message: %w", err)
}
// Write to user outbox
timestamp := time.Now().Unix()
filename := fmt.Sprintf("msg-%d.json", timestamp)
path := filepath.Join("user/outbox", filename)
fid, err := fs.Create(path, plan9.OWRITE, 0644)
// Write to user mail
path := "user/mail"
fid, err := fs.Open(path, plan9.OWRITE)
if err != nil {
return fmt.Errorf("failed to create message file: %w", err)
return fmt.Errorf("failed to open mail file: %w", err)
}
defer fid.Close()
_, err = fid.Write(msgJSON)
if err != nil {
return fmt.Errorf("failed to write message: %w", err)
}
return nil
}
@ -1086,9 +1083,6 @@ func listInboxMessages() ([]Message, []string, error) {
if err := json.Unmarshal(data, &msg); err != nil {
continue
}
if msg.ID == "" {
msg.ID = filename
}
messages = append(messages, msg)
}
@ -1097,7 +1091,10 @@ func listInboxMessages() ([]Message, []string, error) {
func extractTimestamp(filename string) int64 {
var ts int64
fmt.Sscanf(filename, "msg-%d.json", &ts)
parts := strings.Split(strings.TrimSuffix(filename, ".json"), "-")
if len(parts) == 2 {
fmt.Sscanf(parts[1], "%d", &ts)
}
return ts
}
@ -1124,7 +1121,7 @@ func openMessageWindow(idx int) error {
filename := filenames[idx-1]
msg := messages[idx-1]
w, err := acme.New()
if err != nil {
return err
@ -1159,6 +1156,7 @@ func handleMessageWindow(w *acme.Win, msg *Message, filename string) {
if err := archiveMessage(msg.ID); err != nil {
w.Fprintf("errors", "Archive failed: %v\n", err)
} else {
refreshInboxWindowByName()
w.Ctl("delete")
}
} else {
@ -1175,7 +1173,7 @@ func openReplyWindow(originalMsg *Message) error {
replyType := getReplyType(originalMsg.Type)
replySubject := fmt.Sprintf("Re: %s", originalMsg.Subject)
w.Name(fmt.Sprintf("/AnviLLM/reply/%s", originalMsg.From))
w.Write("tag", []byte("Send "))
w.Ctl("clean")
@ -1184,11 +1182,29 @@ func openReplyWindow(originalMsg *Message) error {
return nil
}
func refreshInboxWindowByName() {
wins, err := acme.Windows()
if err != nil {
return
}
for _, info := range wins {
if info.Name == "/AnviLLM/inbox" {
w, err := acme.Open(info.ID, nil)
if err != nil {
continue
}
refreshInboxWindow(w)
w.CloseFiles()
return
}
}
}
func archiveMessage(msgID string) error {
if !isConnected() {
return fmt.Errorf("not connected to anvilsrv")
}
fid, err := fs.Open("user/ctl", plan9.OWRITE)
if err != nil {
return err
@ -1241,7 +1257,7 @@ func sendReply(to, msgType, subject, body string) error {
if !isConnected() {
return fmt.Errorf("not connected to anvilsrv")
}
msg := map[string]interface{}{
"to": to,
"type": msgType,
@ -1252,20 +1268,18 @@ func sendReply(to, msgType, subject, body string) error {
if err != nil {
return fmt.Errorf("failed to marshal message: %w", err)
}
timestamp := time.Now().Unix()
filename := fmt.Sprintf("msg-%d.json", timestamp)
path := filepath.Join("user/outbox", filename)
fid, err := fs.Create(path, plan9.OWRITE, 0644)
path := "user/mail"
fid, err := fs.Open(path, plan9.OWRITE)
if err != nil {
return fmt.Errorf("failed to create message: %w", err)
return fmt.Errorf("failed to open mail file: %w", err)
}
defer fid.Close()
if _, err := fid.Write(msgJSON); err != nil {
return fmt.Errorf("failed to write message: %w", err)
}
return nil
}

View File

@ -357,38 +357,24 @@ func sendPrompt(id, prompt string) error {
if err != nil {
return fmt.Errorf("failed to marshal message: %w", err)
}
// Write to user outbox
timestamp := time.Now().Unix()
filename := fmt.Sprintf("msg-%d.json", timestamp)
path := filepath.Join("user/outbox", filename)
fid, err := fs.Create(path, plan9.OWRITE, 0644)
// Write to user mail
path := "user/mail"
fid, err := fs.Open(path, plan9.OWRITE)
if err != nil {
return fmt.Errorf("failed to create message file: %w", err)
return fmt.Errorf("failed to open mail file: %w", err)
}
defer fid.Close()
_, err = fid.Write(msgJSON)
if err != nil {
return fmt.Errorf("failed to write message: %w", err)
}
return nil
}
func sendPromptDirect(id, prompt string) error {
path := filepath.Join(id, "in")
fid, err := fs.Open(path, plan9.OWRITE)
if err != nil {
return err
}
defer fid.Close()
_, err = fid.Write([]byte(prompt))
return err
}
func updateStatus(msg string) {
statusBar.Clear()
fmt.Fprintf(statusBar, " %s", msg)

View File

@ -13,7 +13,6 @@ import (
"os/exec"
"path/filepath"
"strings"
"time"
"9fans.net/go/plan9"
"9fans.net/go/plan9/client"
@ -359,14 +358,12 @@ func sendPrompt(w http.ResponseWriter, r *http.Request, id string) {
return
}
// Write to user outbox
timestamp := time.Now().Unix()
filename := fmt.Sprintf("msg-%d.json", timestamp)
path := filepath.Join("user/outbox", filename)
// Write to user mail
path := "user/mail"
fid, err := fs.Create(path, plan9.OWRITE, 0644)
fid, err := fs.Open(path, plan9.OWRITE)
if err != nil {
http.Error(w, fmt.Sprintf("failed to create message file: %v", err), http.StatusInternalServerError)
http.Error(w, fmt.Sprintf("failed to open mail file: %v", err), http.StatusInternalServerError)
return
}
defer fid.Close()
@ -631,7 +628,10 @@ func handleInbox(w http.ResponseWriter, r *http.Request) {
}
var ts int64
fmt.Sscanf(d.Name, "msg-%d.json", &ts)
parts := strings.Split(strings.TrimSuffix(d.Name, ".json"), "-")
if len(parts) == 2 {
fmt.Sscanf(parts[1], "%d", &ts)
}
messages = append(messages, InboxMessage{
Message: msg,
@ -719,11 +719,9 @@ func handleInboxReply(w http.ResponseWriter, r *http.Request) {
return
}
timestamp := time.Now().Unix()
filename := fmt.Sprintf("msg-%d.json", timestamp)
path := filepath.Join("user/outbox", filename)
path := "user/mail"
fid, err := fs.Create(path, plan9.OWRITE, 0644)
fid, err := fs.Open(path, plan9.OWRITE)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return

View File

@ -552,8 +552,11 @@ Backends:
(let ((prompt (read-string "Prompt: ")))
(when (> (length prompt) 0)
(condition-case err
(progn
(anvillm--9p-write (concat anvillm-agent-path "/" session-id "/in") prompt)
(let ((msg (json-encode `((to . ,session-id)
(type . "PROMPT")
(subject . "User prompt")
(body . ,prompt)))))
(anvillm--9p-write (concat anvillm-agent-path "/user/mail") msg)
(message "Sent prompt to %s" (substring session-id 0 (min 8 (length session-id)))))
(error
(message "Failed to send prompt: %s" (error-message-string err))))))
@ -586,10 +589,11 @@ Backends:
(if (string-empty-p (string-trim prompt))
(message "Empty prompt, not sending")
(condition-case err
(progn
(anvillm--9p-write
(concat anvillm-agent-path "/" anvillm--prompt-session-id "/in")
prompt)
(let ((msg (json-encode `((to . ,anvillm--prompt-session-id)
(type . "PROMPT")
(subject . "User prompt")
(body . ,prompt)))))
(anvillm--9p-write (concat anvillm-agent-path "/user/mail") msg)
(message "Sent prompt to %s"
(substring anvillm--prompt-session-id 0
(min 8 (length anvillm--prompt-session-id))))

5
go.mod
View File

@ -6,14 +6,15 @@ toolchain go1.24.3
require (
9fans.net/go v0.0.7
github.com/gdamore/tcell/v2 v2.13.8
github.com/google/uuid v1.6.0
github.com/rivo/tview v0.42.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/gdamore/encoding v1.0.1 // indirect
github.com/gdamore/tcell/v2 v2.13.8 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/rivo/tview v0.42.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/term v0.37.0 // indirect

2
go.sum
View File

@ -7,6 +7,8 @@ github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeekl
github.com/gdamore/tcell/v2 v2.13.8 h1:Mys/Kl5wfC/GcC5Cx4C2BIQH9dbnhnkPgS9/wF3RlfU=
github.com/gdamore/tcell/v2 v2.13.8/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/rivo/tview v0.42.0 h1:b/ftp+RxtDsHSaynXTbJb+/n/BxDEi+W3UfF5jILK6c=

View File

@ -511,7 +511,7 @@ func (s *Session) Send(ctx context.Context, prompt string) (string, error) {
originalPrompt := prompt
// Instruction to send response to user via mailbox (triggers idle transition)
outInstruction := fmt.Sprintf("IMPORTANT: When done, write your response summary to user by running:\ncat > /tmp/msg.json <<'MSGEOF'\n{\"to\":\"user\",\"type\":\"STATUS_UPDATE\",\"subject\":\"Response\",\"body\":\"YOUR_SUMMARY_HERE\"}\nMSGEOF\n9p write agent/%s/outbox/msg-$(date +%%s).json < /tmp/msg.json\n\nThe summary MUST include your actual response and key actions taken.\n", s.id)
outInstruction := fmt.Sprintf("IMPORTANT: When done, write your response summary to user by running:\ncat > /tmp/msg.json <<'MSGEOF'\n{\"to\":\"user\",\"type\":\"LOG_INFO\",\"subject\":\"Response\",\"body\":\"YOUR_SUMMARY_HERE\"}\nMSGEOF\n9p write agent/%s/mail < /tmp/msg.json\n\nThe summary MUST include your actual response and key actions taken.\n", s.id)
// Prepend context if set (skip for slash commands)
if s.context != "" && !strings.HasPrefix(prompt, "/") {

View File

@ -13,6 +13,7 @@ type Manager struct {
outboxes map[string][]*Message
completed map[string][]*Message
mu sync.RWMutex
idCounter uint64
}
// NewManager creates a new mailbox manager

View File

@ -19,6 +19,8 @@ import (
"strings"
"sync"
"github.com/google/uuid"
"9fans.net/go/plan9"
"9fans.net/go/plan9/client"
)
@ -69,6 +71,7 @@ const (
qidUserOutbox // user/outbox
qidUserCompleted // user/completed
qidUserCtl // user/ctl
qidUserMail // user/mail
qidSessionBase = 1000
qidPeersBase = 0x10000000 // peers/{id}/file
qidInboxBase = 0x20000000 // session/{id}/inbox
@ -92,10 +95,11 @@ const (
fileRole
fileTasks
fileTmux
fileMail
fileCount
)
var fileNames = []string{"ctl", "in", "out", "log", "state", "pid", "cwd", "alias", "backend", "context", "role", "tasks", "tmux"}
var fileNames = []string{"ctl", "in", "out", "log", "state", "pid", "cwd", "alias", "backend", "context", "role", "tasks", "tmux", "mail"}
// Directory names in session
var dirNames = []string{"inbox", "outbox", "completed"}
@ -285,6 +289,9 @@ func (s *Server) walk(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
case "ctl":
qid = plan9.Qid{Type: QTFile, Path: qidUserCtl}
newPath = "/user/ctl"
case "mail":
qid = plan9.Qid{Type: QTFile, Path: qidUserMail}
newPath = "/user/mail"
default:
return errFcall(fc, "not found")
}
@ -356,46 +363,7 @@ func (s *Server) open(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
}
func (s *Server) create(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
cs.mu.Lock()
defer cs.mu.Unlock()
f, ok := cs.fids[fc.Fid]
if !ok {
return errFcall(fc, "bad fid")
}
// Allow creating files in outbox directories (session or user)
parts := strings.Split(strings.TrimPrefix(f.path, "/"), "/")
isUserOutbox := f.path == "/user/outbox"
isSessionOutbox := len(parts) == 2 && parts[1] == "outbox"
if !isUserOutbox && !isSessionOutbox {
return errFcall(fc, "can only create files in outbox")
}
fileName := fc.Name
// Must be a .json file
if !strings.HasSuffix(fileName, ".json") {
return errFcall(fc, "message files must end with .json")
}
// Create new message file
newPath := f.path + "/" + fileName
var hashKey string
if isUserOutbox {
hashKey = "user" + "outbox" + fileName
} else {
hashKey = parts[0] + "outbox" + fileName
}
qid := plan9.Qid{Type: QTFile, Path: qidMessageBase + hashID(hashKey)}
f.qid = qid
f.path = newPath
f.mode = fc.Mode
f.offset = 0
return &plan9.Fcall{Type: plan9.Rcreate, Tag: fc.Tag, Qid: qid}
return errFcall(fc, "create not supported")
}
func (s *Server) read(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
@ -671,8 +639,8 @@ func (s *Server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
return &plan9.Fcall{Type: plan9.Rwrite, Tag: fc.Tag, Count: uint32(len(fc.Data))}
}
// /{id}/outbox/msg-*.json - write message to outbox
if len(parts) == 3 && parts[1] == "outbox" && strings.HasSuffix(parts[2], ".json") {
// /{id}/mail - write message to outbox (generates UUID filename)
if len(parts) == 2 && parts[1] == "mail" {
sessID := parts[0]
// Track this connection's session ID
@ -693,6 +661,9 @@ func (s *Server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
return errFcall(fc, err.Error())
}
// Generate UUID for message
msg.ID = uuid.New().String()
// Add to outbox
mailMgr := s.mgr.GetMailManager()
if mailMgr == nil {
@ -704,7 +675,6 @@ func (s *Server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
}
// Transition sender to idle after sending any outbox message
// This allows the sender to receive responses and prevents getting stuck in "running"
if sess := s.mgr.Get(sessID); sess != nil {
if tmuxSess, ok := sess.(*tmux.Session); ok {
tmuxSess.TransitionTo("idle")
@ -714,8 +684,8 @@ func (s *Server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
return &plan9.Fcall{Type: plan9.Rwrite, Tag: fc.Tag, Count: uint32(len(fc.Data))}
}
// /user/outbox/msg-*.json - write message from user to bot
if len(parts) == 3 && parts[0] == "user" && parts[1] == "outbox" && strings.HasSuffix(parts[2], ".json") {
// /user/mail - write message from user to bot (generates UUID filename)
if len(parts) == 2 && parts[0] == "user" && parts[1] == "mail" {
// Parse JSON message
msg, err := mailbox.FromJSON(fc.Data)
if err != nil {
@ -730,6 +700,9 @@ func (s *Server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
// Set from field to "user"
msg.From = "user"
// Generate UUID for message
msg.ID = uuid.New().String()
// Add to user's outbox
mailMgr := s.mgr.GetMailManager()
if mailMgr == nil {
@ -743,6 +716,43 @@ func (s *Server) write(cs *connState, fc *plan9.Fcall) *plan9.Fcall {
return &plan9.Fcall{Type: plan9.Rwrite, Tag: fc.Tag, Count: uint32(len(fc.Data))}
}
// /agent/{id}/mail - write message from agent (generates UUID filename)
if len(parts) == 2 && parts[1] == "mail" {
sessID := parts[0]
if s.mgr.Get(sessID) == nil {
return errFcall(fc, "session not found")
}
// Parse JSON message
msg, err := mailbox.FromJSON(fc.Data)
if err != nil {
return errFcall(fc, fmt.Sprintf("invalid message JSON: %v", err))
}
// Validate message type
if err := mailbox.ValidateMessageType(msg.Type); err != nil {
return errFcall(fc, err.Error())
}
// Set from field to session ID
msg.From = sessID
// Generate UUID for message
msg.ID = uuid.New().String()
// Add to agent's outbox
mailMgr := s.mgr.GetMailManager()
if mailMgr == nil {
return errFcall(fc, "mailbox not available")
}
if err := mailMgr.AddToOutbox(sessID, msg); err != nil {
return errFcall(fc, fmt.Sprintf("failed to add message: %v", err))
}
return &plan9.Fcall{Type: plan9.Rwrite, Tag: fc.Tag, Count: uint32(len(fc.Data))}
}
return errFcall(fc, "read-only")
}
@ -842,6 +852,10 @@ func (s *Server) readDir(path string, offset uint64, count uint32) []byte {
Qid: plan9.Qid{Type: QTFile, Path: qidUserCtl},
Mode: 0222, Name: "ctl", Uid: "q", Gid: "q", Muid: "q",
})
dirs = append(dirs, plan9.Dir{
Qid: plan9.Qid{Type: QTFile, Path: qidUserMail},
Mode: 0222, Name: "mail", Uid: "q", Gid: "q", Muid: "q",
})
} else if strings.HasPrefix(path, "/user/") && strings.Count(path, "/") == 2 {
// User mailbox directory (inbox/outbox/completed)
mailboxType := strings.TrimPrefix(path, "/user/")

View File

@ -42,7 +42,7 @@ From the agent list output, the first field is the session ID you need for commu
## Sending Messages
All communication uses the mailbox system. Write JSON messages to your outbox.
All communication uses the mailbox system. Write JSON messages via the mail file.
### To Another Agent
@ -50,7 +50,7 @@ All communication uses the mailbox system. Write JSON messages to your outbox.
cat > /tmp/msg.json <<'EOF'
{"to":"{session_id}","type":"QUERY_REQUEST","subject":"Brief subject","body":"Your message here"}
EOF
9p write agent/{your_id}/outbox/msg-$(date +%s).json < /tmp/msg.json
9p write agent/{your_id}/mail < /tmp/msg.json
```
### To User
@ -61,7 +61,7 @@ The special recipient `"user"` sends messages to the human operator.
cat > /tmp/msg.json <<'EOF'
{"to":"user","type":"LOG_INFO","subject":"Task complete","body":"Summary of what was done"}
EOF
9p write agent/{your_id}/outbox/msg-$(date +%s).json < /tmp/msg.json
9p write agent/{your_id}/mail < /tmp/msg.json
```
### Message Types
@ -86,7 +86,7 @@ EOF
1. **Discover the peer**: `9p read agent/list | grep research`
2. **Extract ID**: Note the session ID (first field)
3. **Create message**: Write JSON to your outbox
3. **Create message**: Write JSON to the mail file
4. **Check inbox**: `9p ls agent/{your_id}/inbox/` for responses
## Mailbox System
@ -94,11 +94,12 @@ EOF
### Structure
Each agent has:
- **outbox/** - Write messages here to send
- **mail** - Write messages here to send (creates UUID in outbox)
- **inbox/** - Receive messages from others
- **outbox/** - Outgoing messages (read-only, populated by mail writes)
- **completed/** - Archive of processed messages
The special `user` participant also has mailboxes at `agent/user/inbox`, `agent/user/outbox`, `agent/user/completed`.
The special `user` participant also has mailboxes at `agent/user/mail`, `agent/user/inbox`, `agent/user/outbox`, `agent/user/completed`.
### Message Format
@ -111,7 +112,10 @@ Messages are JSON files with:
### Message Flow
1. Write message JSON to your outbox
2. Mail processor (runs every 5s) picks it up
### Message Flow
1. Write message JSON to the mail file
2. Mail processor (runs every 5s) picks it up from outbox
3. Delivers to recipient's inbox
4. For agents: when idle, message is formatted and sent to their `in` file
5. For user: message body is written to sender's chat log
@ -119,8 +123,8 @@ Messages are JSON files with:
### User Communication
- **bot → user**: Bot writes to its outbox with `to="user"`. Message body appears in bot's chat log. Sender transitions to idle.
- **user → bot**: User writes to `agent/user/outbox` with `to="{session_id}"`. Delivered to bot's inbox.
- **bot → user**: Bot writes to `agent/{id}/mail` with `to="user"`. Message body appears in bot's chat log. Sender transitions to idle.
- **user → bot**: User writes to `agent/user/mail` with `to="{session_id}"`. Delivered to bot's inbox.
### Checking for Messages
@ -175,7 +179,7 @@ A_ID=$(9p read agent/list | grep agent-a | awk '{print $1}')
cat > /tmp/msg.json <<'EOF'
{"to":"$B_ID","type":"QUESTION","subject":"Work request","body":"Please do X"}
EOF
9p write agent/$A_ID/outbox/msg-$(date +%s).json < /tmp/msg.json
9p write agent/$A_ID/mail < /tmp/msg.json
# Check inbox for B's response
9p ls agent/$A_ID/inbox/
@ -188,7 +192,7 @@ EOF
cat > /tmp/msg.json <<'EOF'
{"to":"$REVIEWER_ID","type":"REVIEW_REQUEST","subject":"Code review","body":"Please review staged changes"}
EOF
9p write agent/$DEV_ID/outbox/msg-$(date +%s).json < /tmp/msg.json
9p write agent/$DEV_ID/mail < /tmp/msg.json
# Developer checks inbox for response
9p ls agent/$DEV_ID/inbox/
@ -202,7 +206,7 @@ EOF
cat > /tmp/msg.json <<'EOF'
{"to":"user","type":"STATUS_UPDATE","subject":"Response","body":"Summary of what was done in THIS interaction"}
EOF
9p write agent/{your_id}/outbox/msg-$(date +%s).json < /tmp/msg.json
9p write agent/{your_id}/mail < /tmp/msg.json
```
Examples of when to send STATUS_UPDATE to user:

View File

@ -63,12 +63,12 @@ You are the developer agent in a two-agent AnviLLM workflow. Your peer is the re
3. Find the reviewer agent (alias contains "reviewer")
4. Send review request with this exact pattern:
- Create /tmp/msg.json with: {"to":"REVIEWER_ID","type":"REVIEW_REQUEST","subject":"Code review","body":"Please review the staged changes"}
- Run: 9p write agent/YOUR_ID/outbox/msg-TIMESTAMP.json < /tmp/msg.json
- Replace REVIEWER_ID with reviewer's session ID, YOUR_ID with your session ID, TIMESTAMP with $(date +%s)
- Run: 9p write agent/YOUR_ID/mail < /tmp/msg.json
- Replace REVIEWER_ID with reviewer's session ID, YOUR_ID with your session ID
- Example: cat > /tmp/msg.json <<'ENDMSG'
{"to":"abc123","type":"REVIEW_REQUEST","subject":"Code review","body":"Please review the staged changes"}
ENDMSG
Then: 9p write agent/xyz789/outbox/msg-$(date +%s).json < /tmp/msg.json
Then: 9p write agent/xyz789/mail < /tmp/msg.json
5. Wait for the reviewer's response
6. If you receive "LGTM" from the reviewer, send a LOG_INFO to user summarizing what was done, then you're done
7. If you receive suggested changes, implement them and repeat from step 2
@ -80,7 +80,7 @@ When your task is complete (after receiving LGTM), you MUST send a LOG_INFO to u
cat > /tmp/msg.json <<'ENDMSG'
{"to":"user","type":"LOG_INFO","subject":"Task complete","body":"YOUR_SUMMARY_HERE"}
ENDMSG
9p write agent/YOUR_ID/outbox/msg-$(date +%s).json < /tmp/msg.json
9p write agent/YOUR_ID/mail < /tmp/msg.json
The body MUST include a summary of what was accomplished and key changes made.
**Never finish without sending this status update to the user.**
@ -109,14 +109,14 @@ You are the code reviewer agent in a two-agent AnviLLM workflow. Your peer is th
cat > /tmp/msg.json <<EOF
{"to":"DEVELOPER_ID","type":"REVIEW_RESPONSE","subject":"Review complete","body":"LGTM"}
EOF
9p write agent/YOUR_ID/outbox/msg-$(date +%s).json < /tmp/msg.json
9p write agent/YOUR_ID/mail < /tmp/msg.json
- If changes needed:
cat > /tmp/msg.json <<EOF
{"to":"DEVELOPER_ID","type":"REVIEW_RESPONSE","subject":"Changes requested","body":"YOUR_FEEDBACK_HERE"}
EOF
9p write agent/YOUR_ID/outbox/msg-$(date +%s).json < /tmp/msg.json
9p write agent/YOUR_ID/mail < /tmp/msg.json
Replace DEVELOPER_ID, YOUR_ID, and YOUR_FEEDBACK_HERE with actual values
5. Wait for the developer to implement changes and send another review request

View File

@ -74,7 +74,7 @@ You are the developer agent in a three-agent AnviLLM workflow. Your peers are th
cat > /tmp/msg.json <<EOF
{"to":"REVIEWER_ID","type":"REVIEW_REQUEST","subject":"Code review","body":"Please review the staged changes"}
EOF
9p write agent/YOUR_ID/outbox/msg-$(date +%s).json < /tmp/msg.json
9p write agent/YOUR_ID/mail < /tmp/msg.json
Replace REVIEWER_ID and YOUR_ID with actual session IDs
5. Wait for the reviewer's response
@ -86,7 +86,7 @@ You are the developer agent in a three-agent AnviLLM workflow. Your peers are th
cat > /tmp/msg.json <<EOF
{"to":"QA_ID","type":"APPROVAL_REQUEST","subject":"Testing needed","body":"YOUR_TEST_SPEC_HERE"}
EOF
9p write agent/YOUR_ID/outbox/msg-$(date +%s).json < /tmp/msg.json
9p write agent/YOUR_ID/mail < /tmp/msg.json
Replace QA_ID, YOUR_ID, and YOUR_TEST_SPEC_HERE (include APIs, test commands, expected behavior)
- Wait for QA results
@ -117,14 +117,14 @@ You are the code reviewer agent in a three-agent AnviLLM workflow. Your peers ar
cat > /tmp/msg.json <<EOF
{"to":"DEVELOPER_ID","type":"REVIEW_RESPONSE","subject":"Review complete","body":"LGTM"}
EOF
9p write agent/YOUR_ID/outbox/msg-$(date +%s).json < /tmp/msg.json
9p write agent/YOUR_ID/mail < /tmp/msg.json
- If changes needed:
cat > /tmp/msg.json <<EOF
{"to":"DEVELOPER_ID","type":"REVIEW_RESPONSE","subject":"Changes requested","body":"YOUR_FEEDBACK_HERE"}
EOF
9p write agent/YOUR_ID/outbox/msg-$(date +%s).json < /tmp/msg.json
9p write agent/YOUR_ID/mail < /tmp/msg.json
Replace DEVELOPER_ID, YOUR_ID, and YOUR_FEEDBACK_HERE with actual values
5. Wait for the developer to implement changes and send another review request
@ -171,14 +171,14 @@ You are the QA agent in a three-agent AnviLLM workflow. Your peers are the devel
cat > /tmp/msg.json <<EOF
{"to":"DEVELOPER_ID","type":"APPROVAL_RESPONSE","subject":"Tests passed","body":"All tests passed. Functional correctness and quality verified."}
EOF
9p write agent/YOUR_ID/outbox/msg-$(date +%s).json < /tmp/msg.json
9p write agent/YOUR_ID/mail < /tmp/msg.json
- If issues found:
cat > /tmp/msg.json <<EOF
{"to":"DEVELOPER_ID","type":"APPROVAL_RESPONSE","subject":"Tests failed","body":"YOUR_DETAILED_FAILURE_REPORT"}
EOF
9p write agent/YOUR_ID/outbox/msg-$(date +%s).json < /tmp/msg.json
9p write agent/YOUR_ID/mail < /tmp/msg.json
Replace DEVELOPER_ID, YOUR_ID, and YOUR_DETAILED_FAILURE_REPORT with actual values
6. Wait for the developer to fix issues and send another test request

View File

@ -50,7 +50,7 @@ You will receive review feedback from the techeditor. Apply changes and send ack
cat > /tmp/msg.json <<'ENDMSG'
{"to":"$EDITOR_ID","type":"LOG_INFO","subject":"Changes applied","body":"YOUR_ACKNOWLEDGMENT"}
ENDMSG
9p write agent/$WRITER_ID/outbox/msg-\$(date +%s).json < /tmp/msg.json
9p write agent/$WRITER_ID/mail < /tmp/msg.json
Replace YOUR_ACKNOWLEDGMENT with actual acknowledgment
@ -65,7 +65,7 @@ Do not edit documents directly. Send recommended changes to the writer using she
cat > /tmp/msg.json <<'ENDMSG'
{"to":"$WRITER_ID","type":"REVIEW_REQUEST","subject":"Documentation review","body":"YOUR_RECOMMENDATIONS"}
ENDMSG
9p write agent/$EDITOR_ID/outbox/msg-\$(date +%s).json < /tmp/msg.json
9p write agent/$EDITOR_ID/mail < /tmp/msg.json
Replace YOUR_RECOMMENDATIONS with actual recommendations

View File

@ -68,7 +68,7 @@ When you receive a query from the architect, research and respond using shell to
cat > /tmp/msg.json <<'ENDMSG'
{"to":"$ARCH_ID","type":"QUERY_RESPONSE","subject":"Research results","body":"YOUR_ANSWER_HERE"}
ENDMSG
9p write agent/$RESEARCH_ID/outbox/msg-\$(date +%s).json < /tmp/msg.json
9p write agent/$RESEARCH_ID/mail < /tmp/msg.json
Replace YOUR_ANSWER_HERE with actual answer
@ -86,7 +86,7 @@ If documentation is insufficient, query the research bot using shell tool with t
cat > /tmp/msg.json <<'ENDMSG'
{"to":"$RESEARCH_ID","type":"QUERY_REQUEST","subject":"Research request","body":"YOUR_QUESTION_HERE"}
ENDMSG
9p write agent/$ARCH_ID/outbox/msg-\$(date +%s).json < /tmp/msg.json
9p write agent/$ARCH_ID/mail < /tmp/msg.json
Replace YOUR_QUESTION_HERE (include question, context, request for docs path or answer)
Wait for the research agent to send the answer back
@ -96,7 +96,7 @@ You will receive edit requests from the techeditor. Apply changes and send ackno
cat > /tmp/msg.json <<'ENDMSG'
{"to":"$EDITOR_ID","type":"LOG_INFO","subject":"Changes applied","body":"YOUR_ACKNOWLEDGMENT"}
ENDMSG
9p write agent/$ARCH_ID/outbox/msg-\$(date +%s).json < /tmp/msg.json
9p write agent/$ARCH_ID/mail < /tmp/msg.json
Replace YOUR_ACKNOWLEDGMENT with actual acknowledgment
@ -112,7 +112,7 @@ Do not edit documents directly. Send recommended changes to the architect using
cat > /tmp/msg.json <<'ENDMSG'
{"to":"$ARCH_ID","type":"REVIEW_REQUEST","subject":"Documentation review","body":"YOUR_RECOMMENDATIONS"}
ENDMSG
9p write agent/$EDITOR_ID/outbox/msg-\$(date +%s).json < /tmp/msg.json
9p write agent/$EDITOR_ID/mail < /tmp/msg.json
Replace YOUR_RECOMMENDATIONS with actual recommendations
Request acknowledgment from the architect