Docs/SDK and API/Go SDK/Developer Integration

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?”

Integration thesis

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.

Local surface
Desktop app or operator console

The runtime lives inside the local product process and can expose streaming, approvals, session titles, and project-aware workspaces directly to the UI.

Service surface
Backend, worker, or platform service

The host injects credentials, persistence, governance, and storage while the runtime stays the execution core behind one or many product APIs.

Embedding core
sdk.Client is the runtime integration boundary

Auth, session persistence, callbacks, prompt shaping, tool surface, memory, monitoring, MCP, multimodal providers, and browser/artifact infrastructure all converge on one client contract.

Session layer
Persistent or ephemeral sessions

The host can create, resume, annotate, and reshape sessions without reimplementing the engine loop.

Governance layer
Permissions, hooks, and tool policy

The host decides whether the runtime is trusted, interactive, locked down, or policy-driven, per client and per session.

Infra layer
State, artifacts, and process boundaries

Persistence, object storage, remote browsers, metrics, and gRPC all sit around the same runtime instead of splitting into separate agent stacks.

Polyglot path
Use gRPC when the runtime should move out-of-process

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.

Product rule
Do not fork the runtime too early

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.

SeamType / SurfaceBehaviorNotes
CredentialResolver + OAuthDeviceFlowAuth seamInject 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 / SessionSQLitePathState seamPersist 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 / ProgressFnLive UX seamObserve 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 / CreateSessionWithAdditionalProduct context seamInject 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 shapingGovernance seamDecide 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 / RegisterHookPolicy seamAttach 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 / BrowserRemoteControlURLInfra seamControl where web, browser, screenshot, and other artifacts live.Important for enterprise persistence, browser tooling, remote browser control, and artifact retention management.
GetMonitoring / MonitoringSystemObservability seamExport 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.

Desktop / operator integration
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)
}
Why OAuth matters here

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.

Why in-process is strong here

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.

Backend / multi-user host
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
}

_ = resp
Important permission rule

If 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.

Per-session live streaming
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.

Governance hooks
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
})
Use pre-tool hooksWhen you need immediate tool-call policy

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
Use lifecycle hooksWhen you need broader runtime telemetry

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.

Artifacts, GC, and monitoring
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, metrics

The 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.

SeamType / SurfaceBehaviorNotes
Go SDK in-processBest 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 boundaryBest 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 packageBest 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 teamsBest 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.