Documentation
¶
Overview ¶
Package sysagent is the in-airlock chat agent that lets operators manage agents, bridges, connections, members, runs, and other tenant resources through tool calls.
No JS VM, no per-agent connections, no Sol — sysagent runs inside airlock and its tools are typed Go functions wrapping existing service.{domain} methods. Every tool call uses the caller's authz.Principal; the service layer's authz.Authorize gates by action.
One sysagent per Airlock instance, per-user multi-conversation chat history in system_conversations, system_messages, and system_audit.
Index ¶
- Constants
- func SystemPrompt(env promptEnv, availableTools tool.Set) string
- type ConversationDetail
- type Deps
- type ListRunsResult
- type PromptInput
- type RunSummary
- type Service
- func (s *Service) CancelRun(ctx context.Context, p authz.Principal, runID uuid.UUID) (bool, error)
- func (s *Service) Compact(ctx context.Context, p authz.Principal, conversationID uuid.UUID) (string, error)
- func (s *Service) ControlConversation(ctx context.Context, p authz.Principal, conversationID uuid.UUID, ...) (bool, error)
- func (s *Service) CreateConversation(ctx context.Context, p authz.Principal, title string) (dbq.SystemConversation, error)
- func (s *Service) DeleteConversation(ctx context.Context, p authz.Principal, id uuid.UUID) error
- func (s *Service) EnsureBridgeConversation(ctx context.Context, p authz.Principal) (dbq.SystemConversation, error)
- func (s *Service) GetConversation(ctx context.Context, p authz.Principal, id uuid.UUID) (ConversationDetail, error)
- func (s *Service) ListConversations(ctx context.Context, p authz.Principal) ([]dbq.SystemConversation, error)
- func (s *Service) ListRuns(ctx context.Context, p authz.Principal, cursor time.Time, limit int32) (ListRunsResult, error)
- func (s *Service) Logger() *zap.Logger
- func (s *Service) NotifyBotCreated(ctx context.Context, conversationID, originRunID uuid.UUID, botUsername string) error
- func (s *Service) NotifyBuildComplete(ctx context.Context, agentID, conversationID, originRunID uuid.UUID, ...) error
- func (s *Service) NotifyUpgradeComplete(ctx context.Context, agentID, conversationID, originRunID uuid.UUID, ...) error
- func (s *Service) ResumePrincipal(ctx context.Context, conversationID, originRunID uuid.UUID) (authz.Principal, error)
- func (s *Service) RunPrompt(ctx context.Context, p authz.Principal, conversationID uuid.UUID, ...) (uuid.UUID, error)
- func (s *Service) RunPromptInline(ctx context.Context, p authz.Principal, conversationID uuid.UUID, ...) (uuid.UUID, error)
- func (s *Service) SetBridgeResumer(r bridgeResumer)
Constants ¶
const MaxToolOutputBytes = 8 * 1024
MaxToolOutputBytes caps every tool result (success and error) at 8 KiB. Tools that hand back larger payloads get truncated with a "[truncated: total=N bytes; refine query or paginate]" suffix, so one runaway list_runs call can't blow through the LLM's context budget. Sized to match the agentsdk JS-binding spill threshold so the LLM sees one consistent "8 KiB-ish" budget across surfaces.
Variables ¶
This section is empty.
Functions ¶
func SystemPrompt ¶
SystemPrompt renders the sysagent chat system prompt: the conventions preamble, the per-turn <env> block, and a tool catalogue generated from the registered (tenant-filtered) tool set so it can't drift from the actual Execute surface. Only the first description line is shown — the full schema reaches the model via the tool envelope.
Types ¶
type ConversationDetail ¶
type ConversationDetail = systemchat.ConversationDetail
type Deps ¶
type Deps struct {
DB *db.DB
Encryptor secrets.Store
PubSub *realtime.PubSub
PublicURL string // base URL for deep-link tools (no trailing slash)
ProviderHTTPClient *http.Client
Logger *zap.Logger
Agents *agentssvc.Service
Bridges *bridgessvc.Service
Catalog *catalogsvc.Service
Conns *connsvc.Service
GitCreds *gitcredssvc.Service
ManagedBots *managedbotssvc.Service
Members *memberssvc.Service
Runs *runssvc.Service
Users *userssvc.Service
}
Deps bundles the dependencies New requires. Pulled out as a struct so the call site stays readable as the service set grows.
type ListRunsResult ¶
type ListRunsResult = systemchat.ListRunsResult
type PromptInput ¶
type PromptInput = systemchat.PromptInput
PromptInput is the per-call payload the HTTP handler hands to RunPrompt. Exactly one of Message / Approved should be set per the /system/conversations/{id}/prompt API:
- Message non-empty + Approved nil = fresh operator turn.
- Approved non-nil + Message empty = resolution of a pending confirmation (true=execute, false=synthesize denial).
- Both empty = auto-resume after an injected system event (see Service.resumeConversation).
type RunSummary ¶
type RunSummary = systemchat.RunSummary
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is the central sysagent hub. Owns the dependency set every tool body needs (db, encryptor, all the per-domain service handles, logger) plus the public URL used to mint deep-link tool results.
One instance per Airlock; constructed in api/router.go and shared across HTTP requests. Per-request state (Principal, conversation, message history) lives on the chat loop, not here.
func New ¶
New wires the sysagent Service. Fail-loud on nil deps — every field is required (AGENTS.md rule).
func (*Service) CancelRun ¶
CancelRun aborts an in-flight chat goroutine. Returns true if a matching run was found and cancelled. The runChat goroutine sees ctx.Done() through sol.Runner and exits with status='cancelled' via the existing sol.RunCancelled branch.
func (*Service) Compact ¶
func (s *Service) Compact(ctx context.Context, p authz.Principal, conversationID uuid.UUID) (string, error)
Compact runs sol's user-triggered compaction on a sysagent conversation in-process. Loads the conversation's post-checkpoint messages, asks the LLM to summarize, persists the summary as a new system_messages row, and advances context_checkpoint_message_id.
Unlike RunPrompt this is synchronous from the caller's perspective: returns once compaction completes with the summary text the user will see in chat. Mirrors the agent path's /compact (which forwards to the agent container and lets Sol.Runner.Compact emit the summary as a normal assistant message), just executed locally because sysagent has no agent container.
func (*Service) ControlConversation ¶
func (*Service) CreateConversation ¶
func (*Service) DeleteConversation ¶
func (*Service) EnsureBridgeConversation ¶
func (*Service) GetConversation ¶
func (*Service) ListConversations ¶
func (*Service) Logger ¶
Logger exposes the package logger for callers that want to surface sysagent events through the same channel (e.g. the API handler logging request failures).
func (*Service) NotifyBotCreated ¶
func (s *Service) NotifyBotCreated(ctx context.Context, conversationID, originRunID uuid.UUID, botUsername string) error
NotifyBotCreated announces a freshly-created managed Telegram bot into the system-agent conversation that requested it (create_tg_bot) and auto-resumes, so the agent hands the operator the open link in-character. Same inject-then- resume mechanism as NotifyBuildComplete; agentID is nil (the event is about a bot, not an agent build — the WS envelope's AgentId is unused on bridges).
func (*Service) NotifyBuildComplete ¶
func (s *Service) NotifyBuildComplete(ctx context.Context, agentID, conversationID, originRunID uuid.UUID, status, message string) error
NotifyBuildComplete satisfies builder.PostBuildSystemNotifier — the initial-build counterpart of NotifyUpgradeComplete. Triggered after a build kicked off from the system-agent create_agent tool finishes; same inject-then-resume mechanism, build-flavored prefix.
func (*Service) NotifyUpgradeComplete ¶
func (s *Service) NotifyUpgradeComplete(ctx context.Context, agentID, conversationID, originRunID uuid.UUID, status, message string) error
NotifyUpgradeComplete satisfies builder.PostUpgradeSystemNotifier for the sysagent surface. Triggered by the builder after a build initiated from a sysagent-conversation upgrade tool finishes (success, failure, or out-of-scope refusal). Mirrors agent chat's conversationsHandler.NotifyUpgradeComplete (api/conversations.go) so the operator sees the outcome through the same render path the LLM-driving "user message" pattern uses.
What lands in the conversation: a single user-role system_messages row prefixed with [Upgrade succeeded] / [Upgrade failed] / [Request declined] — the prefix is what tells the LLM this is a system-injected event, not a real operator statement. Following agentsdk's deliberate choice not to write a separate assistant bubble: keeping the LLM's read of the situation unambiguous matters more than dual rendering.
agentID is the agent that was being upgraded; the message body references it so the LLM has context when responding. conversationID is the sysagent conversation that triggered the upgrade.
func (*Service) ResumePrincipal ¶
func (*Service) RunPrompt ¶
func (s *Service) RunPrompt(ctx context.Context, p authz.Principal, conversationID uuid.UUID, input PromptInput) (uuid.UUID, error)
RunPrompt creates the run row, hands off the chat to a background goroutine, and returns the new run id immediately. The HTTP handler returns {runId, conversationId} to the caller and the frontend subscribes to the conversation topic via WS to receive the streamed events (same pattern agent chat uses).
The synchronous part is intentionally small (create row, return id). Everything else — model resolve, message build, sol.Runner.Run, suspension handling, persistence — runs in the goroutine so a slow LLM doesn't tie up the HTTP request.
func (*Service) RunPromptInline ¶
func (s *Service) RunPromptInline(ctx context.Context, p authz.Principal, conversationID uuid.UUID, text, platform string, approved *bool, resumeRunID string, extraSink eventstream.Sink, onStart func(runID uuid.UUID)) (uuid.UUID, error)
RunPromptInline runs the chat loop synchronously: it runs on the caller's goroutine, and an additional eventstream.Sink is fanned every bus event alongside the WS pubsubSink. Used by the bridge path so a system-bridge poller can block on a turn (its poller is single-threaded per bridge → natural per-thread serialization) and translate sysagent events into bridge ResponseEvents.
Takes a full PromptInput so the bridge path can resolve pending confirmations (Approve/Reject button taps) the same way the web UI does — set Approved + ResumeRunID. A plain user message leaves both nil/empty. When extraSink is non-nil the WS pubsub is bypassed entirely; only the bridge sees events. Returns once the chat loop exits — RunCompleted / RunFailed / RunSuspended / RunCancelled all reach this.
func (*Service) SetBridgeResumer ¶
func (s *Service) SetBridgeResumer(r bridgeResumer)
SetBridgeResumer wires the bridge resume path. Called once at startup; nil leaves bridge follow-up delivery disabled (web notifications still work).