Skip to content

Commit 426a133

Browse files
authored
refactor: remove local HITL task store (#2572)
## Summary - remove the runtime-local SQLite A2A task store added by #2567 - pass canonical waiting task state from the gateway PostgreSQL store to newly resumed runtimes - seed the upstream in-memory task store before HITL continuation - use the typed A2A gRPC transport in the suspension E2E test The reserved continuation metadata is cleared from public input, populated from canonical gateway state, consumed before task history is stored, and supports both input-required and auth-required states. ## Test plan - go test ./api/a2a ./adk/pkg/app ./adk/pkg/a2a ./core/v2/a2agateway - go test ./core/test/e2e -run '^TestAgentInstanceAskUserSurvivesSuspension$' - git diff --check Related to #2567. Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
1 parent 97bc956 commit 426a133

10 files changed

Lines changed: 164 additions & 565 deletions

File tree

go/adk/cmd/main.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,6 @@ func main() {
241241
ShutdownTimeout: 5 * time.Second,
242242
Logger: logger,
243243
Agent: runnerConfig.Agent,
244-
SessionDBURL: agentConfig.SessionDBURL,
245244
}, executor)
246245
if err != nil {
247246
logger.Error(err, "Failed to create app")

go/adk/pkg/app/app.go

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
"github.com/go-logr/zapr"
1616
"github.com/kagent-dev/kagent/go/adk/pkg/a2a"
1717
"github.com/kagent-dev/kagent/go/adk/pkg/a2a/server"
18-
localtaskstore "github.com/kagent-dev/kagent/go/adk/pkg/taskstore"
18+
apia2a "github.com/kagent-dev/kagent/go/api/a2a"
1919
"go.uber.org/zap"
2020
"go.uber.org/zap/zapcore"
2121
adkagent "google.golang.org/adk/v2/agent"
@@ -56,10 +56,6 @@ type AppConfig struct {
5656
// Agent is the ADK agent used to enrich the agent card with skills via
5757
// adka2a.BuildAgentSkills. Optional; when nil, the card is used as-is.
5858
Agent adkagent.Agent
59-
60-
// SessionDBURL locates actor-local durable storage. When set, A2A tasks are
61-
// persisted beside the ADK session database so HITL tasks survive suspension.
62-
SessionDBURL string
6359
}
6460

6561
// KAgentApp wires an AgentExecutor with kagent's A2A server.
@@ -81,12 +77,19 @@ func (i seedTaskInterceptor) Before(ctx context.Context, _ *a2asrv.CallContext,
8177
if !ok || send.Message == nil || send.Message.TaskID == "" {
8278
return ctx, nil, nil
8379
}
80+
storedTask, err := apia2a.TakeStoredTask(send.Message)
81+
if err != nil {
82+
return ctx, nil, err
83+
}
8484
if _, err := i.store.Get(ctx, send.Message.TaskID); err == nil {
8585
return ctx, nil, nil
8686
} else if !errors.Is(err, a2atype.ErrTaskNotFound) {
8787
return ctx, nil, fmt.Errorf("load actor task: %w", err)
8888
}
89-
if _, err := i.store.Create(ctx, a2atype.NewSubmittedTask(send.Message, send.Message)); err != nil && !errors.Is(err, a2ataskstore.ErrTaskAlreadyExists) {
89+
if storedTask == nil {
90+
storedTask = a2atype.NewSubmittedTask(send.Message, send.Message)
91+
}
92+
if _, err := i.store.Create(ctx, storedTask); err != nil && !errors.Is(err, a2ataskstore.ErrTaskAlreadyExists) {
9093
return ctx, nil, fmt.Errorf("seed actor task: %w", err)
9194
}
9295
return ctx, nil, nil
@@ -104,17 +107,7 @@ func New(cfg AppConfig, executor a2asrv.AgentExecutor) (*KAgentApp, error) {
104107
log := cfg.Logger
105108

106109
app := &KAgentApp{logger: log}
107-
authenticator := a2asrv.NewTaskStoreAuthenticator()
108-
var tasks a2ataskstore.Store
109-
if cfg.SessionDBURL == "" {
110-
tasks = a2ataskstore.NewInMemory(&a2ataskstore.InMemoryStoreConfig{Authenticator: authenticator})
111-
} else {
112-
var err error
113-
tasks, err = localtaskstore.New(cfg.SessionDBURL, authenticator)
114-
if err != nil {
115-
return nil, fmt.Errorf("open local task store: %w", err)
116-
}
117-
}
110+
tasks := a2ataskstore.NewInMemory(&a2ataskstore.InMemoryStoreConfig{Authenticator: a2asrv.NewTaskStoreAuthenticator()})
118111
handlerOpts := []a2asrv.RequestHandlerOption{a2asrv.WithTaskStore(tasks)}
119112

120113
// The private runtime receives a gateway-assigned ID for a new task. Seed it

go/adk/pkg/app/app_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
a2atype "github.com/a2aproject/a2a-go/v2/a2a"
1010
"github.com/a2aproject/a2a-go/v2/a2asrv"
1111
a2ataskstore "github.com/a2aproject/a2a-go/v2/a2asrv/taskstore"
12+
"github.com/kagent-dev/kagent/go/adk/pkg/a2a"
13+
apia2a "github.com/kagent-dev/kagent/go/api/a2a"
1214
)
1315

1416
// fakeExecutor implements a2asrv.AgentExecutor for testing.
@@ -60,6 +62,48 @@ func TestSeedTaskInterceptor(t *testing.T) {
6062
}
6163
}
6264

65+
func TestSeedTaskInterceptorRestoresWaitingTask(t *testing.T) {
66+
store := a2ataskstore.NewInMemory(nil)
67+
status := a2a.AttachHitlExtension(a2atype.NewMessage(a2atype.MessageRoleAgent), &a2a.AskUserRequest{
68+
Type: a2a.HITLTypeAskUserRequest, ID: "question-1",
69+
})
70+
waiting := &a2atype.Task{
71+
ID: "task-1", ContextID: "instance-1",
72+
Status: a2atype.TaskStatus{State: a2atype.TaskStateInputRequired, Message: status},
73+
}
74+
reply := a2atype.NewMessage(a2atype.MessageRoleUser)
75+
reply.TaskID, reply.ContextID = waiting.ID, waiting.ContextID
76+
if err := apia2a.AttachStoredTask(reply, waiting); err != nil {
77+
t.Fatal(err)
78+
}
79+
interceptor := seedTaskInterceptor{store: store}
80+
if _, _, err := interceptor.Before(t.Context(), nil, &a2asrv.Request{Payload: &a2atype.SendMessageRequest{Message: reply}}); err != nil {
81+
t.Fatal(err)
82+
}
83+
stored, err := store.Get(t.Context(), waiting.ID)
84+
if err != nil || stored.Task.Status.State != a2atype.TaskStateInputRequired || a2a.GetAskUserRequest(stored.Task.Status.Message) == nil {
85+
t.Fatalf("restored task = %#v, error = %v", stored, err)
86+
}
87+
if restored, err := apia2a.TakeStoredTask(reply); err != nil || restored != nil {
88+
t.Fatalf("private state was not consumed: task = %#v, error = %v", restored, err)
89+
}
90+
}
91+
92+
func TestStoredTaskMetadataSupportsAuthRequired(t *testing.T) {
93+
waiting := &a2atype.Task{
94+
ID: "task-1", ContextID: "instance-1",
95+
Status: a2atype.TaskStatus{State: a2atype.TaskStateAuthRequired},
96+
}
97+
reply := &a2atype.Message{TaskID: waiting.ID, ContextID: waiting.ContextID}
98+
if err := apia2a.AttachStoredTask(reply, waiting); err != nil {
99+
t.Fatal(err)
100+
}
101+
restored, err := apia2a.TakeStoredTask(reply)
102+
if err != nil || restored == nil || restored.Status.State != a2atype.TaskStateAuthRequired || restored.Status.Message != nil {
103+
t.Fatalf("restored task = %#v, error = %v", restored, err)
104+
}
105+
}
106+
63107
func TestApplyDefaults_Port(t *testing.T) {
64108
t.Setenv("PORT", "")
65109
cfg := applyDefaults(AppConfig{})

go/adk/pkg/taskstore/local.go

Lines changed: 0 additions & 260 deletions
This file was deleted.

0 commit comments

Comments
 (0)