Skip to Content

Agent

This page covers building custom kind: agent providers. If you only need to configure an agent backend for a deployment, use Providers > Agent.

Agent providers are canonical backends for agent state. A provider owns its sessions, turns, interactions, capabilities, and turn events. Gestalt owns the public HTTP/CLI/SDK facade and the host tool-callback surface.

Manifest

An agent provider manifest declares kind: agent:

kind: agent source: github.com/your-org/agent/example version: 0.0.1 displayName: Example Agent description: Minimal session-and-turn agent provider spec: configSchemaPath: ./agent_config.json

Use source: ./manifest.yaml during development. For production, publish a provider release archive and reference the resulting metadata URL.

Provider interface

The agent provider service now exposes the canonical lifecycle directly:

MethodPurpose
CreateSessionCreate a new provider-owned session
GetSessionInspect one session by session_id
ListSessionsReturn sessions currently known to this provider
UpdateSessionUpdate session state, metadata, or client_ref
CreateTurnPersist and start a new turn within a session
GetTurnInspect one turn by turn_id
ListTurnsList turns for a session
CancelTurnRequest cancellation for one turn
ListTurnEventsReturn stored events for a turn after a sequence cursor
GetInteractionInspect one interaction
ListInteractionsList interactions for a turn
ResolveInteractionApply a caller-provided resolution to a pending interaction
GetCapabilitiesAdvertise which optional agent features the provider supports

Providers can call back into Gestalt through the host service:

Host methodPurpose
ExecuteToolAsk Gestalt to execute a bound plugin operation during a turn

These callbacks work the same way when the provider runs directly on the gestaltd host or inside a hosted runtime.

Observability

Provider authors do not need to emit the baseline gestaltd agent metrics. gestaltd wraps the configured provider interface and records gestaltd.agent.provider.operation.* for every provider method. The public agent facade records gestaltd.agent.operation.*, and tool resolution records gestaltd.agent.tool.resolve.*.

When OTLP tracing is enabled, the context.Context passed into provider methods carries the active trace. Preserve that context when calling back into AgentHost.ExecuteTool or starting provider-owned work that should remain part of the same trace tree.

Canonical objects

Provider methods exchange canonical objects rather than vendor-native payloads:

  • AgentSession
  • AgentTurn
  • AgentInteraction
  • AgentTurnEvent

Key fields:

  • sessions carry id, provider_name, model, client_ref, state, and timestamps
  • turns carry id, session_id, status, messages, output_text, optional structured_output, and timestamps
  • interactions carry id, turn_id, session_id, type, state, request, and resolution
  • turn events carry turn_id, monotonically increasing seq, canonical type, source, visibility, structured data, and optional display

Providers should return canonical IDs exactly as persisted. Gestalt does not rewrite session or turn IDs on the provider’s behalf.

Turn event display

AgentTurnEvent.display is an optional, provider-neutral projection for CLIs and other clients. It does not replace the raw type and data fields; clients can fall back to raw events when display is absent or malformed.

The initial display.kind values are text, reasoning, tool, interaction, status, and error. The initial display.phase values are delta, completed, started, progress, failed, requested, resolved, and canceled. display.ref is a stable correlation ID scoped to the turn, most commonly a tool call ID.

Gestalt synthesizes display for a small set of known raw event types when a provider omits it, but never overwrites provider-supplied display. Tool input and output are synthesized from the tool payload keys Gestalt clients already render (arguments, input, request, output, result, and body) plus explicit preview aliases. display is not a privacy boundary: clients must still honor the event’s visibility. Gestalt may synthesize display for known private events to preserve existing CLI behavior, but unknown private events are not normalized. AgentHost.ExecuteTool does not emit turn lifecycle events by itself; providers should still persist tool events or set display on their own events.

Go example

package main import ( "context" "log" gestalt "github.com/valon-technologies/gestalt/sdk/go" proto "github.com/valon-technologies/gestalt/sdk/go/gen/v1" "google.golang.org/protobuf/types/known/timestamppb" ) type provider struct { proto.UnimplementedAgentProviderServer } func actorFromSubject(subject *proto.AgentSubjectContext) *proto.AgentActor { if subject == nil { return nil } return &proto.AgentActor{ SubjectId: subject.GetSubjectId(), SubjectKind: subject.GetSubjectKind(), DisplayName: subject.GetDisplayName(), AuthSource: subject.GetAuthSource(), } } func (p *provider) CreateSession(ctx context.Context, req *proto.CreateAgentProviderSessionRequest) (*proto.AgentSession, error) { return &proto.AgentSession{ Id: req.GetSessionId(), ProviderName: "example", Model: req.GetModel(), ClientRef: req.GetClientRef(), State: proto.AgentSessionState_AGENT_SESSION_STATE_ACTIVE, CreatedBy: req.GetCreatedBy(), CreatedAt: timestamppb.Now(), UpdatedAt: timestamppb.Now(), }, nil } func (p *provider) GetSession(_ context.Context, req *proto.GetAgentProviderSessionRequest) (*proto.AgentSession, error) { return &proto.AgentSession{ Id: req.GetSessionId(), ProviderName: "example", State: proto.AgentSessionState_AGENT_SESSION_STATE_ACTIVE, CreatedBy: actorFromSubject(req.GetSubject()), }, nil } func (p *provider) ListSessions(context.Context, *proto.ListAgentProviderSessionsRequest) (*proto.ListAgentProviderSessionsResponse, error) { return &proto.ListAgentProviderSessionsResponse{}, nil } func (p *provider) UpdateSession(_ context.Context, req *proto.UpdateAgentProviderSessionRequest) (*proto.AgentSession, error) { return &proto.AgentSession{ Id: req.GetSessionId(), ProviderName: "example", ClientRef: req.GetClientRef(), State: req.GetState(), CreatedBy: actorFromSubject(req.GetSubject()), UpdatedAt: timestamppb.Now(), }, nil } func (p *provider) CreateTurn(ctx context.Context, req *proto.CreateAgentProviderTurnRequest) (*proto.AgentTurn, error) { return &proto.AgentTurn{ Id: req.GetTurnId(), SessionId: req.GetSessionId(), ProviderName: "example", Model: req.GetModel(), Status: proto.AgentExecutionStatus_AGENT_EXECUTION_STATUS_RUNNING, Messages: req.GetMessages(), CreatedBy: req.GetCreatedBy(), CreatedAt: timestamppb.Now(), StartedAt: timestamppb.Now(), ExecutionRef: req.GetExecutionRef(), }, nil } func (p *provider) GetTurn(_ context.Context, req *proto.GetAgentProviderTurnRequest) (*proto.AgentTurn, error) { return &proto.AgentTurn{ Id: req.GetTurnId(), SessionId: "session-1", ProviderName: "example", Status: proto.AgentExecutionStatus_AGENT_EXECUTION_STATUS_SUCCEEDED, OutputText: "done", CreatedBy: actorFromSubject(req.GetSubject()), CompletedAt: timestamppb.Now(), }, nil } func (p *provider) ListTurns(context.Context, *proto.ListAgentProviderTurnsRequest) (*proto.ListAgentProviderTurnsResponse, error) { return &proto.ListAgentProviderTurnsResponse{}, nil } func (p *provider) CancelTurn(_ context.Context, req *proto.CancelAgentProviderTurnRequest) (*proto.AgentTurn, error) { return &proto.AgentTurn{ Id: req.GetTurnId(), SessionId: "session-1", ProviderName: "example", Status: proto.AgentExecutionStatus_AGENT_EXECUTION_STATUS_CANCELED, StatusMessage: "operator requested", CreatedBy: actorFromSubject(req.GetSubject()), CompletedAt: timestamppb.Now(), }, nil } func (p *provider) ListTurnEvents(context.Context, *proto.ListAgentProviderTurnEventsRequest) (*proto.ListAgentProviderTurnEventsResponse, error) { return &proto.ListAgentProviderTurnEventsResponse{}, nil } func (p *provider) GetInteraction(_ context.Context, req *proto.GetAgentProviderInteractionRequest) (*proto.AgentInteraction, error) { return &proto.AgentInteraction{Id: req.GetInteractionId()}, nil } func (p *provider) ListInteractions(context.Context, *proto.ListAgentProviderInteractionsRequest) (*proto.ListAgentProviderInteractionsResponse, error) { return &proto.ListAgentProviderInteractionsResponse{}, nil } func (p *provider) ResolveInteraction(_ context.Context, req *proto.ResolveAgentProviderInteractionRequest) (*proto.AgentInteraction, error) { return &proto.AgentInteraction{ Id: req.GetInteractionId(), State: proto.AgentInteractionState_AGENT_INTERACTION_STATE_RESOLVED, Resolution: req.GetResolution(), ResolvedAt: timestamppb.Now(), }, nil } func (p *provider) GetCapabilities(context.Context, *proto.GetAgentProviderCapabilitiesRequest) (*proto.AgentProviderCapabilities, error) { return &proto.AgentProviderCapabilities{ StreamingText: true, ToolCalls: true, StructuredOutput: true, Interactions: true, ResumableTurns: true, }, nil } func main() { if err := gestalt.ServeAgentProvider(context.Background(), &provider{}); err != nil { log.Fatal(err) } }

Execution model

CreateTurn should persist the turn and return quickly, usually in RUNNING or PENDING. Long-running model/tool work should continue in the background. Callers then use GetTurn, ListTurnEvents, and ListInteractions to observe progress or resolve pending human input.

If your provider needs durable state, configure providers.agent.<name>.indexeddb and store your canonical records there. Gestalt does not persist session or turn content for you.