Go SDK
A typed, standard-library-only client for Mango.
The standalone module github.com/yanpgwang/mango/sdk/go requires Go 1.24+
and has no server or third-party runtime dependencies. See local installation.
Configure the client
The quickstart imports context, os, time, and the SDK as mango.
This excerpt runs inside a function returning error:
baseURL := os.Getenv("MANGO_BASE_URL")
if baseURL == "" {
baseURL = "http://localhost:8080"
}
client, err := mango.New(mango.Config{
BaseURL: baseURL,
APIKey: os.Getenv("MANGO_API_KEY"),
})
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()Every operation accepts a context. Use a deadline or cancellation for long-lived
streams. BaseURL supports a reverse-proxy prefix; do not append /v1.
Methods and inputs
Operations are grouped by resource: client.Sessions.New,
client.Sessions.Events.Send, and client.MemoryStores.Memories.New.
Path IDs are positional in HTTP hierarchy order, followed by generated request
or query structs. ModelID, ModelSettings, AgentID, AgentVersion,
Coordinator, RosterAgent, RosterAgentVersion, Self, Advisor, Text,
and UserMessage construct common wire variants without pointer boilerplate.
- The zero value of
Optional[T]omits a field. mango.Some(value)sends the value, including zero,false, or an empty list.mango.SomePtr("text")sets a nullable string.mango.Null[T]()sends explicit JSON null; the server checks whether it is allowed.- Set exactly one pointer in a wire union. Unknown response variants remain in
Raw.
Streaming and pagination
Sessions.Events.Stream establishes the subscription before returning. Call it
before sending input, iterate with Next(), decode Event(), check Err(),
and close the stream. The stream is live-only and does not automatically reconnect.
Sessions.Events.ListAutoPaging follows pages; Sessions.Events.List fetches one page.
Use the open-stream-then-list recovery procedure
to recover durable events after disconnects.
Self-hosted Work polling
NewWorkPoller claims and acknowledges Work for one self_hosted Environment.
It is a control-plane iterator, not a sandbox or tool runner. Advancing or
closing it never stops Work. EnvironmentWorker composes it with the Session
runner, lease heartbeat, and final Stop.
poller := mango.NewWorkPoller(ctx, client, mango.WorkPollerOptions{
EnvironmentID: environmentID,
Drain: true,
})
defer poller.Close()
for poller.Next() {
work := poller.Current()
// Launch a per-Session runner for work.Data.ID.
}
if err := poller.Err(); err != nil {
return err
}Drain mode omits block_ms unless explicitly configured and returns normally
when the queue is empty. A long-running poller defaults to the API's 999 ms
long-poll and exits normally when its context is cancelled. If Ack has an
ambiguous failure, the poller exits without calling Stop; Mango's Work TTL then
makes the activation reclaimable.
Composed Environment worker
Use NewEnvironmentWorker for the complete provider-neutral Work lifecycle:
worker := mango.NewEnvironmentWorker(client, mango.EnvironmentWorkerOptions{
EnvironmentID: environmentID,
Workdir: "/workspace",
ToolsFunc: func(env mango.EnvironmentWorkerToolContext) ([]mango.SessionTool, error) {
return agenttoolset.New(agenttoolset.Context{
Workdir: env.Workdir,
AllowedRoots: env.AllowedRoots,
ReadOnlyRoots: env.ReadOnlyRoots,
})
},
})
if err := worker.Run(ctx); err != nil {
return err
}The trusted supervisor client is used only for Poll and Ack. Before executing a tool, the worker decodes the acknowledged Work secret, switches to its scoped Session token, and obtains a successful conditional heartbeat. That token then authorizes heartbeats, Fail, Session events, and final Stop. Missing or malformed secrets fail closed; there is no Workspace-key fallback.
For a launcher that Polls and Acks outside a Session sandbox, call
HandleItem inside the sandbox with EnvironmentWorkerHandleItemOptions.
Non-secret identity fields may be passed explicitly or through MANGO_WORK_ID,
MANGO_ENVIRONMENT_ID, and MANGO_SESSION_ID. Pass WorkSecret explicitly
from a protected launcher transport when the process runs untrusted code. The
item process needs the Mango base URL but must not receive the Workspace key.
An allowlisted child environment prevents ordinary inheritance, but on Linux
the launcher must also keep secrets out of parent environment/command metadata
and prevent same-identity children from inspecting the trusted runner. The
first-party Docker launcher uses one-shot stdin and a non-dumpable item process.
After the first successful heartbeat, the worker fetches the frozen Session
once. It prepares immutable custom Skill pins beneath Workdir, downloads
attached Memory Stores at their frozen absolute mount paths, and only then calls
ToolsFunc with the exact allowed and read-only roots. It verifies Skill and
Memory integrity, confines filesystem access, and applies whole-Session limits.
Permanent invalid input is committed through Work Fail and terminates the
Session; temporary retrieval failure remains reclaimable. Self-hosted Skill
paths exposed to the model are relative to Workdir; Memory paths remain the
absolute /mnt/memory/... paths recorded on the Session.
The worker checks Memory synchronization after each dispatched tool, at a
15-second default cadence with a five-second minimum configurable interval.
Remote Store content wins a simultaneous edit, writable local changes use the existing Memory API with SHA-256
preconditions, and read-only Stores never push. Clean shutdown performs a final
sync; cancellation and error exits still receive a separately bounded
push-only flush. Set MemorySyncInterval negative only when the operator
explicitly wants to disable Memory download and synchronization. The worker
does not create compute or prepare File/Git resources; those remain launcher
responsibilities. On lease loss it cancels the runner, prevents later result
submission, returns ErrEnvironmentWorkLeaseLost, and does not Stop a possibly
newer owner's Work.
Cancel the context passed to EnvironmentWorker.Run or HandleItem on shutdown.
Tool execution is cancelled first; heartbeat renewal continues through Memory
teardown, and Work Stop follows the flush. A full Memory reconciliation and
rescue upload each have a 30-second budget. Allow additional time for tool/result
cleanup and final Stop; the Docker reference grants 120 seconds. Hard kills and
upload failures are not durable local Memory commits.
For launchers that need Mango's core shell and file executors, the independent
tools/agenttoolset package returns bash, read, write, edit, glob, and
grep as SessionTool implementations:
tools, err := agenttoolset.New(agenttoolset.Context{Workdir: "/workspace"})
if err != nil {
return err
}
defer agenttoolset.CloseAll(tools)This package is not a sandbox. The caller must establish the isolation
boundary first. File operations remain beneath Workdir and any explicitly
configured AllowedRoots; write and edit reject ReadOnlyRoots. Reads and
outputs are bounded, and Mango credentials are removed from the Bash
environment. Bash uses one persistent PTY session per toolset, so cwd, exported
variables, and background jobs survive calls. Its input accepts command,
restart, and timeout_ms; the runner-wide tool deadline remains the hard
upper bound. A timeout or cancellation replaces the shell before the next call.
Bash is not path-confined inside the host process, so the caller's sandbox is
the security boundary. A direct caller owns CloseAll because
SessionToolRunner borrows tools and never closes them. EnvironmentWorker is
the exception: it closes per-Session tools returned by ToolsFunc, while a
static Tools slice remains caller-owned.
Self-hosted Session tools
NewSessionToolRunner handles one Session's provider-neutral tool loop. It
opens SSE before listing durable history, reconciles again on reconnect, and
maps owned agent.tool_use and agent.custom_tool_use events to their matching
result events. MCP calls stay server-side, and unregistered tool names remain
pending for another owner.
runner := mango.NewSessionToolRunner(ctx, sessionClient, sessionID,
mango.SessionToolRunnerOptions{Tools: tools})
defer runner.Close()
for runner.Next() {
dispatch := runner.Current()
// Observe dispatch.Owned, dispatch.IsError, and dispatch.Posted.
}
if err := runner.Err(); err != nil &&
!errors.Is(err, mango.ErrSessionTerminated) &&
!errors.Is(err, mango.ErrIdleTimeout) {
return err
}sessionClient should use the acknowledged Work item's sessions_token.
The runner fails closed on approval-gated or unknown permission values, and
returns ErrSessionLeaseLost when that credential loses authority. It does not
heartbeat or Stop Work; use EnvironmentWorker for that composition. Tools
must honor cancellation and use the stable
SessionToolCall.ToolUseID to make external side effects idempotent.
Errors and retry safety
Use errors.As with *mango.APIError to access StatusCode, Type, and
RequestID. Avoid logging full error bodies, which may contain application data.
Generated one-shot methods do not automatically retry writes after an ambiguous
failure. SessionToolRunner is the narrow exception: it checks durable result
history before retrying its own transient result submission.