Documentation
¶
Overview ¶
Package app is the SHARED composition layer for the mecatl server: the one place that wires concrete adapters (LLM provider, tool catalog, permission policy, hooks, session store, MCP, skills) into an agent.Engine and exposes it as a server.Service. It is consumed by two composition roots:
- cmd/mecated — the standalone server binary (parses flags, builds the telemetry sink, calls Build, then serves the Service over gRPC + HTTP).
- cmd/mecatui — the TUI client, which when no external server is running calls Build to host its OWN server in-process over a UNIX socket, so a single binary "just works" with no separately-spawned daemon.
Layering: app sits at the SAME level as cmd/ — it is composition, not domain. It MAY import adapters, engine/agent, and (transitively, via the server adapter) contracts/gen; the domain/port/agent packages must never import it. Nothing imports app except the cmd/ mains.
Index ¶
- Constants
- Variables
- func HasProjectAuthority(cfg Config) bool
- func IsKnownPostureToken(s string) bool
- func LoginMCP(ctx context.Context, cfg mcp.ServerConfig, runtime *oauthlogin.Runtime) error
- func NormalizeReasoningEffort(token string) (string, bool)
- func PostureRefusalReason(p Posture, privileged bool) error
- func RememberTrust(cfg Config, trustedAt time.Time) error
- func ToolhiveAvailable(cfg Config) bool
- type Built
- type Config
- type DeliveryQueueOption
- type FileDeliveryQueue
- func (*FileDeliveryQueue) Close()
- func (q *FileDeliveryQueue) Enqueue(ctx context.Context, origin session.SessionID, text string) (port.DeliveryNote, error)
- func (q *FileDeliveryQueue) MarkDelivered(_ context.Context, origin session.SessionID, seq uint64) error
- func (q *FileDeliveryQueue) Pending(_ context.Context, origin session.SessionID) ([]port.DeliveryNote, error)
- type GuardrailRule
- type InMemoryDeliveryQueue
- func (*InMemoryDeliveryQueue) Close()
- func (q *InMemoryDeliveryQueue) Enqueue(_ context.Context, origin session.SessionID, text string) (port.DeliveryNote, error)
- func (q *InMemoryDeliveryQueue) MarkDelivered(_ context.Context, origin session.SessionID, seq uint64) error
- func (q *InMemoryDeliveryQueue) Pending(_ context.Context, origin session.SessionID) ([]port.DeliveryNote, error)
- type LearningAutomaticConfig
- type Posture
- type ProviderCredentials
- type RetentionCLISet
- type TrustDecision
- type TrustSource
Constants ¶
const DeliveryQueueFormat = "delivery-json/1"
DeliveryQueueFormat is the per-record format tag written on every pending-delivery line. It versions the on-disk encoding the same way jsonlstore's event-log tag does: an unknown tag on Read is an infra error (a forward-incompatible file must fail loud, not silently skip).
const SoulApplyAction = "soul:apply"
SoulApplyAction is the SYNTHETIC governance action key the soul load-gate evaluates (issue #14). It is NOT a real tool — the soul is fenced DATA applied at build time, not a tool the model invokes — but governance.Rule.Tool is a free string matched verbatim by the evaluator, so a synthetic colon-namespaced key rides the SAME deny→ask→allow machinery as a real tool. The colon namespace guarantees it can never collide with a real tool name (tool names are identifier-like, never colon-bearing). The soul is pre-approved at the built-in floor (see defaultRules), so it does not prompt by default but is explicit, auditable in source + logs, and overridable to ask/deny via operator config.
Variables ¶
var ( // ErrMCPLoginConfig reports that the resolved server is not eligible for a // one-shot OAuth login operation. ErrMCPLoginConfig = errors.New("mcp login: invalid server configuration") // ErrMCPLoginFailed is the parent category for redacted operational failures. ErrMCPLoginFailed = errors.New("mcp login: failed") // ErrMCPLoginAuthorization reports an unavailable or rejected authorization flow. ErrMCPLoginAuthorization = mcpLoginCategory("mcp login: authorization unavailable or login required") // ErrMCPLoginConnect reports failure to connect to or verify the MCP server. ErrMCPLoginConnect = mcpLoginCategory("mcp login: MCP connect or verification failed") // ErrMCPLoginCredential reports that a durable credential was not established. ErrMCPLoginCredential = mcpLoginCategory("mcp login: credential was not established or persisted") // ErrMCPLoginCleanup reports failure to close the temporary login resources. ErrMCPLoginCleanup = mcpLoginCategory("mcp login: temporary-session cleanup failed") )
Functions ¶
func HasProjectAuthority ¶
HasProjectAuthority reports whether cfg.Workspace carries a project AUTHORITY SET worth gating behind the pre-TUI trust prompt (a project soul, project-tier agents/commands/skills, or a project settings.yaml with ALLOW rules). It is the composition-side wrapper over the workspacetrust adapter's presence probe, so the mecatui prompt asks ONLY when a trust grant would actually change what is admitted — a not-trusted repo with nothing to gate is never nagged. It reuses trustEnv only for symmetry; the probe itself walks the workspace tree (no XDG read), so it is fail-safe quiet (any IO/parse failure ⇒ "no authority").
func IsKnownPostureToken ¶
IsKnownPostureToken reports whether s is a recognised posture token (so the cmd layer can WARN on an unknown --posture value before it silently fails closed to strict). Empty is "known" (the unset default).
func LoginMCP ¶
func LoginMCP(ctx context.Context, cfg mcp.ServerConfig, runtime *oauthlogin.Runtime) error
LoginMCP runs one host-authorized OAuth login against an already-resolved MCP server configuration. The runtime and credential store are borrowed. A nil error means the authenticated MCP initialize and initial tool listing completed, a usable credential was durably stored or restored, and the temporary server/controller were closed.
func NormalizeReasoningEffort ¶
NormalizeReasoningEffort validates and canonicalises a reasoning-effort token against the neutral vocabulary (ADR 0055). It trims + lowercases, treats "auto" and "" as UNSET (returns "", true), and returns ("", false) for any token outside the closed set — the caller treats false as "unset + WARN" (fail-soft: an unknown token must never produce a 400-causing wire param). It is the SINGLE validator for the neutral effort vocabulary, shared by composition + CLI folds.
func PostureRefusalReason ¶
PostureRefusalReason returns a non-nil error when a posture that GRANTS allow-all (auto or yolo — both waive the built-in mutate-ask floor) is requested while the process is PRIVILEGED (running as root WITHOUT a declared sandbox). It is the ONE definition of the root/no-sandbox refusal, shared by the cmd-layer fast-path and (authoritatively) by Build, which calls it AFTER applyPosture so an operator-YAML `posture:` tier cannot escape the refusal the CLI flags always hit. strict and trusted are NEVER refused (they suppress no prompt). The privileged predicate is computed in the cmd layer (it owns the os.Geteuid / MECATL_SANDBOX reads) and threaded in as a bool, keeping os out of internal/app.
func RememberTrust ¶
RememberTrust persists a "trust" grant for cfg.Workspace to the machine-written trust.yaml registry, capturing the LIVE identity-anchor hash at trustedAt. It is the ONLY production caller of workspacetrust.Remember (Phase 2c), invoked by the mecatui prompt when the operator answers "trust" (persist). "trust-once" trusts the run WITHOUT calling this (nothing is persisted). trustedAt is injected by the caller (composition supplies time.Now via a clock seam) so tests are deterministic. A write failure is returned for the caller to log fail-soft — a failed persist must never abort startup; the run still proceeds trusted.
func ToolhiveAvailable ¶
ToolhiveAvailable reports whether a ToolHive LLM gateway would be registered for this Config — an explicit --toolhive-llm-base-url, or a detected locally-running proxy. It runs the SAME resolveToolhiveIntent detection Build uses (one source of truth), so a client-side provider pre-check (mecatui's config.validate) agrees with what Build will actually resolve.
Types ¶
type Built ¶
Built is the result of Build: the assembled server.Service plus a Close func that tears down composition-owned resources. Close is always safe to call.
func Build ¶
Build assembles the LLM provider, session store, tool catalog, agent engine, and server.Service from cfg. It returns ErrNoProvider-class errors from the provider step and a fatal error if the SkillDraft trust boundary is misconfigured.
The returned Built.Close must be deferred by the caller to release the MCP manager on shutdown. Build itself starts no listeners — serving is the caller's responsibility (see cmd/mecated/serve and cmd/mecatui/embed).
type Config ¶
type Config struct {
Workspace string
// WorkspaceAuthority is the deployment's workspace-selection policy (ADR 0237).
// The zero value is client-selectable, preserving embedded and loopback use.
// The cmd/ main owns this decision: listener topology never reaches the server
// adapter.
WorkspaceAuthority server.WorkspaceAuthority
// AuthoritativeWorkspace is the root assigned to every filesystem session under
// WorkspaceAuthorityServerAssigned, which requires it. A file-less deployment
// selects WorkspaceAuthorityFileless and leaves this empty.
AuthoritativeWorkspace string
Model string
UseOpenAI bool
OpenAIKey string
// OpenAICodexCredential is the validated, immutable manual ChatGPT token
// snapshot consumed only by the distinct openai-codex registry entry.
OpenAICodexCredential openaicodex.Credential
UseMock bool
// MockProvider, when non-nil, REPLACES the canned UseMock turn with this
// scripted provider — the test-only seam for driving a full Build offline
// with scripted tool calls (UseMock scripts a single fixed text turn, which
// can never emit a tool call). It implies the mock registry entry (same
// short-circuit as UseMock); production cmd/ mains never set it.
MockProvider port.LLMProvider
StoreDir string
// RedisURL (ADR 0048, mecak8s) points the session store + durable event log
// at a Redis managed service (internal/adapter/redisstore). It is mutually
// exclusive with StoreDir and SessionStoreURL (validateDriverConfig: one
// store per seam). Empty keeps today's behaviour byte-identical. The Redis
// adapter reuses sessnap-json/1 snapshots + the event-log envelope shape, so
// it is a TRANSPORT alternative to jsonlstore — validated by the same
// conformance suites. The Store doubles as its own EventLog (like jsonlstore).
// The file paths name Kubernetes Secret mounts; their values are read only by
// redisstore and never projected into diagnostics.
RedisURL string
RedisUsernameFile string
RedisPasswordFile string
// RedisTLSCAFile is a PEM CA bundle path that REPLACES the system trust
// store; RedisTLS verifies against the system trust store instead. Either
// one satisfies the credentials-imply-verified-TLS policy (ADR 0233).
RedisTLSCAFile string
RedisTLS bool
RedisAllowPlaintext bool
Shell string
NoBash bool
// AuthorityEvaluator selects the authority evaluator adapter: "local" enforces
// minted sets, while "noop" deliberately disables enforcement. "cedar" loads
// CedarAuthorityPolicy at startup and fails closed when it cannot be loaded.
// Empty selects local; the no-op mode is never inferred from a missing evaluator.
AuthorityEvaluator string
CedarAuthorityPolicy string
// OwnershipEnforced enables application caller isolation when the command edge
// has configured the fail-closed OIDC verifier. Its zero value preserves
// existing ownerless deployments and hand-built test configurations.
OwnershipEnforced bool
// StorageManagementPrincipals are exact verified issuer/subject pairs granted
// process-wide storage health, migration, and cleanup authority. Empty grants
// nobody in an ownership-enforced deployment.
StorageManagementPrincipals []session.Principal
// LocalStorageManagement explicitly grants the private embedded single-user
// server management authority. It is invalid with OwnershipEnforced and is
// never set by remotely reachable composition roots.
LocalStorageManagement bool
// DefaultProvider/DefaultModel are the SERVER-CONFIGURED deployment-wide
// default (issue #21; --default-provider / --default-model — the wire's
// two-field provider_id+model_id grammar, never a slash-joined string),
// DISTINCT from Model (the --model operator override): they slot into the
// effective-model precedence chain BELOW client-side defaults (a client
// selector still wins) and ABOVE the hardcoded per-provider builtin
// (builtinDefaultModel). DefaultProvider overrides the preferred default
// provider when available; DefaultModel is the default model for the
// resolved default provider. Both are validated FAIL-FAST at Build
// (validateDefaultModel): an unknown/unavailable provider or an
// uncatalogued model is a startup error — stricter than per-session
// selectors (which allow passthrough), because a deployment default must
// be known-good. Ignored under UseMock (the mock provider isn't
// catalogued; the mock path never consults the resolved default).
DefaultProvider string
DefaultModel string
// DefaultProviderFlagSet records whether the operator passed an explicit
// --default-provider flag. When true, foldOperatorDefaultProvider leaves the
// operator-YAML models.default_provider: value alone (CLI out-ranks YAML, mirroring
// posture/reasoning-effort). Set by the cmd mains alongside
// DefaultProvider. The YAML value folds onto DefaultProvider so an operator can
// declare "toolhive is my default despite my API key" persistently in settings.yaml.
DefaultProviderFlagSet bool
// OpenRouter (multi-provider Phase 0, S1): the OpenRouter provider rides the
// SAME stateless openai adapter (it speaks the Responses API) with the
// OpenRouter base URL substituted. OpenRouterKey is the credential (the cmd
// layer reads it from OPENROUTER_API_KEY); when empty the registry falls back
// to the OPENROUTER_API_KEY / OPENAI_API_KEY env vars via its envDetector.
// OpenRouterKey is the credential for the OpenRouter Responses-compatible
// endpoint; its effective endpoint comes from ProviderOverrides.
OpenRouterKey string
// ProviderDefinitions and CustomProviderAPIKeys are the validated operator
// snapshot supplied by command wiring. Custom keys are never read from the
// environment and must not be logged.
ProviderDefinitions permconfig.ProviderDefinitions
CustomProviderAPIKeys map[string]string
// ProviderCredentialLoader resolves credentials for the one operator provider-definition
// snapshot Build owns. Command roots inject the cliconfig adapter; Build calls it once
// before default selection and registry construction and closes its lifecycle.
ProviderCredentialLoader interface {
Load(permconfig.ProviderDefinitions) (ProviderCredentials, interface{ Close() error }, error)
}
ProviderCredentialLifecycle interface{ Close() error }
// ProviderOverrides is the effective built-in endpoint source. Command-root CLI
// overrides are merged over operator settings before registry construction.
ProviderOverrides permconfig.ProviderOverrides
// OpenCode Go: the OpenCode Go gateway (https://opencode.ai/zen/go/v1) over
// the native Chat Completions adapter (openaichat). OpenCodeKey is the
// credential (the cmd layer reads it from OPENCODE_API_KEY); when empty the
// registry falls back to the OPENCODE_API_KEY env var via its envDetector.
// OpenCodeKey is the credential; its effective endpoint comes from
// ProviderOverrides.
OpenCodeKey string
// Anthropic (multi-provider P1): the native Anthropic Messages-API provider.
// AnthropicKey is the credential (the cmd layer reads it from ANTHROPIC_API_KEY);
// when empty the registry falls back to the ANTHROPIC_API_KEY env var via its
// envDetector. Its effective compatible/proxy endpoint comes from
// ProviderOverrides.
AnthropicKey string
// ToolhiveLLM (issue #262) opts INTO auto-detecting a locally-running
// ToolHive LLM gateway proxy: reading ToolHive's own config file (via the
// toolhivellm adapter) and, if an `llm:` block is found, registering an
// intent-driven "toolhive" provider entry — no API key needed. The ZERO
// VALUE is false so every existing hand-built Config / test is
// byte-identical with no edits; the `--toolhive-llm` FLAG DEFAULTS TRUE
// (the cmd layer supplies the default-ON posture, mirroring `--toolhive`
// for MCP workload discovery — an unrelated feature despite the similar
// name). Registration is probe-independent (R1.1): the proxy need not be
// running yet for the entry to exist.
ToolhiveLLM bool
// ToolhiveLLMBaseURL, when non-empty, is an EXPLICIT ToolHive LLM proxy
// base URL override: it skips the config-file auto-detect entirely (the
// operator is telling us exactly where the proxy is) but keeps the
// startup probe (WARN, not silent, on failure). Validated at Build to
// resolve to loopback ONLY (validateToolhiveBaseURL) — v1 has no
// off-host path. No environment-variable twin (R4.2): a base URL this
// security-sensitive is a deliberate, visible flag, never an ambient var.
ToolhiveLLMBaseURL string
// ToolhiveLLMMode (issue #265) selects the toolhive provider's routing:
// "auto" (the default) picks direct when the ToolHive config's OIDC trio
// (gateway_url + issuer + client_id) is configured, else falls back to the
// loopback proxy (today's byte-identical behaviour when OIDC is absent);
// "proxy" forces the loopback reverse proxy regardless of OIDC; "direct"
// forces the real gateway_url with an in-process OIDC token, and fails
// Build (validateToolhiveLLMMode) when OIDC is not configured. The empty
// value is "auto" so a hand-built Config / test that never set the flag
// stays byte-identical to the pre-#265 default. An explicit
// --toolhive-llm-base-url override is ALWAYS proxy mode (it is a loopback
// address; direct derives its base URL from the config's gateway_url), so
// this flag is ignored on the override path.
ToolhiveLLMMode string
// Context management: the compaction strategy ("heuristic"|"cascade") and the
// token counter ("heuristic"|"tiktoken"). Empty means "heuristic".
Compaction string
Tokenizer string
// LLM resilience knobs (see internal/adapter/llmresilience).
LLMMaxAttempts int
LLMPerAttemptTimeout time.Duration
LLMStreamIdleTimeout time.Duration
LLMBreakerThreshold int
LLMBreakerCooldown time.Duration
// Provider-side prompt caching (ADR 0100). PromptCacheDisabled (wired from
// --no-prompt-cache) forces every adapter's cache dialect to None,
// reproducing the pre-ADR-0100 wire exactly — the ZERO VALUE is false, so
// caching defaults ON and every existing hand-built Config / test still
// gets the anthropic adapter's byte-identical-default behaviour (only the
// TTL field's default omits "ttl"; the StablePrefix breakpoint itself
// predates this feature). AnthropicCacheTTL (wired from
// --anthropic-cache-ttl) accepts "5m" or "1h"; any other value is
// normalised to "" (omit) with a WARN — see normaliseAnthropicCacheTTL.
PromptCacheDisabled bool
AnthropicCacheTTL string
// MaxNoProgressNudges bounds how many continuation nudges the loop injects after
// a completed turn that produced NEITHER a tool call NOR meaningful text (a
// reasoning-only / empty turn that a reasoning model can emit). It is threaded
// through engineDepsForProvider to agent.Deps.MaxNoProgressNudges. Semantics
// (applied in agent.NewEngine): ZERO (the default; operators who never set it)
// uses the safety-net default of 2; NEGATIVE disables nudging; positive overrides.
// It is operator-tunable but defaults to the safe non-zero behaviour without any
// flag. Child/member/lead engines inherit it via engineDepsForProvider.
MaxNoProgressNudges int
// MaxRunTokens is the loop-level cumulative token ceiling for a single run (the
// shared runaway brake serving main + Subagent + Team + Fork). It is threaded through
// engineDepsForProvider to agent.Deps.MaxRunTokens and INHERITED by every child/
// member/lead engine (childEngineDepsForProvider keeps it). Semantics (in the loop):
// 0 (the default; operators who never set it) DISABLES the budget, so existing
// behaviour is byte-identical; a positive value is the ceiling and a run that crosses
// it terminates cleanly with session.StopBudget (Reopen-recoverable). Operator-tunable
// via --max-run-tokens.
MaxRunTokens int
// MaxTeamTokens is the TEAM-WIDE cumulative token budget threaded into every team
// (agent.WithTeamToolTokenBudget for the in-catalog Team tool, server.Config.TeamTokenBudget
// for the gRPC CreateTeam path). It is checked at the ROUND boundary: when crossed the
// team stops scheduling new rounds while the in-flight round and the lead's synthesis
// still complete. 0 (the default) disables it. It is ORTHOGONAL to MaxRunTokens, which
// bounds ONE member drive and resets on Reopen each round — both compose. Operator-tunable
// via --max-team-tokens.
MaxTeamTokens int
// Memory: per-project memory store directory (empty disables the tools), plus
// the background consolidation (dream) interval (0 disables; only meaningful
// with MemoryDir set).
MemoryDir string
MemoryConsolidateInterval time.Duration
// Child-session retention/GC (issue #38): the delegation paths persist every
// child snapshot (subagent-*/parallel-*/team-* ids) so InspectSubagent/
// InspectMember/resume: work, but nothing ever deleted them — a durable store
// grew without bound. startChildGC sweeps them through the OPTIONAL
// port.PrunableStore seam: an age pass (delete child snapshots whose
// last-modified time is older than ChildRetention; 0 disables) then a
// per-family count cap (the newest ChildRetentionMaxPerFamily per prefix
// family survive, oldest-first past it deleted; 0 disables), skipping ids
// with an in-flight run. UNPREFIXED (operator/service) sessions are NEVER
// touched. ChildGCInterval is the sweep cadence after the startup sweep
// (0 = startup-only) — it is the SINGLE shared interval for the whole sweep,
// so it ALSO governs the MainRetention/MainRetentionMaxTotal passes below
// (there is no separate main-GC interval). Both knobs zero = fully disabled
// (the zero-config default; mecated's flags default to 168h/500/1h). A
// non-prunable store (e.g. a thin remote driver) is never swept — a no-op
// with one INFO.
ChildRetention time.Duration
ChildRetentionMaxPerFamily int
ChildGCInterval time.Duration
// Main-session retention/GC (issue #79): the durable session store also
// accumulates the TOP-LEVEL (operator/service) session snapshots, which the
// child sweep above NEVER touches. When a long-lived mecatui defaults its
// StoreDir on, that store would otherwise grow without bound. The same
// startChildGC sweeper handles them via the OPTIONAL port.PrunableStore seam:
// an age pass (delete main snapshots whose last-modified time is older than
// MainRetention; 0 disables) then a single GLOBAL count cap (the newest
// MainRetentionMaxTotal main snapshots survive, oldest-first past it deleted;
// 0 disables), always skipping ids with an in-flight run. Both knobs zero =
// the main pass is fully disabled (the zero-config default; mecated's flags
// default to 0/0 so its behaviour is byte-unchanged, mecatui defaults them on).
// A non-prunable store (e.g. a thin remote driver) is never swept.
MainRetention time.Duration
MainRetentionMaxTotal int
// Schedule-fire retention/GC (ADR 0059 decision #7 Phase-2): the durable
// session store accumulates a "sched--"-prefixed TOP-LEVEL session per fire
// (the fire id IS the session id). This is a DISTINCT family from the
// operator/service mains (MainRetention) and the delegation children
// (ChildRetention): a sched-- session is swept by its OWN age pass
// (sweepScheduleFires), never the main or child pass. The retention is the age
// horizon — a fire-session snapshot whose last-modified time is older than
// ScheduleFireRetention is deleted, always skipping a LIVE fire (one
// mid-run). 0 disables the pass (fire sessions are never swept). Default
// applied at the cmd layer: 7*24h (7 days) when scheduling is on, so a
// durable store does not grow without bound; 0 (the zero-config default)
// leaves fire sessions untouched (byte-identical to pre-Phase-2). A
// non-prunable store is never swept.
ScheduleFireRetention time.Duration
// ScheduleFireRetentionMaxTotal is the GLOBAL count cap over "sched--" fire
// sessions (the symmetric peer of MainRetentionMaxTotal; ADR 0059 Phase-2): the
// newest ScheduleFireRetentionMaxTotal fire snapshots survive, oldest-first
// past it deleted, always skipping a LIVE fire. The age horizon
// (ScheduleFireRetention) bounds the tail but a per-minute cron accumulates
// ~10k sessions/week the horizon never trims from the head; the cap is the
// head bound. 0 disables it (the zero-config default; byte-identical when
// off). Default applied at the cmd layer alongside ScheduleFireRetention. A
// non-prunable store is never swept.
ScheduleFireRetentionMaxTotal int
// RetentionCLISet records explicit legacy retention flags so CLI outranks settings.yaml.
RetentionCLISet RetentionCLISet
// AcknowledgeMainRetention is explicit consent for destructive main-session cleanup.
AcknowledgeMainRetention bool
// Remote store drivers (Phase B): gRPC driver endpoints that replace the
// LOCAL session/memory stores with internal/adapter/grpcdriver clients.
// SessionStoreURL is mutually exclusive with StoreDir, MemoryStoreURL with
// MemoryDir (validateDriverConfig, fatal at the top of Build). All-empty
// keeps today's behaviour byte-identical. The Driver* auth/TLS fields apply
// to EVERY driver connection (equal URLs share one ClientConn via the
// build-scoped driverConns cache): DriverAuthToken is a bearer token
// (loopback may ride plaintext; a non-loopback target demands DriverTLS or
// the dial refuses), DriverTLS enables transport TLS with the optional
// DriverTLSCA bundle and DriverTLSCert/DriverTLSKey mTLS client pair. The
// user-model store stays LOCAL in Phase B (a deliberate deferral; see
// docs/design/IMPLEMENTATION-NOTES.md).
//
// Phase C1 adds the content-source drivers: SkillSourceURL replaces the
// LOCAL skills discovery (mutually exclusive with SkillsDirs/
// SkillsConventional — one source per seam) with a
// mecatl.driver.v1.SkillSourceService client; the driver's skill bundles
// serve the same Skill tool, with auxiliary payloads fetched lazily by
// logical name through the source port. SoulSourceURL
// replaces the LOCAL user-scoped soul file (mutually exclusive with
// SoulPath; --no-soul still wins) with a mecatl.driver.v1.SoulSourceService
// client occupying the USER slot of the soul selection precedence. Both are
// probed at build (fatal on an unreachable driver — loud-misconfig); both
// share the same Driver* auth/TLS posture and per-target connection cache.
// Phase C2 adds the remaining content-source drivers: AgentSourceURL
// replaces the LOCAL agent-definition discovery (mutually exclusive with
// AgentsDirs; the default-true AgentsConventional is simply SUPERSEDED —
// the driver branch constructs no conventional sources and narrates the
// supersession) with a mecatl.driver.v1.AgentSourceService client whose
// snapshot is taken ONCE at build (fatal if unreachable — defs bake
// per-def child engines, the skills posture). CommandSourceURL COMPOSES
// (no exclusivity): the driver's slash commands are layered AFTER the
// file-backed commands and BEFORE MCP prompts (file commands shadow a
// same-named driver command), consulted LIVE per expansion/listing;
// probed once at build (fatal if unreachable), runtime faults fail soft.
SessionStoreURL string
MemoryStoreURL string
SkillSourceURL string
SoulSourceURL string
AgentSourceURL string
CommandSourceURL string
// EventLogURL (cloud-native Phase 3c) points the DURABLE event log at a
// mecatl.driver.v1.EventLogService driver, INDEPENDENT of the session store
// (the event log is a separate seam — Append-beside-the-relay, server-
// streaming Read). Empty keeps today's behaviour byte-identical: the local
// jsonlstore Store doubles as its own EventLog, the memstore path uses its
// in-memory sibling, and a session-store DRIVER without this flag records
// nothing (the relay no-ops). It shares the same Driver* auth/TLS posture
// and per-target connection cache as the store drivers.
EventLogURL string
// ScheduleStoreURL (cloud-native Phase 5, issue #257) points the durable
// schedule registry at a mecatl.driver.v1.ScheduleStoreService +
// ScheduleOneShotReArmerService driver, INDEPENDENT of the session store
// (the schedule store is a separate seam — ADR 0059 decision #5: an
// independent override wins, else the configured store is type-asserted,
// else no scheduling). Empty keeps today's behaviour byte-identical: the
// scheduler + the fire-path's RecordFireStart/RecordFireProgress/RecordFire
// discover the store by type-asserting the configured store for a
// ScheduleStore() ACCESSOR (the jsonlstore + redisstore expose one); a store
// that does not (the in-memory default) is the byte-identical no-scheduling
// path. When set, the override REPLACES that discovery: the SAME grpcdriver
// client backs the tick loop's Store, the fire-path's fireStore, the
// delivery-queue gate, AND the in-chat Schedule TOOL's manager
// (server.NewScheduleManager — composition passes the resolved store as
// ScheduleManagerConfig.ScheduleStore, so the tool + tick loop + fire path
// share the ONE resolution — no absent tool with an accessor-less session
// store, no split-brain with an accessor-ful one). A dial failure is a
// fatal operator misconfiguration (an explicitly-configured driver that
// won't dial is NOT silently fallen back to no-scheduling). It shares the
// same Driver* auth/TLS posture and per-target connection cache as the
// store/event-log drivers (equal URLs share one connection).
ScheduleStoreURL string
DriverAuthToken string
DriverTLS bool
DriverTLSCA string
DriverTLSCert string
DriverTLSKey string
// Session leasing (cloud-native Phase 4, ADR 0027): OPTIONAL cross-process
// single-writer enforcement for multi-replica deployments over a shared store.
// Exactly ONE backend is selected, in this precedence — an INDEPENDENT override
// first (mirroring --event-log-url being independent of the store), else the
// configured store is type-asserted for port.SessionLease, else NO lease is
// wired (the byte-identical, single-writer-by-affinity v1 default):
// - SessionLeaseURL: a mecatl.driver.v1.SessionLeaseService driver (the
// multi-host / multi-replica path; shares the Driver* auth/TLS + connection
// cache).
// - SessionLeaseK8sNamespace: a coordination.k8s.io Lease per session in that
// namespace (the in-cluster multi-replica path; needs RBAC — see usage.md).
// - SessionLeaseDir: a single-host flock lease under that directory (one
// machine, several processes; flock auto-releases on crash).
// All empty = no explicit override → local StoreDir gets an automatic flock
// lease, otherwise type-assert the store → else no lease.
SessionLeaseURL string
SessionLeaseDir string
SessionLeaseK8sNamespace string
// SessionLeaseTTL is the lease lifetime (default 30s when a lease is wired);
// SessionLeaseRenewInterval is the renewer tick (default TTL/3).
SessionLeaseTTL time.Duration
SessionLeaseRenewInterval time.Duration
// Soul (issue #14, Phase 1): a user-scoped, agent-READ-ONLY persona fragment
// injected as a turn-0 user message. ON by default reading the conventional
// $XDG_CONFIG_HOME/mecatl/soul.md (fallback ~/.config/mecatl/soul.md) — a
// missing file is fail-soft, so it costs nothing. SoulPath overrides the path
// (--soul-file); NoSoul disables it entirely (--no-soul), in which case the
// SoulAssembler is not wired (nil source → no-op). The adapter is read-only by
// construction: no tool can write the soul.
//
// Soul DRIFT BASELINE (issue #14, Phase 3, Item 1): on load the harness records
// the soul's content hash in a sidecar (<soulPath>.sha256) trust-on-first-use; a
// later run whose hash differs logs a drift WARN and still loads (the persona is
// the operator's own). ApproveSoul (--approve-soul) (re)writes the baseline to the
// current hash, accepting an edit. SoulStrict (--soul-strict) makes a DRIFTED soul
// contribute NO fragment. Both default false. The hash is computed in the adapter;
// the baseline WRITE lives only in the composition layer (soulguard) — the soul
// adapter stays write-free.
SoulPath string
NoSoul bool
ApproveSoul bool
SoulStrict bool
// User model (issue #14, Phase 2): a user-scoped, cross-PROJECT memory of
// durable FACTS about the operator, exposed through explicit user-memory tools
// and reloaded per provider request into the bounded volatile system suffix.
// It is a
// SECOND memory.Store under UserModelDir (or the conventional
// <xdg>/mecatl/usermodel). NoUserModel disables it entirely (--no-user-model).
//
// UserModelReview is the temporary compatibility alias for LearningMode Auto.
// Automatic completions are signal-gated and admitted to the Build-owned staged
// reflection coordinator; they no longer run a direct-writing child reviewer.
// UserModelReviewInterval is the process-wide eligible-completion debounce
// (0/1 = admit every signalled completion).
// UserModelConsolidateInterval independently authorizes a process-wide
// dream.Consolidator scoped to the "user/" namespace (0 = off); learning.mode
// controls completed-trajectory observation and does not gate this schedule.
UserModelDir string
NoUserModel bool
UserModelReview bool
UserModelReviewInterval int
UserModelConsolidateInterval time.Duration
// LearningMode is the effective optional completion-observation policy. Off is
// the zero/default. The legacy UserModelReview flag projects to Auto for one
// compatibility window; it now follows the same staged/convergent path.
LearningMode learning.Mode
// SkillActivationPolicy controls automatic learned-skill assurance. Standard
// app Auto defaults an omitted value to validated; engine Pipeline zero remains evaluated.
SkillActivationPolicy learning.SkillActivationPolicy
// LearningSensitivity controls weighted automatic reflection; zero defaults to
// Conservative at the type level, so LearningSensitivitySet distinguishes an
// explicit conservative choice from the product default Balanced.
LearningSensitivity learning.Sensitivity
LearningAutomatic LearningAutomaticConfig
// LearningMetricsEmitter receives content-free closed learning activities.
LearningMetricsEmitter func(learning.Activity)
// SkillEvaluator is trusted host admission control. Nil deliberately ABSTAINS;
// evaluator errors persist as a non-activatable marker, and only generic error
// categories reach diagnostics.
SkillEvaluator learning.SkillEvaluator
// Skills: explicit directories (highest precedence) plus the conventional
// project/user locations when SkillsConventional is set. SkillsDraftDir enables
// the writable SkillDraft tool quarantine (see validateSkillDraftConfig).
SkillsDirs []string
SkillsConventional bool
SkillsDraftDir string
SkillsDraftThreshold float64
// Agent definitions (Tier 1): named subagent specialists (prompt + scoped
// read-only catalog + per-def model) discovered from <name>.md files. Mirrors
// the Skills fields: explicit dirs (highest precedence) plus the conventional
// project/user locations when AgentsConventional is set. Strict opt-in — zero
// sources means Subagent keeps only the default explorer (no behaviour change).
AgentsDirs []string
AgentsConventional bool
// SubagentModel is the global default model for EVERY child engine that does
// not pin its own model (the analogue of CLAUDE_CODE_SUBAGENT_MODEL): the
// def-resolved Subagent specialists AND (issue #35) the default Subagent
// explorer, undefined team members (lead included — lead-strong split
// deferred), and Parallel BRANCH children. The Parallel JUDGE deliberately
// stays on the session model. Resolution precedence is:
// per-call/def model > SubagentModel > parent (session) Model. Same-provider
// only: the id is resolved on the parent's provider (a def's `provider:` is
// the cross-provider seam). Empty disables the override. It is resolved (with
// ModelAliases) ONLY in this composition layer; Build normalizes it once
// (normalizeSubagentModel) and FAILS FAST: a non-empty value that does not
// resolve to a usable model id (unknown alias, or an alias meaning inherit —
// the built-in sonnet/opus/haiku unless overridden) is a Build ERROR, never a
// silent no-op. EXCLUSION: the user-model review engine
// (buildUserModelReviewEngine) stays on cfg.Model — it is a Stop-REVIEW hook
// engine, not a delegation child.
SubagentModel string
// SubagentAskReviewerModel enables the OPT-IN automated child-ask reviewer
// (issue #31): a tool-less one-turn child engine that adjudicates a HEADLESS
// subagent/member/branch permission ask which the 4-step model would otherwise
// blanket auto-deny (the Codex pattern). Empty (the default) disables it —
// behaviour byte-identical to the plain headless auto-deny. The value is a
// concrete model id or a ModelAliases alias, resolved per session on the
// SESSION's provider (same-provider only, like SubagentModel); Build normalizes
// it once (normalizeAskReviewerModel) and FAILS FAST on a value that does not
// resolve to a usable model id (no-op under UseMock). It is wired onto the MAIN
// engine's Deps only (per-session re-derived through the engine factory); child
// engines force it nil (no nesting). It is deliberately a server FLAG, not a
// permconfig key: it grants an autonomous approval capability, which must be an
// operator deployment decision — never something a (project-tier) settings file
// can switch on. Configured Deny/Ask rules always win over the reviewer. A
// configured `ask-reviewer` model slot (ModelSlots / ADR 0030) SUPERSEDES this
// field's model when the reviewer is enabled — but the FLAG stays the enable gate
// (a slot alone does NOT turn the reviewer on).
SubagentAskReviewerModel string
// SubagentAskReviewerMaxDenies is the per-run adjudication circuit-breaker
// threshold (agent.Deps.ChildAskReviewMaxDenies): after this many CONSECUTIVE
// non-allow reviewer outcomes in one run, further asks skip the reviewer and
// fall through to the plain auto-deny. <=0 uses the default (3).
SubagentAskReviewerMaxDenies int
// SubagentAskReviewerPolicy is the TRUSTED policy rubric the reviewer applies,
// as a STRING (the cmd main reads --subagent-ask-reviewer-policy's file — cmd
// mains may use os — and passes the content). Empty keeps the built-in default
// rubric (agent.WithAskReviewPolicy is applied only when non-empty).
SubagentAskReviewerPolicy string
// RouterDisabled is the master kill-switch for the subagent model router (ADR 0042):
// when true the router is forced OFF regardless of the taxonomy. It is the OR of the
// CLI kill-switch (--subagent-model-router=false) and the YAML `models.router.disabled`
// key (folded by foldOperatorModelRouter), documented like GuardrailsDisabled. Default
// false ⇒ the router is ON iff RouterCategories is non-empty.
RouterDisabled bool
// RouterCategories is the operator-defined routing taxonomy (name + description +
// model selector per category), folded from the operator-tier `models.router:`
// subtree by foldOperatorModelRouter. Empty ⇒ no router. Each entry's Model selector
// is resolved through the operator-merged alias map at classification time (operator
// taxonomy targets are UNCAPPED — the operator is authoritative).
RouterCategories []permconfig.RouterCategory
// RouterDefaultCategory is the category the classifier is told to choose when none
// clearly fits (advisory; the fail-soft inherit is the real safety net). Empty = none.
RouterDefaultCategory string
// RouterClassifierSlot names the model slot the CLASSIFIER itself runs on. Empty
// falls through to the `router` slot (which defaults to the cheap tier) — the
// classifier is a tiny housekeeping call, never the routed work.
RouterClassifierSlot string
// GuardrailsModel is the checker model id / --model-alias (resolved per session
// on the session's provider, same-provider only — the SubagentModel discipline).
// Empty disables guardrails. Build normalizes it once (normalizeGuardrailsModel)
// and FAILS FAST on a value that does not resolve to a usable model id.
GuardrailsModel string
// GuardrailsRules is the operator-tier rule list (matcher + phases + mode +
// per-rule prompt + fail-closed). Empty disables guardrails. Sourced only from
// the operator tier (user-global YAML + CLI), never the project file.
GuardrailsRules []GuardrailRule
// GuardrailsMinContentBytes skips the checker for content shorter than this (a
// cost guard — trivially short content cannot carry a meaningful payload). 0
// checks everything.
GuardrailsMinContentBytes int
// GuardrailsDisabled is the master kill-switch (--guardrails=off): when true,
// guardrails are forced OFF regardless of model/rules config.
GuardrailsDisabled bool
// GuardrailsOnCheckerDown is the global posture when the checker model is
// unavailable (error/timeout): "fail" = block all rules (fail-closed); "warn"
// (empty/default) = fail-open. Per-rule failClosed overrides when explicitly set.
GuardrailsOnCheckerDown string
// GuardrailsDefaultMode sets the enforcement mode for the built-in default
// rules when no explicit rules are configured: "block" (default), "advisory",
// or "sanitize". An explicit rules list replaces the defaults entirely.
GuardrailsDefaultMode string
// GuardrailsEscape is the ADR-0080 escape knob (operator-tier `guardrails:`
// `escape:` key): when true AND a checker model is configured, an out-of-root
// FS escape at posture AUTO is routed through the guardrail checker as a
// composition-level pre-check (an unsafe verdict denies; a checker error
// fails closed to the write-escape Ask). Default false = the un-routed
// posture table (auto read-allow / write-ask), byte-identical to before.
GuardrailsEscape bool
// ModelAliases maps a short alias (e.g. "sonnet"/"opus"/"haiku"/"fast") to a
// concrete provider model id. Resolved only here; the domain/agent always
// receives a concrete model string.
ModelAliases map[string]string
// ModelSlots binds a named internal lightweight LLM call (a "slot") to a model
// selector — an alias or a concrete id (ADR 0030, Phase 1+2). The wired slots
// this slice routes are "compaction", "ask-reviewer", and "guardrail"; semantic
// TIER keys ("cheap"/"fast"/"reasoning") give a default a slot falls through to
// (each routed slot defaults to "cheap"). It is COMPOSITION-ONLY: every value is
// resolved THROUGH lookupModelAlias (the same alias machinery the agent-def
// `model:` path uses), so the domain/agent never sees a slot. EMPTY/ABSENT ⇒
// byte-identical default (the call keeps the session model — resolveSlotModel
// returns ("", false) and every routed site keeps its pre-feature behaviour). It
// is OPERATOR-TIER ONLY this slice: read from --model-slot + the user-global
// settings.yaml `models.slots:` subtree (folded by foldOperatorModelSlots), never
// a project-tier file (a project re-pointing a slot is deferred to the
// allowlist-capped Layer-3 work). Resolution is FAIL-SOFT: a typo'd slot key or an
// alias meaning inherit WARNs and degrades to the session model — a broken
// housekeeping slot never wedges a compaction / ask-review / guardrail call.
ModelSlots map[string]string
// Slash commands: directory of <name>.md templates; EnableCommands turns on the
// default directories when CommandsDir is empty.
CommandsDir string
EnableCommands bool
// Optional tools, on by default in the standalone server.
EnableParallel bool
// WebSearch (issue #26): web search is ON by default (Exa anonymous tier) behind
// the always-present WebSearch core tool — see the backend ladder fields below.
// WebSearchURL is the EXPLICIT-override endpoint (e.g. a SearXNG /search URL or
// a generic JSON search API) that wins over the env tiers and the Exa default.
// WebSearchAPIKey is an OPTIONAL credential sent in
// WebSearchAuthHeader (default "Authorization" as a Bearer token) — NEVER in the
// query string. WebSearchQueryParam overrides the URL query parameter the search
// string is placed in (default "q"). The cmd layer reads the key from a
// secret/env source, never a flag value. The adapter carries its OWN per-call
// timeout and concurrency limiter (egress is bounded in the adapter, never the
// dispatcher).
WebSearchURL string
WebSearchAPIKey string
WebSearchAuthHeader string
WebSearchQueryParam string
// WebSearch backend ladder (issue #26): web search is ON by default via the Exa
// anonymous tier (no key, no config). The precedence is, first match wins:
// WebSearchOff (kill switch) > WebSearchURL (explicit override) > SearXNGURL >
// BraveAPIKey > Exa anonymous default. SearXNGURL/BraveAPIKey/ExaAPIKey are read
// from SEARXNG_URL/BRAVE_API_KEY/EXA_API_KEY (secrets/URLs, never flag values);
// WebSearchOff is set by --websearch=off.
SearXNGURL string
BraveAPIKey string
ExaAPIKey string
WebSearchOff bool
// ForkPreservedCap bounds how many PRESERVED winner forks (join=first /
// join=judge) survive at once across the process: a new winner beyond the cap
// LRU-reaps the oldest preserved fork. Zero uses agent.DefaultPreservedForkCap.
// Preserved forks remain the deliverable — they are inspectable/mergeable — but
// are capped so many Parallel calls cannot grow disk without bound.
ForkPreservedCap int
// EnableTeams turns on the agent-teams capability (the CreateTeam /
// SpawnTeammate / RunTeam RPCs). It is OPT-IN and EXPERIMENTAL: default off.
// When false, server.Config.MemberEngine stays nil and the team RPCs return
// ErrTeamsDisabled.
EnableTeams bool
// DisableSteer turns OFF the mid-run steer inbox (steer-while-running, issue
// #512) — an OPT-OUT of a DEFAULT-ON knob, mirroring NoBash/WebSearchOff (the
// zero value false = steer ON, so every existing hand-built Config / test is
// byte-identical and steer is armed by default). Threaded through
// engineDepsForProvider into agent.Deps.EnableSteer (true = armed) and reflected
// — via the SAME wired engine's Engine.SteerEnabled() — in
// ServerCapabilities.steer, so the capability advertisement can never claim a
// path the engine did not arm. The CLI surface is --no-steer (mecated +
// mecatui); the operator-tier settings.yaml `steer: false` scalar (user-global
// + CLI tiers ONLY — a project-tier key is WARN-ignored by permconfig, the
// same operator-only discipline as posture:) folds in via foldOperatorSteer
// (CLI out-ranks YAML). It is POSTURE-INDEPENDENT: the ladder does not derive
// it at any tier (a mid-run operator instruction is not an automation grant).
DisableSteer bool
// DisableSteerFlagSet records whether the operator passed an explicit --no-steer
// flag. When true, foldOperatorSteer leaves the operator-YAML steer: value alone
// (CLI out-ranks YAML, mirroring PostureFlagSet/ReasoningEffortFlagSet). Set by
// the cmd mains alongside DisableSteer.
DisableSteerFlagSet bool
// MCP: static servers, the resource meta-tools toggle, the prompt-expander
// toggle, and the live ToolHive workload source.
MCPServers []mcp.ServerConfig
// MCPProfileLoader resolves operator-tier profiles with the same permission
// resolver Build already owns. Command roots install it so settings are not
// parsed a second time and secret lookup remains a runtime-only operation.
MCPProfileLoader interface {
Load(*permconfig.MCPSection) ([]mcp.ServerConfig, interface{ Close() error }, error)
}
// MCPProfileLifecycle owns credential stores/readers used by MCPServers.
// Build closes it after the global MCP manager/controllers and before other
// source lifecycles. It is nil for programmatic and legacy static configs.
MCPProfileLifecycle interface{ Close() error }
MCPResourceTools bool
MCPPrompts bool
ToolHiveEnabled bool
ToolHiveGroup string
// File-based permission config (issue #13). PermissionsConventional turns on
// auto-discovery of the conventional per-project config (<ws>/.mecatl/settings.yaml
// and, with ImportClaudePermissions, <ws>/.claude/settings.json) plus the
// user-global files; it is re-resolved PER SESSION against each session's
// workspace root. ImportClaudePermissions additionally imports Claude-Code
// settings.json (with the lossy fail-safe table). TrustProject honours a
// project's ALLOW rules (a project's deny/ask is always honoured regardless);
// leave it off to ignore an untrusted repo's grants. PermissionConfigs are
// explicit operator-pointed YAML files, loaded at the user (fully-trusted)
// scope regardless of the conventional toggle. When none of these select any
// source the resolver is nil and the policy behaves exactly as before
// (built-ins + learned rules only).
PermissionsConventional bool
ImportClaudePermissions bool
TrustProject bool
PermissionConfigs []string
// Headless is the explicit deployment identity (issue #359): a root declares
// that no human approver is attached. It is set by each cmd root — NOT inferred
// from Interactive. applyPosture uses it to keep the project-trust floor
// interactive-only: trusted/auto/yolo may raise TrustProject for an interactive
// root, but never for a headless root. Explicit/declarative/remembered trust can
// still trust either root. mecated defaults false, mecatequi/mecak8s default true,
// mecatui defaults false.
Headless bool
// AllowAllTools, when set, injects a single ScopeCLI allow-all rule into BOTH
// the MAIN engine's static ruleset (mainRules, AudienceMain) AND the
// child/member ruleset (childRules, AudienceSubagent) via the shared
// yoloAllowAllRule. The RULE binds main AND children — it loosens the built-in
// mutate-ask floor for both. The built-in substitution Ask floor is
// additionally loosened ONLY for the main engine (mainEvaluatorOptions'
// WithLooseSubstitution; childEvaluatorOptions deliberately omits it), so a
// child's $()/backtick command still resolves through the child-ask model. A
// Deny in any scope and any CONFIGURED Ask still win (see
// docs/adr/0022-allow-all-posture.md).
AllowAllTools bool
// Posture is the graduated operator trust/automation tier (strict < trusted <
// auto < yolo). It is the single source the derived knobs below are computed from
// in applyPosture (run BEFORE resolveTrust in Build). --posture sets it directly;
// --yolo and --trust-project are ALIASES (resolvePosture raises the tier from
// them); the operator-global settings.yaml `posture:` key folds in via
// foldOperatorPosture (CLI out-ranks YAML). PostureStrict (zero) is the
// fail-closed default. See internal/app/posture.go.
Posture Posture
// LooseChildSubstitution loosens the built-in substitution Ask floor for
// CHILD/subagent/branch engines (childEvaluatorOptions adds WithLooseSubstitution
// when set), turning OFF the child prompt-injection defense so a $()/backtick/
// heredoc command auto-runs in a subagent. Derived by applyPosture: TRUE only
// under PostureYolo. strict/trusted/auto leave it false (the child substitution
// floor stands; a child's substitution still resolves through the child-ask
// model). NEVER set directly — it is a posture-derived knob.
LooseChildSubstitution bool
// PostureFlagSet records whether the operator passed an explicit --posture flag.
// When true, foldOperatorPosture leaves the operator-YAML posture: value alone
// (CLI out-ranks YAML) and resolvePosture WARNs if an alias raised above the
// explicit value. Set by the cmd mains alongside Posture.
PostureFlagSet bool
// ReasoningEffort is the OPERATOR-TIER reasoning-effort default (ADR 0055): the
// neutral vocabulary "" / "auto" (unset — provider default) / "low" / "medium" /
// "high" / "xhigh" / "max". It is folded from the operator-YAML reasoning-effort:
// key by foldOperatorReasoningEffort (CLI out-ranks YAML, mirroring posture)
// and threaded into the provider registry as the DEFAULT effort
// each adapter is built with; a per-session CreateSession.reasoning_effort
// OUT-RANKS it (resolveSessionEffort), re-minting the adapter via the engine
// factory when it differs. Operator-tier only: a project-tier reasoning-effort:
// key is WARN-ignored by permconfig. OpenAI clamps xhigh/max→high (with a
// diagnostic); Anthropic identity-maps all five tiers. NEVER a port.LLMRequest
// field — the loop never branches on it.
ReasoningEffort string
// ReasoningEffortFlagSet records whether the operator passed an explicit
// --reasoning-effort flag. When true, foldOperatorReasoningEffort leaves the
// operator-YAML value alone (CLI out-ranks YAML). Set by the cmd mains alongside
// ReasoningEffort.
ReasoningEffortFlagSet bool
// Privileged is the cmd-computed predicate "running as root WITHOUT a declared
// sandbox" (euid 0 && MECATL_SANDBOX/IS_SANDBOX unset). It is the input to the
// authoritative posture root-refusal: Build calls PostureRefusalReason AFTER the
// posture fold (resolvePosture + applyPosture), so an allow-all tier set ONLY via
// the operator-global settings.yaml `posture:` key — which the CLI-only pre-check
// never sees — still hits the refusal a root, unsandboxed `--yolo` always did. The
// cmd layer owns the os.Geteuid / env reads (keeping os out of internal/app) and
// threads the bool here. DEFAULT false: a non-root or sandboxed process never
// refuses, and a binary that does not compute it (tests) is never spuriously refused.
Privileged bool
// Interactive reports whether a HUMAN approver is attached to the main engine's
// runs (a live Converse / HTTP-SSE client that can answer a permission ask). It is
// threaded onto the MAIN engine's agent.Deps.Interactive (per session, via
// engineDepsForProvider) so a SUBAGENT's permission ask that A1/A2 did not
// auto-resolve can be SURFACED to the human (interactive) instead of auto-denied
// (headless). DEFAULT false (fail-safe): a daemon launched without a known approver
// auto-denies subagent asks rather than parking them forever. cmd/mecated sets it
// true (the bidi/HTTP surfaces have a client); the offline demo leaves it false.
Interactive bool
// PlanModeAutoApprove is an OPT-IN, OPERATOR-TIER-ONLY, DEFAULT-OFF flag that
// auto-approves a plan-mode PresentPlan ask when the run ends without a human
// operator. It is a deliberate autonomous-approval capability — an operator
// deployment decision, NEVER load-bearing for safety — and lives ONLY in
// composition (the Service), never the engine loop (mirroring the ChildAskReviewer
// discipline). When enabled and the deployment is headless (Interactive=false), the
// Service auto-resolves a parked plan-approval ask via the EXISTING ApprovePlan path
// (ModeDefault + a loud note). It does NOT fire when interactive (a human can
// approve), NOT in non-plan modes, NOT for non-plan asks. The engine's surfacePlanAsk
// headless guard is also loosened so the PresentPlan EMITS EvPermissionAsk and parks
// (which the Service then observes). DEFAULT false (the existing safe default:
// headless plan ask is auto-denied). Operator-tier only: the operator-global
// settings.yaml plan_mode_auto_approve: key is folded by foldOperatorPlanModeAutoApprove;
// a project-tier key is WARN-ignored by permconfig.
PlanModeAutoApprove bool
// Observability relays, injected by the caller (mecated wires telemetry; the
// embedded TUI server leaves both nil). The engine nil-guards each.
Sink port.EventSink
ToolCallRecorder port.ToolCallRecorder
// MetricsRoleScoper, when non-nil, supplies the role-scoped telemetry pair a
// CHILD engine's Deps.Sink/Deps.ToolCallRecorder are wired to (issue #47). The
// caller (cmd/mecated, the embedded TUI server) builds the closure over the
// telemetry adapter's Metrics.WithRole — keeping internal/app free of the
// telemetry import — and the child deps builders invoke it with the BOUNDED
// family value from roleFamily (never the raw engine role), so every child
// series carries a closed-set role label and no def/member name or session id
// can leak into metric cardinality. Nil (the default, and the no-perf path)
// keeps children unmetered: Sink/ToolCallRecorder stay nil, byte-identical to
// the pre-feature child shape. The role-tagging is METRICS-ONLY — child
// Diagnostics and the conversation event stream are unchanged.
MetricsRoleScoper func(familyRole string) (port.EventSink, port.ToolCallRecorder)
// ScheduleMetricsEmitter, when non-nil, is the composition-injected metrics
// callback the scheduler invokes (via Config.ScheduleMetrics) for every
// fired/skipped/failed schedule fire (issue #233, Phase 2b). The caller
// (cmd/mecated, the embedded TUI server) builds the closure over the
// telemetry adapter's Metrics.EmitSchedule — keeping internal/app free of the
// telemetry import — exactly as MetricsRoleScoper closes over Metrics.WithRole.
// Schedule metrics are NOT a role-family (a fire mints a fresh session whose
// OWN run already carries role="main"); this callback is a separate
// schedule-lifecycle dimension. Nil (the default, and the no-perf path) keeps
// the scheduler metrics-silent: byte-identical to the pre-feature shape.
ScheduleMetricsEmitter func(payload session.SchedulePayload, duration time.Duration)
// Diagnostics is the general-purpose operational logging seam, injected by the
// caller (mecated wires a slogdiag sink to stderr; the embedded TUI passes its
// own). It is the sink the build-once composition facts (token counter /
// compaction strategy / slash-command state) are emitted through EXACTLY ONCE in
// Build. Nil is tolerated: Build defaults it to port.NopDiagnostics so the
// composition stays silent rather than nil-panicking.
Diagnostics port.Diagnostics
// ContextWindowOverride forces the engine's compaction context window (in tokens)
// instead of the live/catalogued/128k resolution. It is a documented operator knob
// (the --context-window-override flag) with a dual purpose: (a) it forces a small,
// cheap compaction window so the live e2e (and ad-hoc stress tests) can trip
// maybeCompact mid-run without accumulating ~100k tokens of history; (b) it is a
// workaround for a model that under-reports its context window or sits behind a
// proxy that does. INVARIANT: 0 = disabled = byte-identical production resolution
// (the live/catalogued/128k path stands untouched).
ContextWindowOverride int
// --- Scheduled tasks (issue #189, Phase 1f): the in-process scheduler
// (internal/adapter/scheduler) owns the tick loop that polls the durable
// ScheduleStore, applies the misfire policy, claim-before-fire advances
// NextFireAt (the at-most-once atomic), fires each claimed schedule via a
// composition-supplied FireFunc (mints a fresh "sched--" top-level session
// via Service.CreateSessionWithProfile + StartRunContent with subagent-grade
// defaults + fail-closed model pinning), and records the outcome. The loop is
// storage-agnostic; engine/agent never imports it. ON by default on any
// schedule-capable store (ADR 0073 decision 2): the cmd layer feeds
// SchedulerEnabled = !--no-scheduler, and a store with no ScheduleStore (the
// in-memory default) takes the byte-identical no-scheduler path whether
// enabled or not. The ScheduleStore is discovered by type-asserting the
// configured store for the ScheduleStore() ACCESSOR (the jsonlstore +
// redisstore expose one). The leader-lease reuses the SAME backend as the
// run-entry session lease (a different id — port.SchedulerLeaderLeaseID — so
// the two never contend); nil Lease = single-replica by affinity. See ADR
// 0059 + ADR 0073.
SchedulerEnabled bool
SchedulerTickInterval time.Duration // 0 → default 30s (the scheduler's own default)
SchedulerMinInterval time.Duration // 0 → no floor enforced at the create-seam
SchedulerMaxConcurrentFires int // 0 → default 4
// DeliveryBacklogCap bounds the per-origin pending-delivery backlog (ADR 0075,
// fire-result-delivery): when the pending count for an origin exceeds this cap,
// the OLDEST pending note is dropped with a WARN rather than growing unboundedly
// on an overloaded origin. 0 (the default) means UNBOUNDED (no drop).
DeliveryBacklogCap int
// contains filtered or unexported fields
}
Config is the build contract for the server composition: everything Build needs to assemble the engine and service, independent of HOW the resulting service is served (TCP, UNIX socket, TLS, auth — all serve-time concerns owned by the caller). Each cmd/ main maps its own CLI/env surface onto this struct.
The zero value is a usable shell-less, provider-less configuration; callers set the fields they need. Sink and ToolCallRecorder are optional (nil installs no telemetry — the engine nil-guards both).
type DeliveryQueueOption ¶
type DeliveryQueueOption func(queueConfig)
DeliveryQueueOption configures a delivery queue at construction.
func WithDeliveryBacklogCap ¶
func WithDeliveryBacklogCap(backlogCap int) DeliveryQueueOption
WithDeliveryBacklogCap bounds the pending backlog per origin: when the pending count exceeds cap, the OLDEST pending note is dropped with a WARN rather than growing unboundedly. 0 (the default) means UNBOUNDED (no drop).
func WithDeliveryDiagnostics ¶
func WithDeliveryDiagnostics(d port.Diagnostics) DeliveryQueueOption
WithDeliveryDiagnostics injects the Diagnostics sink the backlog-drop WARN rides. Nil defaults to NopDiagnostics (the WARN is dropped — an operator who wires a durable queue but no diagnostics sees silent drops, the same posture as the rest of the composition).
type FileDeliveryQueue ¶
type FileDeliveryQueue struct {
// contains filtered or unexported fields
}
FileDeliveryQueue is a DURABLE, file-backed port.DeliveryQueue writing a per-session `.delivery.jsonl` sidecar under a configured dir (the jsonlstore `.events.jsonl` precedent). It survives a process restart: a note queued before a restart drains after it (the persist-in-snapshot List 2 decision).
Layout under dir:
<dir>/<id>.delivery.jsonl — append-only, one record per Enqueue <dir>/<id>.delivery.ledger.json — the delivered-seq ledger (upserted)
The two files together reconstruct the pending set on restart: the .jsonl is the append-only enqueue log, the .ledger.json is the set of seqs already drained. Pending = enqueue log − delivered ledger.
It is a composition-owned adapter (NOT a jsonlstore package sibling) so it does not collide with the shared store package's evolution; it shares the SAME discipline (format-tagged records, filename sanitization, atomic upsert for the ledger, append-only for the log). SINGLE-HOST: the mutex is the fence; a multi-host deployment needs the leader lease (the schedule store precedent) — the queue is process-affinity-routed like the session store.
func NewFileDeliveryQueue ¶
func NewFileDeliveryQueue(dir string, opts ...DeliveryQueueOption) (*FileDeliveryQueue, error)
NewFileDeliveryQueue constructs a durable, file-backed delivery queue under dir, creating dir if needed (mode 0700: it holds rendered note text — owner only, matching the jsonlstore dir). It returns an error only if dir cannot be created.
func (*FileDeliveryQueue) Close ¶
func (*FileDeliveryQueue) Close()
Close is a no-op (files are opened per call, never held), kept for the durability-tier symmetry with the in-memory queue and the defer idiom.
func (*FileDeliveryQueue) Enqueue ¶
func (q *FileDeliveryQueue) Enqueue(ctx context.Context, origin session.SessionID, text string) (port.DeliveryNote, error)
Enqueue appends a note for origin, assigning the next per-session seq. The note is durably appended to the per-session `.delivery.jsonl` before Enqueue returns (nil error = on stable storage, the port contract). When the pending backlog exceeds cap (cap > 0) the OLDEST pending note is dropped with a WARN — "dropped" here means recorded in the delivered ledger so it will not drain (its enqueue line stays in the append-only log for audit, but it is no longer pending).
func (*FileDeliveryQueue) MarkDelivered ¶
func (q *FileDeliveryQueue) MarkDelivered(_ context.Context, origin session.SessionID, seq uint64) error
MarkDelivered records seq as delivered for origin by adding it to the delivered ledger (persisted). Idempotent: a re-mark is a no-op success.
func (*FileDeliveryQueue) Pending ¶
func (q *FileDeliveryQueue) Pending(_ context.Context, origin session.SessionID) ([]port.DeliveryNote, error)
Pending returns the origin's pending notes in enqueue order (the enqueue log minus the delivered ledger). A miss returns an empty slice (absence is data).
type GuardrailRule ¶
type GuardrailRule struct {
// Match is the tool-name matcher: an exact name, a "prefix*" glob (e.g.
// "mcp__github__*"), or "*" (catch-all). Most-specific wins at resolution.
Match string
// Phases lists the directions this rule inspects ("pre" = outbound args, "post" =
// inbound results). Empty inspects BOTH (the conservative default).
Phases []string
// Mode is the enforcement posture: "block" (veto/rewrite-to-error), "sanitize"
// (rewrite to the checker's sanitized_content), or "advisory" (observe only).
// Empty defaults to "block".
Mode string
// Prompt overrides the built-in inspection rubric for the rule's direction. Empty
// keeps the default exfil (pre) / injection (post) rubric.
Prompt string
// FailClosed flips the fail-OPEN default: a checker error/timeout in an enforcing
// mode then treats the content as UNSAFE (block) instead of degrading to "no
// checker". A checker SAYING safe always passes regardless.
FailClosed bool
// FailClosedSet reports whether the operator explicitly set FailClosed on this
// rule. When false, the global GuardrailsOnCheckerDown posture fills in; when
// true, the per-rule value wins over the global.
FailClosedSet bool
}
GuardrailRule is one operator-tier guardrail rule (issue #27): a tool-NAME matcher, the tool-use phases it inspects, an enforcement mode, an optional per-rule inspection prompt, and a fail-closed opt-in. It is the composition-layer mirror of the modelhook adapter's RuleSpec (compiled via modelhook.CompileRule), populated from the operator-tier `guardrails:` YAML subtree + CLI — never the project file.
type InMemoryDeliveryQueue ¶
type InMemoryDeliveryQueue struct {
// contains filtered or unexported fields
}
InMemoryDeliveryQueue is the in-memory, concurrency-safe port.DeliveryQueue — the no-store-dir default the composition layer wires when the SessionStore is memstore (so the delivery seam is never nil). It is the memstore-tier queue: it works in-process but does NOT survive a restart (it degrades honestly to empty across a restart, byte-identical to the no-delivery path for the restarted process — the DURABLE backing is FileDeliveryQueue).
It does NOT round-trip through a serialization (the note text is opaque and immutable once enqueued), so notes are stored by value directly.
func NewInMemoryDeliveryQueue ¶
func NewInMemoryDeliveryQueue(opts ...DeliveryQueueOption) *InMemoryDeliveryQueue
NewInMemoryDeliveryQueue constructs an empty in-memory delivery queue. It is the memstore-tier default; for a durable queue use NewFileDeliveryQueue.
func (*InMemoryDeliveryQueue) Close ¶
func (*InMemoryDeliveryQueue) Close()
Close is a no-op for the in-memory queue (kept for the port.DeliveryQueue durability-tier symmetry with FileDeliveryQueue.Close, so a caller can defer either uniformly).
func (*InMemoryDeliveryQueue) Enqueue ¶
func (q *InMemoryDeliveryQueue) Enqueue(_ context.Context, origin session.SessionID, text string) (port.DeliveryNote, error)
Enqueue appends a note for origin, assigning the next per-session seq. When the pending backlog exceeds cap (cap > 0) the OLDEST pending note is dropped with a WARN. It is NOT idempotent: each call mints a fresh seq.
func (*InMemoryDeliveryQueue) MarkDelivered ¶
func (q *InMemoryDeliveryQueue) MarkDelivered(_ context.Context, origin session.SessionID, seq uint64) error
MarkDelivered records seq as delivered, removing it from the pending set. Idempotent: a re-mark of an already-delivered or unknown seq is a no-op.
func (*InMemoryDeliveryQueue) Pending ¶
func (q *InMemoryDeliveryQueue) Pending(_ context.Context, origin session.SessionID) ([]port.DeliveryNote, error)
Pending returns the origin's pending notes in enqueue order (oldest first).
type LearningAutomaticConfig ¶
type LearningAutomaticConfig struct {
Cooldown time.Duration
Window time.Duration
MaxReflections int
MaxTokens int
MaxReflectionsPerPrincipal int
MaxTokensPerPrincipal int
}
LearningAutomaticConfig is the process-local automatic-reflection budget.
type Posture ¶
type Posture int
Posture is the graduated operator trust/automation tier. Higher = more automation, less prompting. Zero value (PostureStrict) is the fail-closed default.
const ( // PostureStrict is the DEFAULT (iota zero = fail-closed backstop): no allow-all, // no substitution loosening anywhere, project trust left at the operator's own // --trust-project. Every mutate prompts. PostureStrict Posture = iota // PostureTrusted raises the project-trust floor (honour a discovered project's // ALLOW rules) but loosens NOTHING else — it is the alias --trust-project maps to. PostureTrusted // PostureAuto is the recommended UNATTENDED default: allow-all (main + children), // main substitution loosened, project trust raised — but the child // prompt-injection defense stays ON (a child's $()/backtick/heredoc still resolves // through the child-ask model, never auto-run). PostureAuto // PostureYolo is truly-off, GATE-FREE: everything PostureAuto does, PLUS the child // substitution floor is loosened (LooseChildSubstitution) — child prompt-injection // defense OFF, $()/backtick/heredoc auto-run in subagents/branches. Isolated, // ephemeral, single-tenant deployments only. PostureYolo )
ORDER IS CONTRACT: comparisons depend on it. The tiers are ordered strict < trusted < auto < yolo and code RELIES on the integer ordering — the alias MAX-fold (ResolveAliasPosture), the ceiling clamp, narratePosture's `>=` flags, and the root-refusal's `>= PostureAuto` all use `<`/`>=` directly. Adding a tier or reordering these breaks those silently; TestPostureIotaOrderingIsContract pins it. Posture stays an int (not a string-backed type) precisely so these ordered comparisons are cheap and obvious; the string token is only its serialization (String/parsePosture).
func ParsePosture ¶
ParsePosture maps a CLI --posture token to a Posture for the composition roots (cmd/mecated, cmd/mecatui), which set Config.Posture from the flag string. It is the EXPORTED alias of parsePosture so the cmd layer never re-implements the strict/trusted/auto/yolo grammar (empty/whitespace/unknown fail CLOSED to PostureStrict; the cmd WARNs on an unknown value via IsKnownPostureToken).
func ResolveAliasPosture ¶
ResolveAliasPosture folds the explicit --posture tier with the --yolo / --trust-project ALIASES, MAX-tier: --yolo raises to yolo, --trust-project raises to >=trusted, and an alias can only RAISE (never lower an explicit higher --posture). It is the ONE place the alias grammar lives — both cmd roots (their CLI-only pre-check / refusal) AND the composition-layer resolvePosture call it, so the security-relevant fold cannot drift between sites (the same "ONE grammar, cmd never re-implements" discipline as ParsePosture). It is PURE: no YAML, no diagnostics — the WARN for an alias raising above an explicit lower flag lives in resolvePosture, which has the cfg/diag context.
func ResolveAuthoritativePosture ¶
ResolveAuthoritativePosture computes the posture tier Build resolves: it folds the OPERATOR-TIER `posture:` YAML scalar (CLI --posture out-ranks), then resolves the tier (aliases + ceiling clamp). It does NOT fold workspace trust — that is Build's job, surfaced by the `operator posture` startup diagnostic (narratePosture) after resolveTrust. Callers that need only the tier (mecatui embedded posture, the mecated fast-path refusal) use this; callers that need the full root-aware trust decision drive the real Build and read its structured diagnostic.
type ProviderCredentials ¶
type ProviderCredentials struct {
OpenAIKey string
OpenRouterKey string
AnthropicKey string
OpenCodeKey string
OpenAICodexCredential openaicodex.Credential
CustomProviderAPIKeys map[string]string
}
ProviderCredentials is the immutable credential snapshot returned by a ProviderCredentialLoader.
type RetentionCLISet ¶
type RetentionCLISet struct {
MainMaxAge, MainMaxCount, ChildMaxAge, ChildMaxCount bool
ScheduledMaxAge, ScheduledMaxCount, SweepCadence bool
}
RetentionCLISet records which compatibility flags were explicitly supplied.
type TrustDecision ¶
type TrustDecision struct {
// Trusted is the effective admission bool: honour the project's ALLOW rules
// and project soul when true.
Trusted bool
// Source records why (for the slog narration).
Source TrustSource
// Drifted is true when a remembered registry entry existed but its anchor hash
// mismatched the live identity anchor. When Drifted, Trusted is FALSE (fail-safe).
Drifted bool
}
TrustDecision is the single composition-level answer to "is this workspace trusted, and why?" (MUST-FIX 2). Its Trusted field is the effective bool fed to the admission consumers; Source drives the log narration. Drifted is true when a remembered (trust.yaml) registry entry existed but its identity-anchor hash no longer matches the live anchor (Phase 2b) — a drifted entry FAILS SAFE to untrusted here (mecated has no prompt; Phase 2c's mecatui turns Drifted into a re-prompt).
func ResolveTrust ¶
func ResolveTrust(cfg Config) TrustDecision
ResolveTrust is the EXPORTED composition-level trust fold, for a SECOND composition root that must learn the trust decision BEFORE it calls Build — specifically the mecatui pre-TUI first-encounter prompt (Workspace-Trust Phase 2c). It returns the SAME TrustDecision Build computes internally (it delegates to the same unexported resolveTrust), so the prompt and Build never disagree.
The prompt uses it to decide whether to fire: a TrustNone-with-authority or a Drifted decision prompts; an already-trusted (Flag/Declared/Remembered) decision does NOT. When the operator then answers "trust"/"trust-once", mecatui sets cfg.TrustProject=true so Build's own fold short-circuits to TrustFlag (trusted) — the prompt OUTCOME wins and Build does NOT re-resolve or re-prompt. When the operator declines (or the decision was already trusted declaratively), mecatui leaves cfg.TrustProject as-is and Build re-resolves to the SAME answer this call produced (idempotent: resolveTrust performs no writes and no prompt).
type TrustSource ¶
type TrustSource int
TrustSource records WHY a workspace is (or is not) trusted, for the slog narration that mirrors the soul-selection narration (soulMeta).
const ( // TrustNone means the workspace is not trusted this run. TrustNone TrustSource = iota // TrustFlag means trust came from the --trust-project one-shot flag. TrustFlag // TrustDeclared means the workspace's realpath is in the operator-authored // settings.yaml `trustedWorkspaces:` list. TrustDeclared // TrustRemembered is reserved for Phase 2 (the machine-written trust.yaml // registry). It is declared here so the value taxonomy is stable across // phases; Phase 1 never produces it. TrustRemembered )
func (TrustSource) String ¶
func (s TrustSource) String() string
String renders a TrustSource for the slog narration.
Source Files
¶
- agentdefs.go
- approvalreplay.go
- build.go
- capability.go
- catalog.go
- catalog_classification.go
- childgc.go
- delivery_queue.go
- dream_review.go
- driverstore.go
- escapeclassifier.go
- escapepolicy.go
- guardrails.go
- learned_skills.go
- learning.go
- learning_controller.go
- livemeta.go
- mcplogin.go
- modellister.go
- modelsnapshot.go
- posture.go
- project_ingestion.go
- promptcache.go
- reasoning_effort.go
- reflection_coordinator.go
- reflection_observer.go
- reflection_reconcile.go
- reflection_repository.go
- registry.go
- retention.go
- root_authority.go
- scheduler_delivery.go
- scheduler_delivery_run.go
- scheduler_fire.go
- scheduler_reconcile.go
- session_liveness.go
- session_reconcile.go
- skilldraft.go
- slots.go
- soulguard.go
- soulselect.go
- soulsnapshot.go
- storage_health.go
- storage_management.go
- trust.go
- usermodelreview.go