diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b31efbf..bb908999 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -128,7 +128,7 @@ AX is still under heavy development and the database schema is not yet stable. I An example: ```bash -ax exec --input "hello" +ax --input "hello" Error: error creating controller: failed to create event log: sqlite_eventlog: create index exec_checkpoint_id: SQL logic error: no such column: checkpoint_id (1) ``` diff --git a/README.md b/README.md index f142d3a9..2e9dcf0e 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,6 @@ > changes prior to a stable release. > > **Temporary Policy:** We are temporarily pausing the acceptance of external Pull Requests while we stabilize the core architecture. We warmly encourage you to open Issues for feedback and feature requests instead. -> -> We will announce this project -> widely soon. If you are interested in collaborating with us, -> please reach out to **ax-dev@google.com**! AX, short for Agent Executor, is a distributed harness runtime. It dynamically provisions isolated environments from suspendable/resumable @@ -24,9 +20,8 @@ and execution resumption, even in distributed setups. - **Distributed Runtime**: Harnesses, skills, tools, and agents can execute in isolation - **Resumption**: Automatic recovery from failures or interruptions - **Built-in Harnesses**: Support for frontier harnesses and custom implementations -- **Auditing & Policy**: All user and agentic calls are coordinated by a common controller, easy to control and audit the overall execution and skill/tool/agent calls - **Portability**: Runs anywhere, scales to small and large deployments -- **Customizability**: Agnostic of harness and model +- **Customizability**: Bring custom environment, MCP tools, skills, instructions, and more Built-in consistency and resumability features: - **Single-Writer Architecture**: Single controller ensures consistent state management @@ -49,12 +44,12 @@ graph LR subgraph Cluster[" "] Server["AX Server(multi-tenant)"] DB[("Event Log"Storage)] - ControlService["Control API"] + ControlService["Actor Controller"] Actor["AX Harness Server(stateful session-tenant)"] end SnapshotService["Snapshots"] - HarnessService["Harness or modelservice"] + HarnessService["Models"] MCPServer["MCP server"] Client <-->|resumable stream| Server @@ -114,13 +109,6 @@ use. For more details on setup and configuration, see the Read more about [this new layer](https://cloud.google.com/blog/products/containers-kubernetes/bringing-you-agent-sandbox-on-gke-and-agent-substrate) that provides higher density to agentic workloads on Kubernetes. -### Built-in Antigravity harness - -Antigravity SDK is a reference harness implementation. Local execution needs -`python3` and `pip` available on your `PATH`. AX handles the rest: on first -`ax exec` it starts the harness server as a Python sidecar and installs the -pinned Antigravity SDK dependencies for you. - ## Authentication The built-in Antigravity harness supports Google AI Studio and Vertex AI. @@ -141,126 +129,109 @@ export GOOGLE_CLOUD_LOCATION="us-central1" export GOOGLE_GENAI_USE_VERTEXAI=true ``` -## Quick Start - -### Execute +## Quickstart The CLI starts the built-in Antigravity harness automatically. No separate harness server setup is required. ```bash # Using the checked-in ax.yaml, which sets Antigravity as the default harness. -ax exec --input "Can you list this directory?" +ax --input "Can you list this directory?" -# Using exec with an AX server -ax exec --input "Can you list this directory?" --server localhost:8494 +# Executing with an AX server +ax --input "Can you list this directory?" --server localhost:8494 ``` Conversations can be continued any time: ```bash -ax exec \ - --conversation d85a4b4e-c53b-4c84-b879-f10d905bce40 \ - --input "Show me the contents of README.md" +ax --conversation d85a4b4e-c53b-4c84-b879-f10d905bce40 \ + --input "Show me the contents of README.md" ``` -If the client gets disconnected, pass the last sequence it saw to +If the client gets disconnected, pass the last step it saw to replay the events it missed. This catches the client up; it does not rewind the conversation. -In this example, we catch up a client from sequence number 12: +In this example, we catch up a client from step number 12: ```bash -ax exec \ - --conversation d85a4b4e-c53b-4c84-b879-f10d905bce40 \ - --last-seq 12 \ - --resume +ax --conversation d85a4b4e-c53b-4c84-b879-f10d905bce40 \ + --last-step 12 \ + --resume ``` Instead of running the default harness, you can start executing any registered harness: ```bash -ax exec \ - --harness antigravity \ - --input "Can you write me a simple HTTP server in Python?" +ax --input "Can you write me a simple HTTP server in Python?" ``` If anything goes wrong during the execution of a harness, you can resume an incomplete execution in a conversation: ```bash -ax exec \ - --conversation edf98ef5-4bb1-4a9e-a091-3a77e03727e6 \ - --harness antigravity \ - --resume +ax --conversation edf98ef5-4bb1-4a9e-a091-3a77e03727e6 --resume ``` ## Usage -The `ax` command provides several subcommands: - -### Execute +Execute a new conversation or resume an existing one. If no conversation ID is provided, a new UUID will be generated. ```bash -ax exec \ +ax \ [--input ] \ [--conversation ] \ [--harness ] \ - [--harness-config ] \ - [--harness-config-json ] \ + [--config ] \ + [--config-file ] \ [--server ] \ - [--config ] \ + [--ax-config ] \ [--resume] \ - [--last-seq ] + [--last-step ] ``` -Executes a new harness execution or automatically resumes an existing one. If the conversation ID already exists, the execution will be resumed from its last state. - Options: -- `--input`: Input message to send to agents (optional if `--resume` is set, otherwise required) -- `--conversation`: Conversation ID (optional, generates UUID if not provided, or resumes if exists) -- `--harness`: Harness ID to use (optional, defaults to the default harness) -- `--harness-config`: Path to a JSON file with per-request harness configuration (optional) -- `--harness-config-json`: Per-request harness configuration as an inline JSON string (optional, mutually exclusive with `--harness-config`) -- `--server`: gRPC controller server address (optional. If not provided, runs with a local built-in AX server) -- `--config`: Path to YAML configuration file (only used with a local built-in AX server, default: "ax.yaml") -- `--resume`: Resume a conversation without inputs (optional, mutually exclusive with `--input`) -- `--last-seq`: Last sequence number seen by the client to resume from (optional). The server replays any later events so the client can catch up after a disconnect. +- `--ax-config`: Path to YAML configuration file (only used with a local built-in AX server) (default "ax.yaml") +- `--config`: Per-request harness configuration as an inline JSON string (mutually exclusive with `--config-file`) +- `--config-file`: Path to a JSON file with per-request harness configuration +- `--conversation`: Conversation ID (optional, generates UUID if not provided) +- `--harness`: Harness ID (optional, default harness is used if not specified) +- `--input`: Input message to send (optional) +- `--last-step`: Last step number seen by the client +- `--resume`: Resume a conversation without inputs +- `--server`: gRPC controller server address (if specified, connects to remote server; otherwise runs with a local built-in AX server) **Examples:** ```bash # Execute a new execution -ax exec --input "Hello agents!" +ax --input "Hello agents!" # Resume an existing execution with new input -ax exec --conversation a53d4db3-1165-4925-87da-be6c72bbdeb1 --input "Ok, now let's do something else..." +ax --conversation a53d4db3-1165-4925-87da-be6c72bbdeb1 --input "Ok, now let's do something else..." # Execute using server mode -ax exec --server localhost:8494 --input "Hello agents!" - -# Execute using a specific harness -ax exec --harness antigravity --input "Write me a cool Go program!" +ax --server localhost:8494 --input "Hello agents!" # Execute with per-request harness config -ax exec \ - --harness-config-json '{"system_instructions":"Answer in one sentence.","model":"gemini-3.5-flash"}' \ - --input "Explain durable execution." +ax --config '{"system_instructions":"Answer in one sentence.","model":"gemini-3.5-flash"}' \ + --input "Explain durable execution." -# To keep the same JSON in a file, use `--harness-config` instead: -ax exec --harness-config antigravity.json --input "Explain durable execution." +# To keep the same JSON in a file, use `--config-file` instead: +ax --config-file antigravity.json --input "Explain durable execution." ``` ### Serve +Run the AX controller as a gRPC server. Loads configuration from a YAML file (default: ax.yaml). + ```bash ax serve [--config ] ``` -Starts the controller as a gRPC server using a YAML configuration file. - Options: -- `--config`: Path to YAML configuration file (default: "ax.yaml") +- `--config`: Path to YAML configuration file (default "ax.yaml") Example configuration file (`ax.yaml`): ```yaml @@ -311,24 +282,17 @@ and making calls to MCP tools when they are configured. their Kubernetes clusters. * An agentic framework. AX is agnostic of the framework used to build agents. -* A specific harness like a specific coding agent, e.g. Antigravity. - AX provides the serving layer around harnesses and is agnostic of the - harness implementation. Soon, we will allow users to bring their own - harnesses. -* A model specific controller. AX is agnostic of the models used. ## Roadmap Below is an overview of our upcoming features and planned changes: 1. Support for more frontier harnesses besides Antigravity -1. Support for BYOH (Bring Your Own Harness) 1. Support for tool call approvals from harnesses 1. Improvements to resumption protocols 1. Forking from event log and snapshots 1. Trajectory exposition 1. Better telemetry exposition -1. Integrations for policy, auditing, and more ## Contributing diff --git a/ax.yaml b/ax.yaml index 56a980a1..0add0be2 100644 --- a/ax.yaml +++ b/ax.yaml @@ -13,8 +13,8 @@ # limitations under the License. # Sample LOCAL configuration for the AX *harness* build (compiled with -tags=harness). -# Usage: ax serve --config ax.yaml -# ax exec --config ax.yaml --input "hello" # uses harnesses.default +# Usage: ax serve --ax-config ax.yaml +# ax --ax-config ax.yaml --input "hello" # uses default harness (antigravity) version: v1alpha server: @@ -24,9 +24,9 @@ eventlog: sqlite: filename: "eventlog/log.sqlite" -harnesses: - antigravity: - default: true +antigravity: {} + +registry: antigravity-interactions: {} telemetry: diff --git a/cmd/ax/Dockerfile b/cmd/ax/Dockerfile index 16a1b901..7a505cd5 100644 --- a/cmd/ax/Dockerfile +++ b/cmd/ax/Dockerfile @@ -26,7 +26,7 @@ # docker build --platform linux/amd64 --target antigravity -f cmd/ax/Dockerfile -t . && docker push # docker build --platform linux/amd64 --target ax -f cmd/ax/Dockerfile -t . && docker push -# --- Stage 1: build the ax Go binary (with the harness build tag) ------------- +# --- Stage 1: build the ax Go binary ----------------------------------------- FROM --platform=$BUILDPLATFORM golang:1.26 AS build ARG TARGETOS=linux ARG TARGETARCH=amd64 diff --git a/cmd/ax/dashboard.go b/cmd/ax/dashboard.go deleted file mode 100644 index e518f74b..00000000 --- a/cmd/ax/dashboard.go +++ /dev/null @@ -1,475 +0,0 @@ -// Copyright 2026 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "context" - "database/sql" - _ "embed" - "encoding/json" - "fmt" - "log/slog" - "net" - "net/http" - "os" - "os/exec" - "path/filepath" - "runtime" - "time" - - "github.com/google/ax/cmd/ax/internal/cliutil" - "github.com/google/ax/internal/controller/eventlog" - "github.com/google/ax/proto" - "github.com/spf13/cobra" - _ "modernc.org/sqlite" -) - -//go:embed web/index.html -var dashboardHTML string - -var ( - dashboardAddr string - dashboardConfigFile string -) - -var dashboardCmd = &cobra.Command{ - Use: "dashboard", - Short: "Start the AX Dashboard", - Long: `Start a local HTTP server to display AX conversations and executions dashboard.`, - RunE: runDashboard, -} - -func init() { - dashboardCmd.Flags().StringVar(&dashboardAddr, "addr", "localhost:8080", "Server address to listen on") - dashboardCmd.Flags().StringVar(&dashboardConfigFile, "config", "ax.yaml", "Path to YAML configuration file") -} - -type ConversationResponse struct { - ID string `json:"id"` - Agent string `json:"agent"` - Status string `json:"status"` - LastSeq int32 `json:"last_seq"` - Duration string `json:"duration"` -} - -func runDashboard(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil))) - - cfg, err := newConfig(cmd, dashboardConfigFile) - if err != nil { - return err - } - - dbPath := cfg.EventLog.SQLiteConfig.Filename - slog.InfoContext(ctx, "Opening event log database", slog.String("path", dbPath)) - - if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil { - return fmt.Errorf("failed to create database directory: %w", err) - } - - db, err := sql.Open("sqlite", dbPath) - if err != nil { - return fmt.Errorf("failed to open sqlite database: %w", err) - } - defer db.Close() - - // Verify database connection - if err := db.PingContext(ctx); err != nil { - return fmt.Errorf("failed to ping database: %w", err) - } - - // Create tables if they don't exist (to avoid crashes on fresh setup) - if _, err := db.Exec(` - CREATE TABLE IF NOT EXISTS conversation_log ( - conversation_id TEXT NOT NULL, - seq INTEGER NOT NULL, - payload TEXT NOT NULL, - PRIMARY KEY (conversation_id, seq) - )`); err != nil { - return fmt.Errorf("failed to initialize conversation_log table: %w", err) - } - - if _, err := db.Exec(` - CREATE TABLE IF NOT EXISTS execution_log ( - exec_id TEXT NOT NULL, - payload TEXT NOT NULL, - timestamp DATETIME NOT NULL - )`); err != nil { - return fmt.Errorf("failed to initialize execution_log table: %w", err) - } - - if _, err := db.Exec(`CREATE INDEX IF NOT EXISTS idx_execution_log_exec_id ON execution_log(exec_id)`); err != nil { - return fmt.Errorf("failed to create index on execution_log: %w", err) - } - - // Setup API handlers - mux := http.NewServeMux() - mux.HandleFunc("/api/conversations", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - convs, err := fetchConversations(r.Context(), db) - if err != nil { - slog.ErrorContext(r.Context(), "Failed to fetch conversations", slog.Any("error", err)) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(convs); err != nil { - slog.ErrorContext(r.Context(), "Failed to encode conversations response", slog.Any("error", err)) - } - }) - - mux.HandleFunc("/api/trace", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - convID := r.URL.Query().Get("conversation") - if convID == "" { - http.Error(w, "Missing conversation ID", http.StatusBadRequest) - return - } - - data, err := loadTraceData(r.Context(), cfg, convID) - if err != nil { - slog.ErrorContext(r.Context(), "Failed to load trace data", slog.String("conversation_id", convID), slog.Any("error", err)) - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - if err := json.NewEncoder(w).Encode(data); err != nil { - slog.ErrorContext(r.Context(), "Failed to encode trace data response", slog.Any("error", err)) - } - }) - - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/html; charset=utf-8") - fmt.Fprint(w, dashboardHTML) - }) - - listener, err := net.Listen("tcp", dashboardAddr) - if err != nil { - return fmt.Errorf("failed to bind server to %s: %w", dashboardAddr, err) - } - defer listener.Close() - - addr := listener.Addr().String() - host, port, err := net.SplitHostPort(addr) - if err == nil && (host == "::" || host == "0.0.0.0" || host == "" || host == "[::]") { - addr = fmt.Sprintf("localhost:%s", port) - } - url := fmt.Sprintf("http://%s", addr) - - slog.InfoContext(ctx, "AX Dashboard started", slog.String("url", url)) - - go openBrowser(url) - - server := &http.Server{ - Handler: mux, - } - - return server.Serve(listener) -} - -func fetchConversations(ctx context.Context, db *sql.DB) ([]ConversationResponse, error) { - query := ` -SELECT - c.conversation_id, - c.last_seq, - c.state, - e.agent_id, - e.start_time, - e.end_time -FROM ( - SELECT conversation_id, seq AS last_seq, - json_extract(payload, '$.exec_id') AS exec_id, - json_extract(payload, '$.state') AS state - FROM conversation_log - WHERE (conversation_id, seq) IN ( - SELECT conversation_id, MAX(seq) - FROM conversation_log - GROUP BY conversation_id - ) -) c -LEFT JOIN ( - SELECT - exec_id, - json_extract(payload, '$.agent_id') AS agent_id, - MIN(timestamp) AS start_time, - MAX(timestamp) AS end_time - FROM execution_log - GROUP BY exec_id -) e ON c.exec_id = e.exec_id; -` - rows, err := db.QueryContext(ctx, query) - if err != nil { - return nil, err - } - defer rows.Close() - - convs := []ConversationResponse{} - for rows.Next() { - var id string - var lastSeq int32 - var state string - var agentID sql.NullString - var startTimeStr, endTimeStr sql.NullString - - err := rows.Scan(&id, &lastSeq, &state, &agentID, &startTimeStr, &endTimeStr) - if err != nil { - return nil, err - } - - agent := "unknown" - if agentID.Valid && agentID.String != "" { - agent = agentID.String - // Strip special prefix if it starts with "__" - if len(agent) > 2 && agent[:2] == "__" { - agent = agent[2:] - } - } - - durationStr := "N/A" - if startTimeStr.Valid && endTimeStr.Valid { - startTime, err1 := parseSQLiteTime(startTimeStr.String) - endTime, err2 := parseSQLiteTime(endTimeStr.String) - if err1 == nil && err2 == nil { - duration := endTime.Sub(startTime) - durationStr = fmt.Sprintf("%.1fs", duration.Seconds()) - } else { - slog.WarnContext(ctx, "Failed to parse sqlite timestamps", slog.String("start", startTimeStr.String), slog.String("end", endTimeStr.String), slog.Any("err1", err1), slog.Any("err2", err2)) - } - } - - status := state - if len(status) > 6 && status[:6] == "STATE_" { - status = status[6:] - } - if status == "PENDING" { - status = "RUNNING" - } - - convs = append(convs, ConversationResponse{ - ID: id, - Agent: agent, - Status: status, - LastSeq: lastSeq, - Duration: durationStr, - }) - } - - return convs, nil -} - -func parseSQLiteTime(s string) (time.Time, error) { - layouts := []string{ - time.RFC3339Nano, - time.RFC3339, - "2006-01-02 15:04:05.999999999-07:00", - "2006-01-02 15:04:05.999999999", - "2006-01-02 15:04:05.999999999 -0700 MST", - "2006-01-02 15:04:05", - } - var err error - var t time.Time - for _, layout := range layouts { - t, err = time.Parse(layout, s) - if err == nil { - return t, nil - } - } - return time.Time{}, err -} - -func openBrowser(url string) { - var err error - switch runtime.GOOS { - case "linux": - err = exec.Command("xdg-open", url).Start() - case "windows": - err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() - case "darwin": - err = exec.Command("open", url).Start() - } - if err != nil { - fmt.Printf("Failed to open browser: %v\n", err) - } -} - -type Text struct { - Text string `json:"text"` -} - -type Approval struct { - Approved bool `json:"approved"` -} - -type Confirmation struct { - ID string `json:"id"` - Question string `json:"question,omitempty"` - Approval *Approval `json:"approval,omitempty"` -} - -type Content struct { - Role string `json:"role"` - Text *Text `json:"text,omitempty"` - Confirmation *Confirmation `json:"confirmation,omitempty"` -} - -type ExecutionEvent struct { - ExecID string `json:"exec_id"` - AgentID string `json:"agent_id"` - Inputs []Content `json:"inputs"` - Outputs []Content `json:"outputs"` - State string `json:"state"` - Timestamp time.Time `json:"timestamp"` -} - -type ExecTrace struct { - ExecID string `json:"exec_id"` - AgentID string `json:"agent_id"` - Events []ExecutionEvent `json:"events"` -} - -type TraceData struct { - ConversationID string `json:"conversation_id"` - RootExecID string `json:"root_exec_id"` - Execs []ExecTrace `json:"execs"` -} - -func loadTraceData(ctx context.Context, cfg *cliutil.Config, convID string) (*TraceData, error) { - events, rootExecID, execIDs, err := fetch(ctx, cfg, convID) - if err != nil { - return nil, err - } - - // TODO(jbd): Trace view incorrectly displays graph executions. We are not - // changing the EventLog interface to fix this because the executor is being - // removed soon in favor of a linear execution model. We will adopt a different - // style of visualization once that's done. - return &TraceData{ - ConversationID: convID, - RootExecID: rootExecID, - Execs: buildExecTraces(execIDs, events), - }, nil -} - -func fetch(ctx context.Context, cfg *cliutil.Config, convID string) ([]*proto.ConversationEvent, string, []string, error) { - evLog, err := eventlog.OpenSQLiteEventLog(cfg.EventLog.SQLiteConfig.Filename) - if err != nil { - return nil, "", nil, fmt.Errorf("could not open sqlite eventlog: %w", err) - } - defer evLog.Close() - - convEvents, err := evLog.Events(ctx, convID) - if err != nil { - return nil, "", nil, fmt.Errorf("failed to query conversation events: %w", err) - } - - var execIDs []string - seen := make(map[string]bool) - for _, ev := range convEvents { - if ev.ExecId != "" && !seen[ev.ExecId] { - execIDs = append(execIDs, ev.ExecId) - seen[ev.ExecId] = true - } - } - - if len(execIDs) == 0 { - return nil, "", nil, fmt.Errorf("no executions found for conversation: %s", convID) - } - - // Use the first execID as the rootExecID as requested by user - rootExecID := execIDs[0] - - return convEvents, rootExecID, execIDs, nil -} - -func buildExecTraces(execIDs []string, events []*proto.ConversationEvent) []ExecTrace { - execsMap := make(map[string][]ExecutionEvent) - harnessIDs := make(map[string]string) - - for _, protoEv := range events { - exID := protoEv.ExecId - if protoEv.HarnessId != "" { - harnessIDs[exID] = protoEv.HarnessId - } - ev := extractExecutionEvent(exID, protoEv) - execsMap[exID] = append(execsMap[exID], ev) - } - - var execs []ExecTrace - for _, execID := range execIDs { - if evs, ok := execsMap[execID]; ok { - agentID := harnessIDs[execID] - execs = append(execs, ExecTrace{ - ExecID: execID, - AgentID: agentID, - Events: evs, - }) - } - } - - return execs -} - -func extractMsgs(protoContents []*proto.Message) []Content { - var results []Content - for _, c := range protoContents { - content := Content{Role: c.Role} - msgContent := c.GetContent() - if msgContent == nil { - continue - } - if textC := msgContent.GetText(); textC != nil { - content.Text = &Text{Text: textC.Text} - } else if conf := msgContent.GetConfirmation(); conf != nil { - content.Confirmation = &Confirmation{ - ID: conf.Id, - Question: conf.Question, - } - if app := conf.GetApproval(); app != nil { - content.Confirmation.Approval = &Approval{Approved: app.Approved} - } else if dec := conf.GetDecline(); dec != nil { - content.Confirmation.Approval = &Approval{Approved: !dec.Declined} - } - } - results = append(results, content) - } - return results -} - -func extractExecutionEvent(execID string, protoEv *proto.ConversationEvent) ExecutionEvent { - ev := ExecutionEvent{ - ExecID: execID, - AgentID: protoEv.HarnessId, - } - - ev.State = fmt.Sprint(protoEv.State) - if protoEv.HarnessId != "" { - ev.Inputs = extractMsgs(protoEv.Messages) - } else { - ev.Outputs = extractMsgs(protoEv.Messages) - } - - return ev -} diff --git a/cmd/ax/dashboard_test.go b/cmd/ax/dashboard_test.go deleted file mode 100644 index df412296..00000000 --- a/cmd/ax/dashboard_test.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2026 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "context" - "database/sql" - "testing" - "time" - - _ "modernc.org/sqlite" -) - -func TestFetchConversations(t *testing.T) { - // Create an in-memory SQLite database for testing - db, err := sql.Open("sqlite", ":memory:") - if err != nil { - t.Fatalf("failed to open in-memory db: %v", err) - } - defer db.Close() - - // Initialize the schema - schema := ` - CREATE TABLE conversation_log ( - conversation_id TEXT, - seq INTEGER, - timestamp DATETIME, - payload TEXT - ); - - CREATE TABLE execution_log ( - exec_id TEXT, - timestamp DATETIME, - payload TEXT - ); - ` - if _, err := db.Exec(schema); err != nil { - t.Fatalf("failed to create schema: %v", err) - } - - // Insert test data - // Conversation 1: Only conversation_log (V2 execution without execution_log) - _, err = db.Exec(` - INSERT INTO conversation_log (conversation_id, seq, timestamp, payload) - VALUES ('conv-1', 1, ?, '{"exec_id": "exec-1", "state": "STATE_PENDING"}') - `, time.Now().Format(time.RFC3339)) - if err != nil { - t.Fatalf("failed to insert conv-1: %v", err) - } - - // Conversation 2: Both conversation_log and execution_log (V1 execution) - now := time.Now() - start := now.Add(-5 * time.Second) - end := now - - _, err = db.Exec(` - INSERT INTO conversation_log (conversation_id, seq, timestamp, payload) - VALUES ('conv-2', 5, ?, '{"exec_id": "exec-2", "state": "STATE_COMPLETED"}') - `, end.Format(time.RFC3339)) - if err != nil { - t.Fatalf("failed to insert conv-2: %v", err) - } - - _, err = db.Exec(` - INSERT INTO execution_log (exec_id, timestamp, payload) - VALUES - ('exec-2', ?, '{"agent_id": "my-agent"}'), - ('exec-2', ?, '{"agent_id": "my-agent"}') - `, start.Format(time.RFC3339), end.Format(time.RFC3339)) - if err != nil { - t.Fatalf("failed to insert exec-2: %v", err) - } - - // Fetch the conversations - ctx := context.Background() - convs, err := fetchConversations(ctx, db) - if err != nil { - t.Fatalf("fetchConversations failed: %v", err) - } - - if len(convs) != 2 { - t.Fatalf("expected 2 conversations, got %d", len(convs)) - } - - // Create a map for easy lookup - convMap := make(map[string]ConversationResponse) - for _, c := range convs { - convMap[c.ID] = c - } - - // Check conv-1 - c1, ok := convMap["conv-1"] - if !ok { - t.Fatalf("conv-1 not found") - } - if c1.Status != "RUNNING" { // STATE_PENDING -> RUNNING - t.Errorf("conv-1 expected status RUNNING, got %q", c1.Status) - } - if c1.Agent != "unknown" { - t.Errorf("conv-1 expected agent unknown, got %q", c1.Agent) - } - if c1.Duration != "N/A" { - t.Errorf("conv-1 expected duration N/A, got %q", c1.Duration) - } - if c1.LastSeq != 1 { - t.Errorf("conv-1 expected last_seq 1, got %d", c1.LastSeq) - } - - // Check conv-2 - c2, ok := convMap["conv-2"] - if !ok { - t.Fatalf("conv-2 not found") - } - if c2.Status != "COMPLETED" { // STATE_COMPLETED -> COMPLETED - t.Errorf("conv-2 expected status COMPLETED, got %q", c2.Status) - } - if c2.Agent != "my-agent" { - t.Errorf("conv-2 expected agent my-agent, got %q", c2.Agent) - } - // Duration should be roughly 5.0s - if c2.Duration != "5.0s" { - t.Errorf("conv-2 expected duration 5.0s, got %q", c2.Duration) - } - if c2.LastSeq != 5 { - t.Errorf("conv-2 expected last_seq 5, got %d", c2.LastSeq) - } -} diff --git a/cmd/ax/exec.go b/cmd/ax/exec.go index 947b6b18..07bb118c 100644 --- a/cmd/ax/exec.go +++ b/cmd/ax/exec.go @@ -34,15 +34,15 @@ import ( ) var ( - execConversationID string - execHarnessID string - execHarnessConfig string - execHarnessConfigJSON string - execInput string - execServerAddr string - execConfigFile string - execResume bool // allow resuming an execution without inputs - execLastSeq int32 + execConversationID string + execHarnessID string + execConfigFile string + execConfig string + execInput string + execServerAddr string + execAXConfigFile string + execResume bool // allow resuming an execution without inputs + execLastStep int32 ) var execCmd = &cobra.Command{ @@ -54,18 +54,22 @@ If no conversation ID is provided, a new UUID will be generated.`, RunE: runExec, } +func registerExecFlags(cmd *cobra.Command) { + cmd.Flags().StringVar(&execConversationID, "conversation", "", "Conversation ID (optional, generates UUID if not provided)") + cmd.Flags().StringVar(&execHarnessID, "harness", "", "Harness ID (optional, default harness is used if not specified)") + cmd.Flags().StringVar(&execConfigFile, "config-file", "", "Path to a JSON file with per-request harness configuration") + cmd.Flags().StringVar(&execConfig, "config", "", "Per-request harness configuration as an inline JSON string (mutually exclusive with --config-file)") + cmd.Flags().StringVar(&execInput, "input", "", "Input message to send (optional)") + cmd.Flags().StringVar(&execServerAddr, "server", "", "gRPC controller server address (if specified, connects to remote server; otherwise runs with a local built-in AX server)") + cmd.Flags().StringVar(&execAXConfigFile, "ax-config", "ax.yaml", "Path to YAML configuration file (only used with a local built-in AX server)") + cmd.Flags().BoolVar(&execResume, "resume", false, "Resume a conversation without inputs") + cmd.Flags().Int32Var(&execLastStep, "last-step", 0, "Last step number seen by the client") + cmd.MarkFlagsMutuallyExclusive("input", "resume") + cmd.MarkFlagsMutuallyExclusive("config", "config-file") +} + func init() { - execCmd.Flags().StringVar(&execConversationID, "conversation", "", "Conversation ID (optional, generates UUID if not provided)") - execCmd.Flags().StringVar(&execHarnessID, "harness", "", "Harness ID (optional, default harness is used if not specified)") - execCmd.Flags().StringVar(&execHarnessConfig, "harness-config", "", "Path to a JSON file with per-request harness configuration") - execCmd.Flags().StringVar(&execHarnessConfigJSON, "harness-config-json", "", "Per-request harness configuration as an inline JSON string (mutually exclusive with --harness-config)") - execCmd.Flags().StringVar(&execInput, "input", "", "Input message to send (optional)") - execCmd.Flags().StringVar(&execServerAddr, "server", "", "gRPC controller server address (if specified, connects to remote server; otherwise runs with a local built-in AX server)") - execCmd.Flags().StringVar(&execConfigFile, "config", "ax.yaml", "Path to YAML configuration file (only used with a local built-in AX server)") - execCmd.Flags().BoolVar(&execResume, "resume", false, "Resume a conversation without inputs") - execCmd.Flags().Int32Var(&execLastSeq, "last-seq", 0, "Last sequence number seen by the client") - execCmd.MarkFlagsMutuallyExclusive("input", "resume") - execCmd.MarkFlagsMutuallyExclusive("harness-config", "harness-config-json") + registerExecFlags(execCmd) } // TODO(jbd): Add multimodal input flags, e.g. --input-image. @@ -108,7 +112,7 @@ func runExec(cmd *cobra.Command, args []string) error { }() if execServerAddr == "" { - cfg, err := newConfig(cmd, execConfigFile) + cfg, err := newConfig(cmd, execAXConfigFile) if err != nil { return err } @@ -124,20 +128,20 @@ func runExec(cmd *cobra.Command, args []string) error { } var harnessConfig []byte - if execHarnessConfig != "" { - b, err := os.ReadFile(execHarnessConfig) + if execConfigFile != "" { + b, err := os.ReadFile(execConfigFile) if err != nil { - return fmt.Errorf("failed to read harness config %q: %w", execHarnessConfig, err) + return fmt.Errorf("failed to read harness config %q: %w", execConfigFile, err) } harnessConfig = b - } else if execHarnessConfigJSON != "" { - harnessConfig = []byte(execHarnessConfigJSON) + } else if execConfig != "" { + harnessConfig = []byte(execConfig) } - return execLoop(ctx, execConversationID, execHarnessID, harnessConfig, execInput, execLastSeq) + return execLoop(ctx, execConversationID, execHarnessID, harnessConfig, execInput, execLastStep) } -func execLoop(ctx context.Context, id string, harnessID string, harnessConfig []byte, input string, lastSeq int32) error { +func execLoop(ctx context.Context, id string, harnessID string, harnessConfig []byte, input string, lastStep int32) error { d := internal.NewDisplay(id, os.Stdout) d.DisplayHeader() @@ -175,9 +179,9 @@ func execLoop(ctx context.Context, id string, harnessID string, harnessConfig [] HarnessId: harnessID, HarnessConfig: harnessConfig, Inputs: inputs, - LastSeq: lastSeq, + LastStep: lastStep, }) - lastSeq = 0 // disable resuming from sequence, user sees the seq on the screen + lastStep = 0 // disable resuming from step, user sees the step on the screen interruptHandler.ClearActiveCancel() cancel() @@ -297,14 +301,14 @@ func runAutoExec(ctx context.Context, d *internal.Display, req *proto.ExecReques func runExecHeadless(ctx context.Context, d *internal.Display, req *proto.ExecRequest) (*proto.ConfirmationContent, error) { var confirmation *proto.ConfirmationContent - var lastSeq int32 + var lastStep int32 outputHandler := cliutil.ExecHandler(func(resp *proto.ExecResponse) error { for _, m := range resp.Outputs { if conf := m.GetContent().GetConfirmation(); conf != nil { confirmation = conf } } - lastSeq = resp.Seq + lastStep = resp.Step displayContents(d, resp.Outputs) return nil }) @@ -313,7 +317,7 @@ func runExecHeadless(ctx context.Context, d *internal.Display, req *proto.ExecRe } if confirmation == nil { - d.FinishOutput(fmt.Sprintf("seq=%d", lastSeq)) + d.FinishOutput(fmt.Sprintf("step=%d", lastStep)) } return confirmation, nil } @@ -332,7 +336,7 @@ func runExecServer(ctx context.Context, d *internal.Display, req *proto.ExecRequ } var confirmation *proto.ConfirmationContent - var lastSeq int32 + var lastStep int32 for { resp, err := stream.Recv() if err == io.EOF { @@ -341,7 +345,7 @@ func runExecServer(ctx context.Context, d *internal.Display, req *proto.ExecRequ if err != nil { return nil, fmt.Errorf("error receiving response: %w", err) } - lastSeq = resp.Seq + lastStep = resp.Step if resp.Outputs != nil { for _, m := range resp.Outputs { if conf := m.GetContent().GetConfirmation(); conf != nil { @@ -352,7 +356,7 @@ func runExecServer(ctx context.Context, d *internal.Display, req *proto.ExecRequ } } if confirmation == nil { - d.FinishOutput(fmt.Sprintf("seq=%d", lastSeq)) + d.FinishOutput(fmt.Sprintf("step=%d", lastStep)) } return confirmation, nil } diff --git a/cmd/ax/harness.go b/cmd/ax/harness.go index d63e29de..5d7e5e56 100644 --- a/cmd/ax/harness.go +++ b/cmd/ax/harness.go @@ -111,6 +111,7 @@ func runAntigravityHarness(cmd *cobra.Command) error { return fmt.Errorf("failed to resolve antigravity state dir: %w", err) } + addr := net.JoinHostPort("127.0.0.1", strconv.Itoa(harnessPort)) cfg := pythonsidecar.Config{ Module: "python.antigravity.harness_server", Args: []string{ @@ -118,9 +119,11 @@ func runAntigravityHarness(cmd *cobra.Command) error { "--port", strconv.Itoa(harnessPort), "--state-dir", stateDir, }, - Stdout: os.Stdout, - Stderr: os.Stderr, - ReadyFunc: pythonsidecar.TCPReady(net.JoinHostPort("127.0.0.1", strconv.Itoa(harnessPort))), + Stdout: os.Stdout, + Stderr: os.Stderr, + ReadyFunc: pythonsidecar.TCPReady(addr), + KillOrphans: true, + Address: addr, } sidecar := pythonsidecar.New(cfg) @@ -163,7 +166,7 @@ func runAntigravityInteractionsHarness(ctx context.Context) error { } else if cfg, err := config.LoadFromBytes(data); err != nil { log.Printf("AX_CONFIG_CONTENT: parse failed, using defaults: %v", err) } else { - hc = cfg.Harnesses.AntigravityInteractions + hc = cfg.Registry.AntigravityInteractions } } agent := hc.Agent diff --git a/cmd/ax/internal/cliutil/cliutil.go b/cmd/ax/internal/cliutil/cliutil.go index 2c399a4f..67423efe 100644 --- a/cmd/ax/internal/cliutil/cliutil.go +++ b/cmd/ax/internal/cliutil/cliutil.go @@ -58,7 +58,9 @@ func NewControllerFromConfig(ctx context.Context, cfg *Config) (*controller.Cont // Validate config before local-mode antigravity.New forks the Python // sidecar, so a config error surfaces without spawning a subprocess. - if len(cfg.Harnesses.Substrate) > 0 && !substrateMode { + // Validate config before local-mode antigravity.New forks the Python + // sidecar, so a config error surfaces without spawning a subprocess. + if len(cfg.Registry.Substrate) > 0 && !substrateMode { return nil, fmt.Errorf("custom substrate harnesses require AX_SUBSTRATE=1") } @@ -84,7 +86,10 @@ func NewControllerFromConfig(ctx context.Context, cfg *Config) (*controller.Cont // Built-in Antigravity harness. var antigravityHarness harness.Harness if !substrateMode { - address := cfg.Harnesses.Antigravity.Endpoint + address := cfg.Antigravity.Endpoint + if address == "" { + address = cfg.Registry.Antigravity.Endpoint + } if address == "" { address = "127.0.0.1:50053" } @@ -108,14 +113,14 @@ func NewControllerFromConfig(ctx context.Context, cfg *Config) (*controller.Cont if err := reg.RegisterHarness(config.AntigravityHarnessID, antigravityHarness); err != nil { return nil, fmt.Errorf("register antigravity harness: %w", err) } - if cfg.Harnesses.Antigravity.Default { + if cfg.Antigravity.Default || cfg.Registry.Antigravity.Default { defaultHarnessID = config.AntigravityHarnessID } // Built-in Antigravity Interactions harness. var antigravityInteractionsHarness harness.Harness if !substrateMode { - aiCfg := cfg.Harnesses.AntigravityInteractions + aiCfg := cfg.Registry.AntigravityInteractions agent := aiCfg.Agent if agent == "" { agent = antigravityinteractions.DefaultAgent @@ -148,11 +153,11 @@ func NewControllerFromConfig(ctx context.Context, cfg *Config) (*controller.Cont if err := reg.RegisterHarness(config.AntigravityInteractionsHarnessID, antigravityInteractionsHarness); err != nil { return nil, fmt.Errorf("register antigravity-interactions harness: %w", err) } - if cfg.Harnesses.AntigravityInteractions.Default { + if cfg.Registry.AntigravityInteractions.Default { defaultHarnessID = config.AntigravityInteractionsHarnessID } - for _, sc := range cfg.Harnesses.Substrate { + for _, sc := range cfg.Registry.Substrate { h, err := sc.NewHarness("") if err != nil { return nil, fmt.Errorf("substrate harness %q: %w", sc.ID, err) @@ -165,11 +170,14 @@ func NewControllerFromConfig(ctx context.Context, cfg *Config) (*controller.Cont } } + // Antigravity is the default harness by default if no other harness set Default: true. + if defaultHarnessID == "" { + defaultHarnessID = config.AntigravityHarnessID + } + // Set the default harness. - if defaultHarnessID != "" { - if err := reg.SetDefaultHarness(defaultHarnessID); err != nil { - return nil, fmt.Errorf("set default harness %q: %w", defaultHarnessID, err) - } + if err := reg.SetDefaultHarness(defaultHarnessID); err != nil { + return nil, fmt.Errorf("set default harness %q: %w", defaultHarnessID, err) } return controller.New(ctx, controller.Config{ diff --git a/cmd/ax/internal/cliutil/cliutil_test.go b/cmd/ax/internal/cliutil/cliutil_test.go index 31fe2e72..7b99202e 100644 --- a/cmd/ax/internal/cliutil/cliutil_test.go +++ b/cmd/ax/internal/cliutil/cliutil_test.go @@ -32,7 +32,7 @@ func TestNewControllerFromConfig_BuiltinSubstrate(t *testing.T) { Filename: filepath.Join(t.TempDir(), "log.sqlite"), }, }, - Harnesses: config.HarnessesConfig{ + Registry: config.RegistryConfig{ Antigravity: config.AntigravityHarnessConfig{ Default: true, }, @@ -58,7 +58,7 @@ func TestNewControllerFromConfig_InteractionsSubstrate(t *testing.T) { Filename: filepath.Join(t.TempDir(), "log.sqlite"), }, }, - Harnesses: config.HarnessesConfig{ + Registry: config.RegistryConfig{ Antigravity: config.AntigravityHarnessConfig{Default: true}, }, } @@ -86,7 +86,7 @@ func TestNewControllerFromConfig_CustomHarnessRequiresSubstrateMode(t *testing.T Filename: filepath.Join(t.TempDir(), "log.sqlite"), }, }, - Harnesses: config.HarnessesConfig{ + Registry: config.RegistryConfig{ Substrate: []config.SubstrateHarnessConfig{ {ID: "custom", Namespace: "team-ns", Template: "custom-template"}, }, @@ -111,7 +111,7 @@ func TestNewControllerFromConfig_CustomHarnessInSubstrateMode(t *testing.T) { Filename: filepath.Join(t.TempDir(), "log.sqlite"), }, }, - Harnesses: config.HarnessesConfig{ + Registry: config.RegistryConfig{ Substrate: []config.SubstrateHarnessConfig{ {ID: "custom", Namespace: "team-ns", Template: "custom-template"}, }, diff --git a/cmd/ax/internal/display.go b/cmd/ax/internal/display.go index 376e25bf..c3a38072 100644 --- a/cmd/ax/internal/display.go +++ b/cmd/ax/internal/display.go @@ -322,8 +322,8 @@ func (d *Display) PromptForConfigFile() (string, error) { func (d *Display) ShowResumption(id string, server string) { fmt.Fprintln(d.w, d.resumeStyle.Render("To resume the conversation,")) if server != "" { - fmt.Fprintln(d.w, d.resumeStyle.Render(fmt.Sprintf("ax exec --conversation %s --server %s", id, server))) + fmt.Fprintln(d.w, d.resumeStyle.Render(fmt.Sprintf("ax --conversation %s --server %s", id, server))) } else { - fmt.Fprintln(d.w, d.resumeStyle.Render(fmt.Sprintf("ax exec --conversation %s", id))) + fmt.Fprintln(d.w, d.resumeStyle.Render(fmt.Sprintf("ax --conversation %s", id))) } } diff --git a/cmd/ax/main.go b/cmd/ax/main.go index ee65c4a2..e0f36ba9 100644 --- a/cmd/ax/main.go +++ b/cmd/ax/main.go @@ -37,17 +37,18 @@ func main() { var rootCmd = &cobra.Command{ Use: "ax", - Short: "AX - Agent eXecutor", - Long: `ax provides a server and CLI tools for managing agent orchestrator tasks. -It provides commands to execute tasks, resume from checkpoints, -and run the controller server.`, + Short: "Execute a conversation or resume an existing one", + Long: `Execute a new conversation or resume an existing one. +If no conversation ID is provided, a new UUID will be generated.`, + SilenceUsage: true, + RunE: runExec, } func init() { + registerExecFlags(rootCmd) + rootCmd.AddCommand(execCmd) rootCmd.AddCommand(serveCmd) - - rootCmd.AddCommand(dashboardCmd) } func connect(server string) (*grpc.ClientConn, error) { @@ -64,7 +65,9 @@ const currentVersion = "v1alpha" func newConfig(cmd *cobra.Command, configFile string) (*cliutil.Config, error) { cfg, err := cliutil.LoadFromFile(configFile) - if errors.Is(err, os.ErrNotExist) && !cmd.Flags().Changed("config") { + configFlagChanged := (cmd.Flags().Lookup("ax-config") != nil && cmd.Flags().Changed("ax-config")) || + (cmd.Flags().Lookup("config-file") != nil && cmd.Flags().Changed("config-file")) + if errors.Is(err, os.ErrNotExist) && !configFlagChanged { cfg := cliutil.DefaultConfig() cfg.Version = currentVersion return cfg, nil diff --git a/cmd/ax/web/index.html b/cmd/ax/web/index.html deleted file mode 100644 index fc49046a..00000000 --- a/cmd/ax/web/index.html +++ /dev/null @@ -1,1242 +0,0 @@ - - - - - - - -AX Dashboard - - - - - - - - - - - ⬡ - AX Dashboard - - - - Connected - - - - - - - - - - - Total - 0 - - Σ - - - - Active - 0 - - ⚡ - - - - Completed - 0 - - ✓ - - - - Failed - 0 - - ✗ - - - - - - - - - - All Statuses - Running - Completed - Failed - - - - Live Polling (3s) - - - - - - - - - Conversation ID - Agent - Status - Last Seq - Duration - Actions - - - - - - - - 🔍 - No conversations found matching criteria. - - - - - - - - - ← Back to Dashboard - - - Execution Trace - - - - - - - Execution Timeline - - - - - - - - - - - - - - - diff --git a/examples/skills/README.md b/examples/skills/README.md index 72769049..2f4e3883 100644 --- a/examples/skills/README.md +++ b/examples/skills/README.md @@ -23,7 +23,7 @@ AX contains a sample `emoji` skill in `examples/skills/emoji`. If you run a task Run the following command in your terminal: ```bash -ax exec --input "Give me an emoji for happy" +ax --input "Give me an emoji for happy" ``` The planner will: diff --git a/go.mod b/go.mod index 73c022fa..66cfeac8 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( charm.land/huh/v2 v2.0.3 charm.land/lipgloss/v2 v2.0.3 cloud.google.com/go/compute/metadata v0.9.0 - github.com/agent-substrate/substrate v0.0.0-20260706222328-3cb7433bd8a8 + github.com/agent-substrate/substrate v0.0.0-20260725015935-aa1d14a7b33b github.com/google/uuid v1.6.0 github.com/jackc/pgx/v5 v5.10.0 github.com/spf13/cobra v1.10.2 @@ -62,9 +62,9 @@ require ( go.opentelemetry.io/otel/metric v1.44.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect golang.org/x/net v0.55.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/sync v0.21.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/text v0.39.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect modernc.org/libc v1.70.0 // indirect diff --git a/go.sum b/go.sum index a5bc044b..7b506cc3 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdB cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= -github.com/agent-substrate/substrate v0.0.0-20260706222328-3cb7433bd8a8 h1:tUeBjLs9TJgIWfML2dlZ3UOFG3dacbwrIYkas2ctBgY= -github.com/agent-substrate/substrate v0.0.0-20260706222328-3cb7433bd8a8/go.mod h1:2cvSnnHPwZRAhkrC2LH1bQd1tQBfL3p+zU6pZiHBUkA= +github.com/agent-substrate/substrate v0.0.0-20260725015935-aa1d14a7b33b h1:XJsiwQcqxzMQT4eBhdO+Bg4cU0HTRTM9EXFwkmWUc+Q= +github.com/agent-substrate/substrate v0.0.0-20260725015935-aa1d14a7b33b/go.mod h1:FtVc9AA++A1aEyIHUQdjCesf6dP4sxIxJc+Xc+p6Mok= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o= @@ -157,21 +157,21 @@ go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus= +golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= diff --git a/internal/k8s/ate/client.go b/internal/ate/client.go similarity index 86% rename from internal/k8s/ate/client.go rename to internal/ate/client.go index 81a5c061..d9697480 100644 --- a/internal/k8s/ate/client.go +++ b/internal/ate/client.go @@ -58,16 +58,20 @@ func NewClient(ns, template, target string, opts ...grpc.DialOption) (*Client, e } // CreateActor creates a new actor. -func (c *Client) CreateActor(ctx context.Context, id string) (*ateapipb.CreateActorResponse, error) { +func (c *Client) CreateActor(ctx context.Context, id string) (*ateapipb.Actor, error) { client := ateapipb.NewControlClient(c.conn) // TODO(wjjclaud): Configure atespace in manifests instead of reusing the namespace. - if _, err := client.CreateAtespace(ctx, &ateapipb.CreateAtespaceRequest{Name: c.namespace}); err != nil && status.Code(err) != codes.AlreadyExists { + if _, err := client.CreateAtespace(ctx, &ateapipb.CreateAtespaceRequest{ + Atespace: &ateapipb.Atespace{Metadata: &ateapipb.ResourceMetadata{Name: c.namespace}}, + }); err != nil && status.Code(err) != codes.AlreadyExists { return nil, fmt.Errorf("error when calling Control.CreateAtespace: %w", err) } resp, err := client.CreateActor(ctx, &ateapipb.CreateActorRequest{ - ActorRef: &ateapipb.ActorRef{Atespace: c.namespace, Name: id}, - ActorTemplateNamespace: c.namespace, - ActorTemplateName: c.template, + Actor: &ateapipb.Actor{ + Metadata: &ateapipb.ResourceMetadata{Atespace: c.namespace, Name: id}, + ActorTemplateNamespace: c.namespace, + ActorTemplateName: c.template, + }, }) if err != nil { return nil, fmt.Errorf("error when calling Control.CreateActor: %w", err) @@ -80,7 +84,7 @@ func (c *Client) CreateActor(ctx context.Context, id string) (*ateapipb.CreateAc func (c *Client) ResumeActor(ctx context.Context, id string) (*ateapipb.ResumeActorResponse, error) { client := ateapipb.NewControlClient(c.conn) resp, err := client.ResumeActor(ctx, &ateapipb.ResumeActorRequest{ - ActorRef: &ateapipb.ActorRef{Atespace: c.namespace, Name: id}, + Actor: &ateapipb.ObjectRef{Atespace: c.namespace, Name: id}, }) if err != nil { return nil, fmt.Errorf("error when calling Control.ResumeActor: %w", err) @@ -92,7 +96,7 @@ func (c *Client) ResumeActor(ctx context.Context, id string) (*ateapipb.ResumeAc func (c *Client) SuspendActor(ctx context.Context, id string) (*ateapipb.SuspendActorResponse, error) { client := ateapipb.NewControlClient(c.conn) resp, err := client.SuspendActor(ctx, &ateapipb.SuspendActorRequest{ - ActorRef: &ateapipb.ActorRef{Atespace: c.namespace, Name: id}, + Actor: &ateapipb.ObjectRef{Atespace: c.namespace, Name: id}, }) if err != nil { return nil, fmt.Errorf("error when calling Control.SuspendActor: %w", err) diff --git a/internal/config/config.go b/internal/config/config.go index ec89b8b1..d33d9162 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -44,10 +44,11 @@ const ( // Config represents the main configuration for the AX harness server. type Config struct { - Version string `yaml:"version"` - Server ServerConfig `yaml:"server"` - EventLog EventLogConfig `yaml:"eventlog"` - Harnesses HarnessesConfig `yaml:"harnesses,omitempty"` + Version string `yaml:"version"` + Server ServerConfig `yaml:"server"` + EventLog EventLogConfig `yaml:"eventlog"` + Antigravity AntigravityHarnessConfig `yaml:"antigravity,omitempty"` + Registry RegistryConfig `yaml:"registry,omitempty"` // Skills sources skills from the Gemini Enterprise Skill Registry into on-disk folders // before the harness starts. It is harness-agnostic: each actor runs exactly // one harness, which consumes the materialized folder(s). Optional; disabled @@ -88,12 +89,12 @@ type EventLogConfig struct { PostgresConfig PostgresConfig `yaml:"postgres,omitempty"` } -// HarnessesConfig groups harnesses to serve by type. There are two categories: +// RegistryConfig groups harnesses to serve by type. There are two categories: // - Built-in harnesses (e.g. Antigravity, AntigravityInteractions) whose // implementation and container image are provided by AX. // - Custom harnesses on substrate whose implementation and container image are // provided by the user via their own ActorTemplate. -type HarnessesConfig struct { +type RegistryConfig struct { Antigravity AntigravityHarnessConfig `yaml:"antigravity,omitempty"` AntigravityInteractions AntigravityInteractionsHarnessConfig `yaml:"antigravity-interactions,omitempty"` Substrate []SubstrateHarnessConfig `yaml:"substrate,omitempty"` @@ -284,14 +285,14 @@ func (c *Config) Validate() error { } var defaultCount int - if c.Harnesses.Antigravity.Default { + if c.Antigravity.Default || c.Registry.Antigravity.Default { defaultCount++ } - if c.Harnesses.AntigravityInteractions.Default { + if c.Registry.AntigravityInteractions.Default { defaultCount++ } - for _, sc := range c.Harnesses.Substrate { + for _, sc := range c.Registry.Substrate { if sc.ID == "" { return fmt.Errorf("substrate harness id is required") } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index cdcda582..98a7d2c3 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -36,7 +36,7 @@ func TestSubstrateNewHarness(t *testing.T) { // validConfig returns a config that passes Validate, that tests can mutate. func validConfig() *Config { c := DefaultConfig() - c.Harnesses = HarnessesConfig{ + c.Registry = RegistryConfig{ Antigravity: AntigravityHarnessConfig{Default: true}, Substrate: []SubstrateHarnessConfig{ {ID: "custom", Namespace: "team-ns", Template: "custom-template"}, @@ -53,7 +53,7 @@ func TestValidate_ValidConfig(t *testing.T) { func TestValidate_CustomIDRequired(t *testing.T) { c := validConfig() - c.Harnesses.Substrate[0].ID = "" + c.Registry.Substrate[0].ID = "" err := c.Validate() if err == nil || !strings.Contains(err.Error(), "substrate harness id") { t.Fatalf("Validate() = %v, want substrate id error", err) @@ -62,7 +62,7 @@ func TestValidate_CustomIDRequired(t *testing.T) { func TestValidate_CustomIDReserved(t *testing.T) { c := validConfig() - c.Harnesses.Substrate[0].ID = "antigravity" + c.Registry.Substrate[0].ID = "antigravity" err := c.Validate() if err == nil || !strings.Contains(err.Error(), "reserved") { t.Fatalf("Validate() = %v, want reserved id error", err) @@ -71,7 +71,7 @@ func TestValidate_CustomIDReserved(t *testing.T) { func TestValidate_CustomNamespaceRequired(t *testing.T) { c := validConfig() - c.Harnesses.Substrate[0].Namespace = "" + c.Registry.Substrate[0].Namespace = "" err := c.Validate() if err == nil || !strings.Contains(err.Error(), "namespace is required") { t.Fatalf("Validate() = %v, want namespace-required error", err) @@ -80,7 +80,7 @@ func TestValidate_CustomNamespaceRequired(t *testing.T) { func TestValidate_CustomNamespaceReserved(t *testing.T) { c := validConfig() - c.Harnesses.Substrate[0].Namespace = defaultNamespace + c.Registry.Substrate[0].Namespace = defaultNamespace err := c.Validate() if err == nil || !strings.Contains(err.Error(), "reserved") { t.Fatalf("Validate() = %v, want reserved-namespace error", err) @@ -89,7 +89,7 @@ func TestValidate_CustomNamespaceReserved(t *testing.T) { func TestValidate_CustomTemplateRequired(t *testing.T) { c := validConfig() - c.Harnesses.Substrate[0].Template = "" + c.Registry.Substrate[0].Template = "" err := c.Validate() if err == nil || !strings.Contains(err.Error(), "template is required") { t.Fatalf("Validate() = %v, want template-required error", err) @@ -98,7 +98,7 @@ func TestValidate_CustomTemplateRequired(t *testing.T) { func TestValidate_MultipleDefaults(t *testing.T) { c := validConfig() - c.Harnesses.Substrate[0].Default = true + c.Registry.Substrate[0].Default = true err := c.Validate() if err == nil || !strings.Contains(err.Error(), "multiple harnesses marked as default") { t.Fatalf("Validate() = %v, want multiple defaults error", err) @@ -107,7 +107,7 @@ func TestValidate_MultipleDefaults(t *testing.T) { func TestValidate_InteractionsIDReserved(t *testing.T) { c := validConfig() - c.Harnesses.Substrate[0].ID = "antigravity-interactions" + c.Registry.Substrate[0].ID = "antigravity-interactions" err := c.Validate() if err == nil || !strings.Contains(err.Error(), "reserved") { t.Fatalf("Validate() = %v, want reserved id error", err) @@ -116,7 +116,7 @@ func TestValidate_InteractionsIDReserved(t *testing.T) { func TestValidate_InteractionsValid(t *testing.T) { c := validConfig() - c.Harnesses.AntigravityInteractions = AntigravityInteractionsHarnessConfig{ + c.Registry.AntigravityInteractions = AntigravityInteractionsHarnessConfig{ Agent: "projects/p/locations/global/agents/a", } if err := c.Validate(); err != nil { @@ -145,9 +145,7 @@ eventlog: func TestLoadFromBytes(t *testing.T) { cfg, err := LoadFromBytes([]byte(` version: v1alpha -harnesses: - antigravity: - default: true +antigravity: {} `)) if err != nil { t.Fatalf("LoadFromBytes: %v", err) @@ -155,9 +153,6 @@ harnesses: if cfg.Version != "v1alpha" { t.Errorf("Version = %q, want %q", cfg.Version, "v1alpha") } - if !cfg.Harnesses.Antigravity.Default { - t.Error("Harnesses.Antigravity.Default = false, want true") - } // setDefaults must run (same as LoadFromFile). if got, want := cfg.Server.Address, ":8494"; got != want { t.Errorf("Server.Address = %q, want default %q", got, want) @@ -166,7 +161,7 @@ harnesses: // TestLoadFromBytes_Invalid returns an error on malformed YAML. func TestLoadFromBytes_Invalid(t *testing.T) { - if _, err := LoadFromBytes([]byte("harnesses: [unterminated")); err == nil { + if _, err := LoadFromBytes([]byte("registry: [unterminated")); err == nil { t.Fatal("LoadFromBytes(invalid): got nil error, want error") } } diff --git a/internal/controller/controller.go b/internal/controller/controller.go index f0845979..da39ed39 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -174,7 +174,7 @@ type harnessHandler struct { func (a *harnessHandler) OnMessage(ctx context.Context, execID string, msg *proto.Message) error { // Log every response received from the harness // TODO(anj): The harness should send the full input sent to get this particular response. - seq, err := a.logger.LogOutputs(ctx, []*proto.Message{msg}, proto.State_STATE_PENDING, nil, "") + step, err := a.logger.LogOutputs(ctx, []*proto.Message{msg}, proto.State_STATE_PENDING, nil, "") if err != nil { slog.WarnContext(ctx, "Failed to log streamed message to event log", slog.String("conversation_id", a.logger.conversationID), @@ -187,7 +187,7 @@ func (a *harnessHandler) OnMessage(ctx context.Context, execID string, msg *prot } return a.execHandler(&proto.ExecResponse{ Outputs: []*proto.Message{msg}, - Seq: seq, + Step: step, }) } @@ -222,7 +222,7 @@ func (a *harnessHandler) OnFailWithMetadata(ctx context.Context, execID string, } if a.execHandler != nil { if err := a.execHandler(&proto.ExecResponse{ - Seq: seq, + Step: seq, HarnessMetadata: metadata, }); err != nil { slog.WarnContext(ctx, "Failed to stream FAILED terminal metadata to exec handler", @@ -257,7 +257,7 @@ func (a *harnessHandler) complete(ctx context.Context, execID string, metadata [ return nil } return a.execHandler(&proto.ExecResponse{ - Seq: seq, + Step: seq, HarnessMetadata: metadata, }) } diff --git a/internal/controller/controller_test.go b/internal/controller/controller_test.go index b2beec7d..99503932 100644 --- a/internal/controller/controller_test.go +++ b/internal/controller/controller_test.go @@ -254,8 +254,8 @@ func TestController2_ExecPersistsAndStreamsTerminalHarnessMetadata(t *testing.T) if !bytes.Equal(terminal.GetHarnessMetadata(), wantMetadata) { t.Fatalf("event metadata = %q, want %q", terminal.GetHarnessMetadata(), wantMetadata) } - if responses[0].GetSeq() != terminal.GetSeq() { - t.Fatalf("response seq = %d, terminal event seq = %d", responses[0].GetSeq(), terminal.GetSeq()) + if responses[0].GetStep() != terminal.GetStep() { + t.Fatalf("response seq = %d, terminal event seq = %d", responses[0].GetStep(), terminal.GetStep()) } } @@ -334,8 +334,8 @@ func TestController2_ExecPersistsAndStreamsFailedTerminalHarnessMetadata(t *test if !bytes.Equal(terminal.GetHarnessMetadata(), wantMetadata) { t.Fatalf("event metadata = %q, want %q", terminal.GetHarnessMetadata(), wantMetadata) } - if responses[0].GetSeq() != terminal.GetSeq() { - t.Fatalf("response seq = %d, terminal event seq = %d", responses[0].GetSeq(), terminal.GetSeq()) + if responses[0].GetStep() != terminal.GetStep() { + t.Fatalf("response seq = %d, terminal event seq = %d", responses[0].GetStep(), terminal.GetStep()) } } diff --git a/internal/controller/eventlog/eventlogtest/eventlog.go b/internal/controller/eventlog/eventlogtest/eventlog.go index f24abe5b..58235136 100644 --- a/internal/controller/eventlog/eventlogtest/eventlog.go +++ b/internal/controller/eventlog/eventlogtest/eventlog.go @@ -32,19 +32,19 @@ func (m *MemoryEventLog) Append(_ context.Context, event *proto.ConversationEven m.mu.Lock() defer m.mu.Unlock() - seq := event.Seq - if seq == 0 { - maxSeq := int32(0) + step := event.Step + if step == 0 { + maxStep := int32(0) for _, ev := range m.AllEvents { - if ev.ConversationId == event.ConversationId && ev.Seq > maxSeq { - maxSeq = ev.Seq + if ev.ConversationId == event.ConversationId && ev.Step > maxStep { + maxStep = ev.Step } } - seq = maxSeq + 1 - event.Seq = seq + step = maxStep + 1 + event.Step = step } m.AllEvents = append(m.AllEvents, event) - return seq, nil + return step, nil } func (m *MemoryEventLog) Events(_ context.Context, conversationID string) ([]*proto.ConversationEvent, error) { diff --git a/internal/controller/eventlog/postgres.go b/internal/controller/eventlog/postgres.go index c99c103d..db3b4e5e 100644 --- a/internal/controller/eventlog/postgres.go +++ b/internal/controller/eventlog/postgres.go @@ -44,9 +44,9 @@ func OpenPostgresEventLog(dsn string) (EventLog, error) { if _, err := db.ExecContext(ctx, ` CREATE TABLE IF NOT EXISTS conversation_log ( conversation_id TEXT NOT NULL, - seq INTEGER NOT NULL, + step INTEGER NOT NULL, payload TEXT NOT NULL, - PRIMARY KEY (conversation_id, seq) + PRIMARY KEY (conversation_id, step) )`); err != nil { db.Close() return nil, fmt.Errorf("postgres_eventlog: create conversation_log table: %w", err) diff --git a/internal/controller/eventlog/sql.go b/internal/controller/eventlog/sql.go index 522b6717..e584285d 100644 --- a/internal/controller/eventlog/sql.go +++ b/internal/controller/eventlog/sql.go @@ -33,7 +33,7 @@ type sqlEventLog struct { } // Append serializes the event to JSON and inserts it into the database. -func (l *sqlEventLog) Append(ctx context.Context, event *proto.ConversationEvent) (seq int32, err error) { +func (l *sqlEventLog) Append(ctx context.Context, event *proto.ConversationEvent) (step int32, err error) { ctx, endSpan := l.startSpan(ctx, "Append", event.ConversationId) defer func() { endSpan(err) }() @@ -43,12 +43,12 @@ func (l *sqlEventLog) Append(ctx context.Context, event *proto.ConversationEvent } defer tx.Rollback() - seq = event.Seq - if seq == 0 { - if err := tx.QueryRowContext(ctx, "SELECT COALESCE(MAX(seq), 0) + 1 FROM conversation_log WHERE conversation_id = $1", event.ConversationId).Scan(&seq); err != nil { - return 0, fmt.Errorf("eventlog: compute seq: %w", err) + step = event.Step + if step == 0 { + if err := tx.QueryRowContext(ctx, "SELECT COALESCE(MAX(step), 0) + 1 FROM conversation_log WHERE conversation_id = $1", event.ConversationId).Scan(&step); err != nil { + return 0, fmt.Errorf("eventlog: compute step: %w", err) } - event.Seq = seq + event.Step = step } payload, err := marshalOpts.Marshal(event) @@ -57,8 +57,8 @@ func (l *sqlEventLog) Append(ctx context.Context, event *proto.ConversationEvent } if _, err := tx.ExecContext(ctx, - "INSERT INTO conversation_log (conversation_id, seq, payload) VALUES ($1, $2, $3)", - event.ConversationId, event.Seq, string(payload)); err != nil { + "INSERT INTO conversation_log (conversation_id, step, payload) VALUES ($1, $2, $3)", + event.ConversationId, event.Step, string(payload)); err != nil { return 0, fmt.Errorf("eventlog: insert conversation: %w", err) } @@ -66,15 +66,15 @@ func (l *sqlEventLog) Append(ctx context.Context, event *proto.ConversationEvent return 0, fmt.Errorf("eventlog: commit: %w", err) } - return seq, nil + return step, nil } -// Events retrieves all events from the database for a conversation, ordered by seq. +// Events retrieves all events from the database for a conversation, ordered by step. func (l *sqlEventLog) Events(ctx context.Context, conversationID string) (events []*proto.ConversationEvent, err error) { ctx, endSpan := l.startSpan(ctx, "Events", conversationID) defer func() { endSpan(err) }() - rows, err := l.db.QueryContext(ctx, "SELECT payload FROM conversation_log WHERE conversation_id = $1 ORDER BY seq", conversationID) + rows, err := l.db.QueryContext(ctx, "SELECT payload FROM conversation_log WHERE conversation_id = $1 ORDER BY step", conversationID) if err != nil { return nil, fmt.Errorf("eventlog: query conversation: %w", err) } diff --git a/internal/controller/eventlog/sql_test.go b/internal/controller/eventlog/sql_test.go index 62e7f33c..4dacefda 100644 --- a/internal/controller/eventlog/sql_test.go +++ b/internal/controller/eventlog/sql_test.go @@ -44,9 +44,9 @@ func testEventLog(t *testing.T, newLog func(t *testing.T) EventLog) { t.Cleanup(func() { _ = log.DeleteAll(ctx, conv) }) // 1. Conversation log. - cev1 := &proto.ConversationEvent{ConversationId: conv, Seq: 1, ExecId: task1} + cev1 := &proto.ConversationEvent{ConversationId: conv, Step: 1, ExecId: task1} metadata := []byte("agentfleet-metadata-fixture") - cev2 := &proto.ConversationEvent{ConversationId: conv, Seq: 2, ExecId: task2, HarnessMetadata: metadata} + cev2 := &proto.ConversationEvent{ConversationId: conv, Step: 2, ExecId: task2, HarnessMetadata: metadata} if _, err := log.Append(ctx, cev1); err != nil { t.Fatalf("failed to append cev1: %v", err) } @@ -61,8 +61,8 @@ func testEventLog(t *testing.T, newLog func(t *testing.T) EventLog) { if len(cEvents) != 2 { t.Fatalf("expected 2 conversation events, got %d", len(cEvents)) } - if cEvents[0].Seq != 1 || cEvents[1].Seq != 2 { - t.Errorf("conversation events out of order: %d, %d", cEvents[0].Seq, cEvents[1].Seq) + if cEvents[0].Step != 1 || cEvents[1].Step != 2 { + t.Errorf("conversation events out of order: %d, %d", cEvents[0].Step, cEvents[1].Step) } if cEvents[0].ExecId != task1 || cEvents[1].ExecId != task2 { t.Errorf("conversation events mismatch: %q, %q", cEvents[0].ExecId, cEvents[1].ExecId) @@ -101,10 +101,10 @@ func testEventLog(t *testing.T, newLog func(t *testing.T) EventLog) { _ = log.DeleteAll(ctx, conv2) }) - if _, err := log.Append(ctx, &proto.ConversationEvent{ConversationId: conv1, Seq: 1, ExecId: task1}); err != nil { + if _, err := log.Append(ctx, &proto.ConversationEvent{ConversationId: conv1, Step: 1, ExecId: task1}); err != nil { t.Fatalf("append: %v", err) } - if _, err := log.Append(ctx, &proto.ConversationEvent{ConversationId: conv2, Seq: 1, ExecId: task3}); err != nil { + if _, err := log.Append(ctx, &proto.ConversationEvent{ConversationId: conv2, Step: 1, ExecId: task3}); err != nil { t.Fatalf("append: %v", err) } @@ -121,9 +121,9 @@ func testEventLog(t *testing.T, newLog func(t *testing.T) EventLog) { }) - // AutoSeq exercises the seq==0 auto-assignment path: appends with Seq unset + // AutoStep exercises the step==0 auto-assignment path: appends with Step unset // receive sequential numbers starting at 1. - t.Run("AutoSeq", func(t *testing.T) { + t.Run("AutoStep", func(t *testing.T) { ctx := context.Background() log := newLog(t) @@ -133,12 +133,12 @@ func testEventLog(t *testing.T, newLog func(t *testing.T) EventLog) { const n = 3 for i := int32(1); i <= n; i++ { - seq, err := log.Append(ctx, &proto.ConversationEvent{ConversationId: conv, ExecId: "t"}) + step, err := log.Append(ctx, &proto.ConversationEvent{ConversationId: conv, ExecId: "t"}) if err != nil { - t.Fatalf("auto-seq append failed: %v", err) + t.Fatalf("auto-step append failed: %v", err) } - if seq != i { - t.Errorf("expected seq %d, got %d", i, seq) + if step != i { + t.Errorf("expected step %d, got %d", i, step) } } @@ -150,8 +150,8 @@ func testEventLog(t *testing.T, newLog func(t *testing.T) EventLog) { t.Fatalf("expected %d events, got %d", n, len(events)) } for i, e := range events { - if e.Seq != int32(i+1) { - t.Errorf("event %d: expected seq %d, got %d", i, i+1, e.Seq) + if e.Step != int32(i+1) { + t.Errorf("event %d: expected step %d, got %d", i, i+1, e.Step) } } }) diff --git a/internal/controller/eventlog/sqlite.go b/internal/controller/eventlog/sqlite.go index b29ea5a6..ad3f4116 100644 --- a/internal/controller/eventlog/sqlite.go +++ b/internal/controller/eventlog/sqlite.go @@ -43,9 +43,9 @@ func OpenSQLiteEventLog(path string) (EventLog, error) { if _, err := db.Exec(` CREATE TABLE IF NOT EXISTS conversation_log ( conversation_id TEXT NOT NULL, - seq INTEGER NOT NULL, + step INTEGER NOT NULL, payload TEXT NOT NULL, - PRIMARY KEY (conversation_id, seq) + PRIMARY KEY (conversation_id, step) )`); err != nil { db.Close() return nil, fmt.Errorf("sqlite_eventlog: create conversation_log table: %w", err) diff --git a/internal/harness/antigravity/antigravity.go b/internal/harness/antigravity/antigravity.go index 01839376..db69ed9b 100644 --- a/internal/harness/antigravity/antigravity.go +++ b/internal/harness/antigravity/antigravity.go @@ -84,9 +84,11 @@ func New(ctx context.Context, address, stateDir string, autoStart bool) (*Antigr args = append(args, "--state-dir", stateDir) } cfg := pythonsidecar.Config{ - Module: "python.antigravity.harness_server", - Args: args, - ReadyFunc: pythonsidecar.TCPReady(address), + Module: "python.antigravity.harness_server", + Args: args, + ReadyFunc: pythonsidecar.TCPReady(address), + KillOrphans: true, + Address: address, } sidecar := pythonsidecar.New(cfg) path, err := pythonsidecar.Setup(ctx, pythonsidecar.SetupOptions{ @@ -111,6 +113,14 @@ func New(ctx context.Context, address, stateDir string, autoStart bool) (*Antigr return h, nil } +// Stop terminates the underlying Python sidecar process if it was started by this harness. +func (h *AntigravityHarness) Stop() error { + if h.sidecar != nil { + return h.sidecar.Stop() + } + return nil +} + // Start implements Harness.Start. func (h *AntigravityHarness) Start(ctx context.Context, conversationID string, harnessConfig []byte) (harness.Execution, error) { return &antigravityExecution{ diff --git a/internal/harness/harnesstest/harnesstest.go b/internal/harness/harnesstest/harnesstest.go index 8ff9e2fe..3472c1e8 100644 --- a/internal/harness/harnesstest/harnesstest.go +++ b/internal/harness/harnesstest/harnesstest.go @@ -53,23 +53,23 @@ type MockControlServer struct { SuspendErr error // returned from SuspendActor when non-nil } -func (f *MockControlServer) CreateAtespace(_ context.Context, req *ateapipb.CreateAtespaceRequest) (*ateapipb.CreateAtespaceResponse, error) { - return &ateapipb.CreateAtespaceResponse{Atespace: &ateapipb.Atespace{Name: req.GetName()}}, nil +func (f *MockControlServer) CreateAtespace(_ context.Context, req *ateapipb.CreateAtespaceRequest) (*ateapipb.Atespace, error) { + return &ateapipb.Atespace{Metadata: &ateapipb.ResourceMetadata{Name: req.GetAtespace().GetMetadata().GetName()}}, nil } -func (f *MockControlServer) CreateActor(_ context.Context, req *ateapipb.CreateActorRequest) (*ateapipb.CreateActorResponse, error) { +func (f *MockControlServer) CreateActor(_ context.Context, req *ateapipb.CreateActorRequest) (*ateapipb.Actor, error) { f.mu.Lock() - f.createCalls = append(f.createCalls, req.GetActorRef().GetName()) + f.createCalls = append(f.createCalls, req.GetActor().GetMetadata().GetName()) f.mu.Unlock() if f.CreateErr != nil { return nil, f.CreateErr } - return &ateapipb.CreateActorResponse{Actor: &ateapipb.Actor{ActorId: req.GetActorRef().GetName()}}, nil + return &ateapipb.Actor{Metadata: &ateapipb.ResourceMetadata{Name: req.GetActor().GetMetadata().GetName()}}, nil } func (f *MockControlServer) ResumeActor(_ context.Context, req *ateapipb.ResumeActorRequest) (*ateapipb.ResumeActorResponse, error) { f.mu.Lock() - f.resumeCalls = append(f.resumeCalls, req.GetActorRef().GetName()) + f.resumeCalls = append(f.resumeCalls, req.GetActor().GetName()) resumeIP := f.ResumeIP if len(f.ResumeIPs) > 0 { index := min(len(f.resumeCalls)-1, len(f.ResumeIPs)-1) @@ -79,12 +79,12 @@ func (f *MockControlServer) ResumeActor(_ context.Context, req *ateapipb.ResumeA if f.ResumeNilActor { return &ateapipb.ResumeActorResponse{}, nil } - return &ateapipb.ResumeActorResponse{Actor: &ateapipb.Actor{ActorId: req.GetActorRef().GetName(), AteomPodIp: resumeIP}}, nil + return &ateapipb.ResumeActorResponse{Actor: &ateapipb.Actor{Metadata: &ateapipb.ResourceMetadata{Name: req.GetActor().GetName()}, AteomPodIp: resumeIP}}, nil } func (f *MockControlServer) SuspendActor(_ context.Context, req *ateapipb.SuspendActorRequest) (*ateapipb.SuspendActorResponse, error) { f.mu.Lock() - f.suspendCalls = append(f.suspendCalls, req.GetActorRef().GetName()) + f.suspendCalls = append(f.suspendCalls, req.GetActor().GetName()) f.mu.Unlock() if f.SuspendErr != nil { return nil, f.SuspendErr diff --git a/internal/harness/substrate/substrate.go b/internal/harness/substrate/substrate.go index 43cf7b4b..82f83497 100644 --- a/internal/harness/substrate/substrate.go +++ b/internal/harness/substrate/substrate.go @@ -35,7 +35,7 @@ import ( "google.golang.org/grpc/status" "github.com/google/ax/internal/harness" - "github.com/google/ax/internal/k8s/ate" + "github.com/google/ax/internal/ate" "github.com/google/ax/proto" "github.com/google/uuid" ) @@ -224,8 +224,8 @@ func (h *SubstrateHarness) resumeWorkerAddr(ctx context.Context, conversationID if actor == nil { return "", fmt.Errorf("received nil actor in response for %s", conversationID) } - if actor.GetActorId() != conversationID { - return "", fmt.Errorf("received actor %s while resuming %s", actor.GetActorId(), conversationID) + if actor.GetMetadata().GetName() != conversationID { + return "", fmt.Errorf("received actor %s while resuming %s", actor.GetMetadata().GetName(), conversationID) } if actor.GetAteomPodIp() == "" { return "", fmt.Errorf("actor %s has no active worker IP address", conversationID) diff --git a/internal/harness/substrate/substrate_test.go b/internal/harness/substrate/substrate_test.go index 61dca9e8..755dd6bf 100644 --- a/internal/harness/substrate/substrate_test.go +++ b/internal/harness/substrate/substrate_test.go @@ -25,7 +25,7 @@ import ( "time" "github.com/google/ax/internal/harness/harnesstest" - "github.com/google/ax/internal/k8s/ate" + "github.com/google/ax/internal/ate" "github.com/google/ax/proto" "google.golang.org/grpc" "google.golang.org/grpc/codes" diff --git a/internal/historyutil/confirmations.go b/internal/historyutil/confirmations.go deleted file mode 100644 index 65730006..00000000 --- a/internal/historyutil/confirmations.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2026 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package historyutil - -import "github.com/google/ax/proto" - -// WaitsForConfirmation returns true if the last message in the history -// is a confirmation question waiting for user input. -func WaitsForConfirmation(history []*proto.Message) *proto.Message { - if len(history) == 0 { - return nil - } - last := history[len(history)-1] - if last.GetContent().GetConfirmation() != nil && last.GetContent().GetConfirmation().Question != "" { - return last - } - return nil -} - -// HasConfirmationAnswer returns true if the last message in the history -// is a confirmation question waiting for user input. -func HasConfirmationAnswer(history []*proto.Message) (approved bool, conf *proto.ConfirmationContent) { - if len(history) == 0 { - return false, nil - } - last := history[len(history)-1] - if last.GetContent().GetConfirmation() == nil { - return false, nil - } - if last.GetContent().GetConfirmation().GetApproval() != nil { - conf = last.GetContent().GetConfirmation() - approved = true - } - if last.GetContent().GetConfirmation().GetDecline() != nil { - conf = last.GetContent().GetConfirmation() - approved = false - } - return approved, conf -} - -func WaitsForUser(history []*proto.Message) *proto.Message { - if len(history) == 0 { - return nil - } - if msg := WaitsForConfirmation(history); msg != nil { - return msg - } - - last := history[len(history)-1] - if last.GetContent().GetConfirmation() != nil && last.GetContent().GetConfirmation().GetDecline() != nil { - return last - } - return nil -} diff --git a/internal/pythonsidecar/sidecar.go b/internal/pythonsidecar/sidecar.go index d02799ba..6b25c25c 100644 --- a/internal/pythonsidecar/sidecar.go +++ b/internal/pythonsidecar/sidecar.go @@ -23,6 +23,7 @@ import ( "net" "os" "os/exec" + "strconv" "strings" "sync" "syscall" @@ -42,6 +43,11 @@ type Config struct { // ReadyFunc is an optional function to check if the server is ready to accept requests. // When provided, Start will poll ReadyFunc until it returns nil or the context expires. (Optional) ReadyFunc func(ctx context.Context) error + // KillOrphans when true will attempt to terminate any pre-existing orphan process + // listening on Address before starting the sidecar. (Optional) + KillOrphans bool + // Address is the network host:port address used for orphan process cleanup. (Optional) + Address string } // TODO: Use /var/ax_agy_harness_service for communication instead of TCP. @@ -80,6 +86,18 @@ func (s *Sidecar) Start(ctx context.Context, pythonPath string) error { return fmt.Errorf("Module cannot be empty") } + if s.cfg.ReadyFunc != nil { + if err := s.cfg.ReadyFunc(ctx); err == nil { + if s.cfg.KillOrphans && s.cfg.Address != "" { + _ = killOrphanProcessOnAddr(ctx, s.cfg.Address) + } + if err := s.cfg.ReadyFunc(ctx); err == nil { + s.mu.Unlock() + return fmt.Errorf("cannot start sidecar: endpoint %s is already in use by another process", s.cfg.Address) + } + } + } + // Prepare arguments: python -u -m module [args...] // -u forces unbuffered stdout/stderr so logs stream to Go instantly fullArgs := append([]string{"-u", "-m", s.cfg.Module}, s.cfg.Args...) @@ -267,3 +285,59 @@ func TCPReady(addr string) func(ctx context.Context) error { return nil } } + +// killOrphanProcessOnAddr attempts to terminate pre-existing processes listening on addr. +func killOrphanProcessOnAddr(ctx context.Context, addr string) error { + _, portStr, err := net.SplitHostPort(addr) + if err != nil { + return err + } + port, err := strconv.Atoi(portStr) + if err != nil || port <= 0 { + return fmt.Errorf("invalid port %q", portStr) + } + + out, err := exec.CommandContext(ctx, "lsof", "-ti", fmt.Sprintf("tcp:%d", port)).Output() + if err != nil || len(out) == 0 { + return nil + } + + pidStrs := strings.Fields(string(out)) + myPid := os.Getpid() + for _, pStr := range pidStrs { + pid, err := strconv.Atoi(pStr) + if err != nil || pid <= 0 || pid == myPid { + continue + } + proc, err := os.FindProcess(pid) + if err != nil { + continue + } + _ = proc.Signal(syscall.SIGTERM) + } + + deadline := time.Now().Add(1 * time.Second) + for time.Now().Before(deadline) { + var d net.Dialer + conn, err := d.DialContext(ctx, "tcp", addr) + if err != nil { + return nil + } + _ = conn.Close() + time.Sleep(50 * time.Millisecond) + } + + for _, pStr := range pidStrs { + pid, err := strconv.Atoi(pStr) + if err != nil || pid <= 0 || pid == myPid { + continue + } + proc, err := os.FindProcess(pid) + if err != nil { + continue + } + _ = proc.Kill() + } + + return nil +} diff --git a/internal/pythonsidecar/sidecar_test.go b/internal/pythonsidecar/sidecar_test.go index 3052f414..6e469bb7 100644 --- a/internal/pythonsidecar/sidecar_test.go +++ b/internal/pythonsidecar/sidecar_test.go @@ -156,3 +156,67 @@ func TestSidecar_ReadinessFailureOnPrematureExit(t *testing.T) { t.Fatalf("expected IsRunning() to be false") } } + +func TestSidecar_EndpointAlreadyInUse(t *testing.T) { + // Start a dummy TCP listener on a free port + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("failed to listen: %v", err) + } + defer l.Close() + addr := l.Addr().String() + + cfg := pythonsidecar.Config{ + Module: "http.server", + ReadyFunc: pythonsidecar.TCPReady(addr), + } + + s := pythonsidecar.New(cfg) + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + err = s.Start(ctx, "") + if err == nil { + t.Fatalf("expected Start() to fail when endpoint is already in use") + } + if !strings.Contains(err.Error(), "already in use") { + t.Fatalf("expected 'already in use' in error, got: %v", err) + } +} + +func TestSidecar_KillOrphans(t *testing.T) { + port := getFreePort(t) + addr := fmt.Sprintf("127.0.0.1:%d", port) + + // Start a dummy sidecar listening on `port` + dummyCfg := pythonsidecar.Config{ + Module: "http.server", + Args: []string{strconv.Itoa(port), "--bind", "127.0.0.1"}, + ReadyFunc: pythonsidecar.TCPReady(addr), + } + dummySidecar := pythonsidecar.New(dummyCfg) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + if err := dummySidecar.Start(ctx, ""); err != nil { + t.Fatalf("failed to start dummy sidecar: %v", err) + } + + // Now start a new sidecar with KillOrphans: true on the same addr + newCfg := pythonsidecar.Config{ + Module: "http.server", + Args: []string{strconv.Itoa(port), "--bind", "127.0.0.1"}, + ReadyFunc: pythonsidecar.TCPReady(addr), + KillOrphans: true, + Address: addr, + } + newSidecar := pythonsidecar.New(newCfg) + if err := newSidecar.Start(ctx, ""); err != nil { + t.Fatalf("Start() with KillOrphans failed: %v", err) + } + defer newSidecar.Stop() + + if !newSidecar.IsRunning() { + t.Fatalf("expected new sidecar to be running") + } +} diff --git a/manifests/README.md b/manifests/README.md index fe3678de..1bd3291c 100644 --- a/manifests/README.md +++ b/manifests/README.md @@ -73,10 +73,10 @@ export AX_SNAPSHOTS_BUCKET="snapshot-substrate-test-$GOOGLE_CLOUD_PROJECT" # Connect to your existing Postgres: export AX_EVENTLOG_DSN="postgres://user:pass@host:5432/db?sslmode=require" -./hack/install-ax.sh --deploy-ax-server +./manifests/install-ax.sh --deploy-ax-server # Or deploy a bundled Postgres for testing: -./hack/install-ax.sh --deploy-ax-server --deploy-postgres +./manifests/install-ax.sh --deploy-ax-server --deploy-postgres ``` The bundled Postgres uses an auto-generated password. To get its DSN: @@ -122,7 +122,7 @@ kubectl port-forward -n ax rs/ax-server 8494:8494 Run an execution targeting the port-forwarded server. ```bash -ax exec --server=localhost:8494 --input="hello, who are you?" +ax --server=localhost:8494 --input="hello, who are you?" ``` The server should respond with something like: @@ -140,7 +140,7 @@ I am a helpful assistant. How can I help you today? To remove the AX server and its components, run: ```bash -./hack/install-ax.sh --delete-ax-server +./manifests/install-ax.sh --delete-ax-server ``` > [!NOTE] diff --git a/manifests/ax-postgres.yaml b/manifests/ax-postgres.yaml index 3009e2e2..ccf1fb16 100644 --- a/manifests/ax-postgres.yaml +++ b/manifests/ax-postgres.yaml @@ -15,7 +15,7 @@ # --------------------------------------------------------------------------- # Event log storage (PostgreSQL) # -# Applied by hack/install-ax.sh only when --deploy-postgres is passed. By default +# Applied by manifests/install-ax.sh only when --deploy-postgres is passed. By default # ax-server connects to an existing Postgres via AX_EVENTLOG_DSN and these # resources are not deployed. The ax-eventlog-postgres Secret (holding the # password used below and the DSN) is created by install-ax.sh. diff --git a/manifests/ax.yaml b/manifests/ax.yaml index 8c243fef..b40a8381 100644 --- a/manifests/ax.yaml +++ b/manifests/ax.yaml @@ -24,7 +24,7 @@ eventlog: postgres: dsn: ${AX_EVENTLOG_DSN} -harnesses: - antigravity: - default: true +antigravity: {} + +registry: antigravity-interactions: {} diff --git a/hack/install-ax.sh b/manifests/install-ax.sh similarity index 99% rename from hack/install-ax.sh rename to manifests/install-ax.sh index 78f2b630..9c5c02a2 100755 --- a/hack/install-ax.sh +++ b/manifests/install-ax.sh @@ -314,7 +314,7 @@ deploy_ax_server() { echo "Forward the AX server by running the following command (optional)" echo "kubectl port-forward -n ax rs/ax-server 8494:8494" echo "" - echo "Then, run: ax exec --server localhost:8494 [--harness antigravity|antigravity-interactions]" + echo "Then, run: ax --server localhost:8494 [--harness antigravity|antigravity-interactions]" } # delete_ax_server removes the AX server and harness resources but preserves the diff --git a/proto/ax.pb.go b/proto/ax.pb.go index c2d73526..beba3025 100644 --- a/proto/ax.pb.go +++ b/proto/ax.pb.go @@ -204,7 +204,7 @@ func (x *Message) GetContent() *Content { type ConversationEvent struct { state protoimpl.MessageState `protogen:"open.v1"` ConversationId string `protobuf:"bytes,1,opt,name=conversation_id,json=conversationId,proto3" json:"conversation_id,omitempty"` - Seq int32 `protobuf:"varint,2,opt,name=seq,proto3" json:"seq,omitempty"` + Step int32 `protobuf:"varint,2,opt,name=step,proto3" json:"step,omitempty"` ExecId string `protobuf:"bytes,3,opt,name=exec_id,json=execId,proto3" json:"exec_id,omitempty"` HarnessId string `protobuf:"bytes,4,opt,name=harness_id,json=harnessId,proto3" json:"harness_id,omitempty"` HarnessConfig *structpb.Struct `protobuf:"bytes,5,opt,name=harness_config,json=harnessConfig,proto3" json:"harness_config,omitempty"` @@ -253,9 +253,9 @@ func (x *ConversationEvent) GetConversationId() string { return "" } -func (x *ConversationEvent) GetSeq() int32 { +func (x *ConversationEvent) GetStep() int32 { if x != nil { - return x.Seq + return x.Step } return 0 } @@ -754,7 +754,7 @@ type ExecRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ConversationId string `protobuf:"bytes,1,opt,name=conversation_id,json=conversationId,proto3" json:"conversation_id,omitempty"` // Unique conversation identifier Inputs []*Message `protobuf:"bytes,2,rep,name=inputs,proto3" json:"inputs,omitempty"` // New inputs - LastSeq int32 `protobuf:"varint,3,opt,name=last_seq,json=lastSeq,proto3" json:"last_seq,omitempty"` // Last sequence number seen by the client + LastStep int32 `protobuf:"varint,3,opt,name=last_step,json=lastStep,proto3" json:"last_step,omitempty"` // Last step number seen by the client HarnessId string `protobuf:"bytes,4,opt,name=harness_id,json=harnessId,proto3" json:"harness_id,omitempty"` // Harness ID, empty selects the default harness HarnessConfig []byte `protobuf:"bytes,5,opt,name=harness_config,json=harnessConfig,proto3" json:"harness_config,omitempty"` // Per-request harness configuration (opaque JSON), if any unknownFields protoimpl.UnknownFields @@ -805,9 +805,9 @@ func (x *ExecRequest) GetInputs() []*Message { return nil } -func (x *ExecRequest) GetLastSeq() int32 { +func (x *ExecRequest) GetLastStep() int32 { if x != nil { - return x.LastSeq + return x.LastStep } return 0 } @@ -830,8 +830,8 @@ func (x *ExecRequest) GetHarnessConfig() []byte { type ExecResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Outputs []*Message `protobuf:"bytes,1,rep,name=outputs,proto3" json:"outputs,omitempty"` // Output content - Seq int32 `protobuf:"varint,2,opt,name=seq,proto3" json:"seq,omitempty"` // Seq of the outputs - // Opaque metadata associated with the terminal event at seq. + Step int32 `protobuf:"varint,2,opt,name=step,proto3" json:"step,omitempty"` // Step of the outputs + // Opaque metadata associated with the terminal event at step. HarnessMetadata []byte `protobuf:"bytes,3,opt,name=harness_metadata,json=harnessMetadata,proto3" json:"harness_metadata,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -874,9 +874,9 @@ func (x *ExecResponse) GetOutputs() []*Message { return nil } -func (x *ExecResponse) GetSeq() int32 { +func (x *ExecResponse) GetStep() int32 { if x != nil { - return x.Seq + return x.Step } return 0 } @@ -975,10 +975,10 @@ const file_proto_ax_proto_rawDesc = "" + "\x0eproto/ax.proto\x12\x02ax\x1a\x1cgoogle/protobuf/struct.proto\x1a\x13proto/content.proto\"D\n" + "\aMessage\x12\x12\n" + "\x04role\x18\x01 \x01(\tR\x04role\x12%\n" + - "\acontent\x18\x02 \x01(\v2\v.ax.ContentR\acontent\"\xbb\x02\n" + + "\acontent\x18\x02 \x01(\v2\v.ax.ContentR\acontent\"\xbd\x02\n" + "\x11ConversationEvent\x12'\n" + - "\x0fconversation_id\x18\x01 \x01(\tR\x0econversationId\x12\x10\n" + - "\x03seq\x18\x02 \x01(\x05R\x03seq\x12\x17\n" + + "\x0fconversation_id\x18\x01 \x01(\tR\x0econversationId\x12\x12\n" + + "\x04step\x18\x02 \x01(\x05R\x04step\x12\x17\n" + "\aexec_id\x18\x03 \x01(\tR\x06execId\x12\x1d\n" + "\n" + "harness_id\x18\x04 \x01(\tR\tharnessId\x12>\n" + @@ -1012,17 +1012,17 @@ const file_proto_ax_proto_rawDesc = "" + "\x0fconversation_id\x18\x01 \x01(\tR\x0econversationId\x12.\n" + "\aoutputs\x18\x02 \x01(\v2\x12.ax.HarnessOutputsH\x00R\aoutputs\x12\"\n" + "\x03end\x18\x03 \x01(\v2\x0e.ax.HarnessEndH\x00R\x03endB\x06\n" + - "\x04type\"\xbc\x01\n" + + "\x04type\"\xbe\x01\n" + "\vExecRequest\x12'\n" + "\x0fconversation_id\x18\x01 \x01(\tR\x0econversationId\x12#\n" + - "\x06inputs\x18\x02 \x03(\v2\v.ax.MessageR\x06inputs\x12\x19\n" + - "\blast_seq\x18\x03 \x01(\x05R\alastSeq\x12\x1d\n" + + "\x06inputs\x18\x02 \x03(\v2\v.ax.MessageR\x06inputs\x12\x1b\n" + + "\tlast_step\x18\x03 \x01(\x05R\blastStep\x12\x1d\n" + "\n" + "harness_id\x18\x04 \x01(\tR\tharnessId\x12%\n" + - "\x0eharness_config\x18\x05 \x01(\fR\rharnessConfig\"r\n" + + "\x0eharness_config\x18\x05 \x01(\fR\rharnessConfig\"t\n" + "\fExecResponse\x12%\n" + - "\aoutputs\x18\x01 \x03(\v2\v.ax.MessageR\aoutputs\x12\x10\n" + - "\x03seq\x18\x02 \x01(\x05R\x03seq\x12)\n" + + "\aoutputs\x18\x01 \x03(\v2\v.ax.MessageR\aoutputs\x12\x12\n" + + "\x04step\x18\x02 \x01(\x05R\x04step\x12)\n" + "\x10harness_metadata\x18\x03 \x01(\fR\x0fharnessMetadata\"D\n" + "\x19DeleteConversationRequest\x12'\n" + "\x0fconversation_id\x18\x01 \x01(\tR\x0econversationId\"\x1c\n" + diff --git a/proto/ax.proto b/proto/ax.proto index dafc65e8..919eecd5 100644 --- a/proto/ax.proto +++ b/proto/ax.proto @@ -35,7 +35,7 @@ message Message { // before the last execution is completed or failed. message ConversationEvent { string conversation_id = 1; - int32 seq = 2; + int32 step = 2; string exec_id = 3; string harness_id = 4; google.protobuf.Struct harness_config = 5; @@ -122,7 +122,7 @@ enum CancelReason { message ExecRequest { string conversation_id = 1; // Unique conversation identifier repeated Message inputs = 2; // New inputs - int32 last_seq = 3; // Last sequence number seen by the client + int32 last_step = 3; // Last step number seen by the client string harness_id = 4; // Harness ID, empty selects the default harness bytes harness_config = 5; // Per-request harness configuration (opaque JSON), if any @@ -131,8 +131,8 @@ message ExecRequest { // ExecResponse contains the result of an execution. message ExecResponse { repeated Message outputs = 1; // Output content - int32 seq = 2; // Seq of the outputs - // Opaque metadata associated with the terminal event at seq. + int32 step = 2; // Step of the outputs + // Opaque metadata associated with the terminal event at step. bytes harness_metadata = 3; } diff --git a/proto/ax_grpc.pb.go b/proto/ax_grpc.pb.go index dc1e0b81..2aebd64a 100644 --- a/proto/ax_grpc.pb.go +++ b/proto/ax_grpc.pb.go @@ -14,8 +14,8 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.5.1 -// - protoc v5.28.2 +// - protoc-gen-go-grpc v1.6.2 +// - protoc v4.25.3 // source: proto/ax.proto package proto @@ -88,7 +88,7 @@ type HarnessServiceServer interface { type UnimplementedHarnessServiceServer struct{} func (UnimplementedHarnessServiceServer) Connect(grpc.BidiStreamingServer[HarnessRequest, HarnessResponse]) error { - return status.Errorf(codes.Unimplemented, "method Connect not implemented") + return status.Error(codes.Unimplemented, "method Connect not implemented") } func (UnimplementedHarnessServiceServer) mustEmbedUnimplementedHarnessServiceServer() {} func (UnimplementedHarnessServiceServer) testEmbeddedByValue() {} @@ -101,7 +101,7 @@ type UnsafeHarnessServiceServer interface { } func RegisterHarnessServiceServer(s grpc.ServiceRegistrar, srv HarnessServiceServer) { - // If the following call pancis, it indicates UnimplementedHarnessServiceServer was + // If the following call panics, it indicates UnimplementedHarnessServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. @@ -194,7 +194,7 @@ type ExecutionServiceServer interface { type UnimplementedExecutionServiceServer struct{} func (UnimplementedExecutionServiceServer) Exec(*ExecRequest, grpc.ServerStreamingServer[ExecResponse]) error { - return status.Errorf(codes.Unimplemented, "method Exec not implemented") + return status.Error(codes.Unimplemented, "method Exec not implemented") } func (UnimplementedExecutionServiceServer) mustEmbedUnimplementedExecutionServiceServer() {} func (UnimplementedExecutionServiceServer) testEmbeddedByValue() {} @@ -207,7 +207,7 @@ type UnsafeExecutionServiceServer interface { } func RegisterExecutionServiceServer(s grpc.ServiceRegistrar, srv ExecutionServiceServer) { - // If the following call pancis, it indicates UnimplementedExecutionServiceServer was + // If the following call panics, it indicates UnimplementedExecutionServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. @@ -294,7 +294,7 @@ type ConversationServiceServer interface { type UnimplementedConversationServiceServer struct{} func (UnimplementedConversationServiceServer) DeleteConversation(context.Context, *DeleteConversationRequest) (*DeleteConversationResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method DeleteConversation not implemented") + return nil, status.Error(codes.Unimplemented, "method DeleteConversation not implemented") } func (UnimplementedConversationServiceServer) mustEmbedUnimplementedConversationServiceServer() {} func (UnimplementedConversationServiceServer) testEmbeddedByValue() {} @@ -307,7 +307,7 @@ type UnsafeConversationServiceServer interface { } func RegisterConversationServiceServer(s grpc.ServiceRegistrar, srv ConversationServiceServer) { - // If the following call pancis, it indicates UnimplementedConversationServiceServer was + // If the following call panics, it indicates UnimplementedConversationServiceServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. diff --git a/proto/content.pb.go b/proto/content.pb.go index 1cae4f60..70cdabcd 100644 --- a/proto/content.pb.go +++ b/proto/content.pb.go @@ -15,7 +15,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v5.28.2 +// protoc v4.25.3 // source: proto/content.proto package proto diff --git a/python/proto/ax_pb2.py b/python/proto/ax_pb2.py index 8c385330..2386fb36 100644 --- a/python/proto/ax_pb2.py +++ b/python/proto/ax_pb2.py @@ -1,26 +1,22 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE # source: proto/ax.proto -# Protobuf Python Version: 4.25.3 +# Protobuf Python Version: 5.29.0 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 0, + '', + 'proto/ax.proto' +) # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -30,48 +26,48 @@ from proto import content_pb2 as proto_dot_content__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0eproto/ax.proto\x12\x02\x61x\x1a\x1cgoogle/protobuf/struct.proto\x1a\x13proto/content.proto\"5\n\x07Message\x12\x0c\n\x04role\x18\x01 \x01(\t\x12\x1c\n\x07\x63ontent\x18\x02 \x01(\x0b\x32\x0b.ax.Content\"\xe2\x01\n\x11\x43onversationEvent\x12\x17\n\x0f\x63onversation_id\x18\x01 \x01(\t\x12\x0b\n\x03seq\x18\x02 \x01(\x05\x12\x0f\n\x07\x65xec_id\x18\x03 \x01(\t\x12\x12\n\nharness_id\x18\x04 \x01(\t\x12/\n\x0eharness_config\x18\x05 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x1d\n\x08messages\x18\x06 \x03(\x0b\x32\x0b.ax.Message\x12\x18\n\x05state\x18\x07 \x01(\x0e\x32\t.ax.State\x12\x18\n\x10harness_metadata\x18\x08 \x01(\x0c\"E\n\x0cHarnessStart\x12\x16\n\x0eharness_config\x18\x01 \x01(\x0c\x12\x1d\n\x08messages\x18\x02 \x03(\x0b\x32\x0b.ax.Message\"1\n\rHarnessCancel\x12 \n\x06reason\x18\x01 \x01(\x0e\x32\x10.ax.CancelReason\"\x8d\x01\n\x0eHarnessRequest\x12\x17\n\x0f\x63onversation_id\x18\x01 \x01(\t\x12\x12\n\nharness_id\x18\x02 \x01(\t\x12!\n\x05start\x18\x03 \x01(\x0b\x32\x10.ax.HarnessStartH\x00\x12#\n\x06\x63\x61ncel\x18\x04 \x01(\x0b\x32\x11.ax.HarnessCancelH\x00\x42\x06\n\x04type\"/\n\x0eHarnessOutputs\x12\x1d\n\x08messages\x18\x01 \x03(\x0b\x32\x0b.ax.Message\"*\n\x05\x45rror\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x05\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\"Z\n\nHarnessEnd\x12\x18\n\x05state\x18\x01 \x01(\x0e\x32\t.ax.State\x12\x18\n\x05\x65rror\x18\x02 \x01(\x0b\x32\t.ax.Error\x12\x18\n\x10harness_metadata\x18\x03 \x01(\x0c\"x\n\x0fHarnessResponse\x12\x17\n\x0f\x63onversation_id\x18\x01 \x01(\t\x12%\n\x07outputs\x18\x02 \x01(\x0b\x32\x12.ax.HarnessOutputsH\x00\x12\x1d\n\x03\x65nd\x18\x03 \x01(\x0b\x32\x0e.ax.HarnessEndH\x00\x42\x06\n\x04type\"\x81\x01\n\x0b\x45xecRequest\x12\x17\n\x0f\x63onversation_id\x18\x01 \x01(\t\x12\x1b\n\x06inputs\x18\x02 \x03(\x0b\x32\x0b.ax.Message\x12\x10\n\x08last_seq\x18\x03 \x01(\x05\x12\x12\n\nharness_id\x18\x04 \x01(\t\x12\x16\n\x0eharness_config\x18\x05 \x01(\x0c\"S\n\x0c\x45xecResponse\x12\x1c\n\x07outputs\x18\x01 \x03(\x0b\x32\x0b.ax.Message\x12\x0b\n\x03seq\x18\x02 \x01(\x05\x12\x18\n\x10harness_metadata\x18\x03 \x01(\x0c\"4\n\x19\x44\x65leteConversationRequest\x12\x17\n\x0f\x63onversation_id\x18\x01 \x01(\t\"\x1c\n\x1a\x44\x65leteConversationResponse*l\n\x05State\x12\x15\n\x11STATE_UNSPECIFIED\x10\x00\x12\x11\n\rSTATE_PENDING\x10\x01\x12\x10\n\x0cSTATE_FAILED\x10\x02\x12\x13\n\x0fSTATE_COMPLETED\x10\x03\x12\x12\n\x0eSTATE_CANCELED\x10\x04*\x8c\x01\n\x0c\x43\x61ncelReason\x12\x1d\n\x19\x43\x41NCEL_REASON_UNSPECIFIED\x10\x00\x12 \n\x1c\x43\x41NCEL_REASON_USER_REQUESTED\x10\x01\x12\x19\n\x15\x43\x41NCEL_REASON_TIMEOUT\x10\x02\x12 \n\x1c\x43\x41NCEL_REASON_INTERNAL_ERROR\x10\x03\x32H\n\x0eHarnessService\x12\x36\n\x07\x43onnect\x12\x12.ax.HarnessRequest\x1a\x13.ax.HarnessResponse(\x01\x30\x01\x32?\n\x10\x45xecutionService\x12+\n\x04\x45xec\x12\x0f.ax.ExecRequest\x1a\x10.ax.ExecResponse0\x01\x32j\n\x13\x43onversationService\x12S\n\x12\x44\x65leteConversation\x12\x1d.ax.DeleteConversationRequest\x1a\x1e.ax.DeleteConversationResponseB\x1cZ\x1agithub.com/google/ax/protob\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0eproto/ax.proto\x12\x02\x61x\x1a\x1cgoogle/protobuf/struct.proto\x1a\x13proto/content.proto\"5\n\x07Message\x12\x0c\n\x04role\x18\x01 \x01(\t\x12\x1c\n\x07\x63ontent\x18\x02 \x01(\x0b\x32\x0b.ax.Content\"\xe3\x01\n\x11\x43onversationEvent\x12\x17\n\x0f\x63onversation_id\x18\x01 \x01(\t\x12\x0c\n\x04step\x18\x02 \x01(\x05\x12\x0f\n\x07\x65xec_id\x18\x03 \x01(\t\x12\x12\n\nharness_id\x18\x04 \x01(\t\x12/\n\x0eharness_config\x18\x05 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x1d\n\x08messages\x18\x06 \x03(\x0b\x32\x0b.ax.Message\x12\x18\n\x05state\x18\x07 \x01(\x0e\x32\t.ax.State\x12\x18\n\x10harness_metadata\x18\x08 \x01(\x0c\"E\n\x0cHarnessStart\x12\x16\n\x0eharness_config\x18\x01 \x01(\x0c\x12\x1d\n\x08messages\x18\x02 \x03(\x0b\x32\x0b.ax.Message\"1\n\rHarnessCancel\x12 \n\x06reason\x18\x01 \x01(\x0e\x32\x10.ax.CancelReason\"\x8d\x01\n\x0eHarnessRequest\x12\x17\n\x0f\x63onversation_id\x18\x01 \x01(\t\x12\x12\n\nharness_id\x18\x02 \x01(\t\x12!\n\x05start\x18\x03 \x01(\x0b\x32\x10.ax.HarnessStartH\x00\x12#\n\x06\x63\x61ncel\x18\x04 \x01(\x0b\x32\x11.ax.HarnessCancelH\x00\x42\x06\n\x04type\"/\n\x0eHarnessOutputs\x12\x1d\n\x08messages\x18\x01 \x03(\x0b\x32\x0b.ax.Message\"*\n\x05\x45rror\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x05\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\"Z\n\nHarnessEnd\x12\x18\n\x05state\x18\x01 \x01(\x0e\x32\t.ax.State\x12\x18\n\x05\x65rror\x18\x02 \x01(\x0b\x32\t.ax.Error\x12\x18\n\x10harness_metadata\x18\x03 \x01(\x0c\"x\n\x0fHarnessResponse\x12\x17\n\x0f\x63onversation_id\x18\x01 \x01(\t\x12%\n\x07outputs\x18\x02 \x01(\x0b\x32\x12.ax.HarnessOutputsH\x00\x12\x1d\n\x03\x65nd\x18\x03 \x01(\x0b\x32\x0e.ax.HarnessEndH\x00\x42\x06\n\x04type\"\x82\x01\n\x0b\x45xecRequest\x12\x17\n\x0f\x63onversation_id\x18\x01 \x01(\t\x12\x1b\n\x06inputs\x18\x02 \x03(\x0b\x32\x0b.ax.Message\x12\x11\n\tlast_step\x18\x03 \x01(\x05\x12\x12\n\nharness_id\x18\x04 \x01(\t\x12\x16\n\x0eharness_config\x18\x05 \x01(\x0c\"T\n\x0c\x45xecResponse\x12\x1c\n\x07outputs\x18\x01 \x03(\x0b\x32\x0b.ax.Message\x12\x0c\n\x04step\x18\x02 \x01(\x05\x12\x18\n\x10harness_metadata\x18\x03 \x01(\x0c\"4\n\x19\x44\x65leteConversationRequest\x12\x17\n\x0f\x63onversation_id\x18\x01 \x01(\t\"\x1c\n\x1a\x44\x65leteConversationResponse*l\n\x05State\x12\x15\n\x11STATE_UNSPECIFIED\x10\x00\x12\x11\n\rSTATE_PENDING\x10\x01\x12\x10\n\x0cSTATE_FAILED\x10\x02\x12\x13\n\x0fSTATE_COMPLETED\x10\x03\x12\x12\n\x0eSTATE_CANCELED\x10\x04*\x8c\x01\n\x0c\x43\x61ncelReason\x12\x1d\n\x19\x43\x41NCEL_REASON_UNSPECIFIED\x10\x00\x12 \n\x1c\x43\x41NCEL_REASON_USER_REQUESTED\x10\x01\x12\x19\n\x15\x43\x41NCEL_REASON_TIMEOUT\x10\x02\x12 \n\x1c\x43\x41NCEL_REASON_INTERNAL_ERROR\x10\x03\x32H\n\x0eHarnessService\x12\x36\n\x07\x43onnect\x12\x12.ax.HarnessRequest\x1a\x13.ax.HarnessResponse(\x01\x30\x01\x32?\n\x10\x45xecutionService\x12+\n\x04\x45xec\x12\x0f.ax.ExecRequest\x1a\x10.ax.ExecResponse0\x01\x32j\n\x13\x43onversationService\x12S\n\x12\x44\x65leteConversation\x12\x1d.ax.DeleteConversationRequest\x1a\x1e.ax.DeleteConversationResponseB\x1cZ\x1agithub.com/google/ax/protob\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proto.ax_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - _globals['DESCRIPTOR']._options = None +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None _globals['DESCRIPTOR']._serialized_options = b'Z\032github.com/google/ax/proto' - _globals['_STATE']._serialized_start=1231 - _globals['_STATE']._serialized_end=1339 - _globals['_CANCELREASON']._serialized_start=1342 - _globals['_CANCELREASON']._serialized_end=1482 + _globals['_STATE']._serialized_start=1234 + _globals['_STATE']._serialized_end=1342 + _globals['_CANCELREASON']._serialized_start=1345 + _globals['_CANCELREASON']._serialized_end=1485 _globals['_MESSAGE']._serialized_start=73 _globals['_MESSAGE']._serialized_end=126 _globals['_CONVERSATIONEVENT']._serialized_start=129 - _globals['_CONVERSATIONEVENT']._serialized_end=355 - _globals['_HARNESSSTART']._serialized_start=357 - _globals['_HARNESSSTART']._serialized_end=426 - _globals['_HARNESSCANCEL']._serialized_start=428 - _globals['_HARNESSCANCEL']._serialized_end=477 - _globals['_HARNESSREQUEST']._serialized_start=480 - _globals['_HARNESSREQUEST']._serialized_end=621 - _globals['_HARNESSOUTPUTS']._serialized_start=623 - _globals['_HARNESSOUTPUTS']._serialized_end=670 - _globals['_ERROR']._serialized_start=672 - _globals['_ERROR']._serialized_end=714 - _globals['_HARNESSEND']._serialized_start=716 - _globals['_HARNESSEND']._serialized_end=806 - _globals['_HARNESSRESPONSE']._serialized_start=808 - _globals['_HARNESSRESPONSE']._serialized_end=928 - _globals['_EXECREQUEST']._serialized_start=931 - _globals['_EXECREQUEST']._serialized_end=1060 - _globals['_EXECRESPONSE']._serialized_start=1062 - _globals['_EXECRESPONSE']._serialized_end=1145 - _globals['_DELETECONVERSATIONREQUEST']._serialized_start=1147 - _globals['_DELETECONVERSATIONREQUEST']._serialized_end=1199 - _globals['_DELETECONVERSATIONRESPONSE']._serialized_start=1201 - _globals['_DELETECONVERSATIONRESPONSE']._serialized_end=1229 - _globals['_HARNESSSERVICE']._serialized_start=1484 - _globals['_HARNESSSERVICE']._serialized_end=1556 - _globals['_EXECUTIONSERVICE']._serialized_start=1558 - _globals['_EXECUTIONSERVICE']._serialized_end=1621 - _globals['_CONVERSATIONSERVICE']._serialized_start=1623 - _globals['_CONVERSATIONSERVICE']._serialized_end=1729 + _globals['_CONVERSATIONEVENT']._serialized_end=356 + _globals['_HARNESSSTART']._serialized_start=358 + _globals['_HARNESSSTART']._serialized_end=427 + _globals['_HARNESSCANCEL']._serialized_start=429 + _globals['_HARNESSCANCEL']._serialized_end=478 + _globals['_HARNESSREQUEST']._serialized_start=481 + _globals['_HARNESSREQUEST']._serialized_end=622 + _globals['_HARNESSOUTPUTS']._serialized_start=624 + _globals['_HARNESSOUTPUTS']._serialized_end=671 + _globals['_ERROR']._serialized_start=673 + _globals['_ERROR']._serialized_end=715 + _globals['_HARNESSEND']._serialized_start=717 + _globals['_HARNESSEND']._serialized_end=807 + _globals['_HARNESSRESPONSE']._serialized_start=809 + _globals['_HARNESSRESPONSE']._serialized_end=929 + _globals['_EXECREQUEST']._serialized_start=932 + _globals['_EXECREQUEST']._serialized_end=1062 + _globals['_EXECRESPONSE']._serialized_start=1064 + _globals['_EXECRESPONSE']._serialized_end=1148 + _globals['_DELETECONVERSATIONREQUEST']._serialized_start=1150 + _globals['_DELETECONVERSATIONREQUEST']._serialized_end=1202 + _globals['_DELETECONVERSATIONRESPONSE']._serialized_start=1204 + _globals['_DELETECONVERSATIONRESPONSE']._serialized_end=1232 + _globals['_HARNESSSERVICE']._serialized_start=1487 + _globals['_HARNESSSERVICE']._serialized_end=1559 + _globals['_EXECUTIONSERVICE']._serialized_start=1561 + _globals['_EXECUTIONSERVICE']._serialized_end=1624 + _globals['_CONVERSATIONSERVICE']._serialized_start=1626 + _globals['_CONVERSATIONSERVICE']._serialized_end=1732 # @@protoc_insertion_point(module_scope) diff --git a/python/proto/ax_pb2_grpc.py b/python/proto/ax_pb2_grpc.py index b35d9534..b2564e05 100644 --- a/python/proto/ax_pb2_grpc.py +++ b/python/proto/ax_pb2_grpc.py @@ -1,23 +1,29 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! """Client and server classes corresponding to protobuf-defined services.""" import grpc +import warnings from proto import ax_pb2 as proto_dot_ax__pb2 +GRPC_GENERATED_VERSION = '1.70.0' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + f' but the generated code in proto/ax_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + class HarnessServiceStub(object): """Missing associated documentation comment in .proto file.""" @@ -32,7 +38,7 @@ def __init__(self, channel): '/ax.HarnessService/Connect', request_serializer=proto_dot_ax__pb2.HarnessRequest.SerializeToString, response_deserializer=proto_dot_ax__pb2.HarnessResponse.FromString, - ) + _registered_method=True) class HarnessServiceServicer(object): @@ -60,6 +66,7 @@ def add_HarnessServiceServicer_to_server(servicer, server): generic_handler = grpc.method_handlers_generic_handler( 'ax.HarnessService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('ax.HarnessService', rpc_method_handlers) # This class is part of an EXPERIMENTAL API. @@ -77,11 +84,21 @@ def Connect(request_iterator, wait_for_ready=None, timeout=None, metadata=None): - return grpc.experimental.stream_stream(request_iterator, target, '/ax.HarnessService/Connect', + return grpc.experimental.stream_stream( + request_iterator, + target, + '/ax.HarnessService/Connect', proto_dot_ax__pb2.HarnessRequest.SerializeToString, proto_dot_ax__pb2.HarnessResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) class ExecutionServiceStub(object): @@ -97,7 +114,7 @@ def __init__(self, channel): '/ax.ExecutionService/Exec', request_serializer=proto_dot_ax__pb2.ExecRequest.SerializeToString, response_deserializer=proto_dot_ax__pb2.ExecResponse.FromString, - ) + _registered_method=True) class ExecutionServiceServicer(object): @@ -123,6 +140,7 @@ def add_ExecutionServiceServicer_to_server(servicer, server): generic_handler = grpc.method_handlers_generic_handler( 'ax.ExecutionService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('ax.ExecutionService', rpc_method_handlers) # This class is part of an EXPERIMENTAL API. @@ -140,11 +158,21 @@ def Exec(request, wait_for_ready=None, timeout=None, metadata=None): - return grpc.experimental.unary_stream(request, target, '/ax.ExecutionService/Exec', + return grpc.experimental.unary_stream( + request, + target, + '/ax.ExecutionService/Exec', proto_dot_ax__pb2.ExecRequest.SerializeToString, proto_dot_ax__pb2.ExecResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) class ConversationServiceStub(object): @@ -160,7 +188,7 @@ def __init__(self, channel): '/ax.ConversationService/DeleteConversation', request_serializer=proto_dot_ax__pb2.DeleteConversationRequest.SerializeToString, response_deserializer=proto_dot_ax__pb2.DeleteConversationResponse.FromString, - ) + _registered_method=True) class ConversationServiceServicer(object): @@ -186,6 +214,7 @@ def add_ConversationServiceServicer_to_server(servicer, server): generic_handler = grpc.method_handlers_generic_handler( 'ax.ConversationService', rpc_method_handlers) server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('ax.ConversationService', rpc_method_handlers) # This class is part of an EXPERIMENTAL API. @@ -203,8 +232,18 @@ def DeleteConversation(request, wait_for_ready=None, timeout=None, metadata=None): - return grpc.experimental.unary_unary(request, target, '/ax.ConversationService/DeleteConversation', + return grpc.experimental.unary_unary( + request, + target, + '/ax.ConversationService/DeleteConversation', proto_dot_ax__pb2.DeleteConversationRequest.SerializeToString, proto_dot_ax__pb2.DeleteConversationResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/python/proto/content_pb2.py b/python/proto/content_pb2.py index c20ef7bf..049297ae 100644 --- a/python/proto/content_pb2.py +++ b/python/proto/content_pb2.py @@ -1,26 +1,22 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE # source: proto/content.proto -# Protobuf Python Version: 4.25.1 +# Protobuf Python Version: 5.29.0 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 0, + '', + 'proto/content.proto' +) # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -34,8 +30,8 @@ _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'proto.content_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - _globals['DESCRIPTOR']._options = None +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None _globals['DESCRIPTOR']._serialized_options = b'Z\032github.com/google/ax/proto' _globals['_MEDIARESOLUTION']._serialized_start=2638 _globals['_MEDIARESOLUTION']._serialized_end=2736 diff --git a/python/proto/content_pb2_grpc.py b/python/proto/content_pb2_grpc.py index fe9b2e77..3b145290 100644 --- a/python/proto/content_pb2_grpc.py +++ b/python/proto/content_pb2_grpc.py @@ -1,18 +1,24 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! """Client and server classes corresponding to protobuf-defined services.""" import grpc +import warnings + + +GRPC_GENERATED_VERSION = '1.70.0' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + f' but the generated code in proto/content_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + )