Docs/Concepts/Mono-Run Architecture

Mono-Run Architecture

Mono-run is the architectural choice that makes Seshat feel like a real runtime instead of a loose collection of wrappers. One process owns the session, prompt assembly, provider call, tool execution, permissions, compaction, persistence, and recovery. The system is modular internally, but the execution contract is unified.

Short version

Mono-run does not mean one giant package. It means one runtime boundary. The same executable owns the conversation state, the model call, the tool surface, the permission rules, and the local persistence tree, instead of delegating those responsibilities to external orchestrators.

What mono-run really means in Seshat

In Seshat, the execution path is intentionally narrow. The entry surface may change, but the runtime core does not. A CLI command, a gRPC call, or a higher-level product like seshat-ai all converge on the same core layers: sdk.Client, Engine, Session, and Loop.

That matters because the hard parts of agent execution are not split across unrelated services. Prompt assembly, provider routing, permissions, tool execution, transcript compaction, and session persistence live inside the same runtime boundary. The result is less orchestration drift, fewer duplicated contracts, and much stronger recovery semantics.

Runtime map

Entry
CLI

Terminal command surface

Entry
gRPC

Remote runtime access

Entry
Go SDK

Embedding and local control

Entry
seshat-ai

Desktop and product layer

Composition Boundary
sdk.Client

Wires providers, prompt, tools, permissions, runtime memory, browser, persistence, and monitoring once.

Core
Engine

Owns long-lived runtime wiring and session creation.

Core
Session

Owns canonical transcript, counters, tool surface, and permission state.

Core
Loop

Runs the turn until a legitimate terminal state exists.

Input
Prompt

Builder + Assembler compose the provider-facing system prompt.

Control
Permissions

Safety checks, modes, prompts, and session approvals stay in-path.

Surface
Tools

Registry + SurfaceBuilder derive one deterministic tool surface.

Mono-Run BoundaryOne Runtime Owns the Turn

Prompt assembly, model call, tool execution, recovery, persistence, and continuation stay inside the same execution owner.

Model I/O
Providers

Streaming request path, retry strategy, fallback client, and circuit breaker.

Action
Execution

Orchestrator validates, gates, batches, executes, and formats tool calls.

Recovery
Runtime State

Compaction, checkpoints, artifacts, plans, and local session persistence.

Local GravitySESHAT_RUNTIME_ROOT

One local root holds sessions, checkpoints, artifacts, logs, plans, skills, caches, and runtime-owned state. Recovery stays physically attached to execution.

The outer surfaces are replaceable. The inner execution path is not. That is the core mono-run invariant: every serious interaction with the runtime collapses into the same session engine rather than creating parallel orchestration systems for each interface.

1. Wiring happens once, near the edge

The most explicit wiring path lives in pkg/sdk/client.go. The SDK constructs the provider client, execution orchestrator, compaction engine, prompt assembler, permission engine, tool registry, session store, browser manager, and memory service, then injects them into engine.NewEngine().

  • The runtime is composed from subsystems, not hardcoded as one monolith.
  • Once composed, the runtime behaves as one execution owner.
  • Every outer surface inherits the same contracts when it goes through the SDK or engine.

2. The session is the owner of work

Mono-run in Seshat is session-centric. Engine.NewSessionFromState() creates the live tool surface for that session, loads memory, restores metadata, and constructs a SessionState object that survives across turns. The session is not a thin wrapper around a provider API. It owns canonical messages, permission context, discovered deferred tools, token counters, compaction metadata, and turn advancement.

This is one of the most important design decisions in the codebase. The fundamental unit is not a raw request or a transient workflow node. It is a durable session that can resume, recover, and keep responsibility for the evolving transcript.

3. Query loop state machine

Query LoopOne turn is a bounded state machine.

The loop does not stop at the first model response. It keeps ownership until it can legitimately terminate or continue with recovery.

EntryLoop.Run(req)

Initialize turn state and create the bounded execution owner for this query.

SetupinitializeState() + maybeAutoCompact()

Prepare counters, transcript state, recovery context, and compact early if the context window is already tight.

Model CallcallModel()

Send the provider request, stream the answer, classify failures, and preserve recovery context for the same turn.

tool_use blocks?

If the model wants tools, the turn is not done yet.

YesExecute tools

Validate, gate, batch, execute, format, append results, then feed them back into the same transcript.

Continue loop with fresh tool results
end_turn or terminal stop?

If not, the loop can still nudge, recover, or run stop hooks.

Nudge / recovery

Continuation nudge, max-token resume, or recoverable retry path.

Stop hooks

Hooks can inject follow-up work or allow a clean break.

Either re-enter the loop or terminate with RunResult

The real runtime heart is internal/engine/loop.go. A turn is not a single model request. It is a bounded execution cycle where the runtime may compact context, call the provider, parse tool uses, execute tools, refresh the prompt, recover from retryable failures, and continue until it reaches a legitimate terminal state.

4. Prompt assembly pipeline

Prompt AssemblyTwo phases, one provider-facing contract.

The builder prepares stable and dynamic sections, then the assembler injects runtime variables and preserves the cache-safe breakpoint.

Phase 1Stable sections
identitycacheable
runtime_contractcacheable
working_rulescacheable
tool_usecacheable
output_disciplinecacheable
tool_catalogcacheable
Cache boundary
Phase 1Dynamic sections
runtime_guidanceper turn
project_instructionsper turn
stage_overlayper turn
runtime_contextper turn
runtime_memoryper turn
Phase 2BuildCanonicalPrompt()
cwdsession_idturn_idmodeldatememory_contextinstructions_block

The prompt path is one of the places where mono-run becomes operationally useful. The builder and assembler do not produce a random string on the side. They produce the provider-facing system prompt from the exact same session, mode, tool surface, and runtime state that the loop will execute against.

5. Tool execution pipeline

01
Partition by concurrency safety

The orchestrator decides which tool calls can run together and which ones must stay serial.

02
Resolve, enable, validate, backfill

The tool is resolved from the registry, checked for availability, normalized, and enriched before policy decisions.

03
Run hooks and permission gate

Pre-tool hooks fire, then deny rules, tool-owned checks, allow rules, and permission modes decide allow, deny, or ask.

04
Execute and format result

The runtime calls the tool, runs post-tool hooks, serializes the result, applies size limits, and records traces.

05
Re-inject into the same transcript

Tool results become conversation blocks for the same session and the same loop, not an external workflow hop.

Built-in tool families
filesbashwebmcpskillslsptasksagent

The important point is not the count. It is that the same orchestrator owns them all under one permission and transcript contract.

The orchestrator in internal/execution/orchestrator.go is where tool usage stops being decorative and becomes runtime behavior. It partitions concurrent and serial work, validates inputs, runs hooks, applies permissions, executes tools, formats results, and pushes those results back into the same conversation.

6. Permissions are inside the execution path, not outside it

Permission PipelineSafety and approval live inside execution.

A tool call does not jump outside the runtime for governance. The governance path is the execution path.

Deny rules

Path patterns, command patterns, and explicit deny entries can stop the request immediately.

Tool-owned checks

The tool can reject unsafe input before any approval mode is considered.

Always-allow rules

Trivially safe and explicitly approved patterns can pass without user friction.

Permission mode gate

bypass, never, auto, acceptEdits, onRequest, and granular determine the final path.

bypass
never
auto
acceptEdits
onRequest
granular

Permissions in Seshat are not a UI-only feature layered on top of agent output. The runtime enforces them in the same path that executes tools. The orchestrator runs safety checks and permission resolution before a tool call, while the permission integrator manages session approvals, prompt-based confirmation, dont-ask mode, auto mode, and persisted per-session allowances.

7. Provider routing and recovery

Provider RoutingRetry inside a provider, then branch across models and providers.

The runtime keeps the same session owner while it retries, falls back, and classifies recoverable failures.

Primary client call

Create message stream against the selected provider and model.

Recoverable error?

Rate limit, timeout, network, or temporary overload.

Retry

Exponential backoff with jitter inside the same provider.

Fallback model

Try the next configured model in the routing chain.

Fallback provider

Escalate to the next provider client when models are exhausted.

The runtime can recover at several levels without breaking session ownership. A timeout, rate limit, or overload error does not automatically terminate the session. The loop can retry, continue with recovery context, or move to fallback models and fallback providers while preserving the same turn.

8. State, recovery, and artifacts live under one local root

Persistence TopologyOne Local Runtime Root, Multiple State Planes
Core state
seshat.dbdata/cache/tmp/
Session scope
sessions/{id}/transcriptcheckpointartifacts/tools/plans/session.log
Reusable assets
skills/logs/storage/vector data

The runtime root gives the architecture a physical center of gravity. Sessions, checkpoints, artifacts, logs, plans, skills, caches, vector data, and temporary task state all resolve under one local tree viaSESHAT_RUNTIME_ROOT.

9. Mono-run is modular, not monolithic

Internally, the runtime is deliberately split into subsystems: engine, execution,prompt, providers, permissions, runtime, tools,web, memory, and more. Mono-run does not deny modularity. It constrains how those modules cooperate at runtime.

Inside the boundary

Clear packages, explicit wiring, replaceable subsystems, and a stable engine/session/loop core.

At the boundary

One runtime owns the turn lifecycle, rather than outsourcing each concern to a different service.

10. Subsystem reference map

cmd/cli, cmd/grpc, pkg/sdkEntry surfacesmain.go, client.go

Different interfaces exist, but they all collapse into the same runtime owner instead of forking execution logic per surface.

internal/engineEngine coreengine.go, session.go, loop.go, state.go

The engine creates sessions, sessions own the durable work state, and the loop owns turn execution until a real stop reason exists.

internal/promptPrompt systembuilder.go, assembler.go, stages.go

Stable and per-turn prompt sections are assembled with a clear cache boundary, stage overlays, and runtime context injection.

internal/execution, internal/toolsTool runtimeorchestrator.go, registry.go, surface_builder.go

Tool resolution, validation, batching, hooks, execution, and serialization are all in the same turn path.

internal/permissionsGovernanceintegration.go, engine.go, denialTracking.go

Permission modes, safe-path matching, approval prompts, and denial degradation are runtime behavior, not UI cosmetics.

internal/runtime, internal/dbRecovery and statememory/engine.go, state/store.go, session_store.go

Compaction, checkpoints, canonical transcript persistence, and restoration live under the same operational root.

internal/providersProvider layerclient.go, retry.go, circuit_breaker.go

Streaming, retry, fallback models, fallback providers, and circuit-breaking are first-class runtime behavior.

internal/memory, pkg/runtimepathMemory and reusable contextmanager.go, runtimepath.go

Project memory, user memory, caches, skills, and reusable assets attach to the runtime root instead of floating outside it.

Why this architecture matters

The advantage of mono-run is not aesthetic purity. It is operational coherence. The provider request, the prompt cache boundary, the permission mode, the tool map, the transcript, the checkpoint, and the artifact paths all belong to one runtime owner. That makes the system easier to reason about, easier to recover, and much harder to split into contradictory states.

Next concept

Once mono-run is clear, the next useful layer is understanding how the same runtime can expose several host surfaces while also switching session behavior between execute, plan, and pair-programming styles.