Let task agents suggest discarding their task, with reasoning

This commit is contained in:
Ian Gulliver
2026-10-19 17:14:13 -07:00
parent 0be09b3279
commit 0dcbfc57f5
7 changed files with 79 additions and 16 deletions
+41 -1
View File
@@ -14,7 +14,8 @@ import (
)
const readyPrompt = "Your turn has ended and the review of your latest work has finished. If the task is ready to merge, call ready_for_merge with an overview of the whole task, then end your turn with one line. " +
"If it isn't (work remains that you mean to do, or you're waiting on the human), don't call it: say what's left in one line and end your turn. " +
"If the work shouldn't land at all (the approach failed, the task turned out unnecessary or already done), call suggest_discard with your reasoning instead, then end your turn with one line. " +
"If it isn't ready (work remains that you mean to do, or you're waiting on the human), call neither: say what's left in one line and end your turn. " +
"If you're waiting on the human for a decision or input, ask it with ask_human now, even if an earlier message already put it in prose, so it reaches their inbox; only an open ask_human question already counts as asked. Don't change any files this turn."
type readyArgs struct {
@@ -57,6 +58,45 @@ func (m *Manager) readyForMergeTool(l *live) mcp.Tool {
}
}
type discardArgs struct {
Reason string `json:"reason"`
}
func (m *Manager) suggestDiscardTool(l *live) mcp.Tool {
return mcp.Tool{
Name: "suggest_discard",
Description: "Suggest that the human discard this task rather than merge it, with your reasoning: for example the approach didn't work, the task turned out unnecessary, or another task already did it. " +
"Write the reasoning for someone jumping between tasks who hasn't followed this one: what was tried, why it shouldn't land, and anything worth keeping for a later attempt. " +
"byre shows it in the task's transcript with a Discard button and passes it to the human's lead agent. It only suggests: the human decides, and the task stays open until they do.",
InputSchema: json.RawMessage(`{
"type": "object",
"properties": {
"reason": {"type": "string", "description": "Markdown: why this task should be discarded rather than merged."}
},
"required": ["reason"]
}`),
Handler: func(ctx context.Context, raw json.RawMessage) (string, error) {
args := discardArgs{}
err := json.Unmarshal(raw, &args)
if err != nil {
return "", fmt.Errorf("malformed suggest_discard call: %w", err)
}
reason := strings.TrimSpace(args.Reason)
if reason == "" {
return "", fmt.Errorf("%w: reason must not be empty", ErrInvalid)
}
err = m.store.SuggestDiscard(ctx, l.taskID, reason, agentActor)
if errors.Is(err, store.ErrNotFound) {
return "", fmt.Errorf("%w: this task is closed", ErrConflict)
}
if err != nil {
return "", err
}
return "Recorded: the human sees your suggestion to discard this task, with your reasoning.", nil
},
}
}
func (m *Manager) promptReady(taskID string, from any) {
ctx := context.Background()
err := m.tryPromptReady(ctx, taskID, from == string(api.TaskStateReviewing))