Developer Integration
This page is about embedding Seshat as a runtime inside a real product surface. The Go SDK is not only a “send prompt, get text” wrapper: it is the contract for auth bridging, session durability, product metadata, streaming UX, policy enforcement, artifacts, monitoring, and service boundaries.
What this page is really for
`Providers & Auth`, `Agent`, `Prompt`, and `Tools & Surface` document the individual SDK surfaces. `Developer Integration` is where they come together. The question here is not “what does this field do?” but “how should a real host application compose the runtime cleanly?”
The best host integrations keep Seshat responsible for runtime execution while the product owns identity, policy, persistence, UI, and infrastructure. That split is what keeps the integration maintainable as the engine grows.
Integration Map
The same client can power a local operator app, an internal platform service, or a product feature API. The difference is not a different runtime. The difference is which seams you use around the same runtime core.
The runtime lives inside the local product process and can expose streaming, approvals, session titles, and project-aware workspaces directly to the UI.
The host injects credentials, persistence, governance, and storage while the runtime stays the execution core behind one or many product APIs.
Auth, session persistence, callbacks, prompt shaping, tool surface, memory, monitoring, MCP, multimodal providers, and browser/artifact infrastructure all converge on one client contract.
The host can create, resume, annotate, and reshape sessions without reimplementing the engine loop.
The host decides whether the runtime is trusted, interactive, locked down, or policy-driven, per client and per session.
Persistence, object storage, remote browsers, metrics, and gRPC all sit around the same runtime instead of splitting into separate agent stacks.
The Go SDK is the richest embedding surface. gRPC is the right next step when another language or service boundary must own the outer API.
Most product integrations should reshape sessions, prompts, credentials, tools, and hooks before inventing a parallel host-side orchestration layer.
Public integration seams
These are the most important SDK-owned boundaries for product hosts. They are the places where your application should connect, rather than patching internals.
| Seam | Type / Surface | Behavior | Notes |
|---|---|---|---|
| CredentialResolver + OAuthDeviceFlow | Auth seam | Inject per-user or persisted provider credentials at client creation time. | This is the clean bridge for desktop login flows, multi-user backends, Codex/OpenAI OAuth, and vault-backed provider routing. |
| SessionStore / SessionBackend / SessionSQLitePath | State seam | Persist and resume runtime sessions. | Use filesystem, SQLite, or a custom backend adapter depending on whether the host is local, server-side, or shared across workers. |
| ResponseChunkFn / RuntimeEventFn / ProgressFn | Live UX seam | Observe text deltas, structured runtime events, and tool progress. | These callbacks are the main bridge for live UIs, logs, operator consoles, and server-side streaming. |
| SetAppendSystemPrompt / PromptConfig / CreateSessionWithAdditional | Product context seam | Inject product-specific context, metadata, and prompt shaping without forking the runtime. | Use this for tenant, channel, workspace, persona, product mode, and task-context enrichment. |
| PermissionMode + tool surface shaping | Governance seam | Decide whether the host is interactive, trusted, or locked down. | Combine client-level defaults with per-session permission changes and tool unregistration for safer product profiles. |
| PreToolHooks / AddToolHook / RegisterHook | Policy seam | Attach host-side policies before and around execution. | Use shell guards for practical policy enforcement and lifecycle hooks for runtime telemetry or side effects. |
| StorageConfig / ArtifactStore / BrowserRemoteControlURL | Infra seam | Control where web, browser, screenshot, and other artifacts live. | Important for enterprise persistence, browser tooling, remote browser control, and artifact retention management. |
| GetMonitoring / MonitoringSystem | Observability seam | Export runtime metrics snapshots. | Useful for dashboards, health surfaces, operator insight, and internal telemetry pipelines. |
Scenario 1: desktop app or operator console
A local app usually wants persisted login, live streaming, session titles, approvals, and project-aware working directories. That makes the in-process Go SDK the cleanest path.
flow, err := sdk.NewOAuthDeviceFlow(sdk.OAuthDeviceFlowConfig{
Provider: string(sdk.APIProviderCodex),
Persist: true,
})
if err != nil {
log.Fatal(err)
}
challenge, err := flow.Start(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Open %s and enter code %s\n", challenge.VerificationURL, challenge.UserCode)
_, err = flow.Wait(ctx, challenge)
if err != nil {
log.Fatal(err)
}
client, err := sdk.NewClient(&sdk.ClientConfig{
Model: sdk.ModelIdentifier{
Provider: sdk.APIProviderCodex,
Model: "gpt-5.3-codex",
},
CredentialResolver: flow.CredentialResolver(),
PersistSessions: true,
SessionSQLitePath: filepath.Join(appDataDir, "seshat-runtime.sqlite"),
WorkingDir: projectRoot,
PermissionMode: sdk.PermissionModeOnRequest,
EnableMemory: true,
OnSessionTitled: func(id sdk.SessionID, title string) {
ui.UpdateSessionTitle(string(id), title)
},
ResponseChunkFn: func(chunk sdk.ResponseChunk) {
if chunk.Type == sdk.ResponseChunkTypeContentBlockDelta {
ui.AppendDelta(chunk.Delta)
}
},
RuntimeEventFn: func(event sdk.RuntimeEvent) {
ui.HandleRuntimeEvent(event)
},
})
if err != nil {
log.Fatal(err)
}Codex and ChatGPT-backed flows can be initiated directly by the host app through sdk.NewOAuthDeviceFlow, then bridged back into the runtime with the returned credential resolver.
Desktop and operator apps typically need immediate control over deltas, runtime events, permissions, and project files. gRPC adds an unnecessary extra boundary in that case.
Scenario 2: multi-user backend or internal platform
Server-side hosts should think in terms of tenant credentials, controlled working directories, durable session storage, and explicit policy profiles. This is where CredentialResolver, session metadata, and session-specific tool trimming matter most.
type TenantResolver struct {
Store CredentialStore
UserID string
}
func (r TenantResolver) ResolveAPIKey(ctx context.Context, provider string) (string, error) {
return r.Store.LookupProviderKey(ctx, r.UserID, provider)
}
client, err := sdk.NewClient(&sdk.ClientConfig{
Model: sdk.ModelIdentifier{
Provider: sdk.APIProviderAnthropic,
Model: "claude-sonnet-4-20250514",
},
CredentialResolver: TenantResolver{
Store: credentialStore,
UserID: authUser.ID,
},
SessionSQLitePath: filepath.Join(runtimeRoot, "runtime.sqlite"),
WorkingDir: filepath.Join(runtimeRoot, "workspaces", authUser.ID),
PermissionMode: sdk.PermissionModeNever,
PersistSessions: true,
EnableMemory: true,
MCPServers: mcpServers,
})
if err != nil {
return err
}
defer client.Close()
session, err := client.CreateSessionWithAdditional(ctx, map[string]any{
"tenant_id": authUser.TenantID,
"user_id": authUser.ID,
"source": "billing-api",
})
if err != nil {
return err
}
defer session.Close()
session.SetAppendSystemPrompt("You are the billing operations assistant for this workspace.")
session.SetWorkingDirectory(filepath.Join(runtimeRoot, "workspaces", authUser.ID, string(session.GetID())))
for _, blocked := range []string{"bash", "write_file", "edit_file", "apply_patch"} {
_ = session.UnregisterTool(blocked)
}
resp, err := session.SubmitMessage(ctx, request.Prompt)
if err != nil {
return err
}
_ = respIf your backend does not have a real human approval loop, do not pretend it does. Use PermissionModeNever or a tightly-governed trusted mode, then trim the tool surface explicitly for that product route.
Scenario 3: live product streaming and resume
For chat surfaces, dashboards, and operator consoles, the host usually needs more than final text. It needs text deltas, tool progress, runtime events, and session resume across requests.
session, err := client.LoadSessionWithAdditional(ctx, sessionID, map[string]any{
"request_id": requestID,
"surface": "dashboard-live",
})
if err != nil {
return err
}
defer session.Close()
session.SetResponseChunkFn(func(chunk sdk.ResponseChunk) {
if chunk.Type == sdk.ResponseChunkTypeContentBlockDelta {
broadcaster.PublishDelta(session.GetID(), chunk.Delta)
}
})
session.SetRuntimeEventFn(func(event sdk.RuntimeEvent) {
broadcaster.PublishEvent(session.GetID(), event)
})
session.SetProgressFn(func(progress sdk.ToolProgress) {
broadcaster.PublishToolProgress(session.GetID(), progress)
})
resp, err := session.SubmitMessage(ctx, prompt)
if err != nil {
return err
}
broadcaster.PublishFinal(session.GetID(), resp.StopReason, resp.TotalTokens)Client-level callbacks become the defaults for newly-created sessions, but sessions can also override them individually. That is a strong pattern for multi-tab, multi-channel, or per-request streaming surfaces.
Scenario 4: governance, lifecycle hooks, and host policy
The host should treat policy as a first-class integration concern. Practical policy starts with permission modes and tool shaping, then moves into shell pre-tool hooks and lifecycle hooks when the product needs stronger controls or observability.
client, err := sdk.NewClient(&sdk.ClientConfig{
Model: sdk.ModelIdentifier{
Provider: sdk.APIProviderAnthropic,
Model: "claude-sonnet-4-20250514",
},
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
PermissionMode: sdk.PermissionModeOnRequest,
PreToolHooks: []sdk.PreToolHookConfig{
{
Matcher: "bash|write_file|edit_file|apply_patch",
Command: "./scripts/pre_tool_guard.sh",
Timeout: 5,
},
},
})
if err != nil {
log.Fatal(err)
}
client.RegisterHook(sdk.HookEventTurnEnd, func(ctx context.Context, progress sdk.HookProgress) (*sdk.HookResult, error) {
log.Printf("turn_end: %s", progress.Message)
return nil, nil
})Shell-backed pre-tool hooks are the pragmatic way to block, rewrite, or halt risky tool calls before they execute.
- Block or rewrite tool input
- Implement external policy checks
- Integrate shell-era operational guardrails
Lifecycle hooks are useful for turn-end side effects, event-driven metrics, setup notifications, and non-tool-specific host instrumentation.
- Observe turn boundaries
- Attach host-side side effects
- Keep the runtime loop intact
Scenario 5: storage, artifacts, browser, and metrics
Once your product uses web, browser, screenshot, download, notebook, or multimodal flows, artifact storage stops being optional infrastructure. The SDK already exposes this as an integration seam instead of forcing you into a separate sidecar design.
client, err := sdk.NewClient(&sdk.ClientConfig{
Model: sdk.ModelIdentifier{
Provider: sdk.APIProviderOpenAI,
Model: "gpt-5.5",
},
APIKey: os.Getenv("OPENAI_API_KEY"),
StorageConfig: &sdk.StorageConfig{
Provider: sdk.StorageProviderS3,
S3Endpoint: os.Getenv("S3_ENDPOINT"),
S3Bucket: os.Getenv("S3_BUCKET"),
S3AccessKeyID: os.Getenv("S3_ACCESS_KEY_ID"),
S3SecretAccessKey: os.Getenv("S3_SECRET_ACCESS_KEY"),
S3Region: os.Getenv("S3_REGION"),
S3KeyPrefix: "seshat/artifacts",
},
StorageGCEnabled: true,
StorageGCInterval: time.Hour,
StorageGCLimit: 256,
StorageGCNamespaces: []string{
string(sdk.NamespaceWebArtifacts),
string(sdk.NamespaceBrowserDownloads),
},
BrowserRemoteControlURL: os.Getenv("SESHAT_BROWSER_REMOTE_CONTROL_URL"),
})
if err != nil {
log.Fatal(err)
}
artifactStore := client.GetArtifactStore()
metrics, _ := client.GetMonitoring().GetMetricsSnapshot("json")
_, _ = artifactStore, metricsThe host can either inject a full ArtifactStore or provide a structured StorageConfig. The browser manager and reaper are then wired automatically behind the client.
When to use gRPC instead
The Go SDK is the richest integration surface. But it is not always the best deployment boundary. If the outer product is in another language, if the runtime should be isolated as a managed service, or if multiple systems should share one runtime process, gRPC is the better fit.
| Seam | Type / Surface | Behavior | Notes |
|---|---|---|---|
| Go SDK in-process | — | Best when your host is already in Go and needs full runtime control. | Choose this for desktop apps, Go backends, internal tools, automation workers, and product features that need session, prompt, tool, and permission control together. |
| gRPC server boundary | — | Best when the runtime should live in a separate process. | Choose this for non-Go stacks, service isolation, language-agnostic clients, or when the runtime should be managed independently of the product API layer. |
| automation package | — | Best when the product feature is really a background job. | Choose this for stateless recurring workflows. Do not force conversational product flows into automation if they need durable sessions or interactive governance. |
| mailboxes / agent teams | — | Best when work must move between named agents over time. | Choose this when the integration target is closer to a durable multi-agent operating surface than a single embedded assistant session. |