Seshat AIDocumentation

Documentation / SDK and API

Integrating in your product

What to decide when you embed Seshat in a backend, a desktop app or an internal platform: clients, sessions, credentials, sandboxing and storage.

Embedding Seshat in a script is a few lines. Embedding it in a product that serves several people is a set of decisions. This page lists them.

Clients and sessions

A Client holds the provider connection, the tool registry and shared services. A Session holds one conversation.

One client per set of credentialsYour producta backend, a desktop app or an internal platformClienttool registry, MCP serverscredential resolver, limitssandbox, monitoringSession Aone conversation, one userSession Banother conversationThe request contextthis user's prompt bridge, web search runner,a deadline: what changes with every requestStoragesessions: local files and SQLite,or your own SessionStoreartifacts, with cleanupContainmentcommands run in a sandbox,or are refused without oneObservationprogress, events and hooks,monitoring, limits per request
A session belongs to one user. What changes from one request to the next goes in the request context, not on the shared client.
You are buildingUse
A desktop or CLI app for one userOne client for the life of the app, one session per conversation
A server for many usersA client per set of credentials and settings (cached if creation is costly), a session per conversation, never shared between users
A batch or automation jobclient.Ask for a single prompt, or a workflow

Always call Close() on a session when the conversation ends, and on the client when you are done with it.

Per-request state

Several settings change from one request to the next. Do not store them on a shared client.

  • Prompt bridge. client.SetPromptFn changes state shared by every turn on that client. If a client serves concurrent turns, attach the bridge to the request’s context instead:
import "github.com/KPO-Tech/seshat-runtime/pkg/types"

ctx = types.WithPromptFn(ctx, func(ctx context.Context, req types.PromptRequest) (types.PromptResponse, error) {
    return ask(req) // goes to this user's connection
})
resp, err := session.SubmitMessage(ctx, text)
  • Web search runner. Same idea with types.WithWebSearchRunner(ctx, fn).
  • Working directory. Set WorkingDir in the config, or session.SetWorkingDirectory(path). Give each tenant their own folder.
  • User identity. Set UserID so plan documents are stored against the right person.

Credentials

Do not put one global key in the config for a multi-user product. Implement a CredentialResolver that looks up the key of the current user and provider. See Providers and authentication.

Run commands safely

The bash tool is always available and can run anything the host user can. For a multi-user or server deployment, that must be contained:

cfg.RequireSandbox = true            // refuse to run commands when no OS sandbox is available
cfg.SandboxKind = sdk.SandboxKindDocker
SettingEffect
SandboxKindLocal (default)Landlock on Linux. Unconfined elsewhere unless RequireSandbox is set
SandboxKindDockerCommands run in a persistent Docker container. If Docker is not reachable when the client starts, it falls back to local with a logged warning
RequireSandboxThe bash tool refuses to run when no sandbox is available, instead of running unconfined

client.SandboxKind() reports the sandbox this client actually resolved to: SandboxKindDocker only if you asked for it and Docker answered its health check when the client started, SandboxKindLocal otherwise. Use it to show the real state to your users.

sdk.SandboxAvailable() answers a different question: is Landlock, the OS-level sandbox, available on this host (Linux)? It does not look at Docker, so a host with a Docker sandbox configured still gets false on macOS or Windows.

Secrets are removed from the environment of the commands the model runs. See Security and trust.

Storage

SettingPurpose
PersistSessions, SessionStorageDir, SessionSQLitePathWhere conversations are saved (local files and SQLite by default)
SessionStore / SessionBackendYour own storage for sessions, to keep them in your database. Implement the SessionStore interface
ArtifactStore, StorageConfigWhere files produced by the agent are kept
StorageGCEnabled, StorageGCIntervalCleanup of expired artifacts

client.GetSessionStore() and client.GetArtifactStore() give you the stores in use.

Observe and control

  • SetProgressFn, SetResponseChunkFn and SetRuntimeEventFn feed your interface. See Streaming, events and hooks.
  • RegisterHook (on the loop events), PreToolHooks (shell commands before a tool call) and the stop hooks let you audit or block actions. See Streaming, events and hooks for what each can do. AddToolHook registers a Go function that runs before or after every tool call. The types it needs (ToolHook, ToolHookInput, ToolHookResult and ToolHookStop) are exported, so you can use it from your own module.
  • GetMonitoring() returns the monitoring system when EnableMonitoring is on.
  • MaxTurns, TurnTokenBudget and MaxConsecutiveDenials bound what one request can spend.

Rules imposed by the host

A server that serves a whole organization can impose rules that no user can change. Set ClientConfig.ManagedPolicy from an administrator’s configuration, never from what an end user sends:

cfg.ManagedPolicy = &sdk.ManagedPolicy{
    Instructions:   "Never send customer data outside the company network.",
    ForbiddenTools: []string{"web_fetch", "browser_open"},
}
  • Instructions is added to the system prompt after everything else, under a heading that says these are the organization’s rules and that they rank above the user’s. A session cannot replace it.
  • ForbiddenTools lists tool names that no agent may use. A name matches the tool and its aliases, and an MCP tool is named like mcp__server__tool. These tools are removed from what the model sees, and any call is denied by a rule that outranks every other, in every permission mode, bypass included.

Diagnosing connectivity

When a request to a provider or to an internal service fails, the useful question is which layer is broken. The package pkg/netdiag checks one target step by step:

res := netdiag.ProbeHTTP(ctx, "model provider", "https://api.example.com", "/health")
for _, step := range res.Steps {
    fmt.Println(step.Name, step.Status, step.Detail, step.Suggestion)
}
fmt.Println("reachable:", res.OK)

ProbeHTTP runs DNS, then TCP, then TLS (for an https address), then an HTTP request, and times each step. The HTTP step succeeds when there is no transport error, whatever the status code: a 404 still proves that the network path works. ProbeTCP(ctx, name, host, port) stops at the TCP step, for a service that does not speak HTTP. A failed step carries a suggestion and says who should act: the network administrator, IT security or support.

Running one agent type

client.RunAgentTask(ctx, agentType, task, maxTurns) runs a task as a built-in agent type, such as explore or general-purpose, and returns its final text. maxTurns is the number of autonomous turns, and 0 means a single turn. It is what a workflow step with an agent uses. Use it when your program needs a focused sub-agent without opening a conversation.

Checklist before you ship

  1. Credentials come from a resolver, not a shared key.
  2. RequireSandbox is on, or commands are contained another way.
  3. Each tenant has its own working directory and sessions.
  4. The prompt bridge is attached per request, not stored on a shared client.
  5. Limits are set (MaxTurns, token budget).
  6. Hooks log or block what your policy forbids.
  7. Sessions and clients are closed.

Updated on 2026-10-13