diff --git a/cmd/ax/harness.go b/cmd/ax/harness.go index e5660bd6..8f2913ef 100644 --- a/cmd/ax/harness.go +++ b/cmd/ax/harness.go @@ -160,13 +160,11 @@ func runAntigravityInteractionsHarness(ctx context.Context) error { if agent == "" { agent = antigravityinteractions.DefaultAgent } - stateDir := hc.StateDir - if stateDir == "" { - var err error - stateDir, err = antigravityinteractions.DefaultStateDir() - if err != nil { - return err - } + // AX owns the resume-cursor path (a harness implementation detail), so it's + // derived internally rather than read from ax.yaml. + stateDir, err := antigravityinteractions.DefaultStateDir() + if err != nil { + return err } cfg := antigravityinteractions.AntigravityInteractionsConfig{ diff --git a/cmd/ax/internal/cliutil/cliutil.go b/cmd/ax/internal/cliutil/cliutil.go index 3d2e9d1e..f8d0ebb1 100644 --- a/cmd/ax/internal/cliutil/cliutil.go +++ b/cmd/ax/internal/cliutil/cliutil.go @@ -71,9 +71,14 @@ func NewControllerFromConfig(ctx context.Context, cfg *Config) (*controller.Cont if address == "" { address = "127.0.0.1:50053" } - // Local mode: the harness owns the Python sidecar. Empty StateDir - // means the sidecar applies its own default - antigravityHarness, err = antigravity.New(ctx, address, cfg.Harnesses.Antigravity.StateDir, true) + // Local mode: the harness owns the Python sidecar. AX owns the + // trajectory storage path (a harness implementation detail), so it's + // derived internally rather than exposed in ax.yaml. + stateDir, sErr := antigravity.DefaultStateDir() + if sErr != nil { + return nil, fmt.Errorf("antigravity harness: %w", sErr) + } + antigravityHarness, err = antigravity.New(ctx, address, stateDir, true) if err != nil { return nil, fmt.Errorf("antigravity harness: %w", err) } @@ -97,12 +102,11 @@ func NewControllerFromConfig(ctx context.Context, cfg *Config) (*controller.Cont if agent == "" { agent = antigravityinteractions.DefaultAgent } - stateDir := cfg.Harnesses.AntigravityInteractions.StateDir - if stateDir == "" { - stateDir, err = antigravityinteractions.DefaultStateDir() - if err != nil { - return nil, fmt.Errorf("antigravity-interactions harness: %w", err) - } + // AX owns the resume-cursor path (a harness implementation detail), so + // it's derived internally rather than exposed in ax.yaml. + stateDir, sErr := antigravityinteractions.DefaultStateDir() + if sErr != nil { + return nil, fmt.Errorf("antigravity-interactions harness: %w", sErr) } antigravityInteractionsHarness, err = antigravityinteractions.New(antigravityinteractions.AntigravityInteractionsConfig{ Agent: agent, diff --git a/internal/config/config.go b/internal/config/config.go index d4bb10f4..21ec968b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -97,16 +97,14 @@ type HarnessesConfig struct { // AntigravityHarnessConfig registers the built-in Antigravity harness. type AntigravityHarnessConfig struct { Default bool `yaml:"default,omitempty"` - Endpoint string `yaml:"endpoint,omitempty"` // HarnessService address - StateDir string `yaml:"state_dir,omitempty"` // Trajectory storage dir (optional; sidecar defaults to ~/.ax/antigravity/conversations) + Endpoint string `yaml:"endpoint,omitempty"` // HarnessService address } // AntigravityInteractionsHarnessConfig registers the built-in Antigravity // Interactions harness (over the Vertex GenAI Interactions API). type AntigravityInteractionsHarnessConfig struct { - Default bool `yaml:"default,omitempty"` // Default harness or not - Agent string `yaml:"agent,omitempty"` // Interactions API agent (default: antigravityinteractions.DefaultAgent) - StateDir string `yaml:"state_dir,omitempty"` // Resume-cursor directory (optional; defaults to ~/.ax/antigravityinteractions/cursors) + Default bool `yaml:"default,omitempty"` // Default harness or not + Agent string `yaml:"agent,omitempty"` // Interactions API agent (default: antigravityinteractions.DefaultAgent) } // SubstrateHarnessConfig registers a custom harness deployed on substrate diff --git a/internal/config/config_test.go b/internal/config/config_test.go index e137d049..149b7a64 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -115,8 +115,7 @@ func TestValidate_InteractionsIDReserved(t *testing.T) { func TestValidate_InteractionsValid(t *testing.T) { c := validConfig() c.Harnesses.AntigravityInteractions = AntigravityInteractionsHarnessConfig{ - Agent: "projects/p/locations/global/agents/a", - StateDir: "interactions-state", + Agent: "projects/p/locations/global/agents/a", } if err := c.Validate(); err != nil { t.Fatalf("Validate() = %v, want nil", err) @@ -141,22 +140,6 @@ eventlog: } } -// TestLoadFromFile_AntigravityStateDir: yaml -> AntigravityHarnessConfig.StateDir. -func TestLoadFromFile_AntigravityStateDir(t *testing.T) { - data := ` -harnesses: - antigravity: - state_dir: /custom/path -` - var cfg Config - if err := yaml.Unmarshal([]byte(data), &cfg); err != nil { - t.Fatalf("Unmarshal failed: %v", err) - } - if got, want := cfg.Harnesses.Antigravity.StateDir, "/custom/path"; got != want { - t.Errorf("StateDir = %q, want %q", got, want) - } -} - func TestLoadFromBytes(t *testing.T) { cfg, err := LoadFromBytes([]byte(` version: v1alpha diff --git a/internal/harness/antigravity/antigravity.go b/internal/harness/antigravity/antigravity.go index 3b1e8b2a..01839376 100644 --- a/internal/harness/antigravity/antigravity.go +++ b/internal/harness/antigravity/antigravity.go @@ -21,6 +21,7 @@ import ( "net" "os" "os/signal" + "path/filepath" "sync" "syscall" @@ -29,6 +30,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "github.com/google/ax/internal/config" "github.com/google/ax/internal/harness" "github.com/google/ax/internal/pythonsidecar" "github.com/google/ax/proto" @@ -40,6 +42,20 @@ import ( var _ harness.Harness = (*AntigravityHarness)(nil) var _ harness.Execution = (*antigravityExecution)(nil) +// DefaultStateDir returns the default trajectory storage directory, +// ~/.ax/antigravity/conversations. AX owns this path so it stays a harness +// implementation detail rather than a user-facing ax.yaml knob. It lives +// outside the agent's working directory on purpose: the working directory is +// the agent's operating surface (it reads and edits files there), so AX's +// internal state is kept separate to avoid the agent seeing or clobbering it. +func DefaultStateDir() (string, error) { + axDir, err := config.AXAssetsDir() + if err != nil { + return "", err + } + return filepath.Join(axDir, "antigravity", "conversations"), nil +} + // AntigravityHarness implements the Harness interface by connecting to the // Antigravity Python agent server over gRPC. type AntigravityHarness struct { diff --git a/internal/harness/antigravity/antigravity_test.go b/internal/harness/antigravity/antigravity_test.go index a96b47f8..c01ade27 100644 --- a/internal/harness/antigravity/antigravity_test.go +++ b/internal/harness/antigravity/antigravity_test.go @@ -17,6 +17,7 @@ package antigravity import ( "bytes" "context" + "path/filepath" "strings" "testing" @@ -106,3 +107,18 @@ func TestNew_AutoStartFalse_NilSidecar(t *testing.T) { t.Errorf("expected sidecar to be nil, got %v", h.sidecar) } } + +// TestDefaultStateDir returns ~/.ax/antigravity/conversations under the user's +// home directory. +func TestDefaultStateDir(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + + got, err := DefaultStateDir() + if err != nil { + t.Fatalf("DefaultStateDir: %v", err) + } + if want := filepath.Join(home, ".ax", "antigravity", "conversations"); got != want { + t.Errorf("DefaultStateDir() = %q, want %q", got, want) + } +}