Answer a question again by forking at its answer's snapshot

This commit is contained in:
Ian Gulliver
2026-10-19 17:46:44 -07:00
parent 101041fc87
commit ec390958e9
11 changed files with 517 additions and 316 deletions
+78 -11
View File
@@ -3,6 +3,7 @@ package agents
import (
"context"
"fmt"
"log/slog"
"time"
"g.fc.run/ai/byre/internal/api"
@@ -10,38 +11,104 @@ import (
)
func (m *Manager) Fork(ctx context.Context, src api.Task, project api.Project, n int, creator api.User) (api.Task, error) {
t, _, err := m.fork(ctx, src, project, n, creator)
return t, err
}
func (m *Manager) AnswerAgain(ctx context.Context, creator api.User, create api.InboxResponseCreate) (api.Task, error) {
item, err := m.store.GetInboxItem(ctx, create.ItemId)
if err != nil {
return api.Task{}, err
}
if item.State != api.Answered {
return api.Task{}, fmt.Errorf("%w: question %s is %s, not answered", ErrConflict, item.Id, item.State)
}
n := 0
for _, r := range item.Responses {
if r.SnapshotN != nil {
n = *r.SnapshotN
}
}
if n == 0 {
return api.Task{}, fmt.Errorf("%w: question %s's answer reached the agent without a snapshot to fork from", ErrConflict, item.Id)
}
open := item
open.State = api.Open
_, err = CheckResponse(open, create)
if err != nil {
return api.Task{}, err
}
src, err := m.store.GetTask(ctx, item.TaskId)
if err != nil {
return api.Task{}, err
}
project, err := m.store.GetProject(ctx, src.ProjectId)
if err != nil {
return api.Task{}, err
}
t, sessionID, err := m.fork(ctx, src, project, n, creator)
if err != nil {
return api.Task{}, err
}
id := store.NewID()
err = m.store.CreateAnsweredItem(ctx, api.InboxItem{
Id: id,
ProjectId: t.ProjectId,
TaskId: t.Id,
AgentSessionId: sessionID,
ThreadId: id,
Payload: item.Payload,
CreatedAt: time.Now().UTC(),
}, newResponse(id, creator.Login, create))
if err != nil {
return api.Task{}, fmt.Errorf("task %s forked, but its answer was not recorded: %w", t.Id, err)
}
slog.Info("question answered again", "item", item.Id, "fork", t.Id, "snapshot", n, "user", creator.Login)
go func() {
err := m.Deliver(context.Background(), sessionID)
if err != nil {
slog.Error("deliver inbox responses", "session", sessionID, "err", err)
}
}()
return t, nil
}
func (m *Manager) fork(ctx context.Context, src api.Task, project api.Project, n int, creator api.User) (api.Task, string, error) {
if creator.GitName == "" || creator.GitEmail == "" {
return api.Task{}, ErrNoGitIdentity
return api.Task{}, "", ErrNoGitIdentity
}
if src.Status != api.TaskStatusActive {
return api.Task{}, fmt.Errorf("%w: task %s is %s", ErrConflict, src.Id, src.Status)
return api.Task{}, "", fmt.Errorf("%w: task %s is %s", ErrConflict, src.Id, src.Status)
}
snap, err := m.store.GetSnapshot(ctx, src.Id, n)
if err != nil {
return api.Task{}, err
return api.Task{}, "", err
}
srcSess, err := m.store.GetSession(ctx, snap.AgentSessionId)
if err != nil {
return api.Task{}, err
return api.Task{}, "", err
}
if srcSess.Vendor == api.Antigravity {
return api.Task{}, fmt.Errorf("%w: snapshot %d of task %s is from an Antigravity session, and agy can't fork a conversation", ErrConflict, n, src.Id)
return api.Task{}, "", fmt.Errorf("%w: snapshot %d of task %s is from an Antigravity session, and agy can't fork a conversation", ErrConflict, n, src.Id)
}
events, err := m.store.TaskEvents(ctx, src.Id, 0, -1)
if err != nil {
return api.Task{}, err
return api.Task{}, "", err
}
at := forkPoint(events, snap.AgentSessionId, snap.EventSeq)
if at == "" {
return api.Task{}, fmt.Errorf("%w: snapshot %d of task %s has no recorded agent message at or before it to fork the conversation from", ErrConflict, n, src.Id)
return api.Task{}, "", fmt.Errorf("%w: snapshot %d of task %s has no recorded agent message at or before it to fork the conversation from", ErrConflict, n, src.Id)
}
var frozen *store.FrozenWorkflow
if src.Workflow != nil {
f, err := m.store.TaskWorkflow(ctx, src.Id)
if err != nil {
return api.Task{}, err
return api.Task{}, "", err
}
frozen = &f
}
@@ -58,7 +125,7 @@ func (m *Manager) Fork(ctx context.Context, src api.Task, project api.Project, n
ParentTaskId: src.ParentTaskId,
}, frozen, snap.CommitSha, creator.Login)
if err != nil {
return api.Task{}, err
return api.Task{}, "", err
}
id := newUUID()
@@ -76,9 +143,9 @@ func (m *Manager) Fork(ctx context.Context, src api.Task, project api.Project, n
ForkedAt: &at,
}, t.ProjectId)
if err != nil {
return api.Task{}, fmt.Errorf("task %s created, but its agent session was not: %w", t.Id, err)
return api.Task{}, "", fmt.Errorf("task %s created, but its agent session was not: %w", t.Id, err)
}
return t, nil
return t, id, nil
}
func forkPoint(events []api.Event, sessionID string, seq int64) string {