Documentation
¶
Overview ¶
Package settings owns the settings a user changes while the daemon runs: the dry-run switch, the decision models and thresholds, retention, where trashed mail goes, whether the mailbox's own mail is left alone, and the model provider keys. They live in the settings table and lie over the environment (internal/config), which supplies the defaults. Provider keys are stored sealed under the tenant's data key and are never handed back out of this package except inside the config the model adapters are built from.
Index ¶
- Constants
- Variables
- func CheckModel(spec string) string
- func KeyNames() []string
- type ComposerMissing
- type ImportProblem
- type Invalid
- type Patch
- type Settings
- func (s *Settings) Apply(ctx context.Context, tenantID int64, p Patch) error
- func (s *Settings) Composer(ctx context.Context, tenantID int64) (models.Generator, error)
- func (s *Settings) Drafter(ctx context.Context, tenantID int64) (gen models.Generator, ledger store.Ledger, metered bool, err error)
- func (s *Settings) Effective(ctx context.Context, tenantID int64) (config.Config, error)
- func (s *Settings) ExportRules(ctx context.Context, v store.Viewer) (rules.File, error)
- func (s *Settings) Ledger(ctx context.Context, tenantID int64) store.Ledger
- func (s *Settings) Live(ctx context.Context, tenantID int64) (router *models.Router, minConfidence float64)
- func (s *Settings) LookupWorkspaces(ctx context.Context, tenantID int64) (WorkspaceLookup, error)
- func (s *Settings) PrepareImport(ctx context.Context, v store.Viewer, f rules.File) ([]rules.Rule, error)
- func (s *Settings) RetentionDays(ctx context.Context, tenantID int64) int
- func (s *Settings) RouterFor(ctx context.Context, tenantID int64, spec string) *models.Router
- func (s *Settings) View(ctx context.Context, tenantID int64) (View, error)
- type View
- type Warning
- type WorkspaceFinder
- type WorkspaceLookup
Constants ¶
const ( KeyStored = "stored" // saved through the API; it wins over the environment KeyEnvironment = "environment" // set in the daemon's environment only KeyNone = "none" )
Where a provider key in force comes from. Never the key.
const ( WorkspaceNoneNeeded = "none_needed" // the key was made for one workspace: nothing to name WorkspaceOne = "one" // one workspace, which MailRules stored as the setting WorkspaceSeveral = "several" // several: the user picks one WorkspaceFailed = "failed" // Anthropic could not say (no permission, network): the user types it )
What a lookup found out about the Claude key in force.
const DefaultRetentionDays = 30
DefaultRetentionDays is how long message snippets are kept until the user says otherwise.
Variables ¶
var Deciders = []string{"jev", "clef", "anthropic", "openai", "ollama"}
Deciders are the values MAILRULES_DECIDER accepts.
var ErrNoComposer = errors.New("no generative model is configured for the rule composer")
ErrNoComposer means no generative model is configured for the rule composer.
Functions ¶
func CheckModel ¶
CheckModel says, in a sentence, what is wrong with a rule's model override, or "" when it is a decider this daemon knows: empty (the default), a decider name, or name:model. openai and ollama have no default model.
Types ¶
type ComposerMissing ¶
type ComposerMissing struct {
Model string // composer_model as set, e.g. openai:gpt-4o-mini
Path string // the setting to fill in, as the API spells it
What string // what to call it, e.g. "an OpenAI API key"
}
ComposerMissing is ErrNoComposer with what the chosen composer model lacks: the one place that decides what the user is told to add.
func (*ComposerMissing) Error ¶
func (e *ComposerMissing) Error() string
func (*ComposerMissing) Is ¶
func (e *ComposerMissing) Is(target error) bool
Is makes errors.Is(err, ErrNoComposer) true.
func (*ComposerMissing) Message ¶
func (e *ComposerMissing) Message() string
Message is the sentence for a person: what the chosen model needs and where to add it.
type ImportProblem ¶ added in v0.1.2
type ImportProblem struct{ Path, Message string }
ImportProblem is something in a rules file that this install cannot take as written. Path names where it is (defaults.decision_model, rules[2].applies_to, rules[0].match); Message is a sentence for a person, complete without the path.
func (*ImportProblem) Error ¶ added in v0.1.2
func (e *ImportProblem) Error() string
type Invalid ¶
type Invalid struct{ Path, Message string }
Invalid is a settings change the user has to correct. Path names the field; Message is a sentence for a person, complete without the path.
type Patch ¶
type Patch struct {
Reset []string // setting names, as the API spells them
DryRun *bool
Decider *string
DeciderModel *string
FallbackModel *string
ComposerModel *string
EscalateBelow *float64
MinConfidence *float64
RetentionDays *int
TrashToFolder *bool
LeaveOwnMail *bool
OpenAIBaseURL *string // empty = api.openai.com
OllamaURL *string // empty = none
// AnthropicWorkspaceID is empty or a wrkspc_ ID (config.ValidWorkspaceID); empty = none.
AnthropicWorkspaceID *string
Keys map[string]string
}
Patch is a change: nil fields stay as they are. An empty string is a value like any other, stored as it is: no fallback, the provider's default model, no URL. Where an empty value cannot work (the composer's model, the model of a decider that has no default) it is refused. Reset names the settings to forget, which puts the environment's default back in force. In Keys, an empty value removes the stored key: a key cannot be "set to nothing".
type Settings ¶
type Settings struct {
Store *store.Store // with keys (store.Store.WithKeys): it opens the stored provider keys
Env *config.Config // the defaults
Deps models.Deps // shared by every router built here
// Workspaces looks up the workspace a Claude key needs; nil = never looked up, the
// user types it.
Workspaces WorkspaceFinder
// contains filtered or unexported fields
}
Settings reads and changes the stored settings. Every setting belongs to a tenant: each method names the tenant it reads or changes, and a tenant with nothing stored has the environment's defaults.
In MAILRULES_MODE=cloud the provider keys in the environment are the operator's: a tenant's calls use one only while that tenant has no key of its own for the provider, View never reports it (the key reads as not set, with no hint of where one is), and the calls made with it are booked with the operator mark (store.Ledger). In selfhost mode an environment key is the admin's own and is shown as set in the environment.
func (*Settings) Apply ¶
Apply validates a change against the settings it would produce and stores it in one transaction. A problem the user can fix comes back as *Invalid. Nothing needs a restart: the executor reads dry-run and trash_to_folder before every action, the pipeline reads leave_own_mail for every email, and Live rebuilds the router. A Claude key saved or replaced forgets what was learnt about the old one's workspace, and a workspace MailRules found for it; then, with no workspace set, it is looked up.
func (*Settings) Composer ¶
Composer returns the generative model the tenant's rule composer writes rules with: composer_model as config.SplitComposerModel reads it, on Anthropic, an OpenAI-compatible endpoint or Ollama. Until that provider has its key or URL it is a *ComposerMissing, which is ErrNoComposer. Its calls are booked by the caller, through Ledger.
func (*Settings) Drafter ¶ added in v0.3.0
func (s *Settings) Drafter(ctx context.Context, tenantID int64) (gen models.Generator, ledger store.Ledger, metered bool, err error)
Drafter returns the generative model a tenant's draft replies are written with: its rule composer model (Composer), on the tenant's own key or else the operator's, as sorting is paid. ledger books its calls; metered says they go out on a key the operator holds, so each reply written with it counts against the tenant's monthly allowance of AI-sorted emails.
func (*Settings) Effective ¶
Effective returns the tenant's configuration in force: the environment with the tenant's stored settings laid over it. It holds decrypted provider keys, so it is for building model adapters, never for showing.
func (*Settings) ExportRules ¶ added in v0.1.2
ExportRules is the viewer's tenant's rules as a rules file: each rule in priority order, with the address of the mailbox it applies to and of every mailbox its account conditions name (not the account's number, which means nothing on another install), and the models in force as the defaults the rules were written against. It holds no provider key. min_confidence is not a default here: a rule without its own threshold takes the setting at the time, so the file writes none and an import does not pin the current one onto it. A rule whose mailbox was removed has none and is written marked (rules.Rule.MailboxRemoved). Removing a mailbox takes it out of every rule (store.DeleteAccount, ReconcileRemovedMailboxes at start), so a rule naming a mailbox that no longer exists is a broken invariant, and stops the export rather than write a file that names a number. The rules are the tenant's, so a mailbox one of them names is looked up among all of the tenant's mailboxes, a teammate's private one too.
func (*Settings) Ledger ¶ added in v0.2.0
Ledger books a tenant's model calls made outside a router (the rule composer, a module), marking those made on an operator key as the tenant's settings stand now.
func (*Settings) Live ¶
func (s *Settings) Live(ctx context.Context, tenantID int64) (router *models.Router, minConfidence float64)
Live returns the tenant's decision router and act threshold for its settings as stored right now; pipeline.Pipeline.Live is this method. The router is rebuilt only when one of the tenant's stored settings has changed since the last call, whoever changed it, so a save in the browser reaches the next email without a restart. router is nil while the decider lacks its key.
func (*Settings) LookupWorkspaces ¶
LookupWorkspaces says which workspace the tenant's Claude key in force needs, as the Settings screen asks. A known answer (none needed, one, several) is given again without asking Anthropic; otherwise Anthropic is asked, and one workspace found while none is set is stored as the setting. The operator's key (cloud mode) is not the tenant's to ask about: it answers none needed.
func (*Settings) PrepareImport ¶ added in v0.1.2
func (s *Settings) PrepareImport(ctx context.Context, v store.Viewer, f rules.File) ([]rules.Rule, error)
PrepareImport checks a parsed rules file against this install and returns the rules to store, each with the mailbox it applies to and the mailbox ids its account conditions name. Nothing is stored here. A problem comes back as one *ImportProblem per fault, joined (errors.Join), and then nothing may be imported:
- The defaults' models are not applied (they are settings of the daemon, and a shared file must not change which paid model an install uses), so a file written for another model is refused with how to go on; one that names the models in force is fine.
- applies_to names a mailbox by its address, matched ignoring case. One that is not connected here, or that more than one connected mailbox has, is refused, since importing would make the rule act on every mailbox or none. "all" widens the rule to every mailbox. A rule the file says nothing about keeps the mailbox it has, or has none if it is new.
- An account condition, in match or unless at any depth, names mailboxes the same way and is refused the same way, as is one that names a mailbox by number.
- A rule marked mailbox_removed is stored off, marked and with no mailbox, whatever enabled says; one that also has applies_to contradicts itself and is refused. A marked rule here that the file names unmarked, with the same match and unless and no applies_to, keeps its mark: the file chose neither a mailbox nor new conditions.
The rules are compared with the viewer's tenant's, and a mailbox is matched only among the ones the viewer sees.
func (*Settings) RetentionDays ¶
RetentionDays returns the tenant's retention_days setting in force: how long message snippets are kept. When the settings cannot be read it is the default.
func (*Settings) RouterFor ¶
RouterFor returns the tenant's router for a rule's model override (a decider spec, see CheckModel), built from the tenant's settings in force and kept until they change. It is nil when that decider cannot be used, for example because its key is not set; the caller then decides with the default router. pipeline.Pipeline.Override is this method.
type View ¶
type View struct {
DryRun bool
Decider string
DeciderModel string
FallbackModel string // empty = never escalate
// FallbackActive says the fallback will really be asked: a model is set and the router
// can build it. FallbackNote is the reason when one is set and it is not, a sentence
// for a person; empty otherwise.
FallbackActive bool
FallbackNote string
ComposerModel string
EscalateBelow float64
MinConfidence float64
RetentionDays int
TrashToFolder bool // trash goes to actions.TrashFolder, not the server's Trash
LeaveOwnMail bool // mail from the mailbox's own address is left alone (pipeline.OwnMail)
OpenAIBaseURL string // an OpenAI-compatible endpoint; empty = api.openai.com
OllamaURL string // a local Ollama server
Keys map[string]string // KeyStored | KeyEnvironment | KeyNone
Warnings []Warning // never nil
// The Claude workspace every Claude request names; empty = none. Its name is known when
// a lookup listed it; Found says MailRules looked it up and stored it itself.
AnthropicWorkspaceID string
AnthropicWorkspaceName string
AnthropicWorkspaceFound bool
}
View is what may be shown: every setting in force, and for each provider key only where it comes from.
type Warning ¶
type Warning struct {
Code string `json:"code"`
Message string `json:"message"`
Path string `json:"path"`
}
Warning is something about the settings in force that will not work as it stands, which the user may still be on the way to fixing (the first-run wizard saves in steps), so it is reported and not refused. Path names the setting to fill in.
type WorkspaceFinder ¶
type WorkspaceFinder interface {
Find(ctx context.Context, apiKey string) (models.WorkspaceAnswer, error)
}
WorkspaceFinder asks Anthropic which workspace a Claude key needs. models.AnthropicWorkspaces is the one the daemon uses.
type WorkspaceLookup ¶
WorkspaceLookup is what a lookup answers: its status and, for one or several, the organisation's workspaces (never nil).