From 10588c52ac413f499795389a7a65ffa50701ed94 Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 08:43:07 -0700 Subject: [PATCH 01/26] fix(docs): update HarnessService description to clarify terminology --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f142d3a9..3b2ee7dd 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ graph LR end SnapshotService["Snapshots"] - HarnessService["Harness or model
service"] + HarnessService["Models"] MCPServer["MCP server"] Client <-->|resumable stream| Server From 5ac275e373303d10c0e9723af5201c483e98ab00 Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 08:44:24 -0700 Subject: [PATCH 02/26] fix(docs): update Control API label to Actor Controller in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b2ee7dd..cb8df0cf 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ 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 From bff324b7dbcf8579854388396a07e712ecd5d3d3 Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 10:02:05 -0700 Subject: [PATCH 03/26] docs: remove obsolete Antigravity harness documentation from README --- README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.md b/README.md index cb8df0cf..d1ffbe1b 100644 --- a/README.md +++ b/README.md @@ -114,13 +114,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. From 6806eb99d7b976294cad2f3daeccee0d99096cbf Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 10:02:24 -0700 Subject: [PATCH 04/26] docs: remove deprecated BYOH and integrations items from roadmap --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index d1ffbe1b..9334a234 100644 --- a/README.md +++ b/README.md @@ -315,13 +315,11 @@ and making calls to MCP tools when they are configured. 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 From e4c2fbda0015d34b33fdfff32a17244a43bc7161 Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 10:04:03 -0700 Subject: [PATCH 05/26] docs: remove temporary external contribution pause and contact email from README --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 9334a234..859b46ae 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 From 1abc003dca8833fba084be6f67fe999487eaf8fb Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 10:09:23 -0700 Subject: [PATCH 06/26] feat: prevent sidecar startup when ReadyFunc indicates the endpoint is already in use --- internal/pythonsidecar/sidecar.go | 7 +++++++ internal/pythonsidecar/sidecar_test.go | 27 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/internal/pythonsidecar/sidecar.go b/internal/pythonsidecar/sidecar.go index d02799ba..9c8f6fed 100644 --- a/internal/pythonsidecar/sidecar.go +++ b/internal/pythonsidecar/sidecar.go @@ -80,6 +80,13 @@ 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 { + s.mu.Unlock() + return fmt.Errorf("cannot start sidecar: endpoint is already in use by another process") + } + } + // 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...) diff --git a/internal/pythonsidecar/sidecar_test.go b/internal/pythonsidecar/sidecar_test.go index 3052f414..b5942575 100644 --- a/internal/pythonsidecar/sidecar_test.go +++ b/internal/pythonsidecar/sidecar_test.go @@ -156,3 +156,30 @@ 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) + } +} From a0bfb5fb74dfe94f96d8feebf7755c6248856734 Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 11:56:37 -0700 Subject: [PATCH 07/26] feat: add support for terminating existing orphan processes before starting the Python sidecar --- cmd/ax/harness.go | 9 ++- internal/harness/antigravity/antigravity.go | 16 ++++- internal/pythonsidecar/sidecar.go | 71 ++++++++++++++++++++- internal/pythonsidecar/sidecar_test.go | 37 +++++++++++ 4 files changed, 125 insertions(+), 8 deletions(-) diff --git a/cmd/ax/harness.go b/cmd/ax/harness.go index 6ecf3ace..493951b3 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) 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/pythonsidecar/sidecar.go b/internal/pythonsidecar/sidecar.go index 9c8f6fed..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. @@ -82,8 +88,13 @@ func (s *Sidecar) Start(ctx context.Context, pythonPath string) error { if s.cfg.ReadyFunc != nil { if err := s.cfg.ReadyFunc(ctx); err == nil { - s.mu.Unlock() - return fmt.Errorf("cannot start sidecar: endpoint is already in use by another process") + 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) + } } } @@ -274,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 b5942575..6e469bb7 100644 --- a/internal/pythonsidecar/sidecar_test.go +++ b/internal/pythonsidecar/sidecar_test.go @@ -183,3 +183,40 @@ func TestSidecar_EndpointAlreadyInUse(t *testing.T) { 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") + } +} From 701feadd055b872edd02c040531551e532e1b797 Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 12:01:27 -0700 Subject: [PATCH 08/26] refactor: rename CLI flags to --config and --ax-config for improved command naming consistency --- README.md | 23 +++++++++-------------- cmd/ax/exec.go | 8 ++++---- cmd/ax/main.go | 4 +++- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 859b46ae..d49d8327 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,6 @@ any registered harness: ```bash ax exec \ - --harness antigravity \ --input "Can you write me a simple HTTP server in Python?" ``` @@ -179,7 +178,6 @@ you can resume an incomplete execution in a conversation: ```bash ax exec \ --conversation edf98ef5-4bb1-4a9e-a091-3a77e03727e6 \ - --harness antigravity \ --resume ``` @@ -195,10 +193,10 @@ ax exec \ [--input ] \ [--conversation ] \ [--harness ] \ - [--harness-config ] \ - [--harness-config-json ] \ + [--config ] \ + [--config-json ] \ [--server
] \ - [--config ] \ + [--ax-config ] \ [--resume] \ [--last-seq ] ``` @@ -209,10 +207,10 @@ 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`) +- `--config`: Path to a JSON file with per-request harness configuration (optional) +- `--config-json`: Per-request harness configuration as an inline JSON string (optional, mutually exclusive with `--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") +- `--ax-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. @@ -228,16 +226,13 @@ ax exec --conversation a53d4db3-1165-4925-87da-be6c72bbdeb1 --input "Ok, now let # 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!" - # Execute with per-request harness config ax exec \ - --harness-config-json '{"system_instructions":"Answer in one sentence.","model":"gemini-3.5-flash"}' \ + --config-json '{"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` instead: +ax exec --config antigravity.json --input "Explain durable execution." ``` ### Serve diff --git a/cmd/ax/exec.go b/cmd/ax/exec.go index 947b6b18..d24ec9e1 100644 --- a/cmd/ax/exec.go +++ b/cmd/ax/exec.go @@ -57,15 +57,15 @@ If no conversation ID is provided, a new UUID will be generated.`, 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(&execHarnessConfig, "config", "", "Path to a JSON file with per-request harness configuration") + execCmd.Flags().StringVar(&execHarnessConfigJSON, "config-json", "", "Per-request harness configuration as an inline JSON string (mutually exclusive with --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().StringVar(&execConfigFile, "ax-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") + execCmd.MarkFlagsMutuallyExclusive("config", "config-json") } // TODO(jbd): Add multimodal input flags, e.g. --input-image. diff --git a/cmd/ax/main.go b/cmd/ax/main.go index ee65c4a2..c87d8b90 100644 --- a/cmd/ax/main.go +++ b/cmd/ax/main.go @@ -64,7 +64,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") != nil && cmd.Flags().Changed("config") && cmd.Flags().Lookup("ax-config") == nil) + if errors.Is(err, os.ErrNotExist) && !configFlagChanged { cfg := cliutil.DefaultConfig() cfg.Version = currentVersion return cfg, nil From 2fd20cd431259aa2bb3340d7c1e080cdcba2c630 Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 12:04:50 -0700 Subject: [PATCH 09/26] refactor: rename harness configuration flags to --config for inline JSON and --config-file for paths --- README.md | 14 +++++++------- cmd/ax/exec.go | 38 +++++++++++++++++++------------------- cmd/ax/main.go | 2 +- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index d49d8327..c1397441 100644 --- a/README.md +++ b/README.md @@ -193,8 +193,8 @@ ax exec \ [--input ] \ [--conversation ] \ [--harness ] \ - [--config ] \ - [--config-json ] \ + [--config ] \ + [--config-file ] \ [--server
] \ [--ax-config ] \ [--resume] \ @@ -207,8 +207,8 @@ 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) -- `--config`: Path to a JSON file with per-request harness configuration (optional) -- `--config-json`: Per-request harness configuration as an inline JSON string (optional, mutually exclusive with `--config`) +- `--config`: Per-request harness configuration as an inline JSON string (optional, mutually exclusive with `--config-file`) +- `--config-file`: Path to a JSON file with per-request harness configuration (optional) - `--server`: gRPC controller server address (optional. If not provided, runs with a local built-in AX server) - `--ax-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`) @@ -228,11 +228,11 @@ ax exec --server localhost:8494 --input "Hello agents!" # Execute with per-request harness config ax exec \ - --config-json '{"system_instructions":"Answer in one sentence.","model":"gemini-3.5-flash"}' \ + --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 `--config` instead: -ax exec --config antigravity.json --input "Explain durable execution." +# To keep the same JSON in a file, use `--config-file` instead: +ax exec --config-file antigravity.json --input "Explain durable execution." ``` ### Serve diff --git a/cmd/ax/exec.go b/cmd/ax/exec.go index d24ec9e1..f7f14732 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 + execLastSeq int32 ) var execCmd = &cobra.Command{ @@ -57,15 +57,15 @@ If no conversation ID is provided, a new UUID will be generated.`, 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, "config", "", "Path to a JSON file with per-request harness configuration") - execCmd.Flags().StringVar(&execHarnessConfigJSON, "config-json", "", "Per-request harness configuration as an inline JSON string (mutually exclusive with --config)") + execCmd.Flags().StringVar(&execConfigFile, "config-file", "", "Path to a JSON file with per-request harness configuration") + execCmd.Flags().StringVar(&execConfig, "config", "", "Per-request harness configuration as an inline JSON string (mutually exclusive with --config-file)") 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, "ax-config", "ax.yaml", "Path to YAML configuration file (only used with a local built-in AX server)") + execCmd.Flags().StringVar(&execAXConfigFile, "ax-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("config", "config-json") + execCmd.MarkFlagsMutuallyExclusive("config", "config-file") } // TODO(jbd): Add multimodal input flags, e.g. --input-image. @@ -108,7 +108,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,14 +124,14 @@ 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) diff --git a/cmd/ax/main.go b/cmd/ax/main.go index c87d8b90..acc98336 100644 --- a/cmd/ax/main.go +++ b/cmd/ax/main.go @@ -65,7 +65,7 @@ const currentVersion = "v1alpha" func newConfig(cmd *cobra.Command, configFile string) (*cliutil.Config, error) { cfg, err := cliutil.LoadFromFile(configFile) configFlagChanged := (cmd.Flags().Lookup("ax-config") != nil && cmd.Flags().Changed("ax-config")) || - (cmd.Flags().Lookup("config") != nil && cmd.Flags().Changed("config") && cmd.Flags().Lookup("ax-config") == nil) + (cmd.Flags().Lookup("config-file") != nil && cmd.Flags().Changed("config-file")) if errors.Is(err, os.ErrNotExist) && !configFlagChanged { cfg := cliutil.DefaultConfig() cfg.Version = currentVersion From 24b2f399fa17572b9ffb64dd3e2ee1049a15aa7b Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 12:05:48 -0700 Subject: [PATCH 10/26] docs: update README features list to highlight MCP tools and custom environment support --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index c1397441..f2574b57 100644 --- a/README.md +++ b/README.md @@ -20,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 From bab973e877a32444af8283e4f850912ef7345a98 Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 12:13:19 -0700 Subject: [PATCH 11/26] refactor: rename seq to step across protocol and internal controller logic --- README.md | 10 +-- cmd/ax/dashboard.go | 16 ++-- cmd/ax/dashboard_test.go | 14 ++-- cmd/ax/exec.go | 24 +++--- cmd/ax/web/index.html | 4 +- internal/controller/controller.go | 4 +- .../eventlog/eventlogtest/eventlog.go | 16 ++-- internal/controller/eventlog/postgres.go | 4 +- internal/controller/eventlog/sql.go | 22 ++--- internal/controller/eventlog/sql_test.go | 28 +++---- internal/controller/eventlog/sqlite.go | 4 +- proto/ax.pb.go | 36 ++++---- proto/ax.proto | 6 +- python/proto/ax_pb2.py | 82 ++++++++----------- python/proto/ax_pb2_grpc.py | 14 ---- python/proto/content_pb2.py | 14 ---- python/proto/content_pb2_grpc.py | 14 ---- 17 files changed, 128 insertions(+), 184 deletions(-) diff --git a/README.md b/README.md index f2574b57..dc3c9503 100644 --- a/README.md +++ b/README.md @@ -151,16 +151,16 @@ ax exec \ --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 \ + --last-step 12 \ --resume ``` @@ -197,7 +197,7 @@ ax exec \ [--server
] \ [--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. @@ -211,7 +211,7 @@ Options: - `--server`: gRPC controller server address (optional. If not provided, runs with a local built-in AX server) - `--ax-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. +- `--last-step`: Last step number seen by the client to resume from (optional). The server replays any later events so the client can catch up after a disconnect. **Examples:** diff --git a/cmd/ax/dashboard.go b/cmd/ax/dashboard.go index e518f74b..5762964b 100644 --- a/cmd/ax/dashboard.go +++ b/cmd/ax/dashboard.go @@ -60,7 +60,7 @@ type ConversationResponse struct { ID string `json:"id"` Agent string `json:"agent"` Status string `json:"status"` - LastSeq int32 `json:"last_seq"` + LastStep int32 `json:"last_step"` Duration string `json:"duration"` } @@ -194,18 +194,18 @@ func fetchConversations(ctx context.Context, db *sql.DB) ([]ConversationResponse query := ` SELECT c.conversation_id, - c.last_seq, + c.last_step, c.state, e.agent_id, e.start_time, e.end_time FROM ( - SELECT conversation_id, seq AS last_seq, + SELECT conversation_id, step AS last_step, 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) + WHERE (conversation_id, step) IN ( + SELECT conversation_id, MAX(step) FROM conversation_log GROUP BY conversation_id ) @@ -229,12 +229,12 @@ LEFT JOIN ( convs := []ConversationResponse{} for rows.Next() { var id string - var lastSeq int32 + var lastStep int32 var state string var agentID sql.NullString var startTimeStr, endTimeStr sql.NullString - err := rows.Scan(&id, &lastSeq, &state, &agentID, &startTimeStr, &endTimeStr) + err := rows.Scan(&id, &lastStep, &state, &agentID, &startTimeStr, &endTimeStr) if err != nil { return nil, err } @@ -272,7 +272,7 @@ LEFT JOIN ( ID: id, Agent: agent, Status: status, - LastSeq: lastSeq, + LastStep: lastStep, Duration: durationStr, }) } diff --git a/cmd/ax/dashboard_test.go b/cmd/ax/dashboard_test.go index df412296..ea5ceab8 100644 --- a/cmd/ax/dashboard_test.go +++ b/cmd/ax/dashboard_test.go @@ -35,7 +35,7 @@ func TestFetchConversations(t *testing.T) { schema := ` CREATE TABLE conversation_log ( conversation_id TEXT, - seq INTEGER, + step INTEGER, timestamp DATETIME, payload TEXT ); @@ -53,7 +53,7 @@ func TestFetchConversations(t *testing.T) { // 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) + INSERT INTO conversation_log (conversation_id, step, timestamp, payload) VALUES ('conv-1', 1, ?, '{"exec_id": "exec-1", "state": "STATE_PENDING"}') `, time.Now().Format(time.RFC3339)) if err != nil { @@ -66,7 +66,7 @@ func TestFetchConversations(t *testing.T) { end := now _, err = db.Exec(` - INSERT INTO conversation_log (conversation_id, seq, timestamp, payload) + INSERT INTO conversation_log (conversation_id, step, timestamp, payload) VALUES ('conv-2', 5, ?, '{"exec_id": "exec-2", "state": "STATE_COMPLETED"}') `, end.Format(time.RFC3339)) if err != nil { @@ -114,8 +114,8 @@ func TestFetchConversations(t *testing.T) { 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) + if c1.LastStep != 1 { + t.Errorf("conv-1 expected last_step 1, got %d", c1.LastStep) } // Check conv-2 @@ -133,7 +133,7 @@ func TestFetchConversations(t *testing.T) { 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) + if c2.LastStep != 5 { + t.Errorf("conv-2 expected last_step 5, got %d", c2.LastStep) } } diff --git a/cmd/ax/exec.go b/cmd/ax/exec.go index f7f14732..eae517f2 100644 --- a/cmd/ax/exec.go +++ b/cmd/ax/exec.go @@ -42,7 +42,7 @@ var ( execServerAddr string execAXConfigFile string execResume bool // allow resuming an execution without inputs - execLastSeq int32 + execLastStep int32 ) var execCmd = &cobra.Command{ @@ -63,7 +63,7 @@ func init() { 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(&execAXConfigFile, "ax-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.Flags().Int32Var(&execLastStep, "last-step", 0, "Last step number seen by the client") execCmd.MarkFlagsMutuallyExclusive("input", "resume") execCmd.MarkFlagsMutuallyExclusive("config", "config-file") } @@ -134,10 +134,10 @@ func runExec(cmd *cobra.Command, args []string) error { 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 +175,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 +297,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 +313,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 +332,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 +341,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 +352,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/web/index.html b/cmd/ax/web/index.html index fc49046a..1f69c0cb 100644 --- a/cmd/ax/web/index.html +++ b/cmd/ax/web/index.html @@ -831,7 +831,7 @@

Failed

Conversation ID Agent Status - Last Seq + Last Step Duration Actions @@ -984,7 +984,7 @@

Execution Trace

${esc(c.status)} - ${c.last_seq} + ${c.last_step} ${esc(c.duration)} -
-

Execution Trace

-

-
- - - -
-
Execution Timeline
-
- -
-
- - -
- -
- - - - - - From 5ec51983e13d18d8e7ead94972113f19942dad50 Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 12:19:30 -0700 Subject: [PATCH 13/26] refactor: move antigravity configuration to top-level and rename HarnessesConfig to RegistryConfig --- ax.yaml | 8 +++---- cmd/ax/harness.go | 2 +- cmd/ax/internal/cliutil/cliutil.go | 28 ++++++++++++++++--------- cmd/ax/internal/cliutil/cliutil_test.go | 8 +++---- internal/config/config.go | 19 +++++++++-------- internal/config/config_test.go | 27 ++++++++++-------------- manifests/ax.yaml | 6 +++--- 7 files changed, 51 insertions(+), 47 deletions(-) diff --git a/ax.yaml b/ax.yaml index 56a980a1..af227337 100644 --- a/ax.yaml +++ b/ax.yaml @@ -14,7 +14,7 @@ # 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 +# ax exec --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/harness.go b/cmd/ax/harness.go index 493951b3..5c3541e4 100644 --- a/cmd/ax/harness.go +++ b/cmd/ax/harness.go @@ -166,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 7a91f008..296820f2 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 @@ -153,11 +158,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) @@ -170,11 +175,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/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/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: {} From 16db0d7a0fc3a3a96c44b2a7545754f5dfdb48ae Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 12:24:07 -0700 Subject: [PATCH 14/26] refactor: simplify CLI by removing the exec subcommand and moving its flags to the root command --- CONTRIBUTING.md | 2 +- README.md | 28 +++++++++++++--------------- ax.yaml | 2 +- cmd/ax/Dockerfile | 2 +- cmd/ax/exec.go | 26 +++++++++++++++----------- cmd/ax/internal/display.go | 4 ++-- cmd/ax/main.go | 11 +++++++---- examples/skills/README.md | 2 +- hack/install-ax.sh | 2 +- manifests/README.md | 2 +- 10 files changed, 43 insertions(+), 38 deletions(-) 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 dc3c9503..9009b17b 100644 --- a/README.md +++ b/README.md @@ -137,16 +137,16 @@ The CLI starts the built-in Antigravity harness automatically. No separate harne ```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 \ +ax \ --conversation d85a4b4e-c53b-4c84-b879-f10d905bce40 \ --input "Show me the contents of README.md" ``` @@ -158,7 +158,7 @@ rewind the conversation. In this example, we catch up a client from step number 12: ```bash -ax exec \ +ax \ --conversation d85a4b4e-c53b-4c84-b879-f10d905bce40 \ --last-step 12 \ --resume @@ -168,14 +168,14 @@ Instead of running the default harness, you can start executing any registered harness: ```bash -ax exec \ +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 \ +ax \ --conversation edf98ef5-4bb1-4a9e-a091-3a77e03727e6 \ --resume ``` @@ -183,12 +183,10 @@ ax exec \ ## Usage -The `ax` command provides several subcommands: - ### Execute ```bash -ax exec \ +ax \ [--input ] \ [--conversation ] \ [--harness ] \ @@ -217,21 +215,21 @@ Options: ```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!" +ax --server localhost:8494 --input "Hello agents!" # Execute with per-request harness config -ax exec \ +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 `--config-file` instead: -ax exec --config-file antigravity.json --input "Explain durable execution." +ax --config-file antigravity.json --input "Explain durable execution." ``` ### Serve diff --git a/ax.yaml b/ax.yaml index af227337..da534751 100644 --- a/ax.yaml +++ b/ax.yaml @@ -14,7 +14,7 @@ # 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 default harness (antigravity) +# ax --ax-config ax.yaml --input "hello" # uses default harness (antigravity) version: v1alpha server: 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/exec.go b/cmd/ax/exec.go index eae517f2..07bb118c 100644 --- a/cmd/ax/exec.go +++ b/cmd/ax/exec.go @@ -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(&execConfigFile, "config-file", "", "Path to a JSON file with per-request harness configuration") - execCmd.Flags().StringVar(&execConfig, "config", "", "Per-request harness configuration as an inline JSON string (mutually exclusive with --config-file)") - 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(&execAXConfigFile, "ax-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(&execLastStep, "last-step", 0, "Last step number seen by the client") - execCmd.MarkFlagsMutuallyExclusive("input", "resume") - execCmd.MarkFlagsMutuallyExclusive("config", "config-file") + registerExecFlags(execCmd) } // TODO(jbd): Add multimodal input flags, e.g. --input-image. 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 fddaf089..e0f36ba9 100644 --- a/cmd/ax/main.go +++ b/cmd/ax/main.go @@ -37,13 +37,16 @@ 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) } 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/hack/install-ax.sh b/hack/install-ax.sh index 78f2b630..9c5c02a2 100755 --- a/hack/install-ax.sh +++ b/hack/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/manifests/README.md b/manifests/README.md index fe3678de..9ca169c9 100644 --- a/manifests/README.md +++ b/manifests/README.md @@ -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: From d1a3da114281ec9438da0c3c98772433e392938e Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 12:25:38 -0700 Subject: [PATCH 15/26] docs: format bash command examples for better readability in README.md --- README.md | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 9009b17b..51a8e668 100644 --- a/README.md +++ b/README.md @@ -146,9 +146,8 @@ ax --input "Can you list this directory?" --server localhost:8494 Conversations can be continued any time: ```bash -ax \ - --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 step it saw to @@ -158,26 +157,23 @@ rewind the conversation. In this example, we catch up a client from step number 12: ```bash -ax \ - --conversation d85a4b4e-c53b-4c84-b879-f10d905bce40 \ - --last-step 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 \ - --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 \ - --conversation edf98ef5-4bb1-4a9e-a091-3a77e03727e6 \ - --resume +ax --conversation edf98ef5-4bb1-4a9e-a091-3a77e03727e6 \ + --resume ``` @@ -224,9 +220,8 @@ ax --conversation a53d4db3-1165-4925-87da-be6c72bbdeb1 --input "Ok, now let's do ax --server localhost:8494 --input "Hello agents!" # Execute with per-request harness config -ax \ - --config '{"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 `--config-file` instead: ax --config-file antigravity.json --input "Explain durable execution." From b019d135ce1917c6e7defaf7653cca928384c3ea Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 12:27:26 -0700 Subject: [PATCH 16/26] chore: add Google LLC copyright headers to generated protocol buffer files --- python/proto/ax_pb2.py | 14 ++++++++++++++ python/proto/ax_pb2_grpc.py | 14 ++++++++++++++ python/proto/content_pb2.py | 14 ++++++++++++++ python/proto/content_pb2_grpc.py | 14 ++++++++++++++ 4 files changed, 56 insertions(+) diff --git a/python/proto/ax_pb2.py b/python/proto/ax_pb2.py index ca52c808..64befc9e 100644 --- a/python/proto/ax_pb2.py +++ b/python/proto/ax_pb2.py @@ -1,3 +1,17 @@ +# 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 +# +# https://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! # source: proto/ax.proto diff --git a/python/proto/ax_pb2_grpc.py b/python/proto/ax_pb2_grpc.py index df3140bd..78d74565 100644 --- a/python/proto/ax_pb2_grpc.py +++ b/python/proto/ax_pb2_grpc.py @@ -1,3 +1,17 @@ +# 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 +# +# https://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 diff --git a/python/proto/content_pb2.py b/python/proto/content_pb2.py index 3f2bf3c2..c7439ff5 100644 --- a/python/proto/content_pb2.py +++ b/python/proto/content_pb2.py @@ -1,3 +1,17 @@ +# 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 +# +# https://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! # source: proto/content.proto diff --git a/python/proto/content_pb2_grpc.py b/python/proto/content_pb2_grpc.py index 2daafffe..0a3290b2 100644 --- a/python/proto/content_pb2_grpc.py +++ b/python/proto/content_pb2_grpc.py @@ -1,3 +1,17 @@ +# 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 +# +# https://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 From d4c5c352e034547fa7c5d2006d12e3a2c2ce2503 Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 12:28:28 -0700 Subject: [PATCH 17/26] chore: rename --config flag to --ax-config for consistency --- ax.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ax.yaml b/ax.yaml index da534751..0add0be2 100644 --- a/ax.yaml +++ b/ax.yaml @@ -13,7 +13,7 @@ # limitations under the License. # Sample LOCAL configuration for the AX *harness* build (compiled with -tags=harness). -# Usage: ax serve --config ax.yaml +# Usage: ax serve --ax-config ax.yaml # ax --ax-config ax.yaml --input "hello" # uses default harness (antigravity) version: v1alpha From f4ad177adbb697d8ff9458f23eca03e64c280719 Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 12:30:30 -0700 Subject: [PATCH 18/26] docs: rename --config flag to --ax-config in README examples --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 51a8e668..16ced15f 100644 --- a/README.md +++ b/README.md @@ -256,7 +256,7 @@ Example: ax serve # Start server with custom config -ax serve --config my-config.yaml +ax serve --ax-config my-config.yaml ``` ## Extensions From 19be3958f3dec9e0a7175ae85e053b323217ded9 Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 12:31:28 -0700 Subject: [PATCH 19/26] docs: remove specific harness and model controller descriptions from README --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 16ced15f..00400b07 100644 --- a/README.md +++ b/README.md @@ -287,11 +287,6 @@ 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 From 0f8f36c8d5a7299099cfc8e6b9bec31be06fbde6 Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 12:32:24 -0700 Subject: [PATCH 20/26] docs: simplify quickstart section and fix formatting in README.md --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 00400b07..467ac535 100644 --- a/README.md +++ b/README.md @@ -129,9 +129,7 @@ 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. @@ -172,8 +170,7 @@ 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 --conversation edf98ef5-4bb1-4a9e-a091-3a77e03727e6 \ - --resume +ax --conversation edf98ef5-4bb1-4a9e-a091-3a77e03727e6 --resume ``` From d9a0f75ed243547c2df6c6e7fb68d888f27d92e8 Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 12:38:41 -0700 Subject: [PATCH 21/26] docs: update usage documentation for ax execute and serve commands --- README.md | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 467ac535..2e9dcf0e 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,7 @@ ax --conversation edf98ef5-4bb1-4a9e-a091-3a77e03727e6 --resume ## Usage -### Execute +Execute a new conversation or resume an existing one. If no conversation ID is provided, a new UUID will be generated. ```bash ax \ @@ -191,18 +191,16 @@ ax \ [--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) -- `--config`: Per-request harness configuration as an inline JSON string (optional, mutually exclusive with `--config-file`) -- `--config-file`: Path to a JSON file with per-request harness configuration (optional) -- `--server`: gRPC controller server address (optional. If not provided, runs with a local built-in AX server) -- `--ax-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-step`: Last step 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:** @@ -226,14 +224,14 @@ 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 @@ -253,7 +251,7 @@ Example: ax serve # Start server with custom config -ax serve --ax-config my-config.yaml +ax serve --config my-config.yaml ``` ## Extensions From 0f8d4c62ed00b6b2531fae1c14728d0a232d669c Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 12:57:49 -0700 Subject: [PATCH 22/26] refactor: remove unused history confirmation utility functions --- internal/historyutil/confirmations.go | 66 --------------------------- 1 file changed, 66 deletions(-) delete mode 100644 internal/historyutil/confirmations.go 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 -} From 4474dff3cd54303b442ec6eff4adf605e09a9273 Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 12:59:02 -0700 Subject: [PATCH 23/26] refactor: move ate package from internal/k8s to internal/ to decouple from Kubernetes dependencies --- internal/{k8s => }/ate/client.go | 0 internal/harness/substrate/substrate.go | 2 +- internal/harness/substrate/substrate_test.go | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename internal/{k8s => }/ate/client.go (100%) diff --git a/internal/k8s/ate/client.go b/internal/ate/client.go similarity index 100% rename from internal/k8s/ate/client.go rename to internal/ate/client.go diff --git a/internal/harness/substrate/substrate.go b/internal/harness/substrate/substrate.go index b1e84030..4743a602 100644 --- a/internal/harness/substrate/substrate.go +++ b/internal/harness/substrate/substrate.go @@ -34,7 +34,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" ) diff --git a/internal/harness/substrate/substrate_test.go b/internal/harness/substrate/substrate_test.go index 111e0cc3..4403dbd1 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" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" From cbd2c564376d2e487ac0623d6e54650d929e17a7 Mon Sep 17 00:00:00 2001 From: Jaana Dogan Date: Wed, 22 Jul 2026 22:30:04 -0700 Subject: [PATCH 24/26] refactor: update install script paths in documentation and manifest comments --- manifests/README.md | 6 +++--- manifests/ax-postgres.yaml | 2 +- {hack => manifests}/install-ax.sh | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename {hack => manifests}/install-ax.sh (100%) diff --git a/manifests/README.md b/manifests/README.md index 9ca169c9..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: @@ -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/hack/install-ax.sh b/manifests/install-ax.sh similarity index 100% rename from hack/install-ax.sh rename to manifests/install-ax.sh From 29f029f5c6ecf10c0ef0c510a01dc43a72c33235 Mon Sep 17 00:00:00 2001 From: PengFei Bai Date: Sun, 26 Jul 2026 09:43:21 +0800 Subject: [PATCH 25/26] deps: bump substrate 3cb7433b -> 76bac5d0, adapt name-based actor API Squashed agentfleet patch (imagecache Phase 1 + post-Phase-1 fixes through #510). Kept as a single commit on top of upstream sync merges so future upstream follows can cherry-pick it cleanly. - go.mod: substrate v0.0.0-20260706222328-3cb7433bd8a8 -> v0.0.0-20260724043042-76bac5d05142 - internal/ate/client.go, internal/harness/substrate/substrate.go, internal/harness/harnesstest/harnesstest.go: ActorRef/ActorId -> name-based addressing per upstream wire change Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_017TK34TZokEcfPq5JdbUVWo --- go.mod | 8 +++---- go.sum | 24 ++++++++++----------- internal/ate/client.go | 18 ++++++++++------ internal/harness/harnesstest/harnesstest.go | 16 +++++++------- internal/harness/substrate/substrate.go | 4 ++-- 5 files changed, 37 insertions(+), 33 deletions(-) diff --git a/go.mod b/go.mod index 73c022fa..f7167720 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-20260724043042-76bac5d05142 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..b9140b62 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-20260724043042-76bac5d05142 h1:V8zSqH4uc1eWhmxh8j65tJ0PPIE0FkzMQzK6kFoYOUQ= +github.com/agent-substrate/substrate v0.0.0-20260724043042-76bac5d05142/go.mod h1:F8PNLu8RH9XvziamlvdoJsXz5dK94giNJuPBQI2QjFQ= 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/ate/client.go b/internal/ate/client.go index 81a5c061..d9697480 100644 --- a/internal/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/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 85f660f9..82f83497 100644 --- a/internal/harness/substrate/substrate.go +++ b/internal/harness/substrate/substrate.go @@ -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) From 1234e3149bb1acb8e06a051f85b2bc450d8625cc Mon Sep 17 00:00:00 2001 From: PengFei Bai Date: Sun, 26 Jul 2026 09:53:59 +0800 Subject: [PATCH 26/26] deps: advance substrate pin to aa1d14a7 (upstream main tip) Absorbs the 07-24 batch: #237 inter-component mTLS strict verify, #405 per-actor external volumes, #496 ateom de-privileged, #491 router health concurrency, #516 kubectl ate top workers, #523 ateapi graceful shutdown, #532 volume validation, and the three ateom-microvm durable-dir commits. No client-API changes; zero code adaptation. Cert-rotation risk from #237 (upstream #531 still open) stays masked by the 12h ate-cert-restart CronJob. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_017TK34TZokEcfPq5JdbUVWo --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f7167720..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-20260724043042-76bac5d05142 + 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 diff --git a/go.sum b/go.sum index b9140b62..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-20260724043042-76bac5d05142 h1:V8zSqH4uc1eWhmxh8j65tJ0PPIE0FkzMQzK6kFoYOUQ= -github.com/agent-substrate/substrate v0.0.0-20260724043042-76bac5d05142/go.mod h1:F8PNLu8RH9XvziamlvdoJsXz5dK94giNJuPBQI2QjFQ= +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=