cortex

package module
v1.10.0 Latest Latest
Warning

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

Go to latest
Published: Oct 30, 2026 License: MIT Imports: 9 Imported by: 4

README

Cortex, an AI agent orchestration library for Go with tools, personas, memory and multi-agent strategies

Cortex: AI agent orchestration framework for Go

Cortex is an open-source Go library for building LLM agents. You define an agent from a system prompt, tools and, if you want more structure, skills, personality traits, behaviors and personas, and Cortex runs it through a ReAct loop with tool calling, conversation memory, human-in-the-loop checkpoints and runs that suspend and resume. Several agents can work together through sequential, parallel, router, hierarchical and debate orchestration, or message each other directly. Everything is scoped to the tenants or workspaces your app defines, stored in PostgreSQL, SQLite or MongoDB, and works standalone or as a Forge extension.

CI Go Reference GitHub Stars

Documentation · Quick start · Features · Human model · Orchestration · Forge · FAQ

Quick start

You need Go 1.26 or later and an OpenAI API key. Cortex talks to models through an llm.Client, and the adapter that ships with it wraps a Nexus gateway, so you add Nexus and its OpenAI provider too:

go get github.com/xraph/cortex@latest
go get github.com/xraph/nexus@latest github.com/xraph/nexus/providers/openai@latest
go get github.com/xraph/grove/drivers/sqlitedriver@latest

Save this as main.go:

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/xraph/grove"
	"github.com/xraph/grove/drivers/sqlitedriver"
	_ "github.com/xraph/grove/drivers/sqlitedriver/sqlitemigrate" // registers the SQLite migration executor
	"github.com/xraph/nexus"
	"github.com/xraph/nexus/providers/openai"
	"github.com/xraph/nexus/tenant"

	"github.com/xraph/cortex"
	"github.com/xraph/cortex/agent"
	"github.com/xraph/cortex/engine"
	"github.com/xraph/cortex/id"
	"github.com/xraph/cortex/llm"
	nexusllm "github.com/xraph/cortex/llm/nexus"
	sqlitestore "github.com/xraph/cortex/store/sqlite"
)

func main() {
	// Every stored row belongs to a scope you define. Cortex refuses to
	// read or write without one.
	ctx := cortex.WithScope(context.Background(), cortex.Scope{
		Levels: []cortex.Level{{Key: "workspace", Value: "acme"}},
	})

	// Storage: SQLite through Grove.
	drv := sqlitedriver.New()
	if err := drv.Open(ctx, "cortex.db"); err != nil {
		log.Fatal(err)
	}
	db, err := grove.Open(drv)
	if err != nil {
		log.Fatal(err)
	}
	st := sqlitestore.New(db)
	if err := st.Migrate(ctx); err != nil {
		log.Fatal(err)
	}

	// The model: OpenAI behind a Nexus gateway.
	gw := nexus.New(nexus.WithProvider(openai.New(os.Getenv("OPENAI_API_KEY"))))
	if err := gw.Initialize(ctx); err != nil {
		log.Fatal(err)
	}
	defer gw.Shutdown(ctx)

	// Every model call goes out under a Nexus tenant. This one lives in
	// the gateway's in-memory store, so it's made fresh on each start.
	acme, err := gw.Tenants().Create(ctx, &tenant.CreateInput{Name: "Acme", Slug: "acme"})
	if err != nil {
		log.Fatal(err)
	}
	toAcme := func(context.Context, cortex.Scope) (string, error) { return acme.ID.String(), nil }

	// A tool the agent can call.
	clock := llm.Tool{
		Name:        "current_time",
		Description: "Returns the current time in UTC.",
		Parameters:  map[string]any{"type": "object", "properties": map[string]any{}},
	}
	tellTime := func(_ context.Context, _ cortex.Invocation) (string, error) {
		return time.Now().UTC().Format(time.RFC3339), nil
	}

	eng, err := engine.New(
		engine.WithStore(st),
		engine.WithLLM(nexusllm.New(gw, nexusllm.WithTenant(toAcme))),
		engine.WithTool(clock, tellTime),
	)
	if err != nil {
		log.Fatal(err)
	}

	err = eng.CreateAgent(ctx, &agent.Config{
		ID:           id.NewAgentID(),
		Name:         "assistant",
		SystemPrompt: "You are a concise assistant. Use your tools when they help.",
		Model:        "gpt-4o-mini",
		MaxSteps:     5,
	})
	if err != nil {
		log.Fatal(err)
	}

	r, err := eng.RunAgent(ctx, "assistant", "What time is it in UTC right now?", nil)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(r.State, r.StepCount, r.TokensUsed)
	fmt.Println(r.Output)
}

Run it:

go mod tidy
OPENAI_API_KEY=sk-... go run .

The model sees current_time, calls it, gets the timestamp back from your handler and answers with it. You'll see the run's final state, how many steps it took and the tokens used, then the answer. The run, every step and the tool call are now rows in cortex.db, so eng.ListSteps and eng.ListToolCalls can show you exactly what happened. Run it a second time and CreateAgent fails, because an agent named assistant already exists in that scope. Delete cortex.db or use GetAgentByName first.

If you leave out WithLLM, RunAgent still works, in an echo mode that's handy for wiring tests.

The Nexus adapter

The adapter refuses to send a call without a Nexus tenant, which is why the quick start makes one. In a real host you map your scope to its tenant with nexusllm.WithTenant, or set pipeline.WithTenantID on the context next to the scope. Each call is recorded in Nexus's usage with your scope levels and the run and agent behind it, so you can see what a run cost. nexusllm.WithProvider pins the provider a workspace picked, and nexusllm.WithSamplingUnsupported tells the adapter which models reject temperature (Claude 5.5 does, and the adapter already knows those ids).

Thinking models keep their signed thinking between turns, through approvals and reclaims, but it never reaches conversation memory. A turn whose text your publication gate redacted loses its thinking blocks when it is replayed to the model (set the Nexus Anthropic provider's anthropic.WithThinkingBlockBinding("drop_block") if you want that explicit). The thinking blocks are the model's unredacted output, and they sit that way in progress snapshots and continuations. A Nexus guard that blocks a response stops the run with safety_block and no tool from that step runs. Configuration has the details.

The Weave adapter

Every knowledge search runs under a Weave tenant, taken from the run's scope with weaveknowledge.WithTenant (the default is the scope's first level). A run only reads its own tenant's collections, plus any read-only ones you list with weaveknowledge.WithShared, such as your product docs. A call with no tenant fails before Weave is asked. Under the Forge extension, pass these with extension.WithWeaveOptions. Configuration has the details.

The Shield adapter

safety/shield screens what a run reads and writes with a Shield engine, and it can gate tool calls too. It's your safety scanner and your publication gate. Each scan runs under a Shield tenant taken from the run's scope (shieldsafety.WithTenant, first scope level by default) and the app you name with shieldsafety.WithApp, using the profile in the agent's shield_profile guardrail. When the adapter is your publication gate (shieldsafety.EngineOption and the Forge extension install it as both), tool results, tool errors and retrieved knowledge go in as untrusted, so Shield quotes them before the model reads them. A scan that can't run (no tenant, no profile, input too large) blocks the content. It never lets it through.

shieldsafety.NewToolAuthorizer puts Shield's tool-call scan under your own cortex.ToolAuthorizer. Your authorizer answers first, and Shield can then deny the call or ask a person to approve it, but it can't allow a call you refused. Under the Forge extension, pass adapter options with extension.WithShieldOptions. Configuration and Authorization have the details.

Why Cortex?

Calling a chat API in a loop gets you an agent for a demo. Then you need the run history in a database, a person to approve a risky tool call, a run that can wait a day for an external result without holding a goroutine, and a guarantee that one customer's agents never read another's memory. Cortex handles those parts so your code only describes the agents and their tools.

You need Cortex gives you
An agent that uses tools A ReAct loop over any llm.Client, with engine.WithTool handlers and a step limit per agent
A model gateway The llm/nexus adapter, so routing, caching, budgets and guardrails come from Nexus
Agents with a consistent character Skills, traits, behaviors, cognitive and communication styles, perception, and personas that compose them
A record of every run Runs, steps and tool calls stored with state, token counts and timings
A person in the loop Checkpoints, and a ToolAuthorizer that can hide tools, deny calls, or return ErrRequiresApproval to pause the run
Tools that run somewhere else engine.WithExternalTool: the run suspends, your caller runs the tool, and eng.Resume continues it
Several agents on one task Sequential, parallel, router, hierarchical and debate orchestration, plus agent-to-agent messages with a durable ask
Conversations that carry on Sessions and conversation memory per agent
Isolation per customer A scope you define (workspace, org, project) on every stored row, checked on every store call
Safety and retrieval safety/shield for Shield scans and knowledge/weave for Weave retrieval, exposed to the agent as a knowledge_search tool

Scope comes first

Cortex has no built-in tenant. You decide the levels, in order, and put them on the context:

ctx = cortex.WithScope(ctx, cortex.Scope{
	Levels: []cortex.Level{
		{Key: "org", Value: "acme"},
		{Key: "project", Value: "support"},
	},
})

Store methods read it back and return cortex.ErrNoScope when it's empty, so a scope you forgot to pass fails loudly and never becomes a query across every tenant. Lists match by prefix: a context scoped to org=acme sees every project under it. Keep the order of your levels stable between releases, because each position maps to an indexed column.

The human model

You can run an agent on a plain system prompt. When you want reusable structure, Cortex gives you these building blocks, each with its own store and CRUD methods on the engine:

Concept Package What it holds
Skill skill Tool bindings, knowledge references, a prompt fragment and a proficiency level
Trait trait A personality dimension and how it shifts the agent's output
Behavior behavior Trigger and action patterns the agent follows
Cognitive style cognitive Thinking phases and strategies
Communication style communication Tone, formality and verbosity
Perception perception Attention filters and context window settings
Persona persona An identity plus assigned skills, traits, behaviors and the three styles

Point an agent at a persona with PersonaRef, or list skills, traits and behaviors inline with InlineSkills, InlineTraits and InlineBehaviors. RunOverrides lets one run swap the model, persona, tools or session without changing the stored agent, and CloneAgent and ClonePersona copy one under a new name.

Tools

engine.WithTool takes a definition and a handler, and you write the JSON Schema yourself. You don't have to. The tool package builds a tool from a Go function over a struct, generates the schema from the struct's tags and checks every call against it before your function runs:

type ListInput struct {
	Project string `json:"project" jsonschema:"the project id"`
	Limit   int    `json:"limit,omitempty"`
}

eng, err := engine.New(
	engine.WithStore(st),
	tool.Register("list_services", "List the services in a project.",
		func(ctx context.Context, inv cortex.Invocation, in ListInput) ([]string, error) {
			return lookup(ctx, inv.Scope, in.Project, in.Limit)
		},
		tool.RetrySafe(),
	),
)

A call with a wrong type, a missing field or an argument the struct doesn't declare fails before your code sees it, and the model reads the error and can try again. tool.RetrySafe() says the tool can run twice without harm, which matters when a run is reclaimed after its worker died (see below). For a tool you registered with engine.WithTool, use engine.WithToolRetrySafe("name").

Already have an MCP server? The mcpbridge module turns its tools into cortex tools. You connect the *mcp.ClientSession, and mcpbridge.EngineOptions(ctx, cs, mcpbridge.Options{Prefix: "github_"}) gives you the engine options. Tools the server marks read-only come in as retry-safe, the rest don't. It's a separate module, so the engine itself never depends on the MCP SDK. Typed tools and MCP tools have the details.

Runs, checkpoints and suspension

RunAgent returns the finished *run.Run. StreamAgent sends StreamEvents on a channel as the run goes: run_started, step, tool_call, token, checkpoint, safety_block, suspended, done and error.

A run pauses in two cases. Your ToolAuthorizer returns ErrRequiresApproval for a call, which creates a checkpoint that someone resolves with eng.ResolveCheckpoint. Or the model calls a tool you registered with WithExternalTool, and the run suspends until you call eng.Resume(ctx, runID, engine.ResumeInput{ToolResults: ...}). A suspended run is a stored row, not a blocked goroutine, so it survives a restart. WithSuspensionTTL sets the default deadline for a pause, and an approval or an external tool can bring a shorter one of its own. Zero only drops the default; the sweeper keeps running for calls with their own deadline. plugin.RunSuspended fires on every pause, which is where you tell somebody a decision is waiting.

You don't have to continue a run inside the request that answered it. eng.DecideCheckpoint records a decision and eng.ProvideResults records external results, and both return straight away with the run ready. Queue a job from the plugin.RunReady hook and call eng.ContinueRun(ctx, runID) in it, on any replica. The run makes its tool calls as the principal that started it, which you give the engine a way to store with engine.WithPrincipalCodec.

Runs can also survive the worker that was running them, once you turn leasing on with engine.WithRunLease(60*time.Second). It's off by default, and then Stop lets every run finish, as v1.9.0 did. With it on, every run on the built-in stores holds a lease and saves its progress at each step. If the worker dies, or Stop hands the run off, the run becomes reclaimable and plugin.RunReclaimable fires. Nothing in cortex continues it for you: your job calls the same ContinueRun, which carries on from the last saved step, so only turn leasing on once RunReady and RunReclaimable both queue that job. A tool call that was in flight runs again only if the tool is retry-safe. Otherwise the run ends incomplete with cortex.ErrEffectUncertain, because nobody can say whether the call did anything. Runs covers the states and the reclaim rules.

Multi-agent orchestration

You store an orchestration.Config with a strategy and the agents taking part, then run it by name whenever you need it:

Strategy Constant How the agents work
Sequential StrategySequential Each agent takes the previous agent's output
Parallel StrategyParallel All agents get the same input at once
Router StrategyRouter One agent picks which participant handles the input
Hierarchical StrategyHierarchical A manager hands work to workers
Debate StrategyDebate Agents argue and a judge decides
err := eng.CreateOrchestration(ctx, &orchestration.Config{
	Name:     "review",
	Strategy: orchestration.StrategySequential,
	Participants: []orchestration.Participant{
		{AgentName: "drafter"},
		{AgentName: "editor"},
	},
})
// ...
result, err := eng.RunOrchestration(ctx, "review", "Write release notes for v1.7")

Participants share a blackboard with the roster and earlier work. For agents that talk without a fixed plan, the a2a bus sends FIPA-ACL style messages between them: fire-and-forget sends, a mailbox, and an ask that suspends the asking run until a peer replies. Turn it on with engine.WithA2A. The a2aremote module speaks the Agent2Agent protocol over JSON-RPC, REST and SSE for agents in other services, though it isn't tagged yet.

Storage

Backend Package Notes
PostgreSQL store/postgres, postgres.New(db *grove.DB) The first three scope levels get indexed columns
SQLite store/sqlite, sqlite.New(db *grove.DB) Good for one process and for tests
MongoDB store/mongo, mongo.New(db *grove.DB) Needs a replica set or sharded cluster

Every store filters reads by the first three scope levels only. Levels past the third are saved with the row, in scope_extra and in the canonical scope string, but no query matches on them, so two scopes that differ only below the third level read each other's rows. Keep your scopes to three levels.

Call Migrate(ctx) once before you use a store, or let the Forge extension do it for you on start. SQLite migrations need the executor from github.com/xraph/grove/drivers/sqlitedriver/sqlitemigrate, so import it for its side effect, as the quick start does. On MongoDB, saving or clearing a conversation writes the messages and the session's counters in one multi-document transaction, and a standalone mongod can't run transactions, so every conversation write fails against one.

Use it with Forge

The extension module registers Cortex as a Forge extension. It reads config from extensions.cortex (or cortex), takes its store from a *grove.DB in the container, wires in the Nexus, Shield, Weave and Sentinel services when they're registered (each one is skipped when it isn't), builds the engine and provides *engine.Engine. Start runs migrations.

go get github.com/xraph/cortex/extension@latest github.com/xraph/cortex/api@latest
import (
	"github.com/xraph/forge"
	"github.com/xraph/cortex/api"
	"github.com/xraph/cortex/engine"
	cortexext "github.com/xraph/cortex/extension"
)

app := forge.NewApp(forge.AppConfig{
	Name:        "agents",
	HTTPAddress: ":8080",
	Extensions: []forge.Extension{
		cortexext.New(
			cortexext.WithRoutes(func(eng *engine.Engine, r forge.Router) cortexext.RouteSet {
				return api.New(eng, r)
			}),
		),
	},
})

The extension mounts no HTTP routes by itself. The REST handlers live in the separate api module so you only compile them when you want them, and WithRoutes mounts them under /cortex by default. Leave it out and you still get the engine in the container and the dashboard contract.

The REST module doesn't check who's calling unless you give it an api.Authorizer with api.WithAuthorizer. That interface is asked about every route before it runs, and it names the person behind a checkpoint decision. Without one, any caller that reaches the router can use every route and decided_by on a checkpoint is whatever the caller wrote, so run it that way only when every caller is trusted equally.

extensions:
  cortex:
    base_path: /cortex
    default_model: smart
    default_max_steps: 25
    default_max_tokens: 4096
    default_temperature: 0.7
    default_reasoning_loop: react
    run_concurrency: 0      # runs at once in this process; 0 is no limit
    shutdown_timeout: 30s
    grove_database: ""      # a named grove.DB; empty uses the default one
    disable_routes: false
    disable_migrate: false

Those are the defaults. default_model is smart, which only means something if your gateway has an alias with that name. In code you have WithStore, WithGroveDatabase, WithEngineOption, WithExtension, WithConfig, WithBasePath, WithDisableMigrate, WithRequireConfig, WithDashboard and WithoutAutoAdapters. Anything you pass with WithEngineOption or WithStore wins over the config and over the services the extension finds in the container.

Lifecycle hooks

You write a hook as a plain value that implements any of the 21 small interfaces in plugin, and you register it with engine.WithExtension. They cover runs and steps (OnRunStarted, OnStepCompleted), tools (OnToolCalled, OnToolFailed, OnToolDenied), the human model (OnPersonaResolved, OnBehaviorTriggered, OnCognitivePhaseChanged), checkpoints, orchestration and handoffs, agent messages, and shutdown. audit_hook turns them into an audit trail for Chronicle.

Tracing and metrics

Every run is traced. A run is a run agent span, with a span per step, per tool call and per publication check under it, and the model call, store queries and safety scans from Nexus, Grove and Shield nest inside. The engine also records its own metrics: runs finished by state and stop reason, pass latency, steps, tool calls by kind, publication checks, suspensions, orchestrations and agent-to-agent deliveries. Every label is a closed set, and no prompt, tool argument, model output or agent name ever reaches a span or a label.

In a Forge app that's all on already; you'll find the metrics on /_/metrics as forge_cortex_*. Outside Forge, set an OpenTelemetry tracer provider and pass engine.WithMetrics. The observability page lists every span and series. The old observability metrics plugin is deprecated and records nothing now.

Modules

Module What it adds Tagged
github.com/xraph/cortex The engine, stores, human model, orchestration and adapters. No Forge dependency yes
github.com/xraph/cortex/api REST handlers for Forge yes
github.com/xraph/cortex/extension The Forge extension and dashboard contract yes
github.com/xraph/cortex/sentinel Evaluation through Sentinel yes
github.com/xraph/cortex/integrations/fabriq Fabriq as a knowledge provider, with extra tools and a learning-loop plugin yes
github.com/xraph/cortex/mcpbridge An MCP server's tools as cortex tools yes
github.com/xraph/cortex/integrations/sandbox Authenticated evaluation broker and OCI policy builder; Linux runtime qualification pending not yet
github.com/xraph/cortex/a2aremote, .../grpcbind Agent2Agent over the network, and its gRPC binding not yet

Documentation

Topic Read
First agent Getting started, architecture, full example
Human model Overview, skills, traits, personas
Execution Runs, steps and tool calls, memory, checkpoints, orchestration
Scope and config Host-defined scope, configuration
Storage PostgreSQL, SQLite, MongoDB
Forge, HTTP and plugins Forge extension, HTTP API, plugin system, observability, Fabriq brain

A few newer pages are in the repo but not on the site yet: tool authorization, suspension, sessions, agent messaging, remote agents, typed tools and MCP tools. The Go API is on pkg.go.dev.

FAQ

How do I build an AI agent with tools in Go?

Register each tool with engine.WithTool(def, handler), where def is an llm.Tool with a JSON Schema for its arguments and handler returns the result as a string. Give the engine an llm.Client, create an agent, and call RunAgent. The quick start does exactly that.

Which LLM providers does Cortex support?

Any provider you can reach through an llm.Client. The bundled llm/nexus adapter goes through a Nexus gateway, which covers OpenAI, Anthropic, Gemini, Bedrock, Ollama and the rest of Nexus's providers. For anything else, implement Complete and CompleteStream yourself.

Can a person approve a tool call before it runs?

Yes. Return cortex.ErrRequiresApproval from your ToolAuthorizer's Authorize for that call. The run pauses, a checkpoint holds the call, and eng.ResolveCheckpoint with the decision lets it continue.

Can I use Cortex without Forge?

Yes. The root module doesn't import Forge at all. Only api and extension do, and they're separate modules.

Does Cortex need a database?

It needs a store.Store. SQLite is enough to get going, and PostgreSQL or MongoDB suit a deployment with more than one process.

Contributing

Issues and focused pull requests are welcome. Open an issue first for anything large.

git clone https://github.com/xraph/cortex.git
cd cortex
GOWORK=off go build ./...
make test     # root module
make lint     # golangci-lint in every module
cd extension && GOWORK=off go test -race -count=1 ./...

Only make lint walks every module. Test a sub-module from its own directory. The Postgres and Mongo store tests start containers through testcontainers and skip without Docker, so run with -v to see whether they ran. Skip make build, make run and make dev, which need a cmd/cortex that doesn't exist. AGENTS.md has the CI jobs, the scope rules for new store methods and the release steps. Commits follow Conventional Commits with a scope, as in fix(engine): ....

License

MIT. See LICENSE.

Cortex is part of the forgery stack built on Forge, by Rex Raphael and contributors.

Documentation

Overview

Package cortex provides a human-emulating AI agent orchestration system.

Agents are composed from Skills, Traits, Behaviors, and Personas — just as humans are products of personality, learned skills, and thinking patterns. Host-scoped, plugin-extensible, Forge-native.

Index

Constants

View Source
const Version = "1.9.0"

Version is the semantic version of the Cortex module.

Variables

View Source
var (
	// Store errors.
	ErrNoStore         = errors.New("cortex: no store configured")
	ErrStoreClosed     = errors.New("cortex: store closed")
	ErrMigrationFailed = errors.New("cortex: migration failed")
	// ErrConversationUnavailable prevents execution with an unreadable session history.
	ErrConversationUnavailable = errors.New("cortex: conversation history unavailable")
	// ErrContextUnavailable prevents use of an unavailable or changed context policy.
	ErrContextUnavailable = errors.New("cortex: context policy unavailable")

	// Not found errors.
	ErrAgentNotFound                    = errors.New("cortex: agent not found")
	ErrRunConflict                      = errors.New("cortex: run revision conflict")
	ErrRunNotFound                      = errors.New("cortex: run not found")
	ErrStepNotFound                     = errors.New("cortex: step not found")
	ErrToolCallNotFound                 = errors.New("cortex: tool call not found")
	ErrSkillNotFound                    = errors.New("cortex: skill not found")
	ErrTraitNotFound                    = errors.New("cortex: trait not found")
	ErrBehaviorNotFound                 = errors.New("cortex: behavior not found")
	ErrPersonaNotFound                  = errors.New("cortex: persona not found")
	ErrSessionNotFound                  = errors.New("cortex: session not found")
	ErrCheckpointNotFound               = errors.New("cortex: checkpoint not found")
	ErrOrchestrationNotFound            = errors.New("cortex: orchestration not found")
	ErrOrchestrationRunNotFound         = errors.New("cortex: orchestration run not found")
	ErrOrchestrationRunConflict         = errors.New("cortex: orchestration run revision conflict")
	ErrOrchestrationRunClosed           = errors.New("cortex: orchestration run is closed")
	ErrOrchestrationCoordinatorNotFound = errors.New("cortex: orchestration coordinator not found")
	ErrOrchestrationInvocationNotFound  = errors.New("cortex: orchestration invocation not found")
	ErrWorkingMemoryNotFound            = errors.New("cortex: working memory not found")
	ErrOverlayNotFound                  = errors.New("cortex: prompt overlay not found")

	// Continuation errors.
	ErrOrchestrationWaiting                 = errors.New("cortex: orchestration is waiting for a child")
	ErrOrchestrationContinuationUnavailable = errors.New("cortex: orchestration continuation unavailable")

	// Conflict errors.
	ErrAlreadyExists = errors.New("cortex: resource already exists")
	// ErrPreparationUnavailable means the requested consistency guarantee cannot be provided.
	ErrPreparationUnavailable = errors.New("cortex: atomic preparation unavailable")
	// ErrPreparationConflict requires fresh preparation after an observed source change.
	ErrPreparationConflict = errors.New("cortex: preparation source changed or unavailable")

	// Task call errors.
	ErrTaskRunReconciliationRequired = errors.New("cortex: task run start requires reconciliation")
	ErrTaskCallUnavailable           = errors.New("cortex: task call authority unavailable")
	ErrTaskCallUncertain             = errors.New("cortex: task call outcome uncertain")
	ErrInvalidRunCall                = errors.New("cortex: invalid run call")
	ErrRunCallConflict               = errors.New("cortex: run call conflict")
	ErrRunCallNotFound               = errors.New("cortex: run call not found")
	ErrProviderCallUncertain         = errors.New("cortex: provider call uncertain")
	ErrRunCallsSealed                = errors.New("cortex: run calls sealed")
	ErrRunCallUnavailable            = errors.New("cortex: run call transaction unavailable")

	// Scheduler errors.
	ErrInvalidScheduler                = errors.New("cortex: invalid scheduler configuration or snapshot")
	ErrSchedulerFull                   = errors.New("cortex: scheduler queue full")
	ErrSchedulerStopped                = errors.New("cortex: scheduler stopped")
	ErrSchedulerReconciliationRequired = errors.New("cortex: scheduler admission requires reconciliation")

	// State errors.
	ErrInvalidState     = errors.New("cortex: invalid state transition")
	ErrRunCancelled     = errors.New("cortex: run cancelled")
	ErrInvalidPrepared  = errors.New("cortex: invalid prepared execution")
	ErrNoLLM            = errors.New("cortex: no model provider configured")
	ErrRunIncomplete    = errors.New("cortex: run incomplete")
	ErrRunAlreadyDone   = errors.New("cortex: run already completed")
	ErrBudgetExhausted  = errors.New("cortex: budget exhausted")
	ErrMaxStepsReached  = errors.New("cortex: maximum steps reached")
	ErrMaxTokensReached = errors.New("cortex: maximum tokens reached")
	// ErrEngineStopping refuses a run that would start while Stop drains the
	// engine. Nothing was written for it; retry on another worker, or after
	// Start.
	ErrEngineStopping = errors.New("cortex: engine is stopping")

	// ErrInvalidDelegationPlan rejects a manager plan before worker dispatch.
	ErrInvalidDelegationPlan = errors.New("cortex: invalid delegation plan")

	// Suspension errors.
	//
	// ErrNotSuspended is returned by ClaimSuspension to the losing side of
	// a double resume: the run was not paused (or another caller already
	// claimed it) when the claim ran.
	ErrNotSuspended = errors.New("cortex: run not suspended")
	// ErrSuspensionExpired is returned when a resume targets a suspension
	// past its ExpiresAt. The suspension row still exists for the expiry
	// sweep to find and clean up; it is simply no longer resumable.
	ErrSuspensionExpired = errors.New("cortex: suspension expired")
	// ErrResultsMismatch is returned when the results a caller supplies to
	// resume an external-tool suspension do not correspond to the pending
	// calls it was waiting on.
	ErrResultsMismatch = errors.New("cortex: suspension results do not match pending calls")
	// ErrInvalidContinuation is returned when a suspension carries a
	// continuation the loop cannot run: today that means a zero Config,
	// which a resume would otherwise read as a step budget of nothing.
	//
	// It is its own sentinel rather than ErrMaxStepsReached, which is what
	// a zero budget looks like from inside the loop. That error is a
	// matched-on contract meaning "this run used up its steps", and it
	// sends an operator to raise the budget for a run whose real problem
	// is a continuation nothing could have resumed.
	ErrInvalidContinuation = errors.New("cortex: suspension continuation is unusable")

	// Principal errors.
	//
	// ErrPrincipalRevoked is what a PrincipalCodec wraps when a run's
	// identity is permanently gone: the member was removed, the key
	// revoked. A resume that meets it fails the run with the codec's
	// reason rather than continuing under nobody or under the resumer.
	ErrPrincipalRevoked = errors.New("cortex: principal no longer valid")
	// ErrPrincipalUnavailable means a principal could not be encoded or
	// decoded right now. A run is not created without its principal, and a
	// paused run whose principal cannot be decoded stays paused for the
	// next resume to try again.
	ErrPrincipalUnavailable = errors.New("cortex: principal unavailable")
	// ErrPrincipalCodecRequired is returned by engine.New when a tool
	// authorizer is installed without a principal codec. Without one,
	// every resumed run would reach the authorizer as nobody.
	ErrPrincipalCodecRequired = errors.New("cortex: a tool authorizer needs a principal codec so resumed runs keep their principal")

	// Run continuation errors.
	//
	// ErrRunNotContinuable is returned when a continuation targets a run
	// that is not ready or reclaimable: another replica already continued
	// it, or it never got that far.
	ErrRunNotContinuable = errors.New("cortex: run is not ready to continue")
	// ErrRunLeaseLost is returned to a worker whose lease on a running run
	// is gone: another worker holds it, or the run left running. The
	// worker must stop writing the run.
	ErrRunLeaseLost = errors.New("cortex: run lease lost")
	// ErrRunLeased is returned when a run's lease has not lapsed yet, so
	// the run still belongs to the worker holding it.
	ErrRunLeased = errors.New("cortex: run lease is still held")
	// ErrContinuationUnavailable is returned by the engine calls that make a
	// run ready or continue one when the store does not implement
	// run.ContinuationStore.
	ErrContinuationUnavailable = errors.New("cortex: store cannot hold ready runs")
	// ErrProgressNotFound is returned when a run has no saved progress
	// snapshot at the caller's scope.
	ErrProgressNotFound = errors.New("cortex: run progress not found")
	// ErrRunInterrupted is returned by a run that its worker handed off
	// while shutting down. The run is reclaimable, not ended: another
	// replica continues it from its last saved progress.
	ErrRunInterrupted = errors.New("cortex: run handed off before it finished")
	// ErrReclaimLimit fails a run that was reclaimed more often than
	// engine.WithReclaimLimit allows. A run that takes its worker down every
	// time it runs is stopped here instead of being handed to the next one.
	ErrReclaimLimit = errors.New("cortex: run reclaimed too many times")
	// ErrRunNotRecoverable fails a reclaimed run that left nothing to
	// continue from: no progress snapshot and no pause it was claimed from.
	ErrRunNotRecoverable = errors.New("cortex: run has no saved progress to continue from")
)
View Source
var (
	ErrInvalidEvidence           = errors.New("cortex: invalid evidence")
	ErrEvidenceDigestMismatch    = errors.New("cortex: evidence digest mismatch")
	ErrEvidenceAccessDenied      = errors.New("cortex: evidence access denied")
	ErrEvidenceTooLarge          = errors.New("cortex: evidence exceeds size limit")
	ErrEvidenceNotFound          = errors.New("cortex: evidence not found")
	ErrEvidenceDeleted           = errors.New("cortex: evidence deleted")
	ErrEvidenceConflict          = errors.New("cortex: immutable evidence conflict")
	ErrInvalidVerification       = errors.New("cortex: invalid verification record")
	ErrVerificationConflict      = errors.New("cortex: immutable verification conflict")
	ErrVerificationNotFound      = errors.New("cortex: verification record not found")
	ErrVerificationJobNotFound   = errors.New("cortex: verification job not found")
	ErrVerificationJobConflict   = errors.New("cortex: verification job ownership conflict")
	ErrVerificationJobExpired    = errors.New("cortex: verification job deadline or attempts exhausted")
	ErrVerificationJobBudget     = errors.New("cortex: verification job budget exhausted")
	ErrVerificationCallUncertain = errors.New("cortex: verification call already dispatched; reconcile its outcome")
	ErrRunUnverified             = errors.New("cortex: run did not pass required verification")
	ErrVerificationUnavailable   = errors.New("cortex: verification worker unavailable")
	ErrResultCommitUnavailable   = errors.New("cortex: atomic verified result commit unavailable")
	ErrResultAccessDenied        = errors.New("cortex: result content access denied")
	ErrSandboxInputsIncomplete   = errors.New("cortex: sandbox input staging is incomplete")
	ErrSandboxInputsConflict     = errors.New("cortex: sandbox input staging identity conflict")
)

Evidence and verification errors remain independent of execution outcomes.

View Source
var (
	ErrInvalidPublicEvent      = errors.New("cortex: invalid public event")
	ErrPublicEventConflict     = errors.New("cortex: public event identity or sequence conflict")
	ErrPublicEventCursorGap    = errors.New("cortex: public event cursor expired")
	ErrPublicEventAccessDenied = errors.New("cortex: public event replay denied")
	ErrPublicEventUnavailable  = errors.New("cortex: public event replay unavailable")
)

Public replay errors distinguish unsupported requests, gaps and unavailable storage.

View Source
var (
	ErrEffectConflict    = errors.New("cortex: effect operation conflicts with retained request")
	ErrEffectNotFound    = errors.New("cortex: effect operation not found")
	ErrEffectUncertain   = errors.New("cortex: effect outcome requires reconciliation")
	ErrEffectUnavailable = errors.New("cortex: effect executor unavailable")
)

Effect errors distinguish a conflicting request from an unresolved external action.

View Source
var (
	ErrInvalidHandoff     = errors.New("cortex: invalid task handoff")
	ErrHandoffUnavailable = errors.New("cortex: task handoff unavailable")
)

Handoff errors distinguish invalid proposals from unavailable host admission.

View Source
var (
	ErrInvalidReservation     = errors.New("cortex: invalid budget reservation")
	ErrReservationConflict    = errors.New("cortex: budget reservation conflict")
	ErrReservationNotFound    = errors.New("cortex: budget reservation not found")
	ErrSharedBudgetExceeded   = errors.New("cortex: shared budget exceeded")
	ErrBudgetUnavailable      = errors.New("cortex: shared budget storage unavailable")
	ErrInvalidTaskAcceptance  = errors.New("cortex: invalid task acceptance")
	ErrInvalidTaskUsage       = errors.New("cortex: invalid task usage")
	ErrTaskUsageConflict      = errors.New("cortex: task usage conflict")
	ErrTaskUsageClosed        = errors.New("cortex: task usage admission closed")
	ErrInvalidTaskSettlement  = errors.New("cortex: invalid task usage settlement")
	ErrTaskSettlementConflict = errors.New("cortex: task usage settlement conflict")
	ErrTaskAcceptanceConflict = errors.New("cortex: task acceptance conflict")
	ErrTaskAcceptanceNotFound = errors.New("cortex: task acceptance not found")
	ErrInvalidClaim           = errors.New("cortex: invalid task claim")
	ErrClaimConflict          = errors.New("cortex: task claim conflict")
	ErrClaimNotFound          = errors.New("cortex: task claim not found")
	ErrInvalidReview          = errors.New("cortex: invalid assignment review")
	ErrReviewConflict         = errors.New("cortex: assignment review conflict")
	ErrReviewNotFound         = errors.New("cortex: assignment review not found")
	ErrInvalidAssignment      = errors.New("cortex: invalid assignment")
	ErrAssignmentConflict     = errors.New("cortex: assignment conflict")
	ErrAssignmentNotFound     = errors.New("cortex: assignment not found")
	ErrAssignmentUnavailable  = errors.New("cortex: assignment transaction unavailable")
)

Assignment errors describe durable progress journal failures.

View Source
var (
	// ErrInvalidScope identifies an ambiguous or unsupported scope identity.
	ErrInvalidScope = errors.New("cortex: invalid scope")
	// ErrUnsupportedScopeDepth means storage cannot enforce every level.
	ErrUnsupportedScopeDepth = errors.New("cortex: scope exceeds three supported levels")
)
View Source
var ErrNoRescoper = errors.New("cortex: unscoped rows present and no rescoper supplied")

ErrNoRescoper is returned when a migration finds rows carrying no scope and no Rescoper was supplied. Cortex cannot know what a host's app and tenant strings meant, so it refuses to guess and aborts instead.

View Source
var ErrNoScope = errors.New("cortex: no scope on context")

ErrNoScope is returned when an operation that requires a scope receives a zero one. A zero scope means the thread broke somewhere upstream, so failing here is preferable to querying across every host-defined level.

View Source
var ErrRequiresApproval = errors.New("cortex: tool call requires approval")

ErrRequiresApproval is the authorizer's third answer. Returning it from Authorize suspends the run and opens a checkpoint rather than denying the call, so a human can decide and the run can carry on afterwards.

It was held back in v1.10.0 on purpose: with nowhere to suspend into, returning it would have read as an ordinary denial and quietly turned "ask someone" into "no".

Functions

func IsOrchestrationWaiting

func IsOrchestrationWaiting(err error) bool

IsOrchestrationWaiting reports whether every nonnil leaf of an error tree is ErrOrchestrationWaiting. A mixed failure must never be treated as suspension.

func PrincipalFromContext

func PrincipalFromContext(ctx context.Context) any

PrincipalFromContext extracts the caller identity WithPrincipal attached. A missing principal returns nil, mirroring ScopeFromContext's zero-value-on-absence shape rather than panicking.

func RootTenant added in v1.10.0

func RootTenant(_ context.Context, s Scope) (string, error)

RootTenant is the TenantFunc adapters use by default: the value of the scope's first level, which for most hosts is the organization or workspace. It returns ErrNoScope for a zero scope.

func ValidateRescopedScope

func ValidateRescopedScope(s Scope) error

ValidateRescopedScope applies shared validation to an operator's legacy mapping.

func ValidateScope

func ValidateScope(s Scope) error

ValidateScope checks the identity the built-in stores can enforce. Keys are unique and ordered. Keys and values must be valid UTF-8; no Unicode normalization is applied. The canonical codec reserves slash and equals.

func WithPrincipal

func WithPrincipal(ctx context.Context, principal any) context.Context

WithPrincipal attaches the host's caller identity to ctx. Cortex never interprets this value — it only carries it from wherever a host authenticates a request through to Subject.Principal, for a ToolAuthorizer to interpret however its host defines identity.

It travels on the context, alongside Scope, rather than through RunOverrides: both are ambient request identity with the same lifetime, and RunOverrides mirrors a JSON wire type that either can't carry an uninterpreted any or would force cortex to start interpreting it. Dispatch also has no overrides parameter, so a struct field would leave direct dispatch permanently principal-less.

func WithScope

func WithScope(ctx context.Context, s Scope) context.Context

WithScope attaches a detached scope, including an invalid request. Invalid requests mask any parent authority; RequireScope reports the validation error.

Types

type AgentResult

type AgentResult struct {
	AgentName string        `json:"agent_name"`
	RunID     id.AgentRunID `json:"run_id,omitempty"`
	Output    string        `json:"output"`
	Err       error         `json:"-"`
}

AgentResult is the caller-facing view of an agent run. A waiting result retains its identity without a terminal answer; callers must inspect both error values.

type AgentRunner

type AgentRunner interface {
	RunAgent(ctx context.Context, agentName, input string, opts *RunOpts) (*AgentResult, error)
}

AgentRunner is the one host capability the coordination packages depend on: run a named agent and hand back its result. The engine satisfies it through a thin adapter, which is what keeps orchestration and a2a from importing the engine.

type ApprovalRequired added in v1.10.0

type ApprovalRequired struct {
	// Reason is shown to the decider and on the checkpoint.
	Reason string
	// TTL is how long the decision may take. Zero uses the engine's
	// suspension TTL. A negative TTL is refused, and the call is denied
	// rather than paused with a deadline that has already passed.
	TTL time.Duration
}

ApprovalRequired is ErrRequiresApproval with something to say: why the call needs a person, and how long that person has. Return it from Authorize, bare or wrapped, and the run pauses exactly as it does for the sentinel, because errors.Is matches it against ErrRequiresApproval.

The TTL is per call because one engine-wide deadline does not fit every decision. A production deploy can reasonably wait days for the right person, while a routine check that nobody answers within the hour is better failed than left in the queue.

The name breaks the XxxError convention on purpose: it reads as the authorizer's answer, alongside ErrRequiresApproval, at the call site that returns it.

func (ApprovalRequired) Error added in v1.10.0

func (a ApprovalRequired) Error() string

Error says the call needs approval, and why.

func (ApprovalRequired) Is added in v1.10.0

func (a ApprovalRequired) Is(target error) bool

Is reports a match for ErrRequiresApproval, so code that only knows the sentinel still recognizes an approval.

type Config

type Config struct {
	// DefaultModel is the LLM model used when an agent does not specify one.
	DefaultModel string

	// DefaultMaxSteps is the maximum reasoning steps per run.
	DefaultMaxSteps int

	// DefaultMaxTokens is the maximum output tokens per LLM call.
	DefaultMaxTokens int

	// DefaultTemperature is the LLM sampling temperature.
	DefaultTemperature float64

	// DefaultReasoningLoop is the reasoning strategy when none is set.
	DefaultReasoningLoop string

	// ShutdownTimeout is the maximum time to wait for graceful shutdown.
	// The Forge extension applies it, bounding Engine.Stop from its own
	// Stop; engine.New alone does not read it.
	ShutdownTimeout time.Duration

	// RunConcurrency is the maximum number of agent runs executing at once,
	// zero for no limit (the default). The Forge extension applies it with
	// engine.WithRunConcurrency; engine.New alone does not read it.
	RunConcurrency int
}

Config holds configuration for the Cortex engine.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns a Config with sensible defaults.

type Entity

type Entity struct {
	CreatedAt time.Time `json:"created_at" bun:"created_at,notnull,default:current_timestamp"`
	UpdatedAt time.Time `json:"updated_at" bun:"updated_at,notnull,default:current_timestamp"`
}

Entity is the base type embedded by all cortex domain objects.

func NewEntity

func NewEntity() Entity

NewEntity returns an Entity with timestamps set to now.

type ID

type ID = id.ID

ID is the primary identifier type for all Cortex entities.

type Invocation

type Invocation struct {
	Subject
	Call llm.ToolCall
}

Invocation is one tool call about to be dispatched. It embeds Subject so a handler receives scope and principal explicitly.

The alternative is leaving handlers to pull those off the context. That works and is idiomatic Go, and it is also the exact pattern that let a tenant identifier sit available and unread until every tenant shared one conversation bucket. An explicit Invocation means a handler that ignores scope has visibly ignored it.

type Level

type Level struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

Level is one rung of a host-defined scope hierarchy. Cortex never interprets Key; it only matches on it.

type MigrateOption

type MigrateOption func(*MigrateOptions)

MigrateOption configures a migration run.

func WithRescoper

func WithRescoper(r Rescoper) MigrateOption

WithRescoper supplies the mapping from legacy identifiers to a Scope.

type MigrateOptions

type MigrateOptions struct {
	Rescoper Rescoper
}

MigrateOptions carries everything Migrate needs beyond a context.

func ApplyMigrateOptions

func ApplyMigrateOptions(opts ...MigrateOption) MigrateOptions

ApplyMigrateOptions folds the variadic options into a value. A zero MigrateOptions is valid: a fresh install has no unscoped rows and so never calls the rescoper.

type Prefix

type Prefix = id.Prefix

Prefix identifies the entity type encoded in a TypeID.

type PrincipalCodec added in v1.10.0

type PrincipalCodec interface {
	EncodePrincipal(ctx context.Context, principal any) ([]byte, error)
	// DecodePrincipal restores a run's principal. Return an error wrapping ErrPrincipalRevoked when
	// the identity is permanently gone; any other error is treated as transient.
	DecodePrincipal(ctx context.Context, data []byte) (any, error)
}

PrincipalCodec carries a run's principal across a pause. A principal lives on the context, and a context does not survive a suspension: the run is stored, and whoever resumes it brings a context of their own. So the engine encodes the principal when it creates a run, stores the bytes with it, and decodes them on every resume, putting the original caller back where the resumer's would otherwise be.

The bytes are the host's own. Cortex stores them and hands them back, and never reads them. A codec can write the whole identity, or only a reference it looks up again on decode, which is also the moment to find out the identity is gone.

func JSONPrincipalCodec added in v1.10.0

func JSONPrincipalCodec[T any]() PrincipalCodec

JSONPrincipalCodec encodes a principal of type T with json.Marshal and decodes it with json.Unmarshal into a T value, so an authorizer that asserts principal.(T) on a fresh run sees the same type on a resumed one. A principal of any other type is refused rather than written, because it would come back as a T the host never put on the context.

Stored bytes that no longer decode into a T, because they are not JSON or because T changed shape since the run was created, are reported as ErrPrincipalRevoked: retrying cannot change them, so the run is failed rather than left paused. Any other decode error stays transient.

It has no other way to tell a revoked identity from a live one. A host that needs that check writes its own codec, or wraps this one.

type Rescoper

type Rescoper interface {
	Rescope(ctx context.Context, appID, tenantID string) (Scope, error)
}

Rescoper maps a row's legacy identifiers onto a Scope during the v1.8.0 upgrade. It is called once per distinct (appID, tenantID) pair, not once per row, so an expensive implementation is fine.

type ReservedAgentRunner

type ReservedAgentRunner interface {
	ReserveAgent(context.Context, id.OrchestrationID, string, string, *RunOpts) (*ReservedInvocation, error)
}

ReservedAgentRunner reserves capacity and identity without executing tools or calling a provider. Return the invocation with an error when its identity needs reconciliation. Callers must persist the identity before execution and must never execute an invocation returned with an error.

type ReservedInvocation

type ReservedInvocation struct {
	RunID   id.AgentRunID
	Execute func(context.Context) (*AgentResult, error)
}

ReservedInvocation identifies a child before execution. Execute must start that exact run at most once. Unused allocations remain reserved until the host can prove that releasing them is safe.

type RunOpts

type RunOpts struct {
	Model        string
	Temperature  *float64
	MaxSteps     int
	SystemPrompt string
}

RunOpts is the subset of run overrides a caller needs when invoking an agent through AgentRunner. The engine maps it to its own RunOverrides.

type Scope

type Scope struct {
	Levels []Level `json:"levels"`
}

Scope is an ordered hierarchy the host defines. TwinOS uses workspace then project; another host might use org, team, environment. Order is significant: it determines which indexed column each level lands in, so a host must keep its ordering stable across releases.

func DecodeStoredScope

func DecodeStoredScope(canonical string, indexed [3]string, hasExtra bool) (Scope, error)

DecodeStoredScope checks that canonical and indexed identity agree. Overflow rows must be inventoried and migrated by an operator, never truncated on read.

func ParseCanonical

func ParseCanonical(canon string) (Scope, error)

ParseCanonical parses a Canonical() string back into a Scope, preserving level order. It is the inverse of Canonical: stores that persist only the canonical string (or the flattened scope_l0/l1/l2 columns plus a scope_canon column) use this to reconstruct Scope on read. An empty string parses to the zero Scope.

A segment that doesn't contain "=" is an error rather than something to skip: silently dropping it would reconstruct a scope NARROWER than what was actually stored, which a caller comparing it against the row's own scope_l0/l1/l2 columns (or using it to authorize a write) would never notice. Callers should treat a non-nil error here as a corrupt row — the same as any other scan failure — rather than falling back to whatever levels did parse.

func RequireScope

func RequireScope(ctx context.Context) (Scope, error)

RequireScope returns detached, validated request identity. Invalid attached scopes mask their parent and retain a typed error for boundary adapters.

func ScopeFromContext

func ScopeFromContext(ctx context.Context) Scope

ScopeFromContext returns detached valid identity, or zero for missing or invalid scope. Use RequireScope at boundaries to preserve the typed error.

func (Scope) Canonical

func (s Scope) Canonical() string

Canonical renders the scope as "key=value/key=value", preserving order. It is stored alongside the indexed columns so an exact-match lookup does not need to compare three columns separately.

func (Scope) Covers

func (s Scope) Covers(other Scope) bool

Covers reports whether s is other, or an ancestor of it: every level s carries appears in other at the same position, with the same key and value. A scope covers itself.

This asks "is s somewhere in other's own ancestry", which is a different question from the prefix matching the stores do on a list. Prefix matching widens DOWNWARD, so a workspace filter reaches every project inside it. Covers reads UPWARD, which is what a project-level run needs when it wants to know what its workspace configured.

func (Scope) Get

func (s Scope) Get(key string) (string, bool)

Get returns the value for key, and whether the level was present.

func (Scope) IsZero

func (s Scope) IsZero() bool

type Subject

type Subject struct {
	Scope     Scope
	Principal any
	AgentID   id.AgentID
	RunID     id.AgentRunID
}

Subject is who is asking. Principal is host-defined and cortex never interprets it: a host puts whatever its own authorization layer needs there, and gets it back unchanged.

type TenantFunc added in v1.10.0

type TenantFunc func(ctx context.Context, s Scope) (string, error)

TenantFunc names the tenant an external service (a model gateway, a safety engine, a knowledge store) should bill and isolate a call under, given the scope the call runs in. Adapters that talk to such a service take one, so a host can map its own scope to the service's tenant IDs. It returns ErrNoScope, or an error wrapping it, when the scope names no tenant.

type ToolAuthorizer

type ToolAuthorizer interface {
	// Visible filters the tool list before it reaches the model. Called
	// once per step.
	Visible(ctx context.Context, s Subject, tools []llm.Tool) []llm.Tool

	// Authorize gates one dispatch. Returning nil allows the call; any
	// error denies it, and the error's text is fed back to the model as
	// the tool result so it can react rather than silently retrying.
	//
	// The one exception is ErrRequiresApproval, which is not a denial:
	// nothing goes back to the model, the run pauses, and a checkpoint
	// carries the call to whoever decides. Wrap it if you want to say
	// why (fmt.Errorf("...: %w", cortex.ErrRequiresApproval)); it is
	// matched with errors.Is. Return ApprovalRequired instead to put the
	// reason on the checkpoint and give the decision its own deadline.
	Authorize(ctx context.Context, s Subject, call llm.ToolCall) error
}

ToolAuthorizer decides what the model may see and what it may call. It is consulted at two points because they are different questions: a model can name a tool it was never shown, so filtering the list is not a substitute for gating the call.

A nil authorizer allows everything, so a host that sets none sees no change.

Directories

Path Synopsis
Package a2a gives cortex agents direct, addressable communication: FIPA-ACL messages, durable conversations, mailboxes, and an ask that suspends the sender's run until a peer answers.
Package a2a gives cortex agents direct, addressable communication: FIPA-ACL messages, durable conversations, mailboxes, and an ask that suspends the sender's run until a peer answers.
Package agent defines the agent configuration entity and its store interface.
Package agent defines the agent configuration entity and its store interface.
api module
Package audithook bridges Cortex lifecycle events to an audit trail backend.
Package audithook bridges Cortex lifecycle events to an audit trail backend.
Package behavior defines the Behavior entity — what an agent does.
Package behavior defines the Behavior entity — what an agent does.
Package checkpoint defines the Checkpoint entity for human-in-the-loop control.
Package checkpoint defines the Checkpoint entity for human-in-the-loop control.
Package cognitive defines persona cognitive settings and trusted operational observations.
Package cognitive defines persona cognitive settings and trusted operational observations.
Package communication defines the CommunicationStyle value object — how an agent talks.
Package communication defines the CommunicationStyle value object — how an agent talks.
Package coordination defines accountable assignments around Cortex execution.
Package coordination defines accountable assignments around Cortex execution.
dashboard module
Package engine provides the central Cortex agent orchestration coordinator.
Package engine provides the central Cortex agent orchestration coordinator.
Package event defines bounded, metadata-only public execution replay.
Package event defines bounded, metadata-only public execution replay.
Package evidence defines immutable, scoped artifacts used by result checks.
Package evidence defines immutable, scoped artifacts used by result checks.
Package execution defines cumulative execution limits and accounting.
Package execution defines cumulative execution limits and accounting.
extension module
Package id defines TypeID-based identity types for all Cortex entities.
Package id defines TypeID-based identity types for all Cortex entities.
integrations
fabriq module
sandbox module
internal
hostport
Package hostport validates trusted host interfaces and bounds callback lifetimes.
Package hostport validates trusted host interfaces and bounds callback lifetimes.
resulttest
Package resulttest supplies committed-result fixtures for transport tests.
Package resulttest supplies committed-result fixtures for transport tests.
streamhttp
Package streamhttp shares HTTP streaming behavior across transport adapters.
Package streamhttp shares HTTP streaming behavior across transport adapters.
telemetry
Package telemetry holds the tracing and metrics helpers the engine and the message bus share.
Package telemetry holds the tracing and metrics helpers the engine and the message bus share.
telemetry/telemetrytest
Package telemetrytest is a recording metrics sink for cortex's own tests.
Package telemetrytest is a recording metrics sink for cortex's own tests.
Package knowledge defines the abstraction for knowledge retrieval providers.
Package knowledge defines the abstraction for knowledge retrieval providers.
weave
Package weave adapts the Weave RAG engine to the cortex knowledge.Provider interface.
Package weave adapts the Weave RAG engine to the cortex knowledge.Provider interface.
Package learning defines lessons accepted from governed evidence.
Package learning defines lessons accepted from governed evidence.
llm
Package llm defines a provider-agnostic LLM client interface for the Cortex engine.
Package llm defines a provider-agnostic LLM client interface for the Cortex engine.
nexus
Package nexus adapts the Nexus LLM gateway to the cortex llm.Client interface.
Package nexus adapts the Nexus LLM gateway to the cortex llm.Client interface.
mcpbridge module
Package memory defines the Message entity and memory store interface.
Package memory defines the Message entity and memory store interface.
Package observability used to hold Cortex's metrics plugin.
Package observability used to hold Cortex's metrics plugin.
optimization module
Package orchestration coordinates multiple Cortex agents working together, in sequence, in parallel, hierarchically, via a router, or in a debate.
Package orchestration coordinates multiple Cortex agents working together, in sequence, in parallel, hierarchically, via a router, or in a debate.
Package perception defines the Perception value object — how an agent sees.
Package perception defines the Perception value object — how an agent sees.
Package persona defines the Persona entity — the whole person.
Package persona defines the Persona entity — the whole person.
Package planning validates immutable task graphs before a host schedules work.
Package planning validates immutable task graphs before a host schedules work.
Package plugin defines the extension system for Cortex.
Package plugin defines the extension system for Cortex.
Package prompt models a system prompt as an ordered set of addressable sections rather than one opaque string.
Package prompt models a system prompt as an ordered set of addressable sections rather than one opaque string.
Package run defines the Run, Step, and ToolCall entities for agent execution.
Package run defines the Run, Step, and ToolCall entities for agent execution.
Package safety defines the interface for content safety scanning in the Cortex agent engine.
Package safety defines the interface for content safety scanning in the Cortex agent engine.
rules
Package rules screens bounded text against explicit host-owned regular expressions.
Package rules screens bounded text against explicit host-owned regular expressions.
shield
Package shield bridges the Shield safety engine to Cortex: content scans through safety.Scanner and safety.PublicationGate, streamed model output a segment at a time through safety.IncrementalGate, and tool-call scans layered under the host's cortex.ToolAuthorizer (see NewToolAuthorizer).
Package shield bridges the Shield safety engine to Cortex: content scans through safety.Scanner and safety.PublicationGate, streamed model output a segment at a time through safety.IncrementalGate, and tool-call scans layered under the host's cortex.ToolAuthorizer (see NewToolAuthorizer).
Package sandbox defines bounded evaluation jobs without importing a runtime.
Package sandbox defines bounded evaluation jobs without importing a runtime.
sentinel module
Package session defines the Session entity and its store interface.
Package session defines the Session entity and its store interface.
Package skill defines the Skill entity — what an agent can do.
Package skill defines the Skill entity — what an agent can do.
Package store defines the composite store interface for Cortex.
Package store defines the composite store interface for Cortex.
internal/assurance
Package assurance shares immutable evidence and report persistence rules.
Package assurance shares immutable evidence and report persistence rules.
internal/orchestrationstate
Package orchestrationstate shares atomic child retention across store backends.
Package orchestrationstate shares atomic child retention across store backends.
internal/preparation
Package preparation binds snapshot readers to a scope and callback lifetime.
Package preparation binds snapshot readers to a scope and callback lifetime.
internal/recipientdiscovery
Package recipientdiscovery validates bounded delivery identity projections.
Package recipientdiscovery validates bounded delivery identity projections.
internal/replydiscovery
Package replydiscovery validates bounded suspension identity projections.
Package replydiscovery validates bounded suspension identity projections.
internal/runstate
Package runstate encodes storage states that fence unsupported workers.
Package runstate encodes storage states that fence unsupported workers.
internal/scopeaudit
Package scopeaudit implements read-only stored-scope inventory shared by adapters.
Package scopeaudit implements read-only stored-scope inventory shared by adapters.
mongo
Package mongo provides a MongoDB implementation of the Cortex composite store using Grove ORM with the mongodriver backend.
Package mongo provides a MongoDB implementation of the Cortex composite store using Grove ORM with the mongodriver backend.
postgres
Package postgres provides a PostgreSQL implementation of the Cortex composite store using grove ORM with programmatic migrations.
Package postgres provides a PostgreSQL implementation of the Cortex composite store using grove ORM with programmatic migrations.
scopespy
Package scopespy provides a recording store double for asserting that every store call made by the engine carries a non-zero scope.
Package scopespy provides a recording store double for asserting that every store call made by the engine carries a non-zero scope.
sqlite
Package sqlite provides a SQLite implementation of the Cortex composite store using grove ORM with programmatic migrations.
Package sqlite provides a SQLite implementation of the Cortex composite store using grove ORM with programmatic migrations.
storetest
Package storetest is a backend-agnostic conformance suite for store.Store implementations.
Package storetest is a backend-agnostic conformance suite for store.Store implementations.
Package suspension defines a paused run and everything needed to continue it.
Package suspension defines a paused run and everything needed to continue it.
Package tool builds engine tools from typed Go handlers.
Package tool builds engine tools from typed Go handlers.
Package trait defines the Trait entity — who an agent is.
Package trait defines the Trait entity — who an agent is.
Package verification defines result assurance independently of execution state.
Package verification defines result assurance independently of execution state.

Jump to

Keyboard shortcuts

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