Skip to content

Commit 77d3da6

Browse files
committed
dashboard/app: AI patching jobs
1 parent 31e2117 commit 77d3da6

File tree

16 files changed

+478
-4
lines changed

16 files changed

+478
-4
lines changed

dashboard/app/ai.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2025 syzkaller project authors. All rights reserved.
2+
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3+
4+
package main
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"net/http"
10+
"strings"
11+
12+
"github.com/google/syzkaller/dashboard/app/aidb"
13+
"github.com/google/syzkaller/dashboard/dashapi"
14+
"google.golang.org/appengine/v2/log"
15+
)
16+
17+
type uiAIPage struct {
18+
Header *uiHeader
19+
Workflows []*aidb.Workflow
20+
}
21+
22+
func handleAIPage(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
23+
hdr, err := commonHeader(ctx, r, w, "")
24+
if err != nil {
25+
return err
26+
}
27+
workflows, err := aidb.LoadWorkflows(ctx)
28+
if err != nil {
29+
return err
30+
}
31+
page := &uiAIPage{
32+
Header: hdr,
33+
Workflows: workflows,
34+
}
35+
return serveTemplate(w, "ai.html", page)
36+
}
37+
38+
func apiAIJobPoll(ctx context.Context, req *dashapi.AIJobPollReq) (any, error) {
39+
if err := aidb.UpdateWorkflows(ctx, req.Workflows); err != nil {
40+
log.Errorf(ctx, "storeAIWorkflows: %v", err)
41+
}
42+
resp := &dashapi.AIJobPollResp{}
43+
return resp, nil
44+
}
45+
46+
func apiAIJobDone(ctx context.Context, req *dashapi.AIJobDoneReq) (any, error) {
47+
return nil, nil
48+
}
49+
50+
func apiAIJournal(ctx context.Context, req *dashapi.AIJournalReq) (any, error) {
51+
return nil, nil
52+
}
53+
54+
func aiWorkflowCreate(ctx context.Context, workflow string, bug *Bug) error {
55+
switch typ := aidb.WorkflowType(strings.Split(workflow, "-")[0]); typ {
56+
case aidb.WorkflowPatching:
57+
return aiPatchingWorkflowCreate(ctx, workflow, bug)
58+
default:
59+
return fmt.Errorf("unknown workflow type %q", typ)
60+
}
61+
}
62+
63+
func aiPatchingWorkflowCreate(ctx context.Context, workflow string, bug *Bug) error {
64+
crash, _, err := findCrashForBug(ctx, bug)
65+
if err != nil {
66+
return err
67+
}
68+
if crash.ReproSyz == 0 {
69+
return fmt.Errorf("the bug does not have a reproducer")
70+
}
71+
return aidb.CreatePatchingJob(ctx, workflow, &aidb.PatchingJob{
72+
ReproOpts: crash.ReproOpts,
73+
ReproSyz: crash.ReproSyz,
74+
ReproC: crash.ReproC,
75+
//KernelConfig: c
76+
//SyzkallerCommit: crash.
77+
})
78+
}

dashboard/app/aidb/entities.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2025 syzkaller project authors. All rights reserved.
2+
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3+
4+
package aidb
5+
6+
import (
7+
"time"
8+
)
9+
10+
type WorkflowType string
11+
12+
const (
13+
WorkflowPatching = WorkflowType("patching")
14+
)
15+
16+
type Workflow struct {
17+
Name string
18+
Type WorkflowType
19+
Active bool
20+
}
21+
22+
type Job struct {
23+
ID string
24+
Type WorkflowType
25+
Workflow string
26+
Error string
27+
Created time.Time
28+
Started time.Time
29+
Finished time.Time
30+
CodeRevision string
31+
32+
Patching *PatchingJob `spanner:"->"`
33+
//LastEvent TIMESTAMP,
34+
//NumEvents INT64 NOT NULL,
35+
}
36+
37+
type PatchingJob struct {
38+
ID string
39+
ReproOpts []byte
40+
ReproSyz int64
41+
ReproC int64
42+
KernelConfig int64
43+
SyzkallerCommit string
44+
}
45+
46+
type SpanType string
47+
48+
const (
49+
SpanFlow = SpanType("flow")
50+
SpanAction = SpanType("action")
51+
SpanAgent = SpanType("agent")
52+
SpanLLM = SpanType("llm")
53+
SpanTool = SpanType("tool")
54+
)
55+
56+
type TrajectorySpan struct {
57+
JobID string
58+
Type SpanType
59+
Nesting int64
60+
Seq int64
61+
62+
Name string // action/tool name
63+
Timestamp time.Time
64+
Finished bool
65+
Duration time.Duration // relevant if Finished
66+
Error string // relevant if Finished
67+
NestedError bool // relevant if Finished
68+
69+
// Args/results for actions/tools.
70+
Args map[string]any
71+
Results map[string]any
72+
73+
// Agent invocation.
74+
Instruction string
75+
Prompt string
76+
Reply string
77+
78+
// LLM invocation.
79+
Thoughts string
80+
}

dashboard/app/aidb/helpers.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright 2025 syzkaller project authors. All rights reserved.
2+
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3+
4+
package aidb
5+
6+
import (
7+
"context"
8+
"strings"
9+
"time"
10+
11+
"cloud.google.com/go/spanner"
12+
"github.com/google/uuid"
13+
"google.golang.org/appengine/v2"
14+
)
15+
16+
func LoadWorkflows(ctx context.Context) ([]*Workflow, error) {
17+
client, err := dbClient(ctx)
18+
if err != nil {
19+
return nil, err
20+
}
21+
defer client.Close()
22+
iter := client.Single().Query(ctx, spanner.Statement{
23+
SQL: `select * from Workflows`,
24+
})
25+
defer iter.Stop()
26+
var workflows []*Workflow
27+
err = spanner.SelectAll(iter, &workflows)
28+
return workflows, err
29+
}
30+
31+
func LoadActiveWorkflows(ctx context.Context) ([]string, error) {
32+
workflows, err := LoadWorkflows(ctx)
33+
if err != nil {
34+
return nil, err
35+
}
36+
var active []string
37+
for _, flow := range workflows {
38+
if flow.Active {
39+
active = append(active, flow.Name)
40+
}
41+
}
42+
return active, nil
43+
}
44+
45+
func UpdateWorkflows(ctx context.Context, active []string) error {
46+
workflows, err := LoadWorkflows(ctx)
47+
if err != nil {
48+
return err
49+
}
50+
m := make(map[string]bool)
51+
for _, flow := range active {
52+
m[flow] = true
53+
}
54+
update := false
55+
for _, flow := range workflows {
56+
active := m[flow.Name]
57+
delete(m, flow.Name)
58+
if flow.Active != active {
59+
update = true
60+
flow.Active = active
61+
}
62+
}
63+
for name := range m {
64+
update = true
65+
workflows = append(workflows, &Workflow{
66+
Name: name,
67+
Type: WorkflowType(strings.Split(name, "-")[0]),
68+
Active: true,
69+
})
70+
}
71+
if !update {
72+
return nil
73+
}
74+
client, err := dbClient(ctx)
75+
if err != nil {
76+
return err
77+
}
78+
defer client.Close()
79+
var mutations []*spanner.Mutation
80+
for _, flow := range workflows {
81+
mut, err := spanner.InsertOrUpdateStruct("Workflows", flow)
82+
if err != nil {
83+
return err
84+
}
85+
mutations = append(mutations, mut)
86+
}
87+
_, err = client.Apply(ctx, mutations)
88+
return err
89+
}
90+
91+
func CreatePatchingJob(ctx context.Context, workflow string, patchingJob *PatchingJob) error {
92+
client, err := dbClient(ctx)
93+
if err != nil {
94+
return err
95+
}
96+
job := &Job{
97+
ID: uuid.NewString(),
98+
Type: WorkflowPatching,
99+
Workflow: workflow,
100+
Created: TimeNow(ctx),
101+
}
102+
patchingJob.ID = job.ID
103+
insertJob, err := spanner.InsertStruct("Jobs", job)
104+
if err != nil {
105+
return err
106+
}
107+
insertPatchingJob, err := spanner.InsertStruct("PatchingJobs", patchingJob)
108+
if err != nil {
109+
return err
110+
}
111+
_, err = client.Apply(ctx, []*spanner.Mutation{insertJob, insertPatchingJob})
112+
return err
113+
}
114+
115+
var TimeNow = func(ctx context.Context) time.Time {
116+
return time.Now()
117+
}
118+
119+
func dbClient(ctx context.Context) (*spanner.Client, error) {
120+
database := "projects/" + appengine.AppID(ctx) + "/instances/syzbot/databases/ai"
121+
return spanner.NewClient(ctx, database)
122+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
ALTER TABLE Jobs DROP CONSTRAINT TypeEnum;
2+
ALTER TABLE Jobs DROP CONSTRAINT FK_JobWorkflow;
3+
ALTER TABLE PatchingJobs DROP CONSTRAINT TypeEnum;
4+
ALTER TABLE PatchingJobs DROP CONSTRAINT FK_PatchingJobJob;
5+
ALTER TABLE TrajectorySpans DROP CONSTRAINT TypeEnum;
6+
ALTER TABLE TrajectorySpans DROP CONSTRAINT FK_EventJob;
7+
8+
DROP TABLE Workflows;
9+
DROP TABLE Jobs;
10+
DROP TABLE PatchingJobs;
11+
DROP TABLE TrajectorySpans;
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
CREATE TABLE Workflows (
2+
Name STRING(1000) NOT NULL,
3+
Type STRING(1000) NOT NULL,
4+
Active BOOL NOT NULL,
5+
6+
CONSTRAINT TypeEnum CHECK (Type IN ('patching')),
7+
) PRIMARY KEY (Name);
8+
9+
CREATE TABLE Jobs (
10+
ID STRING(36) NOT NULL,
11+
Type STRING(1000) NOT NULL,
12+
Workflow STRING(1000) NOT NULL,
13+
-- Status STRING(128) NOT NULL,
14+
Error STRING(1000),
15+
Created TIMESTAMP NOT NULL,
16+
Started TIMESTAMP,
17+
Finished TIMESTAMP,
18+
CodeRevision STRING(1000),
19+
20+
-- LastEvent TIMESTAMP,
21+
-- NumEvents INT64 NOT NULL,
22+
23+
CONSTRAINT TypeEnum CHECK (Type IN ('patching')),
24+
CONSTRAINT FK_JobWorkflow FOREIGN KEY (Workflow) REFERENCES Workflows (Name),
25+
) PRIMARY KEY (ID);
26+
27+
CREATE TABLE PatchingJobs (
28+
ID STRING(36) NOT NULL,
29+
ReproOpts BYTES NOT NULL,
30+
ReproSyz INT64 NOT NULL,
31+
ReproC INT64,
32+
KernelConfig INT64 NOT NULL,
33+
SyzkallerCommit STRING(100) NOT NULL,
34+
35+
CONSTRAINT FK_PatchingJobJob FOREIGN KEY (ID) REFERENCES Jobs (ID),
36+
) PRIMARY KEY (ID);
37+
38+
CREATE TABLE TrajectorySpans (
39+
JobID STRING(36) NOT NULL,
40+
Type STRING(1000) NOT NULL,
41+
Nesting INT64 NOT NULL,
42+
Seq INT64 NOT NULL,
43+
Name STRING(1000) NOT NULL,
44+
Timestamp TIMESTAMP NOT NULL,
45+
Finished BOOL NOT NULL,
46+
Duration INT64,
47+
Error STRING(1000),
48+
NestedError BOOL,
49+
Args JSON,
50+
Results JSON,
51+
Instruction STRING(MAX),
52+
Prompt STRING(MAX),
53+
Reply STRING(MAX),
54+
Thoughts STRING(MAX),
55+
56+
CONSTRAINT TypeEnum CHECK (Type IN ('flow', 'action', 'agent', 'llm', 'tool')),
57+
CONSTRAINT FK_EventJob FOREIGN KEY (JobID) REFERENCES Jobs (ID),
58+
) PRIMARY KEY (JobID, Seq);

dashboard/app/api.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ var apiHandlers = map[string]APIHandler{
6060
"save_discussion": typedHandler(apiSaveDiscussion),
6161
"create_upload_url": typedHandler(apiCreateUploadURL),
6262
"send_email": typedHandler(apiSendEmail),
63+
"ai_job_poll": typedHandler(apiAIJobPoll),
64+
"ai_job_done": typedHandler(apiAIJobDone),
65+
"ai_journal": typedHandler(apiAIJournal),
6366
"save_coverage": gcsPayloadHandler(apiSaveCoverage),
6467
"upload_build": nsHandler(apiUploadBuild),
6568
"builder_poll": nsHandler(apiBuilderPoll),
@@ -1016,7 +1019,7 @@ func purgeOldCrashes(c context.Context, bug *Bug, bugKey *db.Key) {
10161019
uniqueTitle := make(map[string]bool)
10171020
deleted, reproCount, noreproCount := 0, 0, 0
10181021
for _, crash := range crashes {
1019-
if !crash.Reported.IsZero() {
1022+
if !crash.Reported1.IsZero() {
10201023
log.Errorf(c, "purging reported crash?")
10211024
continue
10221025
}

dashboard/app/app.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ handlers:
2626
static_dir: dashboard/app/static
2727
secure: always
2828
# debug is for net/http/pprof handlers.
29-
- url: /(admin|debug/.*|cron/.*)
29+
- url: /(admin|ai|debug/.*|cron/.*)
3030
script: auto
3131
login: admin
3232
secure: always

dashboard/app/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ type Config struct {
8787
// If set, this namespace is not actively tested, no notifications are sent, etc.
8888
// It's kept mostly read-only for historical reference.
8989
Decommissioned bool
90+
// Enable AI workflows for the namespace.
91+
AI bool
9092
// Name used in UI.
9193
DisplayTitle string
9294
// Unique string that allows to show "similar bugs" across different namespaces.

0 commit comments

Comments
 (0)