Questions tab, question bubbles, answer snapshots; errors to transcript
This commit is contained in:
+33
-34
@@ -15,13 +15,13 @@ import (
|
||||
)
|
||||
|
||||
const leadGuidance = `You are the human's lead agent in byre, shown in the left pane of byre's app. The human talks to you there, and you coordinate every task agent on their behalf. You don't do task work yourself: task agents work in their own worktrees, and you read, direct and report on them.
|
||||
- Signals: byre sends you, as messages from byre, what happens across tasks: questions and errors that task agents file, turns they finish with their final message, questions the human answered, tasks created, reviews started and finished, starting to merge, merged or discarded, and every change of a task's state, as "<task> state: <before> → <now>." listed first among what happened at the same moment. The states are running, idle, review_pending, reviewing, merging, discarding, merged, discarded and no_agent. A finished turn arrives with its state change: review_pending when byre is about to review the turn's work, reviewing while the review runs; neither is done, since the review can still find problems, so don't treat a task's work as finished until its state goes to idle. Several may arrive in one message, followed by the current task list (as list_tasks gives it) and what your right pane shows, so you rarely need to call list_tasks after a signal. The human doesn't see signals. Messages from the human arrive separately.
|
||||
- Signals: byre sends you, as messages from byre, what happens across tasks: questions that task agents ask, errors and rate limits their sessions hit, turns they finish with their final message, questions the human answered, tasks created, reviews started and finished, starting to merge, merged or discarded, and every change of a task's state, as "<task> state: <before> → <now>." listed first among what happened at the same moment. The states are running, idle, review_pending, reviewing, merging, discarding, merged, discarded and no_agent. A finished turn arrives with its state change: review_pending when byre is about to review the turn's work, reviewing while the review runs; neither is done, since the review can still find problems, so don't treat a task's work as finished until its state goes to idle. Several may arrive in one message, followed by the current task list (as list_tasks gives it) and what your right pane shows, so you rarely need to call list_tasks after a signal. The human doesn't see signals. Messages from the human arrive separately.
|
||||
- Don't respond to what the human just did. A signal about their own action (a question they answered, a task they created, merged, started merging or discarded) tells them nothing new: don't report, confirm or comment on it. Act on it only if it changes what you are doing, and then end the turn with one short line at most. When a batch of signals needs nothing from you to the human, make your final message exactly NO_REPLY and nothing else; byre folds that turn away so the human doesn't see it.
|
||||
- "Your right pane is free" means the human finished what was in the right pane. Show them the next thing that needs them, most urgent first (questions and errors, then finished turns, then tasks ready to merge), with one line saying what it is. If nothing needs them, show that project's project tab, then post a short status: one line per active task (what it's doing or waiting on), then up to three TODO items you'd start next (from list_tasks), each with a one-line reason. Write each item as a link to #/todo/<project_id>/<title, URL-encoded with parentheses as %28 and %29>, for example [Fix login redirect](#/todo/3f2a9c1b7d4e/Fix%20login%20redirect): byre draws it with a Start button the human can click, and once the task exists, its state. Start one with start_todo only when the human picks it. If that says nothing new since your last status, make your final message NO_REPLY.
|
||||
- "Your right pane is free" means the human finished what was in the right pane. Show them the next thing that needs them, most urgent first (questions, then errors on the task's transcript tab, then finished turns, then tasks ready to merge), with one line saying what it is. If nothing needs them, show that project's project tab, then post a short status: one line per active task (what it's doing or waiting on), then up to three TODO items you'd start next (from list_tasks), each with a one-line reason. Write each item as a link to #/todo/<project_id>/<title, URL-encoded with parentheses as %28 and %29>, for example [Fix login redirect](#/todo/3f2a9c1b7d4e/Fix%20login%20redirect): byre draws it with a Start button the human can click, and once the task exists, its state. Start one with start_todo only when the human picks it. If that says nothing new since your last status, make your final message NO_REPLY.
|
||||
- Whenever you suggest new work that isn't in TODO.md (a task you think is worth doing, an improvement to byre or to your own role), write it as a link to #/suggest/<project_id>/<title>/<brief>, both URL-encoded with parentheses as %28 and %29, for example [Lead tool for admin status](#/suggest/3f2a9c1b7d4e/Lead%20tool%20for%20admin%20status/Add%20a%20lead%20tool%20that%20reads%20%2Fapi%2Fadmin%2Fstatus%20...). The brief is the new task agent's whole first message, so make it a complete brief, as for create_task. byre draws the link with two buttons: Start creates the task with that title and brief at once, without you, and Discuss sends you a message asking to talk the suggestion over. Once an active task has that title, it shows the task's state instead. Don't ask whether to start a suggestion; the buttons do that.
|
||||
- Steering the right pane is a core part of your job, as important as what you write. The right pane shows one task, or a project with no task; point it with show (the task or project, the tabs to put first, the tab to select). By default, keep it on whatever you and the human are discussing, so they can see it while they read your reply; don't make them go looking for it. Call show in the same turn you bring something up:
|
||||
- Discussing, picking or starting TODO items, or a project's tasks as a whole: the project's project tab (show with project_id when no single task of that project is involved).
|
||||
- A question that needs the human: that task's inbox tab, with one line naming it and what you recommend.
|
||||
- A question that needs the human: that task's questions tab, with one line naming it and what you recommend.
|
||||
- A finished turn that needs the human, discussing a task's work, what its agent did or is doing, or whether it is ready to merge: its transcript tab, which ends with the agent's final message. A specific file in its worktree: its files tab.
|
||||
- The changes tab only when the human asks to see the diff; they rarely want it.
|
||||
- Show, don't tell. What the right pane shows, the human reads there: never repeat, quote or paraphrase a task agent's question, choices, context or final message in chat. Add only what the pane doesn't say, such as your recommendation and why, or how it connects to other work.
|
||||
@@ -34,7 +34,7 @@ const leadGuidance = `You are the human's lead agent in byre, shown in the left
|
||||
- You can create tasks, start TODO items, message, interrupt and compact task agents, and merge or discard tasks. Merge pushes to the default branch and Discard destroys work: do either only when the human asks for it.
|
||||
- Projects: when the human asks you to add a project, create it with create_project from its name and the path of its git repository on this machine.
|
||||
- Workflows: you manage the server's workflows with list_workflows, read_workflow, save_workflow and delete_workflow, and run one by giving its name to create_task or start_todo. Every stage of a workflow runs as its own task on the stage's model, once the stages it comes after have finished, so stages fan out (several stages after one) and fan in (one stage after several); a stage's agent calls finish_stage to hand its output to the stages after it, signals tell you when a stage starts and when the workflow finishes, and when the final stage finishes byre discards the run's other stage tasks, leaving the final one to merge. Whenever you show or propose a workflow, draw it as a Mermaid flowchart of its stage names only, with no models, for example a fenced mermaid block holding "flowchart LR", "Build --> Review", "Build --> Test", "Review --> Fix" and "Test --> Fix".
|
||||
- Whenever you name a task in a message, link it as a Markdown link to #/task/<task_id>, for example [Fix login redirect](#/task/da7e487305e4). byre draws it in that task's colour, and clicking it shows the task in the right pane. To open a specific tab, append it: #/task/<task_id>/<tab>. When you point the human at a question to answer, link to #/task/<task_id>/inbox; at a finished turn, to #/task/<task_id>/transcript.
|
||||
- Whenever you name a task in a message, link it as a Markdown link to #/task/<task_id>, for example [Fix login redirect](#/task/da7e487305e4). byre draws it in that task's colour, and clicking it shows the task in the right pane. To open a specific tab, append it: #/task/<task_id>/<tab>. When you point the human at a question to answer, link to #/task/<task_id>/questions; at a finished turn, to #/task/<task_id>/transcript.
|
||||
- Keep messages short; the human reads them in half the screen. Put long detail in collapsed sections: a line <details><summary>Title</summary>, the Markdown content, then a line </details>.
|
||||
- You are long-running. Your context window is large. byre sends you a signal once your context passes half of its window; then call compact_self at a logical point (a piece of work wrapped up, a topic closed), and not before; when the turn ends, byre replaces you with a fresh session that starts from your handoff.
|
||||
- Don't commit, push, or touch git branches yourself.`
|
||||
@@ -326,6 +326,10 @@ func (m *Manager) Observe(events []api.Event) {
|
||||
if ev.TaskId == nil || !humanActor(ev.Actor) {
|
||||
continue
|
||||
}
|
||||
case "session.error", "session.rate_limited":
|
||||
if ev.TaskId == nil {
|
||||
continue
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
@@ -473,6 +477,12 @@ func (m *Manager) signalText(ctx context.Context, ev api.Event) (string, error)
|
||||
}
|
||||
return m.itemText(ctx, item), nil
|
||||
|
||||
case "session.error":
|
||||
return fmt.Sprintf("%s hit an error: %v", m.taskLabel(ctx, deref(ev.TaskId)), ev.Payload["error"]), nil
|
||||
|
||||
case "session.rate_limited":
|
||||
return fmt.Sprintf("%s was rate limited: %v", m.taskLabel(ctx, deref(ev.TaskId)), ev.Payload["error"]), nil
|
||||
|
||||
case "task.created":
|
||||
task := api.Task{}
|
||||
err := remarshal(ev.Payload, &task)
|
||||
@@ -547,34 +557,26 @@ func (m *Manager) signalText(ctx context.Context, ev api.Event) (string, error)
|
||||
func (m *Manager) itemText(ctx context.Context, item api.InboxItem) string {
|
||||
label := m.taskLabel(ctx, item.TaskId)
|
||||
p := item.Payload
|
||||
switch item.Kind {
|
||||
case api.Question:
|
||||
b := &strings.Builder{}
|
||||
fmt.Fprintf(b, "%s asked question %s: %v\n", label, item.Id, p["question"])
|
||||
recommended := map[string]bool{}
|
||||
for _, r := range anyStrings(p["recommended"]) {
|
||||
recommended[r] = true
|
||||
}
|
||||
kind := "pick one"
|
||||
if p["multiple"] == true {
|
||||
kind = "pick any"
|
||||
}
|
||||
fmt.Fprintf(b, "Choices (%s):\n", kind)
|
||||
for _, o := range anyStrings(p["options"]) {
|
||||
mark := ""
|
||||
if recommended[o] {
|
||||
mark = " (recommended)"
|
||||
}
|
||||
fmt.Fprintf(b, "- %s%s\n", clip(o, 300), mark)
|
||||
}
|
||||
fmt.Fprintf(b, "Blocked on: %v\nThe full context is in read_inbox.", p["why_blocked"])
|
||||
return b.String()
|
||||
case api.SessionError:
|
||||
return fmt.Sprintf("%s hit an error (item %s): %v", label, item.Id, p["error"])
|
||||
case api.RateLimited:
|
||||
return fmt.Sprintf("%s was rate limited (item %s): %v", label, item.Id, p["error"])
|
||||
b := &strings.Builder{}
|
||||
fmt.Fprintf(b, "%s asked question %s: %v\n", label, item.Id, p["question"])
|
||||
recommended := map[string]bool{}
|
||||
for _, r := range anyStrings(p["recommended"]) {
|
||||
recommended[r] = true
|
||||
}
|
||||
return ""
|
||||
kind := "pick one"
|
||||
if p["multiple"] == true {
|
||||
kind = "pick any"
|
||||
}
|
||||
fmt.Fprintf(b, "Choices (%s):\n", kind)
|
||||
for _, o := range anyStrings(p["options"]) {
|
||||
mark := ""
|
||||
if recommended[o] {
|
||||
mark = " (recommended)"
|
||||
}
|
||||
fmt.Fprintf(b, "- %s%s\n", clip(o, 300), mark)
|
||||
}
|
||||
fmt.Fprintf(b, "Blocked on: %v\nThe full context is in read_inbox.", p["why_blocked"])
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (m *Manager) answeredText(ctx context.Context, item api.InboxItem) string {
|
||||
@@ -594,9 +596,6 @@ func (m *Manager) answeredText(ctx context.Context, item api.InboxItem) string {
|
||||
if r.Text != nil && strings.TrimSpace(*r.Text) != "" {
|
||||
answer = strings.TrimSpace(answer + " " + *r.Text)
|
||||
}
|
||||
if item.Kind != api.Question {
|
||||
return fmt.Sprintf("%s: %s answered %s %s: %s", m.taskLabel(ctx, item.TaskId), r.User, item.Kind, item.Id, clip(answer, 600))
|
||||
}
|
||||
recommended := strings.Join(anyStrings(item.Payload["recommended"]), "; ")
|
||||
return fmt.Sprintf("%s: %s answered question %s %q: %s\nThe agent recommended: %s", m.taskLabel(ctx, item.TaskId), r.User, item.Id, clip(fmt.Sprint(item.Payload["question"]), 300), clip(answer, 600), clip(recommended, 600))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user