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.
| You are building | Use |
|---|---|
| A desktop or CLI app for one user | One client for the life of the app, one session per conversation |
| A server for many users | A client per set of credentials and settings (cached if creation is costly), a session per conversation, never shared between users |
| A batch or automation job | client.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.SetPromptFnchanges 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
WorkingDirin the config, orsession.SetWorkingDirectory(path). Give each tenant their own folder. - User identity. Set
UserIDso 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
| Setting | Effect |
|---|---|
SandboxKindLocal (default) | Landlock on Linux. Unconfined elsewhere unless RequireSandbox is set |
SandboxKindDocker | Commands run in a persistent Docker container. If Docker is not reachable when the client starts, it falls back to local with a logged warning |
RequireSandbox | The 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
| Setting | Purpose |
|---|---|
PersistSessions, SessionStorageDir, SessionSQLitePath | Where conversations are saved (local files and SQLite by default) |
SessionStore / SessionBackend | Your own storage for sessions, to keep them in your database. Implement the SessionStore interface |
ArtifactStore, StorageConfig | Where files produced by the agent are kept |
StorageGCEnabled, StorageGCInterval | Cleanup of expired artifacts |
client.GetSessionStore() and client.GetArtifactStore() give you the stores in use.
Observe and control
SetProgressFn,SetResponseChunkFnandSetRuntimeEventFnfeed 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.AddToolHookregisters a Go function that runs before or after every tool call. The types it needs (ToolHook,ToolHookInput,ToolHookResultandToolHookStop) are exported, so you can use it from your own module.GetMonitoring()returns the monitoring system whenEnableMonitoringis on.MaxTurns,TurnTokenBudgetandMaxConsecutiveDenialsbound 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"},
}
Instructionsis 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.ForbiddenToolslists tool names that no agent may use. A name matches the tool and its aliases, and an MCP tool is named likemcp__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,bypassincluded.
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
- Credentials come from a resolver, not a shared key.
RequireSandboxis on, or commands are contained another way.- Each tenant has its own working directory and sessions.
- The prompt bridge is attached per request, not stored on a shared client.
- Limits are set (
MaxTurns, token budget). - Hooks log or block what your policy forbids.
- Sessions and clients are closed.
Updated on 2026-10-13