Documentation
¶
Overview ¶
auth.go is the WEB-03 in-binary web-auth boundary (D-01..D-04): a constant-time operator-secret login that mints an HMAC-signed session cookie, a RequireAuth middleware that gates the whole origin except the public paths (login + assets + GET /healthz), a POST /logout that clears the cookie, and a capability gate on the only mutating route (POST /agent/run). The cookie crypto lives in auth_cookie.go; this file owns the HTTP surface + the consumer-side identity seam.
The boundary is stdlib-only (crypto/hmac + crypto/sha256 + crypto/subtle + net/http + time) — no gorilla/* or other 3rd-party session library (RESEARCH §Alternatives Considered). It reads ONLY the session cookie, never a client-supplied auth/identity header (T-24-13: trusting X-Forwarded-User / Authorization from the client is the golang-security client-header anti-pattern; the trust-proxy posture means "Aura stays hands-off", not "Aura reads a proxy-injected identity header").
CSRF posture (T-24-19, RESEARCH §Discretion Resolution): SameSite=Strict is the only CSRF control this phase. For a same-origin SPA with no cross-origin write path that covers the classic vector; a double-submit token is NOT added in Phase 24. Re-evaluate if Phase 28/29 introduces a cross-origin write surface.
auth_cookie.go holds the stdlib-only session-cookie crypto for the WEB-03 in-binary web-auth boundary (D-01/D-02). It is split out of auth.go to keep each file well under the 600-LOC ceiling (CLAUDE.md NO GOD CLASS): this file owns the secret compare + the HMAC sign/verify + the cookie (un)set helpers; auth.go owns the HTTP middleware + handlers.
The session is a STATELESS, HMAC-SHA256-signed cookie — no session store, no new credential storage (D-01). The cookie value is base64url(payload)"."base64url(MAC) where payload is "{identityID}|{issuedUnix}"; a single server key derived once from sha256(AURA_WEB_AUTH_SECRET) (RESEARCH A2 — one operator secret governs both login and signing) keys the MAC. Tamper ⇒ MAC mismatch ⇒ reject; expiry ⇒ issued_at+TTL check ⇒ reject. Every compare is constant-time (subtle.ConstantTimeCompare for the secret, hmac.Equal for the MAC) so the boundary leaks no timing oracle.
gorilla/securecookie + gorilla/sessions are deliberately NOT used: they are not vendored and adding them would violate the no-framework posture (server.go:88) for what is ~40 LOC of stdlib HMAC (RESEARCH §Alternatives Considered).
Package agui is the AG-UI protocol transport adapter (Slice 8a). It consumes Aura's in-process iter.Seq2[*agent.Event, error] stream and maps it onto the official AG-UI community Go SDK event surface. The boundary is one-way: agui imports agent, the agent runtime NEVER imports agui (D-17, CI-enforced via scripts/agui_boundary_check.sh). The translator (translator.go) is a pure function — no I/O, no goroutines — so it is property- and golden-testable in isolation.
Index ¶
- Constants
- Variables
- func RequireAuth(next http.Handler, deps AuthDeps) http.Handler
- func RequireCapability(next http.Handler, deps AuthDeps, capability string) http.Handler
- func SanitizeString(msg string) string
- func Subscribe(ctx context.Context, threadID, runID string, idgen IDGenerator, ...) <-chan Event
- func Translate(threadID, runID string, idgen IDGenerator, seq iter.Seq2[*agent.Event, error], ...) iter.Seq2[events.Event, error]
- func ValidateRunInput(in types.RunAgentInput) error
- type ApprovalStore
- type AssetService
- type AuthDeps
- type CapabilitySource
- type ConversationStore
- type Event
- type EventType
- type Fanout
- type GovernanceProviders
- type GraphView
- type IDGenerator
- type Identity
- type ImageFetcher
- type MCPBoardProvider
- type OnboardingService
- type ReadinessProbe
- type Runner
- type SchedulerBoardProvider
- type Server
- func (s *Server) Mux() http.Handler
- func (s *Server) SetApprovalStore(store ApprovalStore)
- func (s *Server) SetAssetService(service AssetService)
- func (s *Server) SetGovernanceProviders(p GovernanceProviders)
- func (s *Server) SetGraphView(gv GraphView)
- func (s *Server) SetImageProxy(images ImageFetcher)
- func (s *Server) SetOnboardingService(svc OnboardingService)
- type ServerConfig
- type SkillsBoardProvider
Constants ¶
const ArtifactEventName = "aura.artifact"
ArtifactEventName is the stable AG-UI CUSTOM-event name the artifact branch emits (D-06). A channel consumer keys on this name to render a delivered file (Telegram → sendDocument, CLI → print path, AG-UI HTTP → pass-through). It is namespaced so it never collides with another application's custom events. Exported so the channel consumers (e.g. telegram artifact.go, plan 13-06) key on the SAME canonical name rather than a duplicated magic string.
const DisplayEventName = "aura.display"
DisplayEventName is the stable AG-UI CUSTOM-event name the typed-display branch emits (Phase 26, D-PROTO). It is the additive twin of ArtifactEventName: the cockpit sseAdapter keys on this name to attach the DisplayPayload to the matching tool part by tool_call_id. Namespaced so it never collides with another application's custom events.
Variables ¶
var ErrEmptyThreadID = errors.New("agui: threadId must not be empty")
ErrEmptyThreadID is the Aura-semantic validation sentinel ValidateRunInput returns. It is distinct from the SDK's parse errors: the SDK's RunAgentInput.UnmarshalJSON owns camel/snake JSON shape, while this guards Aura's one hard run precondition — a thread to resolve.
Functions ¶
func RequireAuth ¶ added in v1.0.0
RequireAuth gates the WHOLE origin (D-03) except the public paths. When auth is not configured (SecretConfigured==false, loopback dev) it returns `next` unchanged — a no-op pass-through (mirrors server.go withCORS), safe because the Plan-01 boot guard only permits an unconfigured secret on a loopback bind. When active it:
- lets isPublicPath requests + GET /healthz through (login + assets + liveness);
- reads ONLY r.Cookie(sessionCookieName) — never a client auth/identity header (T-24-13, golang-security client-header anti-pattern);
- redirects a browser GET to the login page (302) / 401s an API request on a missing or invalid cookie;
- verifySession (HMAC + absolute TTL);
- confirms the bound identity still exists (a deleted identity invalidates the session);
- stashes the principal on the request context and calls next.
func RequireCapability ¶ added in v1.0.0
RequireCapability wraps a mutating-route handler with the capability_grants check (D-04): it reads the principal RequireAuth stashed, asks the identity store HasCapability(principal, capability), and 403s on a missing principal, a store error, or a denied capability. This is the seam that exercises the dormant capability_grants scaffolding on the ONLY mutating route (POST /agent/run); the seeded `local` identity passes via its `*` wildcard. It invents NO governance write routes — those land in Phase 28. Exported so the composition root (cmd/aura) interposes it on the parent mux.
func SanitizeString ¶
SanitizeString strips credential-bearing substrings from an arbitrary string: whole DB DSNs collapse to a scheme marker, generic URL userinfo collapses to `scheme://[redacted]@`, and bearer/api-key/token shapes collapse to a `prefix[redacted]` marker (WR-03). The DSN pass runs first so the generic userinfo pass never reaches the DB schemes.
func Subscribe ¶
func Subscribe(ctx context.Context, threadID, runID string, idgen IDGenerator, turn iter.Seq2[*agent.Event, error]) <-chan Event
Subscribe composes Plan 01's Translate with a single-subscriber Fanout and returns a receive-only channel of AG-UI events for ONE in-process consumer of an agent turn. Given the threadID/runID, an IDGenerator, and the agent's iter.Seq2[*agent.Event, error] turn stream, it translates the turn and pumps it through the cap-64, drop-on-full, ctx-cancel-clean Fanout — the same backpressure + leak discipline a future Telegram channel consumes (amendment #35 agui_subscriber.go seam). The channel closes when the turn ends or ctx is cancelled.
This seam is transport-free: no HTTP, no SSE (that is server.go in a later plan). For N concurrent consumers, build a *Fanout directly and Subscribe() per consumer; this helper is the single-consumer convenience over that same pump.
Reasoning forward-compat: the translated stream may later carry REASONING_* events (the dual-field reasoning/reasoning_content data-plane, amendment #57). This phase only surfaces the alias — no reasoning parsing is added here.
func Translate ¶
func Translate(threadID, runID string, idgen IDGenerator, seq iter.Seq2[*agent.Event, error], showReasoning bool) iter.Seq2[events.Event, error]
Translate maps Aura's per-token *agent.Event stream onto a valid AG-UI events.Event sequence. It is a PURE function (no I/O, no goroutines): RUN_STARTED first, RUN_FINISHED(success) last, with a chunk-coalescing text-message state machine in between. idgen owns id minting so every emitted messageId/toolCallId is non-empty (both are Validate()-required by the SDK).
Run-boundary policy (locked OQ1 a): the real (*LlmAgent).Run streams per-token chunk Events AND a final Event carrying the full answer + FinishReason. We stream the deltas (one TEXT_MESSAGE_CONTENT per non-empty delta) and treat the final Event as END-only — its Content is NOT re-emitted as a CONTENT (no double-stream); its usage StateDelta becomes a STATE_DELTA event.
showReasoning controls the REASONING_MESSAGE_CONTENT payload: false (the default privacy posture) substitutes redactedReasoningDelta; true passes the real CoT delta through so an opted-in consumer (Telegram's live 💭 window) can display it. The REASONING_* lifecycle envelope is emitted either way — only the delta text differs.
func ValidateRunInput ¶
func ValidateRunInput(in types.RunAgentInput) error
ValidateRunInput checks the Aura-semantic preconditions for a run: a non-empty threadId (resolved to a conversation downstream by conversations.Get, which owns the UUID-shape check). It deliberately does NOT require a message: an empty Messages list is the continue-after-resume signal (D-05 / CR-01) — handleRun resolves userMsg=nil (lastUserMessage) and drives Runner.Turn(…, nil) over the rehydrated history (the resolved ask_user answer is already a RoleTool turn the cockpit re-drive POSTs against after an inline approval resolves). It does NOT re-implement JSON parsing (the SDK's UnmarshalJSON does) nor re-validate the UUID shape (conversations.Get's parseUUID does).
Types ¶
type ApprovalStore ¶ added in v1.0.0
type ApprovalStore interface {
ListPendingAll(ctx context.Context, limit int) ([]askuser.Pending, error)
}
ApprovalStore is the narrow cross-thread HITL-read surface the approval center consumes (APRV-01 / D-04; *askuser.Store satisfies it implicitly). ListPendingAll aggregates every still-pending pause across ALL conversations in total order (priority DESC, created_at ASC, token ASC). Declared consumer-side so the server depends only on the one method the approvals read calls — the three-action resolve goes through the Runner (SubmitAnswers), not this store. A Server with no ApprovalStore wired answers the approvals routes 503 (the read is optional wiring; the daemon composition root sets it via SetApprovalStore).
type AssetService ¶ added in v1.0.0
type AssetService interface {
Presign(context.Context, assets.PresignRequest) (assets.PresignResponse, error)
Finalize(context.Context, string, string) (assets.Asset, error)
GetForIdentity(context.Context, string, string) (assets.Asset, error)
ListForThread(context.Context, string, string) ([]assets.Asset, error)
Promote(context.Context, string, string) (assets.Asset, error)
Delete(context.Context, string, string) (assets.Asset, error)
Retry(context.Context, string, string) (assets.Asset, error)
}
AssetService is the narrow asset API surface consumed by AG-UI handlers.
type AuthDeps ¶ added in v1.0.0
type AuthDeps struct {
// Secret is the raw operator passphrase (AURA_WEB_AUTH_SECRET) the login form is
// compared against constant-time. Empty ⇒ login fail-closed.
Secret string
// SigningKey is the derived HMAC-SHA256 key (sha256(Secret)) that signs/verifies
// the session cookie.
SigningKey []byte
// TTL is the absolute session lifetime; a zero value falls back to defaultSessionTTL.
TTL time.Duration
// SecretConfigured reports whether in-binary auth is active. When false the whole
// gate is bypassed (loopback dev) — safe because GuardWebBind only permits an
// unconfigured secret on a loopback bind.
SecretConfigured bool
// LocalIdentityID is the seeded `local` identity a successful login binds the
// session to (the capability_grants principal, D-02/CORE-03).
LocalIdentityID string
// Identities is the capability/identity store seam (a *identity.Store at runtime).
Identities identityChecker
// LoginPath is the public login page route an unauthenticated browser GET is
// redirected to (default "/login").
LoginPath string
// AuthBasePath, when non-empty, is the credential-flow route prefix served by the
// embedded Authula provider (its BasePath, "/auth"). Every path under it is public
// (reachable WITHOUT a session) because login / TOTP-verify happen before a session
// exists; Authula's own per-route metadata + CSRF middleware gate those routes. Empty
// (the passphrase path) means no such prefix is public.
AuthBasePath string
// PublicAsset reports whether a path is a real embedded static asset the login page
// needs before a session exists (the shared SPA bundle/styles, the PWA service
// worker/manifest, icons). It is wired from webui.IsPublicAsset at the composition
// root (cmd/aura/serve_webui.go); a nil predicate means no static asset is public
// (the gate still serves the login route + GET /healthz).
PublicAsset func(path string) bool
// PublicRoute reports whether a non-asset route is intentionally reachable before
// a session. The composition root uses this for tiny bootstrap contracts such as
// the auth-provider config; credential subtrees should still use AuthBasePath.
PublicRoute func(r *http.Request) bool
// SessionValidator, when non-nil, REPLACES the HMAC cookie-validation core inside
// RequireAuth (the Option-A2 seam, AURA_WEB_AUTH_PROVIDER=authula): given a request
// it returns the bound Aura identity UUID + ok. When nil (the default,
// AURA_WEB_AUTH_PROVIDER=passphrase) RequireAuth keeps the stdlib HMAC verifySession
// path verbatim. Either way the result feeds the SAME existence re-check +
// withPrincipal + RequireCapability contract — only the issuer/validator of the
// cookie changes, never the principalKey{} downstream.
SessionValidator func(r *http.Request) (identityID string, ok bool)
}
AuthDeps carries everything the login handler + RequireAuth + the capability gate need, threaded from bootServe. SecretConfigured is the master switch: when false (loopback dev, which the Plan-01 boot guard confines to loopback) RequireAuth is a no-op pass-through and login is unavailable. SigningKey is sha256(AURA_WEB_AUTH_SECRET).
func (AuthDeps) LoginHandler ¶ added in v1.0.0
func (d AuthDeps) LoginHandler() http.HandlerFunc
LoginHandler returns the POST /login handler: it reads the form passphrase (body size-bounded like server.go's handleRun), constant-time compares it against the configured secret (validateSecret — fail-closed on an empty/unconfigured secret), and on success mints a session cookie bound to the local identity and 303-redirects to "/". On any failure it returns a GENERIC 401 with no Set-Cookie and no oracle distinguishing "wrong password" from "no secret configured" (T-24-15, golang-security V7). The passphrase is never echoed.
func (AuthDeps) LogoutHandler ¶ added in v1.0.0
func (d AuthDeps) LogoutHandler() http.HandlerFunc
LogoutHandler returns the POST /logout handler: it clears the session cookie (MaxAge < 0) and 303-redirects to the login page. It is safe to call without a session (idempotent) and never reads the cookie value.
type CapabilitySource ¶ added in v1.0.1
type CapabilitySource interface {
ListCapabilities(ctx context.Context, identityID string) ([]string, error)
}
CapabilitySource is the D-06 capability-picker source: the creating operator's own grants (the handler filters out '*' for the checklist and re-validates the subset before the saga). identity.Store satisfies it.
type ConversationStore ¶
type ConversationStore interface {
Get(ctx context.Context, conversationID string) (conversations.Conversation, error)
LoadHistory(ctx context.Context, conversationID string) ([]llm.Message, error)
List(ctx context.Context, includeArchived bool) ([]conversations.Conversation, error)
SearchConversationTurns(ctx context.Context, query string, limit int) ([]conversations.SearchResult, error)
UpdateStatus(ctx context.Context, conversationID, status string) error
Rename(ctx context.Context, conversationID, title string) error
SetTitleIfNull(ctx context.Context, conversationID, title string) error
Delete(ctx context.Context, conversationID string) error
ListContextRotEvents(ctx context.Context, conversationID string) ([]conversations.RotEvent, error)
// D-09 / CHAT-05 branch surface (plan 25-07). ListBranches enumerates the navigable
// branch leaves; ForkBranch writes a new sibling branch (edit-a-user-turn /
// regenerate) chained off the diverging turn's parent and returns the new leaf seq.
// CanonicalBranchLeaf is the default selection (the conversation's canonical tip).
// All three are on the concrete *conversations.Store; declared here so agui depends
// only on the methods it calls.
ListBranches(ctx context.Context, conversationID string) ([]conversations.Branch, error)
ForkBranch(ctx context.Context, conversationID string, divergeSeq int, role, content string) (int, uuid.UUID, error)
CanonicalBranchLeaf(ctx context.Context, conversationID string) (int, error)
}
ConversationStore is the narrow conversation surface the AG-UI server consumes (D-A2-02, "accept interfaces, return structs"). *conversations.Store satisfies it implicitly. Get resolves a threadId → conversation (404 chokepoint); LoadHistory rehydrates the byte-identical turn history for the GET messages projection. The CHAT-02 conversation-management surface (Phase 25) widens this with List/SearchConversationTurns/UpdateStatus/Rename/Delete/SetTitleIfNull and a read-only ListContextRotEvents (the D-11 microcompact ladder gauge marker) — all already on the concrete *conversations.Store, declared here consumer-side so agui depends only on the methods it calls, never the whole Store.
type Event ¶
Event is the AG-UI event surface re-exported under the agui package so in-process consumers (e.g. the Phase-13 Telegram channel) reference agui.Event, not the external events.Event — keeping github.com/ag-ui-protocol/... out of every call site (PRD Slice 8: "Type aliases per evitare leak del package esterno nei call sites"). Only the subscriber surface is aliased; the SDK is not blanket re-exported.
type EventType ¶
EventType is the aliased AG-UI event discriminator (agui.Event.Type() returns it), re-exported alongside Event so a consumer can switch on event kind without importing the SDK.
type Fanout ¶
type Fanout struct {
// contains filtered or unexported fields
}
Fanout distributes a single translated AG-UI event stream to N concurrent in-process subscribers. It wraps the Translate output (iter.Seq2[events.Event, error]) and, for each event, sends to every subscriber via a cap-64 buffered, drop-on-full, never-block pump (the openai_compat Stream analog plus a default-drop arm). The single producer goroutine started by Run is the SOLE sender on every subscriber channel and closes them all on exit (ctx-cancel OR source end), so the stream is goleak-clean (Pitfall 4).
Fanout is NOT on the HTTP path (RESEARCH diagram line 134): server.go does not route through it. It is the in-process seam the Phase-13 Telegram channel consumes.
Errors from the wrapped source are already mapped to a RUN_ERROR event by Translate (the translated stream is pure events.Event), so Fanout never inspects the error slot — it just forwards whatever Translate yields.
func NewFanout ¶
NewFanout wraps a translated event stream. Subscribe before Run to register a consumer; Run then drives the source once and fans out to all registered subscribers.
func (*Fanout) Run ¶
Run starts the single producer goroutine that ranges the wrapped source once and fans each event to every subscriber. It returns immediately; the goroutine exits on ctx-cancel or source end, closing every subscriber channel on the way out (sole sender closes — golang-concurrency principle #4). Run is non-blocking and must be called AT MOST ONCE: a second Run would launch a second producer that double-closes the subscriber channels (panic: close of closed channel). The started guard makes the misuse loud at the call site instead (WR-06).
func (*Fanout) Subscribe ¶
Subscribe registers a new consumer and returns its receive-only channel. The channel is cap-64 buffered and is closed by the producer goroutine when the source ends or ctx is cancelled. Subscribe MUST be called before Run: a subscriber registered after the producer has snapshotted f.subs is never fed (and racing the snapshot is a data race), so Subscribe-after-Run panics loudly rather than failing silently (WR-06).
type GovernanceProviders ¶ added in v1.0.1
type GovernanceProviders struct {
MCP MCPBoardProvider
Skills SkillsBoardProvider
Scheduler SchedulerBoardProvider
}
GovernanceProviders bundles the three read-only board providers so the composition root wires them in one call. Any field may be nil; the corresponding board's handler (Plan 02) answers 503 when its provider is unset.
type GraphView ¶ added in v1.0.0
type GraphView interface {
Schema(ctx context.Context) (knowledge.GraphSchema, error)
Query(ctx context.Context, in knowledge.GraphIntent) (knowledge.GraphResult, error)
}
GraphView is the narrow read-only graph surface the handlers consume (D-A2-02: declared consumer-side so the handler depends only on the two methods it calls, not the whole *knowledge.GraphView). *knowledge.GraphView satisfies it. Schema returns the live label/rel-type/property-key overview (D-06); Query dispatches a structured intent through the plan-01 compile → assertReadOnly → Read → normalize path. A Server with no GraphView wired answers both routes 503 (the wiring is optional; the daemon composition root sets it via SetGraphView).
type IDGenerator ¶
type IDGenerator interface {
NewMessageID() string
NewReasoningID() string
NewToolResultID(toolCallID string) string
}
IDGenerator owns AG-UI id minting so the translator can guarantee non-empty messageId/toolCallId (both are Validate()-required by the SDK) and so tests can inject deterministic ids for stable golden compares. NewMessageID mints an assistant message run id; NewReasoningID mints the SEPARATE rsn- message id a REASONING lifecycle carries (distinct from the assistant TEXT_MESSAGE id so a consumer never conflates CoT with the answer); NewToolResultID mints the message id carrying a tool result (correlated to its toolCallID).
func NewIDGenerator ¶
func NewIDGenerator() IDGenerator
NewIDGenerator returns the default uuid-v4-backed IDGenerator.
type Identity ¶ added in v1.0.0
Identity is the minimal projection the auth boundary observes — it mirrors the public field set of identity.Identity so *identity.Store satisfies identityChecker structurally without agui importing the concrete return type's full surface.
type ImageFetcher ¶ added in v1.0.0
type ImageFetcher interface {
FetchImage(ctx context.Context, convID, rawURL string) ([]byte, string, error)
}
ImageFetcher is the narrow SSRF-safe image-fetch surface the proxy consumes (*web.Client satisfies it via FetchImage). Declared consumer-side (D-A2-02) so the agui server depends only on the one method it calls, not the whole web.Client — and so a unit test can pass a scripted fake. The returned (bytes, mediaType) is already gated by the web SSRF guard + image content-type allowlist + size cap; an error is a sanitized *web.WebError (no IP/host leak).
type MCPBoardProvider ¶ added in v1.0.1
type MCPBoardProvider interface {
Servers() mcp.ManagedConfig
Probe(ctx context.Context, name string, server mcp.ManagedServer) mcp.ProbeResult
}
MCPBoardProvider is the read-only MCP-governance surface (GOV-01): the static row snapshot from the managed config plus a bounded, per-row live probe. ProbeServer is the structured live probe extracted in this plan; the handler bounds the supplied ctx with a per-request timeout so a hung server fails only its own row.
type OnboardingService ¶ added in v1.0.1
type OnboardingService interface {
CapabilitySource
}
OnboardingService is the server-held provisioning + interview surface the onboarding wizard handlers (Plan 05) consume: start a session, apply one step intent to the server-held session, run the cross-store provisioning saga, and poll the Telegram link. The concrete service (session store + saga) is built in Plan 05; this plan only declares the seam so the field + setter exist. The capability source is embedded so the picker read goes through the same narrow interface.
type ReadinessProbe ¶
ReadinessProbe is one named dependency-readiness check the server runs on /readyz. Check returns nil when the dependency is reachable, a non-nil error (surfaced sanitized in the 503 body) when it is not. Probes are injected by the composition root (cmd/aura wires the real PG + Neo4j probes) so they stay testable and the set is easy to extend without touching the handler.
type Runner ¶
type Runner interface {
Turn(ctx context.Context, convID string, userMsg *string) iter.Seq2[*agent.Event, error]
SubmitAnswers(ctx context.Context, answers map[string]runner.ResponseInput) (int, error)
NewConversation(ctx context.Context) (string, error)
// TurnBranch is the D-09 / CHAT-05 re-run-from-a-point: it drives a fresh agent
// round over the SELECTED branch path (leafSeq) with no fresh user message — the
// branch's turns were already persisted by ForkBranch. *runner.Runner satisfies it.
TurnBranch(ctx context.Context, convID string, leafSeq int) iter.Seq2[*agent.Event, error]
}
Runner is the narrow agent-driver surface the server consumes (D-A2-02; *runner.Runner satisfies it implicitly). Turn drives one round over a thread and yields the agent Event stream; SubmitAnswers resolves protocol-native HITL resume entries (resolved→accept, cancelled→cancel) before the Turn. Declared consumer-side so the server depends only on the two methods it calls, not the whole Runner — and so unit tests pass scripted fakes.
type SchedulerBoardProvider ¶ added in v1.0.1
type SchedulerBoardProvider interface {
ListActiveTasks(ctx context.Context) ([]cron.Task, error)
ListRunsForTask(ctx context.Context, taskID string, limit, offset int) ([]cron.Run, error)
}
SchedulerBoardProvider is the read-only scheduler-governance surface (GOV-03): the active task list and a paginated, newest-first run history per task.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the minimal AG-UI HTTP gateway (Slice 8b): POST /agent/run streams a translated agent turn as SSE, GET /threads/{id}/messages returns the persisted history as a MESSAGES_SNAPSHOT JSON body. It is the thinnest glue over EXISTING seams — the narrow Runner + ConversationStore, the translator, and the SDK SSE writer. The bind is hardcoded loopback by the daemon (auth deferred this phase, amendment #35); the loopback bind IS the compensating control (T-12-08).
func NewServer ¶
func NewServer(run Runner, conv ConversationStore, cfg ServerConfig) *Server
NewServer builds the gateway over the supplied driver + store + config. The IDGenerator is the default uuid-v4 minter; tests inject a deterministic one via the exported field for stable frame ids. The cross-thread approval read store is wired separately via SetApprovalStore (optional, kept off the constructor so the existing NewServer callers/tests stay unchanged — D-A2-02 narrow seams).
func (*Server) Mux ¶
Mux registers the two routes using Go 1.22+ method-pattern routing (no chi/gorilla — matches the no-router codebase posture). When CORSPermissive is on (the dev knob) the handler is wrapped in withCORS so a browser cross-origin POST works end to end: the preflight OPTIONS is answered and ACAO is set on every response, including errors.
func (*Server) SetApprovalStore ¶ added in v1.0.0
func (s *Server) SetApprovalStore(store ApprovalStore)
SetApprovalStore wires the cross-thread HITL read store (APRV-01). It is set by the daemon composition root after NewServer; until set, the approvals read route answers 503 (the resolve route only needs the Runner and works regardless).
func (*Server) SetAssetService ¶ added in v1.0.0
func (s *Server) SetAssetService(service AssetService)
SetAssetService wires the upload/finalize/list asset API used by web and channels.
func (*Server) SetGovernanceProviders ¶ added in v1.0.1
func (s *Server) SetGovernanceProviders(p GovernanceProviders)
SetGovernanceProviders wires the read-only governance board providers (GOV-01/02/03). Set by the daemon composition root after NewServer; until set, the governance board routes (registered in Plan 02) answer 503. Kept off the constructor so existing NewServer callers/tests stay unchanged (D-A2-02 narrow seam).
func (*Server) SetGraphView ¶ added in v1.0.0
SetGraphView wires the read-only Phase-27 graph normalizer (GRAPH-01) the /api/graph/schema + /api/graph/query routes delegate to. Set by the daemon composition root after NewServer (boot-time knowledge.Client → knowledge.NewGraphView); until set, both routes answer 503 (a missing graph client must not abort serve boot). Kept off the constructor so existing NewServer callers/tests stay unchanged (D-A2-02 narrow seam).
func (*Server) SetImageProxy ¶ added in v1.0.0
func (s *Server) SetImageProxy(images ImageFetcher)
SetImageProxy wires the SSRF-safe image fetcher (D-09) the /api/image-proxy route delegates to. Set by the daemon composition root after NewServer (the *web.Client already wired for web_search/web_fetch); until set, the route answers 503. Kept off the constructor so existing NewServer callers/tests stay unchanged (D-A2-02).
func (*Server) SetOnboardingService ¶ added in v1.0.1
func (s *Server) SetOnboardingService(svc OnboardingService)
SetOnboardingService wires the onboarding provisioning + interview service (ONBD-01/ 02). Set by the daemon composition root after NewServer; until set, the onboarding routes (registered in Plan 05) answer 503. Kept off the constructor so existing NewServer callers/tests stay unchanged (D-A2-02 narrow seam).
type ServerConfig ¶
type ServerConfig struct {
CORSPermissive bool
BufferCap int
HealthCheck func(context.Context) error
HealthDetails func() map[string]any
ReadinessProbes []ReadinessProbe
}
ServerConfig carries the AG-UI server knobs the daemon resolves from config (AURA_AGUI_*). CORSPermissive gates the `Access-Control-Allow-Origin: *` header (default restrictive, T-12-13); BufferCap is the cap of the per-connection SSE pump channel (drop-on-full, never block the Loop, T-12-09). A non-positive BufferCap falls back to the fanout default. ReadinessProbes are the required-dependency checks /readyz runs (O-05/AP-14): /healthz stays a cheap LIVENESS check, /readyz reflects whether the required backends (PG + Neo4j) are reachable. An empty list reports ready (the daemon was started without gated deps).
type SkillsBoardProvider ¶ added in v1.0.1
type SkillsBoardProvider interface {
ActiveSkills() []skills.Skill
StageSkills(stage string) ([]skills.StageSkill, error)
AuditLog(ctx context.Context, filter skills.AuditFilter) ([]skills.AuditRow, error)
}
SkillsBoardProvider is the read-only skills-governance surface (GOV-02): the active snapshot (Loader.List), the per-stage pending/archived metadata (ListStage — never a mounted body), and the append-only audit ledger (newest-first). The pending/archived reader returns metadata only, so a pending body never enters context by construction.