policy

package
v0.32.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 5, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package policy is memcode's user-authored behavior layer: typed settings on named decision points, resolved deterministically at runtime.

The rule the whole package exists to enforce:

Policy chooses behavior. The model may not synthesize policy.

A user says "always review plans with grok"; the model translates that into one Set call; the runtime reads the result. What must never happen is a model deciding on its own that some task deserves a different model — that is the automatic routing memcode deleted, and it is not allowed back in through a settings API.

Two consequences run through the design. Policies are UNCONDITIONAL: every one applies every time, and nothing inspects or classifies a task to decide whether a policy fires. And fallbacks are OPERATIONAL only: a declared fallback exists for a provider that cannot be reached, never for a result that looked weak.

This is deliberately NOT internal/prefs. That system infers standing preferences from repeated signals and injects advisory prose into the prompt. This one is explicit, immediate, programmatic, and never inferred. Neither writes to the other.

Index

Constants

View Source
const (
	// ModeOff / ModeOffer / ModeAlways are the shared vocabulary for a step
	// that can be disabled, offered, or run every time. They are an enum rather
	// than a bool because "never review", "offer review" and "always review
	// with X" are three independent things, and a bool plus a nil model can
	// only express two of them.
	ModeOff    = "off"
	ModeOffer  = "offer"
	ModeAlways = "always"
)

Variables

View Source
var PersistedScopes = []Scope{ScopeWorkspace, ScopeUser}

PersistedScopes are the scopes backed by a file. Session and override are deliberately absent: they are held in memory by whoever owns them.

Functions

func Save

func Save(path string, s Set) error

Save writes a policy file atomically.

func SetField

func SetField(path string, t Target, field string, raw any) error

SetField validates a value and writes it to one scope's file.

func UnsetTarget

func UnsetTarget(path string, t Target) error

UnsetTarget removes a whole target from one scope, leaving every other scope untouched — resetting "how explore agents behave" in this repo must not disturb what was set everywhere.

func UserPath

func UserPath() string

UserPath is $XDG_CONFIG_HOME/memcode/policy.json, else ~/.config/memcode/policy.json. "" when no home directory can be determined.

func Validate

func Validate(t Target, field string, raw any) (any, error)

Validate normalises and checks a value against its field's kind. It runs at SET time so a bad policy is refused where the user can see it, not two turns later inside a failing call.

func WorkspacePath

func WorkspacePath(root string) string

WorkspacePath is <root>/.memcode/policy.json.

Types

type Field

type Field struct {
	Name string
	Kind Kind
	Enum []string // KindEnum / KindStrategy: the permitted values
	Min  int      // KindInt
	Max  int      // KindInt
	Def  any      // the component default — what applies when nothing is set anywhere
	Doc  string   // shown in `/policy` and in the tool description
}

Field is one typed knob on a target.

type Kind

type Kind string

Kind is the closed set of field types. Values are validated against their kind at SET time, so a malformed policy is refused where the user can see the refusal, rather than surfacing as a broken turn later.

const (
	KindModel     Kind = "model"      // a pinnable catalog label
	KindModelList Kind = "model_list" // an ordered list of pinnable labels
	KindEnum      Kind = "enum"       // one of Field.Enum
	KindInt       Kind = "int"        // an integer within [Min,Max]
	KindStrategy  Kind = "strategy"   // an enum that selects HOW a value is produced
)

type Option

type Option func(*resolveOpts)

Option customises one Resolve call.

func Override

func Override(t Target, fields map[string]any) Option

Override supplies values scoped to THIS operation. It is passed in by whoever owns the operation and is never stored: "review this plan with kimi" belongs to that plan and disappears when the plan does. There is no global consume-on-next-use state that could leak into an unrelated later operation.

type Resolved

type Resolved struct {
	Target Target
	Values map[string]Value
}

Resolved is a target's effective policy.

func (Resolved) Enum

func (r Resolved) Enum(field string) string

Enum returns an enum/strategy field.

func (Resolved) Int

func (r Resolved) Int(field string) int

Int returns an int field, falling back to the schema default.

func (Resolved) List

func (r Resolved) List(field string) []string

List returns a model_list field.

func (Resolved) Mode

func (r Resolved) Mode() string

Mode is the conventional name for a target's off/offer/always field.

func (Resolved) Model

func (r Resolved) Model(field string) string

Model returns a model-label field, "" when unset all the way down.

func (Resolved) Source

func (r Resolved) Source(field string) Scope

Source reports which scope supplied a field.

type Resolver

type Resolver struct {
	Session   Set
	Workspace Set
	User      Set

	// Primary is the session's primary model pin — where a model chain ends
	// for a schema that declares InheritsPrimaryModel.
	Primary string
}

Resolver holds the layers and answers Resolve. One per session.

func (*Resolver) Resolve

func (r *Resolver) Resolve(t Target, opts ...Option) Resolved

Resolve computes a target's effective policy, field by field.

Per field: override -> session -> workspace -> user -> parent target -> primary pin (if the schema ends there) -> the schema default. Per-field rather than per-target means a user-level model and a workspace-level fallback list compose instead of one silently replacing the other.

type Schema

type Schema struct {
	Target Target
	Doc    string
	Fields []Field

	// Parent is the target an unset field falls through to. Inheritance is
	// DECLARED here, never special-cased in the resolver: agent.explore names
	// agent.delegated as its parent, and a future agent.research or plan.scout
	// does the same and needs no resolution code of its own.
	Parent Target

	// InheritsPrimaryModel ends a model chain at the session's primary pin.
	// This is how "unset delegated means run everything on the user's own
	// model" is expressed as schema rather than as a hand-written chain.
	InheritsPrimaryModel bool
}

Schema declares a target's fields and where it inherits from.

func Lookup

func Lookup(t Target) (Schema, bool)

Lookup returns a target's schema.

func (Schema) Field

func (s Schema) Field(name string) (Field, bool)

Field returns a field by name.

type Scope

type Scope string

Scope names where a resolved value came from. Every resolved field reports one, because per-field resolution across four layers is only comprehensible if the user can ask "why is it that, and who set it?".

const (
	ScopeOverride  Scope = "override"  // this operation only
	ScopeSession   Scope = "session"   // this session
	ScopeWorkspace Scope = "workspace" // this repo
	ScopeUser      Scope = "user"      // everywhere
	ScopeInherited Scope = "inherited" // a parent target, or the primary pin
	ScopeDefault   Scope = "default"   // the schema's declared default
)

type Set

type Set map[Target]map[string]any

Set is one scope's contents: target -> field -> value.

func Load

func Load(path string) Set

Load reads a policy file. A missing or corrupt file is "no policy", never an error: a settings file that fails to parse must not stop a session starting.

func (Set) Clear

func (s Set) Clear(t Target)

Clear removes a whole target — the granularity "reset how explore agents behave" needs.

func (Set) Put

func (s Set) Put(t Target, field string, v any)

Put stores a validated value. Callers should go through Validate first; SetField does both.

type Target

type Target string

Target names a stable decision point. Adding a controllable behavior means registering a Target with a schema and calling Resolve where the decision is made — there is no other extension mechanism, and no target-specific resolution code anywhere.

const (
	// AgentDelegated is the model for delegated work: agent-tool workers,
	// scouts, plan research. Unset inherits the session's primary pin.
	AgentDelegated Target = "agent.delegated"
	// AgentExplore narrows AgentDelegated for read-only explore/scout agents.
	AgentExplore Target = "agent.explore"
	// PlanReview governs the second-model plan critique.
	PlanReview Target = "plan.review"
	// PlanAdvisor governs the plan-mode advisor.
	PlanAdvisor Target = "plan.advisor"
	// StartupModel governs how a session picks its primary model at launch.
	StartupModel Target = "startup.model"
	// UITheme governs the colour theme.
	UITheme Target = "ui.theme"
	// SessionEffort governs the default thinking depth.
	SessionEffort Target = "session.effort"
)

func Targets

func Targets() []Target

Targets lists every registered target in a stable order.

type Value

type Value struct {
	Raw    any
	Source Scope
}

Value is one resolved field plus where it came from.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL