feat: add imperative AgentInstance lifecycle - #2436
Conversation
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Adds an imperative AgentInstance lifecycle to the v2 control plane, including persistence, gRPC surface area, and integration into the app/controller runtime so create/delete execute via synchronous Substrate Actor workflows.
Changes:
- Introduces
agentinstanceservice layer + gRPC server wiring and method policy entries. - Adds PostgreSQL schema + sqlc queries + postgres client implementation for AgentInstance state, shares, and revision retention.
- Extends v2 controller reconciler to support manager lifecycle (
Start) and revision cleanup that accounts for AgentInstance references.
Reviewed changes
Copilot reviewed 18 out of 22 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| go/core/v2/controller/reconciler.go | Exposes reconciler as a manager runnable and exports revision cleanup method used by workflows. |
| go/core/v2/agentinstance/workflow.go | Implements synchronous Actor create/delete workflow used by AgentInstance RPCs. |
| go/core/v2/agentinstance/workflow_test.go | Adds workflow coverage for expected Actor lifecycle side effects. |
| go/core/v2/agentinstance/service.go | Adds AgentInstance API (create/get/list/delete + shares) with authz, validation, and paging. |
| go/core/v2/agentinstance/service_test.go | Adds service-level tests for auth fencing, idempotency error mapping, paging, and share token hashing. |
| go/core/v2/agentinstance/grpc.go | Adds gRPC handlers and proto/db conversions for AgentInstance + shares. |
| go/core/pkg/migrations/core/000009_agent_instances.up.sql | Adds agent_instance / agent_instance_share tables and labels column on pairs table. |
| go/core/pkg/migrations/core/000009_agent_instances.down.sql | Drops the above tables/column. |
| go/core/pkg/app/app.go | Wires v2 runtime + reconciler + AgentInstance service into the application when Substrate is enabled. |
| go/core/internal/grpcserver/server.go | Registers the new AgentInstance gRPC service when configured. |
| go/core/internal/grpcserver/policy.go | Adds default access policies for AgentInstance RPCs. |
| go/core/internal/database/queries/runtime_revisions.sql | Tracks AgentInstance references when deciding which runtime revisions are unreferenced. |
| go/core/internal/database/queries/agent_instances.sql | Adds sqlc queries for AgentInstance lifecycle and shares. |
| go/core/internal/database/client_test.go | Updates DB test truncation list to include new tables. |
| go/core/internal/database/client_postgres.go | Implements AgentInstance + share CRUD, idempotency behavior, and runtime revision lookup. |
| go/core/internal/database/client_agent_instance_test.go | Adds DB-level idempotency test for CreateAgentInstance. |
| go/api/database/models.go | Extends database models with AgentTemplate labels and AgentInstanceShare model. |
| go/api/database/client.go | Extends DB client interface with AgentInstance lifecycle and share methods. |
Files not reviewed (4)
- go/core/internal/database/gen/agent_instances.sql.go: Generated file
- go/core/internal/database/gen/models.go: Generated file
- go/core/internal/database/gen/querier.go: Generated file
- go/core/internal/database/gen/runtime_revisions.sql.go: Generated file
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| labels, err := json.Marshal(pair.AgentTemplateLabels) | ||
| if err != nil { | ||
| return fmt.Errorf("marshal AgentTemplate labels: %w", err) | ||
| } | ||
| return c.q.UpsertAgentTemplateHarnessPair(ctx, dbgen.UpsertAgentTemplateHarnessPairParams{ | ||
| Namespace: pair.Namespace, AgentTemplateName: pair.AgentTemplateName, | ||
| AgentTemplateUid: pair.AgentTemplateUID, HarnessName: pair.HarnessName, | ||
| HarnessUid: pair.HarnessUID, DesiredRevision: pair.DesiredRevision, | ||
| AgentTemplateLabels: labels, | ||
| }) |
There was a problem hiding this comment.
Fixed in 83ebba5. Nil stored labels and nil selectors are both normalized to {}; the database test covers the empty-label/empty-selector path.
| if permission != "READ_ONLY" && permission != "READ_WRITE" { | ||
| return nil, "", serviceerrors.NewInvalidArgument("share permission is required", nil) | ||
| } |
There was a problem hiding this comment.
Fixed in 83ebba5. The validation error now says the permission must be READ_ONLY or READ_WRITE.
| shares, err := s.store.ListAgentInstanceShares(ctx, namespace, instanceID, creator) | ||
| if err != nil { | ||
| return ShareListResult{}, serviceerrors.NewInternal("Failed to list AgentInstance shares", err) | ||
| } | ||
| start := 0 | ||
| for start < len(shares) && shares[start].ID <= afterID { | ||
| start++ | ||
| } | ||
| shares = shares[start:] | ||
| result := ShareListResult{Shares: shares} | ||
| if len(result.Shares) > pageSize { | ||
| result.NextPageToken = encodePageToken(result.Shares[pageSize-1].ID) | ||
| result.Shares = result.Shares[:pageSize] | ||
| } | ||
| return result, nil |
There was a problem hiding this comment.
Fixed in 83ebba5. Share pagination now applies after_id and limit in PostgreSQL using the existing index.
| for name, value := range map[string]string{"harness": harness, "agent_template": template} { | ||
| if problems := utilvalidation.IsDNS1123Subdomain(value); len(problems) > 0 { | ||
| return serviceerrors.NewInvalidArgument(fmt.Sprintf("%s is invalid: %s", name, strings.Join(problems, "; ")), nil) | ||
| } | ||
| } |
There was a problem hiding this comment.
Fixed in 83ebba5. Harness and agent_template validation now runs in a fixed order.
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
83ebba5 to
eda1dd5
Compare
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
Summary
Testing