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.jsonUse 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:
| Method | Purpose |
|---|---|
CreateSession | Create a new provider-owned session |
GetSession | Inspect one session by session_id |
ListSessions | Return sessions currently known to this provider |
UpdateSession | Update session state, metadata, or client_ref |
CreateTurn | Persist and start a new turn within a session |
GetTurn | Inspect one turn by turn_id |
ListTurns | List turns for a session |
CancelTurn | Request cancellation for one turn |
ListTurnEvents | Return stored events for a turn after a sequence cursor |
GetInteraction | Inspect one interaction |
ListInteractions | List interactions for a turn |
ResolveInteraction | Apply a caller-provided resolution to a pending interaction |
GetCapabilities | Advertise which optional agent features the provider supports |
Providers can call back into Gestalt through the host service:
| Host method | Purpose |
|---|---|
ExecuteTool | Ask 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:
AgentSessionAgentTurnAgentInteractionAgentTurnEvent
Key fields:
- sessions carry
id,provider_name,model,client_ref,state, and timestamps - turns carry
id,session_id,status,messages,output_text, optionalstructured_output, and timestamps - interactions carry
id,turn_id,session_id,type,state,request, andresolution - turn events carry
turn_id, monotonically increasingseq, canonicaltype,source,visibility, structureddata, and optionaldisplay
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
Go
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.
What to read next
- Providers > Agent: deployment-side configuration
- HTTP API: caller-facing session and turn routes
- Reference > Config File:
providers.agentYAML