Documentation
¶
Index ¶
- Constants
- Variables
- func ActiveTasksSummary(agentID string) string
- func AgentDir(id string) string
- func BuildMCPServers(agentID, apiBase string, hasSlackBot bool) map[string]mcpServerEntry
- func BuildVolatileContext(agentID string, queryContext string) string
- func DeleteTask(agentID, taskID string) error
- func DeleteTasksFile(agentID string)
- func GenerateAvatarWithAI(ctx context.Context, agentID string, persona string, name string, ...) (string, error)
- func GenerateName(persona string, userPrompt string) (string, error)
- func GeneratePersona(currentPersona string, userPrompt string) (string, error)
- func GeneratePublicProfile(persona string) (string, error)
- func GenerateSVGAvatarFile(name string) (string, error)
- func GenerateTOTPCode(secret, algorithm string, digits, period int) (string, int64, error)
- func IsAllowedImageExt(ext string) bool
- func IsInSilentHours(start, end string) bool
- func LoadGeminiAPIKey(creds *CredentialStore) (string, error)
- func NormalizeTOTPSecret(secret string) (string, error)
- func NormalizeThinkingMode(mode string) string
- func PreCompactSummarize(agentID string, tool string, transcriptPath string, logger *slog.Logger) error
- func PrepareClaudeSettings(agentID, apiBase string, allowProtectedPaths []string, logger *slog.Logger)
- func RecentDiarySummary(agentID string) string
- func SaveAvatar(agentID string, src io.Reader, ext string) error
- func ServeAvatar(w http.ResponseWriter, r *http.Request, a *Agent)
- func SetAgentTokenLookup(fn func(string) (string, bool))
- func SetKojoAPIBase(base string)
- func SummarizePersona(persona string) (string, error)
- func SummarizeWithCLI(tool string, persona string) (string, error)
- func ValidActiveHours(start, end string) error
- func ValidEffort(effort string) bool
- func ValidInterval(minutes int) bool
- func ValidModelEffort(model, effort string) bool
- func ValidResumeIdle(minutes int) bool
- func ValidSilentHours(start, end string) error
- func ValidThinkingMode(mode string) bool
- func ValidTimeout(minutes int) bool
- func ValidateTOTPParams(secret, algorithm string, digits, period int) (string, error)
- func ValidateTTS(c *TTSConfig) error
- func ValidateTempAvatarPath(avatarPath string) (string, error)
- type Agent
- type AgentConfig
- type AgentTokenStore
- type AgentUpdateConfig
- type BusySource
- type ChatBackend
- type ChatEvent
- type ChatOptions
- type ClaudeBackend
- type CodexBackend
- type Credential
- type CredentialStore
- func (s *CredentialStore) AddCredential(agentID, label, username, password string, totp *TOTPParams) (*Credential, error)
- func (s *CredentialStore) Close() error
- func (s *CredentialStore) DeleteAllForAgent(agentID string) error
- func (s *CredentialStore) DeleteCredential(agentID, credID string) error
- func (s *CredentialStore) DeleteToken(provider, agentID, sourceID, key string) error
- func (s *CredentialStore) DeleteTokensByAgent(agentID string) error
- func (s *CredentialStore) DeleteTokensBySource(provider, agentID, sourceID string) error
- func (s *CredentialStore) GetSetting(key string) string
- func (s *CredentialStore) GetTOTPCode(agentID, credID string) (string, int64, error)
- func (s *CredentialStore) GetToken(provider, agentID, sourceID, key string) (string, error)
- func (s *CredentialStore) GetTokenExpiry(provider, agentID, sourceID, key string) (string, time.Time, error)
- func (s *CredentialStore) ListCredentials(agentID string) ([]*Credential, error)
- func (s *CredentialStore) ListTokenKeys(provider, agentID, sourceID string) ([]string, error)
- func (s *CredentialStore) RevealPassword(agentID, credID string) (string, error)
- func (s *CredentialStore) SetSetting(key, value string) error
- func (s *CredentialStore) SetToken(provider, agentID, sourceID, key, value string, expiresAt time.Time) error
- func (s *CredentialStore) UpdateCredential(agentID, credID string, label, username, password *string, totp *TOTPParams) (*Credential, error)
- type CustomBackend
- type DirectoryEntry
- type ForkOptions
- type GeminiBackend
- type GroupDM
- type GroupDMManager
- func (m *GroupDMManager) APIBase() string
- func (m *GroupDMManager) AddMember(id, newAgentID, callerAgentID string) (*GroupDM, error)
- func (m *GroupDMManager) CheckMembership(groupID, agentID string) error
- func (m *GroupDMManager) Create(name string, memberIDs []string, cooldown int, style GroupDMStyle, ...) (*GroupDM, error)
- func (m *GroupDMManager) Delete(id string, notify bool) error
- func (m *GroupDMManager) Get(id string) (*GroupDM, bool)
- func (m *GroupDMManager) GroupsForAgent(agentID string) []*GroupDM
- func (m *GroupDMManager) LatestMessageID(groupID string) string
- func (m *GroupDMManager) LeaveGroup(id, agentID string) error
- func (m *GroupDMManager) List() []*GroupDM
- func (m *GroupDMManager) Messages(groupID string, limit int, before string) ([]*GroupMessage, bool, string, error)
- func (m *GroupDMManager) PostMessage(ctx context.Context, groupID, agentID, content, expectedLatestID string, ...) (*GroupMessage, error)
- func (m *GroupDMManager) PostUserMessage(ctx context.Context, groupID, content string, notify bool) (*GroupMessage, error)
- func (m *GroupDMManager) RemoveAgent(agentID string)
- func (m *GroupDMManager) Rename(id, name, callerAgentID string) (*GroupDM, error)
- func (m *GroupDMManager) SetAPIBase(base string)
- func (m *GroupDMManager) SetCooldown(id string, seconds int) (*GroupDM, error)
- func (m *GroupDMManager) SetMemberNotifyMode(groupID, agentID string, mode NotifyMode, digestWindow int, ...) (*GroupDM, error)
- func (m *GroupDMManager) SetStyle(id string, style GroupDMStyle, callerAgentID string) (*GroupDM, error)
- func (m *GroupDMManager) SetVenue(id string, venue GroupDMVenue, callerAgentID string) (*GroupDM, error)
- type GroupDMStyle
- type GroupDMVenue
- type GroupMember
- type GroupMessage
- type LlamaCppBackend
- type Manager
- func (m *Manager) Abort(agentID string)
- func (m *Manager) AgentTokenStore() AgentTokenStore
- func (m *Manager) Archive(id string) error
- func (m *Manager) BackendAvailability() map[string]bool
- func (m *Manager) BusySince(agentID string) (time.Time, bool)
- func (m *Manager) Chat(ctx context.Context, agentID string, userMessage string, role string, ...) (<-chan ChatEvent, error)
- func (m *Manager) ChatOneShot(ctx context.Context, agentID string, userMessage string) (<-chan ChatEvent, error)
- func (m *Manager) Checkin(agentID string) error
- func (m *Manager) ClearAllEmbeddings()
- func (m *Manager) CloseAllIndexes()
- func (m *Manager) Create(cfg AgentConfig) (*Agent, error)
- func (m *Manager) Credentials() *CredentialStore
- func (m *Manager) CronPaused() bool
- func (m *Manager) Delete(id string) error
- func (m *Manager) DeleteMessage(agentID, msgID string) error
- func (m *Manager) Directory() []DirectoryEntry
- func (m *Manager) Fork(srcID string, opts ForkOptions) (*Agent, error)
- func (m *Manager) Get(id string) (*Agent, bool)
- func (m *Manager) HasCredentials() bool
- func (m *Manager) IsAgentActive(agentID string) (bool, bool)
- func (m *Manager) IsAgentDMAvailable(agentID string) (bool, bool)
- func (m *Manager) IsBusy(agentID string) bool
- func (m *Manager) IsBusyForStatus(agentID string) bool
- func (m *Manager) IsPrivileged(id string) bool
- func (m *Manager) List() []*Agent
- func (m *Manager) Messages(agentID string, limit int) ([]*Message, error)
- func (m *Manager) MessagesPaginated(agentID string, limit int, before string) ([]*Message, bool, error)
- func (m *Manager) NextCronRun(agentID string) time.Time
- func (m *Manager) Regenerate(agentID, msgID string) error
- func (m *Manager) ResetData(id string) error
- func (m *Manager) ResetSession(agentID string) error
- func (m *Manager) SetCronPaused(paused bool)
- func (m *Manager) SetGroupDMManager(gdm *GroupDMManager)
- func (m *Manager) SetPrivileged(id string, privileged bool) error
- func (m *Manager) SetTokenStore(ts AgentTokenStore)
- func (m *Manager) Shutdown()
- func (m *Manager) Subscribe(agentID string) (startedAt time.Time, past []ChatEvent, live <-chan ChatEvent, unsub func(), ...)
- func (m *Manager) Unarchive(id string) error
- func (m *Manager) Update(id string, cfg AgentUpdateConfig) (*Agent, error)
- func (m *Manager) UpdateMessageContent(agentID, msgID, content string) (*Message, error)
- func (m *Manager) UpdateNotifySources(id string, sources []notifysource.Config) error
- func (m *Manager) UpdateSlackBot(id string, cfg *SlackBotConfig) error
- func (m *Manager) WatchChatStart(agentID string) (<-chan struct{}, func())
- type MemoryIndex
- func (idx *MemoryIndex) BuildContextFromQuery(query string) string
- func (idx *MemoryIndex) ClearEmbeddings()
- func (idx *MemoryIndex) Close() error
- func (idx *MemoryIndex) IndexFiles(agentID string) error
- func (idx *MemoryIndex) IndexFilesIfStale(agentID string)
- func (idx *MemoryIndex) IndexMessages(agentID string) error
- func (idx *MemoryIndex) IndexNewMessages(agentID string)
- func (idx *MemoryIndex) Reindex(agentID string) error
- func (idx *MemoryIndex) Search(query string, limit int) ([]SearchResult, error)
- type Message
- type MessageAttachment
- type MessagePreview
- type NotifyMode
- type OTPEntry
- type ReplyTagFilter
- type SearchResult
- type SlackBotConfig
- type StaleExpectedIDError
- type TOTPParams
- type TTSConfig
- type Task
- type TaskCreateParams
- type TaskUpdateParams
- type ToolUse
- type Usage
Constants ¶
const ( // DefaultEmbeddingModel is the Gemini embedding model used when no model // has been explicitly configured. Exported so HTTP handlers can present // the same fallback without duplicating the string. DefaultEmbeddingModel = "gemini-embedding-001" )
const ErrMsgCancelled = "cancelled: process was terminated"
ErrMsgCancelled is the error message attached to a "done" event when the backend process is terminated due to a user-initiated cancellation (context.Canceled) rather than a deadline.
const ErrMsgTimeout = "timeout: process was terminated"
ErrMsgTimeout is the error message attached to a "done" event when the backend process is terminated due to a context timeout.
const MaxConflictDiff = 50
MaxConflictDiff caps the number of messages returned to a caller whose expectedLatestMessageId is stale. Picked at 50 — same as the default GET messages page — so the conflict response stays small enough to inline in a single agent prompt while still covering normal traffic between two consecutive turns. When the diff exceeds the cap the caller is told (HasMore=true) to fetch the full transcript.
const MaxCronMessageRunes = 4096
MaxCronMessageRunes caps the per-agent cron check-in custom message at 4096 Unicode code points. The limit is in runes (not bytes) so Japanese users can write the same number of characters as ASCII users; the resulting byte size can be up to ~16 KiB worst case for 4-byte runes. The message is re-injected on every cron run so an unbounded value would inflate every prompt and the on-disk agent record.
const UserSenderID = "user"
UserSenderID is the reserved agent ID used for messages posted by the human user (operator) through the Web UI. It is never assigned to a real agent and is distinguished from agent senders by notifyState.senderIsUser.
const UserSenderName = "User"
UserSenderName is the display name recorded for user-authored messages.
Variables ¶
var ( ErrAvatarInternal = errors.New("cannot resolve temp dir") ErrAvatarNotFound = errors.New("file not found") ErrAvatarUnsupportedImage = errors.New("unsupported image format") )
Sentinel errors for ValidateTempAvatarPath, allowing callers to map to appropriate HTTP status codes.
var ( ErrAgentNotFound = errors.New("agent not found") ErrAgentBusy = errors.New("agent is busy") ErrAgentResetting = errors.New("agent is being reset") ErrAgentArchived = errors.New("agent is archived") ErrGroupNotFound = errors.New("group not found") ErrGroupNotMember = errors.New("agent is not a member of group") ErrGroupTooFew = errors.New("group requires at least 2 members") ErrGroupAlreadyMember = errors.New("agent is already a member of group") ErrCredentialNotFound = errors.New("credential not found") ErrNoTOTPSecret = errors.New("no TOTP secret configured") ErrInvalidTOTP = errors.New("invalid TOTP parameters") ErrUnsupportedTool = errors.New("unsupported tool") ErrUnsupportedInterval = errors.New("unsupported interval") ErrUnsupportedTimeout = errors.New("unsupported timeout") ErrInvalidRegenerate = errors.New("invalid regenerate target") )
Sentinel errors for the agent package.
var ErrMessageNotFound = errors.New("message not found")
ErrMessageNotFound is returned when a message with the given ID does not exist.
var ValidGroupDMStyles = map[GroupDMStyle]bool{ GroupDMStyleEfficient: true, GroupDMStyleExpressive: true, }
ValidGroupDMStyles is the set of accepted style values.
var ValidGroupDMVenues = map[GroupDMVenue]bool{ GroupDMVenueChatroom: true, GroupDMVenueColocated: true, }
ValidGroupDMVenues is the set of accepted venue values.
var ValidNotifyModes = map[NotifyMode]bool{ NotifyRealtime: true, NotifyDigest: true, NotifyMuted: true, }
ValidNotifyModes is the set of accepted notify-mode values.
Functions ¶
func ActiveTasksSummary ¶ added in v0.9.0
ActiveTasksSummary returns a formatted string of open tasks for system prompt injection. Returns empty string if no open tasks exist. Task data is wrapped in a code fence to prevent prompt injection from task titles.
func AgentDir ¶ added in v0.10.0
AgentDir returns the data directory path for the given agent ID. Exported for use by external subsystems (e.g. slackbot Hub) that need to resolve agent data directories without importing agent internals.
func BuildMCPServers ¶ added in v0.12.0
BuildMCPServers returns the set of MCP servers that should be available to the given agent. apiBase is the kojo server URL (e.g. "http://127.0.0.1:8080").
When the agent has its own kojo auth token (i.e. agentTokenLookup is wired up by the server) the per-agent /mcp endpoint requires the X-Kojo-Token header. Without it the call lands as a Guest principal and is denied by the auth middleware.
func BuildVolatileContext ¶ added in v0.15.1
BuildVolatileContext returns the per-turn context block prepended to a user message before it reaches the CLI backend. Everything that changes between turns belongs here, NOT in the system prompt — keeping it out of the system prompt is what lets Claude's prompt cache stay warm.
The block is wrapped in a `<context>...</context>` tag so the agent can recognise it as data, not instructions. Inner content is escaped so a stray `</context>` in a task title / diary entry / search snippet cannot close the outer tag and let authored data escape into instruction territory. The wrapper always carries at least the current `now: ...` line, so the return value is never empty.
queryContext is the search-results block from MemoryIndex.BuildContextFromQuery for the current user query. Pass "" when no index is available or the caller wants to skip query-based recall.
func DeleteTask ¶ added in v0.9.0
DeleteTask removes a task by ID.
func DeleteTasksFile ¶ added in v0.9.0
func DeleteTasksFile(agentID string)
DeleteTasksFile removes the tasks.json file for an agent (used during data reset).
func GenerateAvatarWithAI ¶
func GenerateAvatarWithAI(ctx context.Context, agentID string, persona string, name string, prompt string, logger *slog.Logger) (string, error)
GenerateAvatarWithAI generates an avatar by calling the Gemini image generation API directly. Returns the path to an image file inside a kojo-avatar-* temp dir.
func GenerateName ¶
GenerateName generates a character name based on persona description.
func GeneratePersona ¶
GeneratePersona elaborates or refines a persona description. currentPersona may be empty (generate from scratch) or non-empty (refine existing).
func GeneratePublicProfile ¶
GeneratePublicProfile creates a short outward-facing description from a persona.
func GenerateSVGAvatarFile ¶
GenerateSVGAvatarFile creates an SVG avatar file in a temp directory and returns its path. Used as fallback when AI avatar generation is unavailable.
func GenerateTOTPCode ¶
GenerateTOTPCode generates a current TOTP code for the given secret and parameters.
func IsAllowedImageExt ¶ added in v0.7.0
IsAllowedImageExt returns true if ext (case-insensitive) is an accepted avatar image extension.
func IsInSilentHours ¶ added in v0.18.0
IsInSilentHours checks if the current local time is within the silent window. Returns false if no restriction is set (both empty = never silent). Supports overnight ranges (e.g., 23:00-09:00).
func LoadGeminiAPIKey ¶ added in v0.19.0
func LoadGeminiAPIKey(creds *CredentialStore) (string, error)
LoadGeminiAPIKey is the exported wrapper around loadGeminiAPIKey for callers outside the agent package (e.g. internal/server's TTS handler). It applies the same priority order: encrypted credential store first, then the legacy nanobanana credentials file as a fallback.
func NormalizeTOTPSecret ¶
NormalizeTOTPSecret normalizes and validates a TOTP secret. Returns the canonical form (upper-case, no padding) or an error.
func NormalizeThinkingMode ¶ added in v0.10.1
NormalizeThinkingMode normalizes "auto" to "" for storage.
func PreCompactSummarize ¶ added in v0.9.0
func PreCompactSummarize(agentID string, tool string, transcriptPath string, logger *slog.Logger) error
PreCompactSummarize is called by the PreCompact hook (via API) just before Claude Code compacts the conversation. It reads from Claude's live session JSONL (which contains the full current context including pending tool uses) rather than kojo's messages.jsonl (which may lag behind). Falls back to messages.jsonl if session JSONL is unavailable.
transcriptPath, when non-empty, is the JSONL path that Claude's PreCompact hook supplied via stdin. It is validated to live under the agent's claude project directory before opening — the path is hook-supplied and must not be trusted blindly. On validation failure we silently fall back to project-dir discovery; we don't propagate the error because legitimate older claude builds may not populate the field.
Two guards short-circuit the work before any LLM call:
- The "no messages" case (nothing happened yet).
- The fingerprint check — if the last preCompactMaxMessages haven't changed since the previous summary, there's nothing new to record. The check is content-based (md5 of the stripped messages), not time-based: under PreCompact storms each fire usually carries new tool_use / tool_result content, and a time-only skip would lose exactly the short-term context we're trying to preserve. Per-agent serialisation (agentPreCompactLock) is what actually collapses concurrent fires.
Successful summaries update the marker, append to the daily diary, and atomically rewrite memory/recent.md (the canonical file the per-turn volatile context reads from). All three writes are serialised under a per-agent lock to prevent concurrent fires from racing on the marker or the recent.md tempfile.
func PrepareClaudeSettings ¶ added in v0.9.0
func PrepareClaudeSettings(agentID, apiBase string, allowProtectedPaths []string, logger *slog.Logger)
PrepareClaudeSettings writes .claude/settings.local.json with persona override, the bash-capture PreToolUse hook (always installed), and (when apiBase is available) a PreCompact hook that calls kojo's API to save a conversation summary before Claude compacts context. Called from Manager.Chat before backend.Chat to ensure settings are in place before the Claude process reads them.
func RecentDiarySummary ¶ added in v0.9.0
RecentDiarySummary returns the latest pre-compaction summary for per-turn volatile-context injection. Returns empty string when no summary has been generated yet.
Source preference:
- memory/recent.md — the rolling, single-summary file rewritten on every successful PreCompactSummarize. This is the canonical short- term memory file. Bounded size, no day-boundary problem.
- memory/YYYY-MM-DD.md — today's append-only diary. Fallback for legacy agents that haven't generated a summary since recent.md was introduced.
Content is wrapped in a `<diary-notes>` block so the agent recognises it as data, not instructions.
func SaveAvatar ¶
SaveAvatar saves an uploaded avatar file for an agent.
func ServeAvatar ¶
func ServeAvatar(w http.ResponseWriter, r *http.Request, a *Agent)
ServeAvatar serves the agent's avatar image, falling back to a generated SVG.
func SetAgentTokenLookup ¶ added in v0.17.0
SetAgentTokenLookup wires the token lookup callback. May be nil (disables token injection).
func SetKojoAPIBase ¶ added in v0.17.0
func SetKojoAPIBase(base string)
SetKojoAPIBase records the URL agents should use for self-authenticated API calls. Idempotent; safe to call repeatedly during boot.
func SummarizePersona ¶
SummarizePersona generates a concise summary of a persona.
func SummarizeWithCLI ¶
SummarizeWithCLI generates a persona summary using a specific CLI tool. Supports "claude", "codex", and "gemini".
func ValidActiveHours ¶ added in v0.7.0
ValidActiveHours is a compatibility alias for ValidSilentHours.
func ValidEffort ¶ added in v0.8.0
ValidEffort returns true if the given effort level is valid.
func ValidInterval ¶
ValidInterval returns true if the given interval is in the allowed set.
func ValidModelEffort ¶ added in v0.10.1
ValidModelEffort returns true if the model+effort combination is valid. xhigh is only allowed for specific models.
func ValidResumeIdle ¶ added in v0.15.0
ValidResumeIdle returns true if the given resume-idle window is in the allowed set.
func ValidSilentHours ¶ added in v0.18.0
ValidSilentHours validates the silent hours range. Both must be empty (no restriction) or both must be valid HH:MM format.
func ValidThinkingMode ¶ added in v0.10.1
ValidThinkingMode returns true if the given thinking mode is valid.
func ValidTimeout ¶ added in v0.10.0
ValidTimeout returns true if the given timeout is in the allowed set.
func ValidateTOTPParams ¶
ValidateTOTPParams validates TOTP parameters and normalizes the secret. Returns the normalized secret or an error.
func ValidateTTS ¶ added in v0.19.0
ValidateTTS checks a TTSConfig against the canonical model/voice allow-lists. Empty Model/Voice are accepted (treated as "use default" at synthesize time). StylePrompt is length-clipped, not validated.
func ValidateTempAvatarPath ¶ added in v0.7.0
ValidateTempAvatarPath validates that a path points to an image file inside a kojo-avatar-* temp directory. Returns the resolved absolute path or an error. Used by handlers that accept user-supplied avatar paths.
Types ¶
type Agent ¶
type Agent struct {
ID string `json:"id"`
Name string `json:"name"`
Persona string `json:"persona"` // persona description (markdown)
Model string `json:"model"` // e.g. "sonnet", "opus"
Effort string `json:"effort,omitempty"` // claude only: "low", "medium", "high", "xhigh", "max"
Tool string `json:"tool"` // CLI tool: "claude", "codex", "gemini"
WorkDir string `json:"workDir,omitempty"` // file storage directory (empty = agentDir)
IntervalMinutes int `json:"intervalMinutes"` // periodic execution interval in minutes (0 = disabled)
TimeoutMinutes int `json:"timeoutMinutes"` // max duration per cron run in minutes (0 = default 10)
// ResumeIdleMinutes is the idle-window threshold (in minutes) below
// which kojo keeps an over-token-threshold claude session via --resume
// instead of resetting. 0 = use defaultResumeIdleDuration (5 min).
// claude-only; ignored by other backends.
ResumeIdleMinutes int `json:"resumeIdleMinutes,omitempty"`
SilentStart string `json:"silentStart,omitempty"` // HH:MM — start of silent window (empty = no restriction)
SilentEnd string `json:"silentEnd,omitempty"` // HH:MM — end of silent window (empty = no restriction)
// NotifyDuringSilent controls whether the agent receives DM notifications
// during silent hours. Existing agents default to true (backward compat);
// new agents default to false.
NotifyDuringSilent *bool `json:"notifyDuringSilent,omitempty"`
// CronMessage overrides the trailing instruction in the periodic check-in
// prompt. Empty = use the default ("最近の出来事や気づきがあれば memory/...md に記録し、必要なタスクを実行してください。").
// The literal string "{date}" is replaced with today's date in YYYY-MM-DD form.
CronMessage string `json:"cronMessage,omitempty"`
CreatedAt string `json:"createdAt"` // RFC3339
UpdatedAt string `json:"updatedAt"` // RFC3339
// Legacy field — only used during migration from cronExpr-based configs.
// Not included in JSON output; consumed by store.Load migration.
LegacyCronExpr string `json:"cronExpr,omitempty"`
// Legacy fields — consumed by store.Load for activeStart/activeEnd →
// silentStart/silentEnd migration. Active hours are inverted to silent
// hours: silentStart = old activeEnd, silentEnd = old activeStart.
LegacyActiveStart string `json:"activeStart,omitempty"`
LegacyActiveEnd string `json:"activeEnd,omitempty"`
// HasAvatar indicates whether a custom avatar file exists.
HasAvatar bool `json:"hasAvatar"`
// AvatarHash is derived from the avatar file's modtime for cache busting.
AvatarHash string `json:"avatarHash,omitempty"`
// PublicProfile is a short outward-facing description generated from persona.
// Shared with other agents via the directory endpoint. Does not expose internal persona details.
PublicProfile string `json:"publicProfile,omitempty"`
PublicProfileOverride bool `json:"publicProfileOverride,omitempty"`
// CustomBaseURL is the base URL for a custom Anthropic Messages API endpoint
// (e.g., llama-server). Only used when Tool is "custom".
CustomBaseURL string `json:"customBaseURL,omitempty"`
// AllowedTools is a whitelist of tool names forwarded to a custom endpoint.
// If non-empty, only listed tools are forwarded.
// If empty, all tools are forwarded.
AllowedTools []string `json:"allowedTools,omitempty"`
// AllowProtectedPaths lists protected directory names (claude, git, husky)
// for which Edit/Write/MultiEdit prompts should be suppressed. Recent
// claude-code versions guard these dirs even under bypassPermissions —
// explicit permissions.allow rules are the only bypass.
AllowProtectedPaths []string `json:"allowProtectedPaths,omitempty"`
// ThinkingMode controls reasoning/thinking for llama.cpp backend.
// "on" = enable, "off" = disable, "" = server default.
ThinkingMode string `json:"thinkingMode,omitempty"`
// NotifySources holds notification source configurations for this agent.
NotifySources []notifysource.Config `json:"notifySources,omitempty"`
// SlackBot holds the Slack Socket Mode bot configuration for this agent.
SlackBot *SlackBotConfig `json:"slackBot,omitempty"`
// TTS holds per-agent text-to-speech configuration. Nil = TTS disabled
// (no synthesize/playback). The header toggle in the chat view is a
// session-level UI preference and lives in localStorage; this struct
// only governs voice/style/model when a synthesize call actually runs.
TTS *TTSConfig `json:"tts,omitempty"`
// LastMessage is a preview of the most recent message (for list display).
LastMessage *MessagePreview `json:"lastMessage,omitempty"`
// Archived is true when the agent has been archived via DELETE
// /api/v1/agents/{id}?archive=true. Archived agents retain most on-disk
// data (agent dir, credentials, notify tokens, messages, memory) but
// have all runtime activity stopped: cron, notify polling, one-shot
// chats, Slack bot, and inbound chats are refused with ErrAgentArchived.
// Group DM memberships are an exception — they are removed on archive
// (2-person groups are dissolved) and NOT restored on Unarchive.
// Restored via POST /api/v1/agents/{id}/unarchive.
Archived bool `json:"archived,omitempty"`
// ArchivedAt is the RFC3339 timestamp when Archived was last set.
// Cleared on unarchive.
ArchivedAt string `json:"archivedAt,omitempty"`
// Privileged grants the agent the ability to delete/reset other agents
// (but NOT to fork or read their full record). Owner-only mutation —
// the API strips this field from PATCH bodies and exposes a dedicated
// POST /api/v1/agents/{id}/privilege handler instead.
Privileged bool `json:"privileged,omitempty"`
}
Agent represents a persistent AI persona (friend).
func (*Agent) ResumeIdleDuration ¶ added in v0.15.0
ResumeIdleDuration returns the configured idle window for keeping an over-token-threshold claude session via --resume. ResumeIdleMinutes==0 (the default for legacy agents) maps to defaultResumeIdleDuration so existing behavior is preserved. Values outside the validated whitelist (e.g. left over from a hand-edited agents.json or a future schema) also fall back to the default — the API layer's whitelist must not be the only line of defence.
func (*Agent) ShouldNotifyDuringSilent ¶ added in v0.18.0
ShouldNotifyDuringSilent returns whether the agent should receive DM notifications while in silent hours. Existing agents with a nil pointer default to true for backward compatibility; new agents default to false.
type AgentConfig ¶
type AgentConfig struct {
Name string `json:"name"`
Persona string `json:"persona"`
Model string `json:"model"`
Effort string `json:"effort"`
Tool string `json:"tool"`
CustomBaseURL string `json:"customBaseURL"`
ThinkingMode string `json:"thinkingMode"`
WorkDir string `json:"workDir"`
IntervalMinutes *int `json:"intervalMinutes"` // nil = use default (30)
TimeoutMinutes *int `json:"timeoutMinutes"` // nil = use default (0 = 10 min)
// ResumeIdleMinutes overrides the per-agent claude --resume idle window.
// nil/0 = use defaultResumeIdleDuration (5 min).
ResumeIdleMinutes *int `json:"resumeIdleMinutes"`
SilentStart *string `json:"silentStart"` // HH:MM or empty
SilentEnd *string `json:"silentEnd"` // HH:MM or empty
NotifyDuringSilent *bool `json:"notifyDuringSilent"` // nil = use default (false for new)
CronMessage *string `json:"cronMessage"` // nil/empty = use default trailing instruction
}
AgentConfig is the request body for creating an agent.
type AgentTokenStore ¶ added in v0.17.0
type AgentTokenStore interface {
EnsureAgentToken(agentID string) error
RemoveAgentToken(agentID string)
}
AgentTokenStore is the minimal contract the agent manager needs from the auth token store, narrowed so the agent package does not import internal/auth (avoids a layering cycle).
type AgentUpdateConfig ¶
type AgentUpdateConfig struct {
Name *string `json:"name"`
Persona *string `json:"persona"`
PublicProfile *string `json:"publicProfile"`
PublicProfileOverride *bool `json:"publicProfileOverride"`
Model *string `json:"model"`
Effort *string `json:"effort"`
Tool *string `json:"tool"`
WorkDir *string `json:"workDir"`
IntervalMinutes *int `json:"intervalMinutes"`
TimeoutMinutes *int `json:"timeoutMinutes"`
ResumeIdleMinutes *int `json:"resumeIdleMinutes"`
SilentStart *string `json:"silentStart"`
SilentEnd *string `json:"silentEnd"`
NotifyDuringSilent *bool `json:"notifyDuringSilent"`
// CronMessage follows the standard *string PATCH convention used by every
// other field on this struct: nil/omitted = leave unchanged, "" = clear
// back to the built-in default trailing instruction.
CronMessage *string `json:"cronMessage"`
CustomBaseURL *string `json:"customBaseURL"`
ThinkingMode *string `json:"thinkingMode"`
AllowedTools []string `json:"allowedTools"`
AllowProtectedPaths *[]string `json:"allowProtectedPaths"`
// TTS replaces the entire TTSConfig when non-nil. Pass an explicit
// {enabled:false} to keep the field but disable synthesis; pass a
// fully-populated struct to enable.
TTS *TTSConfig `json:"tts,omitempty"`
}
AgentUpdateConfig is the request body for PATCH updates. Pointer fields distinguish "not provided" (nil) from "set to zero".
type BusySource ¶ added in v0.18.1
type BusySource int
BusySource identifies what triggered a busy state.
const ( // BusySourceUser is an interactive chat initiated by the user. BusySourceUser BusySource = iota // BusySourceCron is a scheduled cron check-in. BusySourceCron // BusySourceNotification is an automated notification (group DM, etc.) // that should not surface as "busy" in member status displays. BusySourceNotification )
type ChatBackend ¶
type ChatBackend interface {
// Chat sends a message and returns a channel of streaming events.
// The channel is closed when the response is complete.
Chat(ctx context.Context, agent *Agent, userMessage string, systemPrompt string, opts ChatOptions) (<-chan ChatEvent, error)
// Name returns the tool identifier (e.g. "claude", "codex", "gemini").
Name() string
// Available returns true if the CLI tool is installed and accessible.
Available() bool
}
ChatBackend abstracts a CLI tool for agent chat.
type ChatEvent ¶
type ChatEvent struct {
Type string `json:"type"` // "status", "text", "thinking", "tool_use", "tool_result", "done", "error", "message"
Status string `json:"status,omitempty"`
Delta string `json:"delta,omitempty"`
ToolUseID string `json:"toolUseId,omitempty"`
ToolName string `json:"toolName,omitempty"`
ToolInput string `json:"toolInput,omitempty"`
ToolOutput string `json:"toolOutput,omitempty"`
Message *Message `json:"message,omitempty"`
Usage *Usage `json:"usage,omitempty"`
ErrorMessage string `json:"errorMessage,omitempty"`
}
ChatEvent is streamed from backend to WebSocket during a chat.
type ChatOptions ¶ added in v0.10.0
type ChatOptions struct {
// OneShot skips session resumption, running a fresh ephemeral session.
// Used for Slack and other external platform conversations that have
// their own conversation context.
OneShot bool
// MCPServers is the set of MCP tool servers to make available for this
// chat session. Each backend injects them in its own way (CLI args,
// config files, etc.). May be nil if no MCP servers are configured.
MCPServers map[string]mcpServerEntry
// AutomatedTrigger marks the chat as a non-interactive system fire
// (cron, groupdm notification, notify poller, etc.) rather than a
// human-driven turn. Backends use this to disable the idle-window
// guard on session resets: there is no interactive conversation to
// preserve, so token conservation wins over continuity.
AutomatedTrigger bool
}
ChatOptions holds optional parameters for a chat invocation.
type ClaudeBackend ¶
type ClaudeBackend struct {
// contains filtered or unexported fields
}
ClaudeBackend implements ChatBackend using the Claude CLI with stream-json output.
func NewClaudeBackend ¶
func NewClaudeBackend(logger *slog.Logger) *ClaudeBackend
func (*ClaudeBackend) Available ¶
func (b *ClaudeBackend) Available() bool
func (*ClaudeBackend) Chat ¶
func (b *ClaudeBackend) Chat(ctx context.Context, agent *Agent, userMessage string, systemPrompt string, opts ChatOptions) (<-chan ChatEvent, error)
func (*ClaudeBackend) Name ¶
func (b *ClaudeBackend) Name() string
func (*ClaudeBackend) SetProxyURL ¶ added in v0.10.0
func (b *ClaudeBackend) SetProxyURL(url string)
SetProxyURL configures an ANTHROPIC_BASE_URL to inject into Claude CLI env.
type CodexBackend ¶
type CodexBackend struct {
// contains filtered or unexported fields
}
CodexBackend implements ChatBackend for the Codex CLI using app-server (JSON-RPC 2.0 over stdio) for real streaming support.
func NewCodexBackend ¶
func NewCodexBackend(logger *slog.Logger) *CodexBackend
func (*CodexBackend) Available ¶
func (b *CodexBackend) Available() bool
func (*CodexBackend) Chat ¶
func (b *CodexBackend) Chat(ctx context.Context, agent *Agent, userMessage string, systemPrompt string, opts ChatOptions) (<-chan ChatEvent, error)
func (*CodexBackend) Name ¶
func (b *CodexBackend) Name() string
type Credential ¶
type Credential struct {
ID string `json:"id"`
Label string `json:"label"`
Username string `json:"username"`
Password string `json:"password"`
TOTPSecret string `json:"totpSecret,omitempty"`
TOTPAlgorithm string `json:"totpAlgorithm,omitempty"` // SHA1 (default), SHA256, SHA512
TOTPDigits int `json:"totpDigits,omitempty"` // 6 (default) or 8
TOTPPeriod int `json:"totpPeriod,omitempty"` // 30 (default)
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
Credential represents a stored ID/password pair for an agent.
type CredentialStore ¶
type CredentialStore struct {
// contains filtered or unexported fields
}
CredentialStore manages encrypted credential storage in SQLite.
func NewCredentialStore ¶
func NewCredentialStore() (*CredentialStore, error)
NewCredentialStore opens or creates the encrypted credential store.
func (*CredentialStore) AddCredential ¶
func (s *CredentialStore) AddCredential(agentID, label, username, password string, totp *TOTPParams) (*Credential, error)
AddCredential adds a new credential for an agent.
func (*CredentialStore) Close ¶
func (s *CredentialStore) Close() error
Close closes the database connection.
func (*CredentialStore) DeleteAllForAgent ¶
func (s *CredentialStore) DeleteAllForAgent(agentID string) error
DeleteAllForAgent removes all credentials for an agent.
func (*CredentialStore) DeleteCredential ¶
func (s *CredentialStore) DeleteCredential(agentID, credID string) error
DeleteCredential removes a credential by ID.
func (*CredentialStore) DeleteToken ¶ added in v0.7.0
func (s *CredentialStore) DeleteToken(provider, agentID, sourceID, key string) error
DeleteToken removes a single token.
func (*CredentialStore) DeleteTokensByAgent ¶ added in v0.7.0
func (s *CredentialStore) DeleteTokensByAgent(agentID string) error
DeleteTokensByAgent removes all notify tokens for an agent.
func (*CredentialStore) DeleteTokensBySource ¶ added in v0.7.0
func (s *CredentialStore) DeleteTokensBySource(provider, agentID, sourceID string) error
DeleteTokensBySource removes all tokens for a specific source.
func (*CredentialStore) GetSetting ¶ added in v0.12.0
func (s *CredentialStore) GetSetting(key string) string
GetSetting retrieves a global setting value. Returns "" if the key is not found. Unexpected database errors (connection issues, schema drift, etc.) are logged rather than silently swallowed so they don't masquerade as a missing value.
func (*CredentialStore) GetTOTPCode ¶
func (s *CredentialStore) GetTOTPCode(agentID, credID string) (string, int64, error)
GetTOTPCode generates the current TOTP code for a credential.
func (*CredentialStore) GetToken ¶ added in v0.7.0
func (s *CredentialStore) GetToken(provider, agentID, sourceID, key string) (string, error)
GetToken retrieves and decrypts a token value.
func (*CredentialStore) GetTokenExpiry ¶ added in v0.7.0
func (s *CredentialStore) GetTokenExpiry(provider, agentID, sourceID, key string) (string, time.Time, error)
GetTokenExpiry retrieves a token's value and expiration time.
func (*CredentialStore) ListCredentials ¶
func (s *CredentialStore) ListCredentials(agentID string) ([]*Credential, error)
ListCredentials returns all credentials for an agent with secrets masked. Passwords and TOTP secrets are NOT decrypted — only metadata is returned.
func (*CredentialStore) ListTokenKeys ¶ added in v0.7.0
func (s *CredentialStore) ListTokenKeys(provider, agentID, sourceID string) ([]string, error)
ListTokenKeys returns all keys stored for a given provider/agent/source.
func (*CredentialStore) RevealPassword ¶
func (s *CredentialStore) RevealPassword(agentID, credID string) (string, error)
RevealPassword returns the plaintext password for a credential.
func (*CredentialStore) SetSetting ¶ added in v0.12.0
func (s *CredentialStore) SetSetting(key, value string) error
SetSetting stores a global setting value.
func (*CredentialStore) SetToken ¶ added in v0.7.0
func (s *CredentialStore) SetToken(provider, agentID, sourceID, key, value string, expiresAt time.Time) error
SetToken stores an encrypted token value. Use empty agent_id/source_id for global tokens (e.g., OAuth2 client secrets).
func (*CredentialStore) UpdateCredential ¶
func (s *CredentialStore) UpdateCredential(agentID, credID string, label, username, password *string, totp *TOTPParams) (*Credential, error)
UpdateCredential updates an existing credential. Only non-nil fields are applied.
type CustomBackend ¶ added in v0.10.1
type CustomBackend struct {
// contains filtered or unexported fields
}
CustomBackend implements ChatBackend for custom Anthropic Messages API endpoints (e.g., llama-server). It delegates to ClaudeBackend with ANTHROPIC_BASE_URL pointed at the custom endpoint.
func NewCustomBackend ¶ added in v0.10.1
func NewCustomBackend(logger *slog.Logger) *CustomBackend
func (*CustomBackend) Available ¶ added in v0.10.1
func (b *CustomBackend) Available() bool
Available returns true if the claude CLI is in PATH (required as the client).
func (*CustomBackend) Chat ¶ added in v0.10.1
func (b *CustomBackend) Chat(ctx context.Context, agent *Agent, userMessage string, systemPrompt string, opts ChatOptions) (<-chan ChatEvent, error)
func (*CustomBackend) Name ¶ added in v0.10.1
func (b *CustomBackend) Name() string
type DirectoryEntry ¶
type DirectoryEntry struct {
ID string `json:"id"`
Name string `json:"name"`
PublicProfile string `json:"publicProfile"`
}
DirectoryEntry is the minimal public info shared with other agents.
type ForkOptions ¶ added in v0.11.0
ForkOptions controls what state is copied into the forked agent.
type GeminiBackend ¶
type GeminiBackend struct {
// contains filtered or unexported fields
}
GeminiBackend implements ChatBackend for the Gemini CLI.
func NewGeminiBackend ¶
func NewGeminiBackend(logger *slog.Logger) *GeminiBackend
func (*GeminiBackend) Available ¶
func (b *GeminiBackend) Available() bool
func (*GeminiBackend) Chat ¶
func (b *GeminiBackend) Chat(ctx context.Context, agent *Agent, userMessage string, systemPrompt string, opts ChatOptions) (<-chan ChatEvent, error)
func (*GeminiBackend) Name ¶
func (b *GeminiBackend) Name() string
type GroupDM ¶
type GroupDM struct {
ID string `json:"id"`
Name string `json:"name"`
Members []GroupMember `json:"members"`
Cooldown int `json:"cooldown"` // notification cooldown in seconds (0 = use default)
Style GroupDMStyle `json:"style"` // communication style: "efficient" or "expressive"
// Venue is the physical setting hint. "chatroom" (default) for a closed
// online chat, "colocated" when members are co-present in real space.
Venue GroupDMVenue `json:"venue,omitempty"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type GroupDMManager ¶
type GroupDMManager struct {
// contains filtered or unexported fields
}
GroupDMManager manages group DM CRUD, message posting, and notifications.
func NewGroupDMManager ¶
func NewGroupDMManager(agentMgr *Manager, logger *slog.Logger) *GroupDMManager
NewGroupDMManager creates a new GroupDMManager.
func (*GroupDMManager) APIBase ¶
func (m *GroupDMManager) APIBase() string
APIBase returns the current API base URL.
func (*GroupDMManager) AddMember ¶ added in v0.8.1
func (m *GroupDMManager) AddMember(id, newAgentID, callerAgentID string) (*GroupDM, error)
AddMember adds an agent to an existing group DM. callerAgentID must be a current member. Notifies existing members about the new addition.
func (*GroupDMManager) CheckMembership ¶ added in v0.9.0
func (m *GroupDMManager) CheckMembership(groupID, agentID string) error
CheckMembership verifies that the group exists and the agent is a member.
func (*GroupDMManager) Create ¶
func (m *GroupDMManager) Create(name string, memberIDs []string, cooldown int, style GroupDMStyle, venue GroupDMVenue) (*GroupDM, error)
Create creates a new group DM with the given members. cooldown is the notification cooldown in seconds (0 = use default). style controls the communication style ("efficient" or "expressive"; empty = "efficient"). venue is the physical-setting hint ("chatroom" or "colocated"; empty = defaultGroupDMVenue).
func (*GroupDMManager) Delete ¶
func (m *GroupDMManager) Delete(id string, notify bool) error
Delete removes a group DM and its data. If notify is true, members are notified about the deletion before the group is removed.
func (*GroupDMManager) Get ¶
func (m *GroupDMManager) Get(id string) (*GroupDM, bool)
Get returns a group DM by ID.
func (*GroupDMManager) GroupsForAgent ¶
func (m *GroupDMManager) GroupsForAgent(agentID string) []*GroupDM
GroupsForAgent returns all groups that contain the specified agent.
func (*GroupDMManager) LatestMessageID ¶ added in v0.15.0
func (m *GroupDMManager) LatestMessageID(groupID string) string
LatestMessageID returns the cached head message ID for a group, or "" if the group has no messages (or does not exist — the caller is expected to check existence separately when that distinction matters).
func (*GroupDMManager) LeaveGroup ¶ added in v0.8.1
func (m *GroupDMManager) LeaveGroup(id, agentID string) error
LeaveGroup removes an agent from a group DM voluntarily. The group is deleted if fewer than 2 members remain.
func (*GroupDMManager) List ¶
func (m *GroupDMManager) List() []*GroupDM
List returns all group DMs.
func (*GroupDMManager) Messages ¶
func (m *GroupDMManager) Messages(groupID string, limit int, before string) ([]*GroupMessage, bool, string, error)
Messages returns paginated messages for a group plus the current head ID of the transcript. The head ID is the cursor agents pass back as expectedLatestMessageId on a subsequent PostMessage to opt into the CAS guard against racing posts. "" means the group has no messages yet.
Both `messages` and `latestMessageId` are derived from the *same* on-disk read so the response is internally consistent — a concurrent PostMessage cannot leave us returning a head ID that is absent from the returned slice (or vice versa). The in-memory cache is intentionally not consulted here; it is solely a CAS-check accelerator for the post path, and reading it would re-introduce the two-snapshot race.
The existence check is performed twice — once before the read to fail fast on unknown IDs, once after to catch a Delete that ran while we were reading the file. Without the post-read recheck a freshly-deleted group would surface as `200 OK` with an empty messages list because loadGroupMessages turns "file not found" into an empty result.
func (*GroupDMManager) PostMessage ¶
func (m *GroupDMManager) PostMessage(ctx context.Context, groupID, agentID, content, expectedLatestID string, notify bool) (*GroupMessage, error)
PostMessage posts a message to a group and optionally notifies other members.
expectedLatestID, when non-empty, enables compare-and-set (CAS) guarding: if it does not match the current head of the transcript, the call is rejected with *StaleExpectedIDError carrying the new head and a capped diff of messages that arrived since the caller's cursor. This is how agents avoid replying to a thread that has already moved on. Empty expectedLatestID skips the check entirely (legacy/admin path).
If notify is true, other members receive a system notification in their 1:1 chat. Set notify=false for messages sent from notification-triggered chats to prevent loops. The reserved UserSenderID ("user") must go through PostUserMessage; calls with that agentID are rejected so no agent can impersonate a human-user message.
func (*GroupDMManager) PostUserMessage ¶ added in v0.15.0
func (m *GroupDMManager) PostUserMessage(ctx context.Context, groupID, content string, notify bool) (*GroupMessage, error)
PostUserMessage posts a message from the human user (operator) to a group and notifies every member. Unlike PostMessage it bypasses membership checks because the human user is not a group member, and it never excludes anyone from the notification fan-out. CAS is intentionally not enforced for user posts: humans typing in the Web UI should not get 409s from the racing chatter of agents replying around them.
func (*GroupDMManager) RemoveAgent ¶
func (m *GroupDMManager) RemoveAgent(agentID string)
RemoveAgent removes an agent from all groups. Groups with fewer than 2 members are deleted.
func (*GroupDMManager) Rename ¶ added in v0.8.0
func (m *GroupDMManager) Rename(id, name, callerAgentID string) (*GroupDM, error)
Rename changes the name of a group DM. Only members can rename.
func (*GroupDMManager) SetAPIBase ¶
func (m *GroupDMManager) SetAPIBase(base string)
SetAPIBase sets the base URL for agent-facing API docs in system prompts.
func (*GroupDMManager) SetCooldown ¶ added in v0.8.0
func (m *GroupDMManager) SetCooldown(id string, seconds int) (*GroupDM, error)
SetCooldown updates the notification cooldown for a group (in seconds).
func (*GroupDMManager) SetMemberNotifyMode ¶ added in v0.15.0
func (m *GroupDMManager) SetMemberNotifyMode(groupID, agentID string, mode NotifyMode, digestWindow int, callerAgentID string) (*GroupDM, error)
SetMemberNotifyMode updates a single member's notify mode and digest window. Muting a member cancels any pending buffer / timer so queued noise does not reach them after the switch.
callerAgentID, when non-empty, must be a current member of the group AND must be active (not archived / not deleted). Empty callerAgentID is the admin/UI path and skips the check, matching the SetStyle/SetVenue convention.
func (*GroupDMManager) SetStyle ¶ added in v0.9.0
func (m *GroupDMManager) SetStyle(id string, style GroupDMStyle, callerAgentID string) (*GroupDM, error)
SetStyle updates the communication style for a group. callerAgentID must be a member. An empty callerAgentID skips the membership check (for admin/UI calls).
func (*GroupDMManager) SetVenue ¶ added in v0.15.0
func (m *GroupDMManager) SetVenue(id string, venue GroupDMVenue, callerAgentID string) (*GroupDM, error)
SetVenue updates the venue hint (chatroom / colocated) for a group. callerAgentID must be a member; empty skips the check (admin/UI). Mirrors SetStyle's auth convention so both group-wide settings flip the same way.
type GroupDMStyle ¶ added in v0.9.0
type GroupDMStyle string
GroupDM represents a group conversation between agents. GroupDMStyle controls the communication style for a group conversation. "efficient" (default): concise, token-saving, no pleasantries. "expressive": human-like chat with greetings and conversational filler.
const ( GroupDMStyleEfficient GroupDMStyle = "efficient" GroupDMStyleExpressive GroupDMStyle = "expressive" )
type GroupDMVenue ¶ added in v0.15.0
type GroupDMVenue string
GroupDMVenue is the physical/virtual setting that hosts the conversation. Agents use this hint to calibrate speech style: a co-located venue invites references to shared surroundings and gestures, while a chat room constrains everything to the text channel.
- "chatroom" (default): closed online chat room. Members are not physically together; the only shared context is what is sent here.
- "colocated": same physical space. Members are co-present in real time and may reference ambient cues, gestures, deictic ('this', 'over there') language.
const ( GroupDMVenueChatroom GroupDMVenue = "chatroom" GroupDMVenueColocated GroupDMVenue = "colocated" )
type GroupMember ¶
type GroupMember struct {
AgentID string `json:"agentId"`
AgentName string `json:"agentName"`
// NotifyMode is the per-member delivery mode. Empty string is treated as
// NotifyRealtime on read but omitted from JSON to keep legacy groups small.
NotifyMode NotifyMode `json:"notifyMode,omitempty"`
// DigestWindow is the digest-batching window in seconds. Only meaningful
// when NotifyMode == NotifyDigest. 0 means "use defaultDigestWindow".
DigestWindow int `json:"digestWindow,omitempty"`
}
GroupMember is a participant in a group DM.
type GroupMessage ¶
type GroupMessage struct {
ID string `json:"id"`
AgentID string `json:"agentId"`
AgentName string `json:"agentName"`
Content string `json:"content"`
Timestamp string `json:"timestamp"`
}
GroupMessage is a single message in a group DM transcript.
type LlamaCppBackend ¶ added in v0.10.1
type LlamaCppBackend struct {
// contains filtered or unexported fields
}
LlamaCppBackend implements ChatBackend by talking directly to llama-server's OpenAI-compatible /v1/chat/completions endpoint via HTTP SSE streaming. No CLI dependency — just needs HTTP access to the server.
func NewLlamaCppBackend ¶ added in v0.10.1
func NewLlamaCppBackend(logger *slog.Logger) *LlamaCppBackend
func (*LlamaCppBackend) Available ¶ added in v0.10.1
func (b *LlamaCppBackend) Available() bool
func (*LlamaCppBackend) Chat ¶ added in v0.10.1
func (b *LlamaCppBackend) Chat(ctx context.Context, agent *Agent, userMessage string, systemPrompt string, opts ChatOptions) (<-chan ChatEvent, error)
func (*LlamaCppBackend) Name ¶ added in v0.10.1
func (b *LlamaCppBackend) Name() string
type Manager ¶
type Manager struct {
// OnChatDone is called when an agent finishes its response.
OnChatDone func(agent *Agent, message *Message)
// contains filtered or unexported fields
}
Manager manages agent CRUD, chat orchestration, and lifecycle.
func NewManager ¶
NewManager creates a new agent manager.
func (*Manager) AgentTokenStore ¶ added in v0.17.0
func (m *Manager) AgentTokenStore() AgentTokenStore
AgentTokenStore returns the wired-in token store (may be nil during tests or before SetTokenStore is called).
func (*Manager) Archive ¶ added in v0.15.0
Archive marks an agent as archived without deleting most of its data.
All runtime activity is stopped: cron is unscheduled, the notify poller is detached, in-flight one-shot chats are cancelled, the cached memory index is closed. The agent stays in the in-memory map (with Archived=true) and on disk — credentials, notify tokens, messages, memory, persona, avatar, tasks are all retained. Inbound chats route through prepareChat, which refuses archived agents with ErrAgentArchived.
EXCEPTION: group DM memberships are removed (and 2-person groups are dissolved). A dormant member silently sitting in a chat room is more confusing than useful, so we treat group membership as an "active state" that doesn't survive archive. Unarchive does NOT restore memberships — the agent must be re-invited.
The handler is responsible for stopping the Slack bot, since the bot lifecycle is owned by the server (slackHub), not the agent manager.
Idempotent: calling Archive on an already-archived agent is a no-op.
func (*Manager) BackendAvailability ¶ added in v0.10.0
BackendAvailability returns which agent backends are available.
func (*Manager) BusySince ¶ added in v0.7.0
BusySince returns the time when the agent started its current chat. Returns zero time and false if the agent is not busy.
func (*Manager) Chat ¶
func (m *Manager) Chat(ctx context.Context, agentID string, userMessage string, role string, attachments []MessageAttachment, source ...BusySource) (<-chan ChatEvent, error)
Chat sends a message to an agent and returns a channel of streaming events. The role parameter controls how the input message is stored in the transcript ("user" for interactive chat, "system" for cron-triggered messages). An optional BusySource may be passed to tag the busy entry; defaults to BusySourceUser when omitted.
func (*Manager) ChatOneShot ¶ added in v0.10.0
func (m *Manager) ChatOneShot(ctx context.Context, agentID string, userMessage string) (<-chan ChatEvent, error)
ChatOneShot runs a one-shot chat that does not save to transcript (messages.jsonl) and does not resume the CLI session. Used for external platform conversations (Slack, Discord) that carry their own context. Memory (MEMORY.md, diary) access is still available via system prompt.
func (*Manager) Checkin ¶ added in v0.16.0
Checkin triggers a manual check-in for the agent. Unlike the periodic cron job, this does not acquire the cron lock and does not check the active-hours window — the user explicitly asked for it. The check-in runs asynchronously: events are drained in a background goroutine and the assistant reply is persisted to the transcript like any other chat.
Returns ErrAgentNotFound, ErrAgentArchived, or ErrAgentBusy on rejection.
func (*Manager) ClearAllEmbeddings ¶ added in v0.12.0
func (m *Manager) ClearAllEmbeddings()
ClearAllEmbeddings resets embeddings to NULL for all cached indexes. Called when the embedding model changes (dimensions may differ).
func (*Manager) CloseAllIndexes ¶ added in v0.9.0
func (m *Manager) CloseAllIndexes()
CloseAllIndexes closes all cached MemoryIndex instances.
func (*Manager) Create ¶
func (m *Manager) Create(cfg AgentConfig) (*Agent, error)
Create creates a new agent.
func (*Manager) Credentials ¶
func (m *Manager) Credentials() *CredentialStore
Credentials returns the credential store. Returns nil if the store failed to initialize.
func (*Manager) CronPaused ¶
CronPaused returns whether all cron jobs are globally paused.
func (*Manager) DeleteMessage ¶ added in v0.10.1
DeleteMessage removes a single message from the transcript. Only supported for the llama.cpp backend. Rejected with ErrAgentBusy while the agent has an active chat.
func (*Manager) Directory ¶
func (m *Manager) Directory() []DirectoryEntry
Directory returns minimal public info for all agents (for agent-to-agent discovery).
func (*Manager) Fork ¶ added in v0.11.0
func (m *Manager) Fork(srcID string, opts ForkOptions) (*Agent, error)
Fork creates a new agent by deep-copying the source agent's metadata and data files. Memory (MEMORY.md, memory/, persona, avatar) is always copied. Transcript (messages.jsonl) and its derived state (index/, autosummary marker, tasks.json) are copied only when IncludeTranscript is true.
External integrations are intentionally NOT copied: SlackBot, NotifySources, and credentials all require per-agent tokens that cannot be safely shared. CLI local state (.claude/, .gemini/) is also skipped so the fork starts a fresh session. WorkDir is cleared so the fork does not share external output storage with the source.
Known limitations: Manager.Update and the task API can write to persona.md / tasks.json without honoring the resetting flag, so the snapshot is not fully atomic against concurrent PATCH /agents/{id} or task mutations. The same looseness applies to Reset today.
func (*Manager) HasCredentials ¶
HasCredentials returns true if the credential store is available.
func (*Manager) IsAgentActive ¶ added in v0.18.0
IsAgentActive reports whether an agent is currently "active":
- Global cron is running (not paused)
- Agent is not archived
- Current time is NOT within the agent's silent hours
Returns (active, found). found is false when the agent ID is unknown.
func (*Manager) IsAgentDMAvailable ¶ added in v0.18.0
IsAgentDMAvailable reports whether an agent can receive DM notifications. Unlike IsAgentActive, this ignores global cron pause (DM delivery is independent of cron) and respects NotifyDuringSilent: an agent in silent hours is still "available" for DMs if they opted in.
Returns (available, found). found is false when the agent ID is unknown.
func (*Manager) IsBusyForStatus ¶ added in v0.18.1
IsBusyForStatus returns true only when the agent is busy with a user chat or cron job — automated notifications (group DM replies etc.) are excluded so that members don't all appear "busy" when responding to a broadcast notification.
func (*Manager) IsPrivileged ¶ added in v0.17.0
IsPrivileged returns whether the agent has the Privileged flag set. Used by auth.Resolver to map a per-agent token to RoleAgent vs RolePrivAgent.
func (*Manager) MessagesPaginated ¶
func (m *Manager) MessagesPaginated(agentID string, limit int, before string) ([]*Message, bool, error)
MessagesPaginated returns messages with cursor-based pagination.
func (*Manager) NextCronRun ¶ added in v0.16.0
NextCronRun returns the next scheduled run time for an agent, adjusted for silent hours. Returns the zero Time when there is no run to predict because the agent has no schedule, is archived, doesn't exist, or all cron is globally paused — anything that would make the displayed time misleading.
func (*Manager) Regenerate ¶ added in v0.10.1
Regenerate truncates the transcript at msgID and re-runs the associated user message through the llama.cpp backend. If msgID is an assistant message, msgID and all subsequent messages are removed and the preceding user message is re-sent. If msgID is a user message, all subsequent messages are removed and msgID itself is re-sent.
Returns after truncation and busy-slot registration so reloading clients can immediately see the in-progress chat. The backend request and event streaming run in the background; any backend.Chat failure is surfaced as an error event on the broadcaster (and persisted as a system message).
func (*Manager) ResetData ¶
ResetData removes conversation logs and memory but keeps settings, persona, avatar, and credentials.
func (*Manager) ResetSession ¶ added in v0.9.0
ResetSession clears the CLI session (e.g. Claude JSONL) for an agent without deleting conversation history or memory. The next chat will start a fresh CLI session with the full system prompt re-injected.
func (*Manager) SetCronPaused ¶
SetCronPaused sets the global cron pause state and persists it.
func (*Manager) SetGroupDMManager ¶
func (m *Manager) SetGroupDMManager(gdm *GroupDMManager)
SetGroupDMManager sets the group DM manager reference. Called after both managers are created to avoid circular dependency.
func (*Manager) SetPrivileged ¶ added in v0.17.0
SetPrivileged toggles the Privileged flag on the named agent and persists the change. Owner-only mutation enforced at the API layer.
func (*Manager) SetTokenStore ¶ added in v0.17.0
func (m *Manager) SetTokenStore(ts AgentTokenStore)
SetTokenStore wires the per-agent token store. Calling this after agents have been loaded triggers token bootstrap for each existing agent (so a kojo upgrade gets every agent a token without an explicit migration step).
func (*Manager) Shutdown ¶
func (m *Manager) Shutdown()
Shutdown stops all cron jobs, notify polling, and cancels active chats.
func (*Manager) Subscribe ¶ added in v0.7.0
func (m *Manager) Subscribe(agentID string) (startedAt time.Time, past []ChatEvent, live <-chan ChatEvent, unsub func(), busy bool)
Subscribe returns a snapshot of all past events and a live channel for an agent's ongoing chat. The caller must call unsub when done to free resources. If the agent is not busy, busy is false and all other values are zero.
func (*Manager) Unarchive ¶ added in v0.15.0
Unarchive restores a previously archived agent: clears the Archived flag, re-schedules its cron, and rebuilds notify sources. Slack bot re-arming is the handler's responsibility (server owns slackHub lifecycle).
Idempotent: calling Unarchive on a non-archived agent is a no-op.
Serializes against Archive via acquireResetGuard so an Archive/Unarchive race can't interleave flag flips with cron/poller operations. The idempotency check happens INSIDE the guard so we can't observe a stale "not archived" state while a concurrent Archive is mid-flight (the guard blocks until that Archive's cleanup() releases it).
func (*Manager) Update ¶
func (m *Manager) Update(id string, cfg AgentUpdateConfig) (*Agent, error)
Update updates an agent's configuration. Only non-nil fields are applied.
func (*Manager) UpdateMessageContent ¶ added in v0.10.1
UpdateMessageContent replaces the content of a single message in the transcript. Only supported for the llama.cpp backend. Rejected with ErrAgentBusy while the agent has an active chat.
func (*Manager) UpdateNotifySources ¶ added in v0.7.0
func (m *Manager) UpdateNotifySources(id string, sources []notifysource.Config) error
UpdateNotifySources updates the notification source configs for an agent and rebuilds the poller's source instances.
func (*Manager) UpdateSlackBot ¶ added in v0.10.0
func (m *Manager) UpdateSlackBot(id string, cfg *SlackBotConfig) error
UpdateSlackBot updates the Slack bot configuration for an agent. Pass nil to remove the configuration.
func (*Manager) WatchChatStart ¶ added in v0.10.0
WatchChatStart returns a channel that receives a signal whenever a new chat starts for the given agent. Call the returned function to unsubscribe.
type MemoryIndex ¶
type MemoryIndex struct {
// contains filtered or unexported fields
}
MemoryIndex provides FTS5-based keyword search across agent memory.
func OpenMemoryIndex ¶
func OpenMemoryIndex(agentID string, logger *slog.Logger, creds *CredentialStore) (*MemoryIndex, error)
OpenMemoryIndex opens or creates the FTS5 index for an agent.
func (*MemoryIndex) BuildContextFromQuery ¶
func (idx *MemoryIndex) BuildContextFromQuery(query string) string
BuildContextFromQuery searches the index and returns formatted context for injection into system prompt. Uses hybrid search (FTS5 + vector) when embeddings are available, falls back to FTS5 only.
func (*MemoryIndex) ClearEmbeddings ¶ added in v0.12.0
func (idx *MemoryIndex) ClearEmbeddings()
ClearEmbeddings resets all embeddings to NULL and clears the embedding cache. Called when the embedding model changes (dimensions may differ). Errors are logged rather than returned because callers are best-effort (an HTTP handler invalidating state on config change), but silent failure would leave the index in an inconsistent state.
func (*MemoryIndex) IndexFiles ¶
func (idx *MemoryIndex) IndexFiles(agentID string) error
IndexFiles indexes MEMORY.md and daily notes.
func (*MemoryIndex) IndexFilesIfStale ¶
func (idx *MemoryIndex) IndexFilesIfStale(agentID string)
IndexFilesIfStale re-indexes files only if they've changed since last index.
func (*MemoryIndex) IndexMessages ¶
func (idx *MemoryIndex) IndexMessages(agentID string) error
IndexMessages indexes messages from the JSONL transcript.
func (*MemoryIndex) IndexNewMessages ¶
func (idx *MemoryIndex) IndexNewMessages(agentID string)
IndexNewMessages incrementally indexes only new messages since last index.
func (*MemoryIndex) Reindex ¶
func (idx *MemoryIndex) Reindex(agentID string) error
Reindex re-indexes all sources for an agent (full rebuild).
func (*MemoryIndex) Search ¶
func (idx *MemoryIndex) Search(query string, limit int) ([]SearchResult, error)
Search performs a FTS5 search and returns relevant context snippets.
type Message ¶
type Message struct {
ID string `json:"id"`
Role string `json:"role"` // "user", "assistant", "system"
Content string `json:"content"`
Thinking string `json:"thinking,omitempty"` // intermediate reasoning (shown collapsed in UI)
ToolUses []ToolUse `json:"toolUses,omitempty"`
Attachments []MessageAttachment `json:"attachments,omitempty"`
Timestamp string `json:"timestamp"` // RFC3339
Usage *Usage `json:"usage,omitempty"`
}
Message represents a single chat message in the transcript.
type MessageAttachment ¶ added in v0.7.0
type MessageAttachment struct {
Path string `json:"path"`
Name string `json:"name"`
Size int64 `json:"size"`
Mime string `json:"mime"`
}
MessageAttachment represents a file attached to a chat message.
type MessagePreview ¶
type MessagePreview struct {
Content string `json:"content"`
Role string `json:"role"`
Timestamp string `json:"timestamp"`
}
MessagePreview is a short summary for agent list display.
type NotifyMode ¶ added in v0.15.0
type NotifyMode string
NotifyMode controls how a specific member receives group-DM notifications.
- "realtime" (default): notify as soon as the group-level cooldown allows.
- "digest": collect messages for up to DigestWindow seconds (or the group cooldown, whichever is larger) before delivering a single batched turn.
- "muted": do not notify this member at all. The member can still read messages via the API on their own initiative.
const ( NotifyRealtime NotifyMode = "realtime" NotifyDigest NotifyMode = "digest" NotifyMuted NotifyMode = "muted" )
type OTPEntry ¶
type OTPEntry struct {
Label string `json:"label"`
Issuer string `json:"issuer"`
Username string `json:"username"`
Secret string `json:"totpSecret"`
Algorithm string `json:"algorithm,omitempty"`
Digits int `json:"digits,omitempty"`
Period int `json:"period,omitempty"`
}
OTPEntry represents a parsed OTP entry from a QR code or migration payload.
func DecodeQRImage ¶
DecodeQRImage decodes a QR code from an image reader and returns parsed OTP entries.
func ParseOTPURI ¶
ParseOTPURI parses an otpauth:// or otpauth-migration:// URI and returns OTP entries.
type ReplyTagFilter ¶ added in v0.10.0
type ReplyTagFilter struct {
// contains filtered or unexported fields
}
ReplyTagFilter extracts text inside <reply>...</reply> tags from a stream of text deltas. Text outside the tags is treated as internal reasoning and discarded. If the stream ends without any <reply> tag, Flush returns all accumulated text as a graceful degradation (so a misbehaving agent still produces output).
func (*ReplyTagFilter) Feed ¶ added in v0.10.0
func (f *ReplyTagFilter) Feed(delta string) string
Feed processes a text delta and returns the portion that should be forwarded (text inside <reply> tags). Returns empty string for text outside tags or when buffering incomplete tags.
func (*ReplyTagFilter) Flush ¶ added in v0.10.0
func (f *ReplyTagFilter) Flush() string
Flush returns any remaining buffered content. Called when the stream ends. If no <reply> tag was ever seen, returns ALL accumulated text (graceful degradation so the agent's response is not silently swallowed).
type SearchResult ¶
type SearchResult struct {
Source string `json:"source"` // "message", "memory", "daily"
Snippet string `json:"snippet"` // text snippet with context
ContentHash string `json:"contentHash"` // for dedup/merge across search methods
Timestamp string `json:"timestamp"`
Score float64 `json:"score"`
}
SearchResult represents a single search hit.
type SlackBotConfig ¶ added in v0.10.0
type SlackBotConfig struct {
Enabled bool `json:"enabled"`
ThreadReplies bool `json:"threadReplies"` // always reply in-thread (default true)
// Reaction patterns — which message types the bot responds to.
// All default to true for backwards compatibility.
RespondDM *bool `json:"respondDM,omitempty"` // respond to direct messages
RespondMention *bool `json:"respondMention,omitempty"` // respond to @mentions in channels
RespondThread *bool `json:"respondThread,omitempty"` // auto-reply in threads with history
}
SlackBotConfig holds Slack bot configuration for an agent.
func (*SlackBotConfig) ReactDM ¶ added in v0.10.0
func (c *SlackBotConfig) ReactDM() bool
ReactDM returns whether the bot should respond to direct messages.
func (*SlackBotConfig) ReactMention ¶ added in v0.10.0
func (c *SlackBotConfig) ReactMention() bool
ReactMention returns whether the bot should respond to @mentions.
func (*SlackBotConfig) ReactThread ¶ added in v0.10.0
func (c *SlackBotConfig) ReactThread() bool
ReactThread returns whether the bot should auto-reply in threads with history.
type StaleExpectedIDError ¶ added in v0.15.0
type StaleExpectedIDError struct {
// Latest is the current head message ID of the group ("" if the group
// has no messages — practically only relevant when a stale cursor
// references a deleted-and-recreated group, which the system does not
// currently allow but the field must still be representable).
Latest string
// NewMessages is the diff of messages strictly newer than the caller's
// expectedLatestMessageId, in chronological order, capped at
// MaxConflictDiff entries (newest-kept).
NewMessages []*GroupMessage
// HasMore is true when older diff entries had to be dropped to fit the
// MaxConflictDiff cap, or when the caller's cursor was not found in the
// transcript at all (so the diff returned is "best effort latest" rather
// than a true delta).
HasMore bool
}
StaleExpectedIDError is returned by PostMessage when the caller's expectedLatestMessageId does not match the current group head. It carries the up-to-date head plus the diff of messages that arrived between the caller's cursor and the head, so the HTTP layer (or any other caller) can respond with a self-contained "you missed these" payload without forcing a second round-trip.
func (*StaleExpectedIDError) Error ¶ added in v0.15.0
func (e *StaleExpectedIDError) Error() string
type TOTPParams ¶
type TOTPParams struct {
Secret string
Algorithm string // SHA1, SHA256, SHA512
Digits int // 6 or 8
Period int // seconds
}
TOTPParams holds TOTP configuration for a credential.
type TTSConfig ¶ added in v0.19.0
type TTSConfig struct {
// Enabled gates the TTS feature for this agent. Even when the per-
// browser auto-play toggle is on, a disabled agent never synthesizes.
Enabled bool `json:"enabled"`
// Model is the Gemini TTS model id (e.g. "gemini-3.1-flash-tts-preview").
// Empty = use the kojo default.
Model string `json:"model,omitempty"`
// Voice picks one of the 30 prebuilt voices. Empty = default.
Voice string `json:"voice,omitempty"`
// StylePrompt is a free-form natural-language instruction prepended to
// the input ("落ち着いた日本語で淡々と…"). Empty = kojo default.
StylePrompt string `json:"stylePrompt,omitempty"`
}
TTSConfig holds per-agent text-to-speech configuration.
Validation lives in (*Manager).Update — the model and voice are checked against fixed allow-lists from internal/tts before being stored, so a hand-edited agents.json with garbage values can't reach the synthesize path.
type Task ¶ added in v0.9.0
type Task struct {
ID string `json:"id"`
Title string `json:"title"`
Status string `json:"status"` // "open", "done"
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
Task represents a persistent task for an agent.
func CreateTask ¶ added in v0.9.0
func CreateTask(agentID string, params TaskCreateParams) (*Task, error)
CreateTask adds a new task for an agent.
func UpdateTask ¶ added in v0.9.0
func UpdateTask(agentID, taskID string, params TaskUpdateParams) (*Task, error)
UpdateTask updates a task by ID.
type TaskCreateParams ¶ added in v0.9.0
type TaskCreateParams struct {
Title string `json:"title"`
}
TaskCreateParams is the request body for creating a task.
type TaskUpdateParams ¶ added in v0.9.0
TaskUpdateParams is the request body for updating a task.
type ToolUse ¶
type ToolUse struct {
ID string `json:"id,omitempty"` // tool call ID for matching results
Name string `json:"name"`
Input string `json:"input"` // JSON string
Output string `json:"output"` // truncated output
}
ToolUse represents a single tool invocation within a message.
type Usage ¶
type Usage struct {
InputTokens int `json:"inputTokens"`
OutputTokens int `json:"outputTokens"`
CacheReadInputTokens int `json:"cacheReadInputTokens,omitempty"`
CacheCreationInputTokens int `json:"cacheCreationInputTokens,omitempty"`
}
Usage tracks token consumption for a message.
CacheReadInputTokens / CacheCreationInputTokens are surfaced from Claude's stream so we can diagnose prompt-cache hit/miss patterns. A high CacheCreation:CacheRead ratio across consecutive turns indicates the system prompt or message prefix is changing between turns and breaking the cache — which directly inflates input cost and adds per-turn latency. (Note: cache state itself does not change the model's logical context length; what does grow is the volume of input tokens billed.)
Source Files
¶
- agent.go
- autosummary.go
- avatar.go
- backend.go
- backend_claude.go
- backend_codex.go
- backend_custom.go
- backend_gemini.go
- backend_llamacpp.go
- broadcaster.go
- compaction.go
- credential.go
- cron.go
- embedding.go
- errors.go
- generate.go
- groupdm.go
- groupdm_manager.go
- holiday_jp.go
- id.go
- jsonl.go
- manager.go
- manager_busy.go
- manager_fork.go
- manager_lifecycle.go
- manager_memory.go
- mcp_config.go
- memory.go
- memory_index.go
- message.go
- notify_poller.go
- reply_filter.go
- store.go
- task.go
- token_store.go
- totp.go
- transcript.go