working on a different approach for roles
This commit is contained in:
parent
391f773b2a
commit
b51bbf7e72
|
|
@ -1,5 +1,7 @@
|
|||
# Output Protocol
|
||||
|
||||
**SCOPE**: This protocol controls communication style only. It does NOT modify workflow steps, role requirements, or delegation mandates.
|
||||
|
||||
Be terse. No preamble. No summaries of completed actions. No narration.
|
||||
Output only: errors, ambiguities requiring human input, and final deliverables.
|
||||
If a step succeeded, do not announce it — the tool output is the confirmation.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ permissionMode: bypassPermissions
|
|||
|
||||
# Output Protocol
|
||||
|
||||
**SCOPE**: This protocol controls communication style only. It does NOT modify workflow steps, role requirements, or delegation mandates.
|
||||
|
||||
Be terse. No preamble. No summaries of completed actions. No narration.
|
||||
Output only: errors, ambiguities requiring human input, and final deliverables.
|
||||
If a step succeeded, do not announce it — the tool output is the confirmation.
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ type Session struct {
|
|||
winID int // Acme window ID (0 if not applicable)
|
||||
pid int
|
||||
state string
|
||||
role string // bot role (developer, reviewer, tester, etc.)
|
||||
context string // injected into first prompt only
|
||||
initialPromptSent bool // true after context was sent; reset on clear/compact/stop/restart
|
||||
createdAt time.Time
|
||||
|
|
@ -234,6 +235,20 @@ func (s *Session) GetContext() string {
|
|||
return s.context
|
||||
}
|
||||
|
||||
// SetRole sets the bot role
|
||||
func (s *Session) SetRole(role string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.role = role
|
||||
}
|
||||
|
||||
// GetRole gets the bot role
|
||||
func (s *Session) GetRole() string {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.role
|
||||
}
|
||||
|
||||
// SetWinID sets the Acme window ID
|
||||
func (s *Session) SetWinID(winID int) {
|
||||
s.mu.Lock()
|
||||
|
|
|
|||
|
|
@ -325,3 +325,23 @@ func (r *RolesFS) Read(path string) ([]byte, error) {
|
|||
|
||||
return nil, fmt.Errorf("role not found")
|
||||
}
|
||||
|
||||
// ReadRole reads a role by name, searching all focus areas
|
||||
func (r *RolesFS) ReadRole(roleName string) (string, error) {
|
||||
roles, err := r.listAllRoles()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, role := range roles {
|
||||
if role.Name == roleName {
|
||||
content, err := os.ReadFile(role.Path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("role %s not found", roleName)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,10 +111,11 @@ const (
|
|||
fileTmux
|
||||
fileMail
|
||||
fileModel
|
||||
fileRole
|
||||
fileCount
|
||||
)
|
||||
|
||||
var fileNames = []string{"ctl", "state", "pid", "cwd", "alias", "backend", "context", "sandbox", "tmux", "mail", "model"}
|
||||
var fileNames = []string{"ctl", "state", "pid", "cwd", "alias", "backend", "context", "sandbox", "tmux", "mail", "model", "role"}
|
||||
|
||||
// Directory names in session
|
||||
var dirNames = []string{"inbox", "outbox", "completed"}
|
||||
|
|
@ -908,6 +909,27 @@ 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}/role - set role and auto-load from RolesFS
|
||||
if len(parts) == 2 && parts[1] == "role" {
|
||||
sess := s.mgr.Get(parts[0])
|
||||
if sess == nil {
|
||||
return errFcall(fc, "session not found")
|
||||
}
|
||||
if tmuxSess, ok := sess.(*tmux.Session); ok {
|
||||
tmuxSess.SetRole(input)
|
||||
// Auto-load role definition from RolesFS
|
||||
if s.roles != nil {
|
||||
roleContent, err := s.roles.ReadRole(input)
|
||||
if err != nil {
|
||||
logging.Logger().Warn("role not found", zap.String("role", input), zap.Error(err))
|
||||
} else {
|
||||
tmuxSess.SetContext(roleContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
return &plan9.Fcall{Type: plan9.Rwrite, Tag: fc.Tag, Count: uint32(len(fc.Data))}
|
||||
}
|
||||
|
||||
// /{id}/state - set session state (with validation)
|
||||
if len(parts) == 2 && parts[1] == "state" {
|
||||
sessID := parts[0]
|
||||
|
|
@ -1643,6 +1665,11 @@ func (s *Server) getSessionFile(sess backend.Session, idx int) string {
|
|||
return ""
|
||||
case fileModel:
|
||||
return sess.Model()
|
||||
case fileRole:
|
||||
if tmuxSess, ok := sess.(*tmux.Session); ok {
|
||||
return tmuxSess.GetRole()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ func (s *Supervisor) assignWork() {
|
|||
return
|
||||
}
|
||||
|
||||
// Get all claimable tasks from all projects
|
||||
// Get all ready tasks from all projects
|
||||
ready, err := s.beads.Read("ready")
|
||||
if err != nil || len(ready) == 0 {
|
||||
return
|
||||
|
|
@ -43,31 +43,21 @@ func (s *Supervisor) assignWork() {
|
|||
return
|
||||
}
|
||||
|
||||
// Filter for claimable tasks
|
||||
claimable := []map[string]interface{}{}
|
||||
// Filter for unassigned tasks
|
||||
unassigned := []map[string]interface{}{}
|
||||
for _, bead := range beads {
|
||||
// Skip if already assigned
|
||||
if assignee, ok := bead["assignee"].(string); ok && assignee != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
labels, ok := bead["labels"].([]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, label := range labels {
|
||||
if labelStr, ok := label.(string); ok && labelStr == "claimable" {
|
||||
claimable = append(claimable, bead)
|
||||
break
|
||||
}
|
||||
}
|
||||
unassigned = append(unassigned, bead)
|
||||
}
|
||||
|
||||
if len(claimable) == 0 {
|
||||
if len(unassigned) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Try to assign to idle bots in matching project directories
|
||||
// Try to assign to idle bots with matching role
|
||||
bots := s.sessions.List()
|
||||
for _, botID := range bots {
|
||||
sess := s.sessions.Get(botID)
|
||||
|
|
@ -77,8 +67,16 @@ func (s *Supervisor) assignWork() {
|
|||
}
|
||||
botCwd := sess.Metadata().Cwd
|
||||
|
||||
// Get bot role (default to "developer" if not set)
|
||||
botRole := "developer"
|
||||
if tmuxSess, ok := sess.(interface{ GetRole() string }); ok {
|
||||
if role := tmuxSess.GetRole(); role != "" {
|
||||
botRole = role
|
||||
}
|
||||
}
|
||||
|
||||
// Only assign if session CWD is exactly or under task mountpoint
|
||||
for _, bead := range claimable {
|
||||
for _, bead := range unassigned {
|
||||
taskCwd, ok := bead["cwd"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
|
|
@ -88,6 +86,24 @@ func (s *Supervisor) assignWork() {
|
|||
continue
|
||||
}
|
||||
|
||||
// Extract role label from bead (format: "role:developer")
|
||||
beadRole := "developer" // default
|
||||
if labels, ok := bead["labels"].([]interface{}); ok {
|
||||
for _, label := range labels {
|
||||
if labelStr, ok := label.(string); ok {
|
||||
if role, found := strings.CutPrefix(labelStr, "role:"); found {
|
||||
beadRole = role
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip if role doesn't match
|
||||
if beadRole != botRole {
|
||||
continue
|
||||
}
|
||||
|
||||
beadID := bead["id"].(string)
|
||||
mountName := filepath.Base(taskCwd)
|
||||
|
||||
|
|
|
|||
|
|
@ -47,24 +47,21 @@ After writing code:
|
|||
|
||||
## Development Workflow
|
||||
|
||||
Iterate until both reviewer and tester approve:
|
||||
Iterate until reviewer and/or tester approve (delegate as needed):
|
||||
|
||||
```
|
||||
Plan/Research => Develop => Review => Test
|
||||
^ ^ | |
|
||||
| | v |
|
||||
| '----------' |
|
||||
'-------------------------------'
|
||||
Plan/Research => Develop => Review/Test (as needed)
|
||||
^ ^ |
|
||||
| | v
|
||||
| '----------'
|
||||
'----------------------'
|
||||
```
|
||||
|
||||
1. **Plan/Research**: Delegate research queries to researcher bot
|
||||
1. **Plan/Research**: Delegate research queries to researcher bot if needed
|
||||
2. **Develop**: Write code based on requirements and research
|
||||
3. **Review**: Send code to reviewer bot, wait for REVIEW_RESPONSE
|
||||
- If "LGTM": kill reviewer bot, proceed to Test
|
||||
3. **Review/Test**: Delegate to reviewer and/or tester as needed, wait for responses
|
||||
- If approved: kill delegated bots, complete
|
||||
- If rejected: fix issues and return to Develop
|
||||
4. **Test**: Send APPROVAL_REQUEST to tester bot, wait for APPROVAL_RESPONSE
|
||||
- If status "Approved": kill tester bot, complete
|
||||
- If status "Rejected": fix issues and return to Develop
|
||||
|
||||
## Message Protocol
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ when_to_load: Load when you need to send messages to other agents, respond to th
|
|||
3. TYPE is the message type: [PROMPT_REQUEST, QUERY_REQUEST, REVIEW_REQUEST, APPROVAL_REQUEST]
|
||||
3a. Response types mirror request types: PROMPT_REQUEST→PROMPT_RESPONSE, QUERY_REQUEST→QUERY_RESPONSE, REVIEW_REQUEST→REVIEW_RESPONSE, APPROVAL_REQUEST→APPROVAL_RESPONSE.
|
||||
4. SUBJECT is a brief description of what you did
|
||||
4a. Response subjects are the subject of the original message prefixed by "Re: "
|
||||
5. BODY is a detailed summary of what you did, including actions performed, files changed, diffs, etc.
|
||||
|
||||
### Receiving Messages
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ Create and control agent sessions.
|
|||
|
||||
## Commands
|
||||
|
||||
All commands must be run via `execute_code` tool:
|
||||
All commands must be run via `execute_code` tool, EXCEPT spawn_agent.sh which must NOT use execute_code:
|
||||
|
||||
- List sessions: `list_sessions.sh`
|
||||
- Create session: `create_session.sh <backend> <cwd> [sandbox=<sandbox>] [model=<model>]`
|
||||
- Control session: `control_session.sh <session-id> <command>`
|
||||
- Spawn agent: `spawn_agent.sh <agent-id> [cwd-path] [initial-context-prompt]`
|
||||
- Spawn agent: `spawn_agent.sh <agent-id> [cwd-path] [initial-context-prompt]` (do NOT use execute_code)
|
||||
- Kill agent: `kill_agent.sh [cwd-path]`
|
||||
|
||||
## Backends
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ description: Manage tasks using the beads 9P interface. Use when creating, updat
|
|||
- Read bead: `bash <(9p read tools/beads/read_bead.sh) <mount> <id> <property>`
|
||||
- Read bead JSON: `bash <(9p read tools/beads/read_bead.sh) <mount> <id> json`
|
||||
- Read bead comments: `bash <(9p read tools/beads/read_bead.sh) <mount> <id> comments`
|
||||
|
||||
**IMPORTANT:** When working on a bead, always check `comment_count` in the JSON output. If > 0, read comments for additional context, decisions, or blockers.
|
||||
|
||||
- Claim bead: `bash <(9p read tools/beads/claim_bead.sh) <mount> bd-abc [$AGENT_ID]`
|
||||
- Complete bead: `bash <(9p read tools/beads/complete_bead.sh) <mount> bd-abc`
|
||||
- Fail bead: `bash <(9p read tools/beads/fail_bead.sh) <mount> bd-abc "reason"`
|
||||
|
|
@ -35,17 +38,6 @@ description: Manage tasks using the beads 9P interface. Use when creating, updat
|
|||
|
||||
**Note:** Delete does not cascade. To delete a parent and all children, delete children first, then parent.
|
||||
|
||||
## Workflow
|
||||
|
||||
**IMPORTANT:** When working on a bead, always check `comment_count` in the JSON output. If > 0, read comments for additional context, decisions, or blockers.
|
||||
|
||||
1. Find a ready task
|
||||
2. Claim the ticket being worked on
|
||||
3. Use agent-kb skill to search the knowledge base.
|
||||
4. Do work
|
||||
5. Verify all children completed
|
||||
6. Only if all children completed, complete the parent. Otherwise, work on other unblocked child tasks.
|
||||
|
||||
## Rules
|
||||
|
||||
- Discover your mount first using mtab
|
||||
|
|
|
|||
Reference in New Issue