agentplane

package module
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Jul 30, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

README

agent-plane-sdk-go

Go SDK for building Agent Plane runtimes.

An Agent Plane runtime never talks to the Kubernetes API server for its configuration. It:

  1. pulls resolved config from the Registry — one-shot or as a hot-reload stream (agentplane.Client), and
  2. reads only Secret values through its own RBAC — model API keys, memory connection strings (secrets.Reader). The Registry serves Secret coordinates, never values.

This SDK is both halves of that contract, plus optional reference building blocks. The Registry server imports the same wire types, so the contract cannot drift between server and runtimes.

Packages

Package What it is Required?
agentplane (root) Wire types (AgentConfig, Tool, Model, …) + Registry client (FetchConfig, Watch) yes
secrets Secret reader via the pod's own RBAC (in-cluster config, kubeconfig fallback) yes, unless you read Secrets yourself
agentloop Reference OpenAI-compatible tool-calling loop (http + mcp tools, multi-turn sessions) optional
memory Reference conversation store for the Memory CRD (Redis, zero deps) optional
retriever Reference RAG retrieval for http-source KnowledgeBases optional
policy Call-time enforcement of the Policy / ToolPolicy CRDs and Skill allowedTools (allow/deny, per-session call caps, skill-scoped tools) optional

Bring your own agent framework? Use only the root package + secrets and translate AgentConfig into your framework's structures. The dependency footprint of those two is client-go and nothing else.

Quickstart

package main

import (
	"context"
	"log"

	agentplane "github.com/hkmdxlftjf/agent-plane-sdk-go"
	"github.com/hkmdxlftjf/agent-plane-sdk-go/agentloop"
	"github.com/hkmdxlftjf/agent-plane-sdk-go/secrets"
)

func main() {
	ctx := context.Background()

	// 1. Pull resolved config from the Registry.
	client := agentplane.NewClient("http://agentplane-registry:9090")
	cfg, err := client.FetchConfig(ctx, "default", "my-agent")
	if err != nil {
		log.Fatal(err)
	}
	if cfg.Phase != agentplane.PhaseReady {
		log.Fatalf("agent not ready: phase=%s", cfg.Phase)
	}

	// 2. Read the model API key via the pod's own RBAC.
	sec, err := secrets.NewReader(cfg.Namespace)
	if err != nil {
		log.Fatal(err)
	}
	apiKey, err := sec.Read(ctx, cfg.Model.SecretName, cfg.Model.SecretKey)
	if err != nil {
		log.Fatal(err)
	}

	// 3. Compose the system prompt: PromptTemplate + Skill packs.
	system := "You are a helpful assistant."
	if cfg.Prompt != nil && cfg.Prompt.System != "" {
		system = cfg.Prompt.System
	}
	for _, sk := range cfg.Skills {
		system += "\n\n# Skill: " + sk.Name + "\n" + sk.Content
	}

	// 4. Run the reference loop (or feed cfg into your own framework here).
	answer, err := agentloop.Run(ctx, agentloop.Config{
		Endpoint: cfg.Model.Endpoint,
		APIKey:   apiKey,
		Model:    cfg.Model.ModelName,
		System:   system,
		Tools:    cfg.Tools,
	}, "What is the status of order A-42?")
	if err != nil {
		log.Fatal(err)
	}
	log.Println(answer)
}
Hot reload (long-running runtimes)

Watch streams full config snapshots over SSE, reconnects on disconnect, and dedups on ConfigHash — your callback fires only when the effective config actually changed:

err := client.Watch(ctx, "default", "my-agent", func(cfg *agentplane.AgentConfig) {
	// Atomically swap your model client / tool table here.
})
Multi-turn sessions with memory
session := agentloop.NewSession(base)

store, _ := memory.Open(m.Backend, dsn, m.Namespace) // dsn read via secrets.Reader
turns, _ := store.Load(ctx, sessionID)
for _, t := range turns {
	session.AppendHistory(t.Role, t.Content)
}

answer, _ := session.Send(ctx, userText)
_ = store.Append(ctx, sessionID,
	memory.Turn{Role: "user", Content: userText},
	memory.Turn{Role: "assistant", Content: answer})
Workflow-driven runtimes

If the Agent references a Workflow CR, the config carries the resolved, engine-neutral step graph in cfg.Workflow (name, engine, steps with next edges). Agent Plane never executes it — your runtime decides what each step type means:

if wf := cfg.Workflow; wf != nil {
	log.Printf("workflow %s (engine=%s)", wf.Name, wf.Engine)
	for _, step := range wf.Steps {
		// e.g. "plan" (planner) -> "act" (tool) -> "reflect" -> "finish"
		log.Printf("  %s (%s) -> %v", step.Name, step.Type, step.Next)
	}
}

A complete runnable interpreter — mapping planner/tool/reflect/finish steps onto agentloop calls with a bounded graph walk — lives in examples/workflow-runner:

go run ./examples/workflow-runner --registry http://localhost:9090 \
  --namespace default --name support-agent --prompt "What is the status of order A-42?"

Instead of a one-shot --prompt, --serve starts a small web UI that streams each workflow step live (SSE) and renders the final answer:

go run ./examples/workflow-runner --registry http://localhost:9090 \
  --namespace default --name support-agent --serve :8090
# open http://localhost:8090
Enforcing Policy and ToolPolicy

If the Agent references any Policy or ToolPolicy CR, the Registry serves the merged result in cfg.Policy. The control plane already refuses to run an Agent whose declared refs violate a Policy, but which tool a model reaches for on a given turn — and how many times — is only knowable in the runtime, so that half is enforced here. Session.Guard fits agentloop.Config.ToolGuard directly:

enf := policy.New(cfg.Policy) // nil-safe: no policy referenced => allow all
for _, line := range enf.Describe() {
	log.Printf("policy: %s", line)
}

answer, err := agentloop.Run(ctx, agentloop.Config{
	// … endpoint, key, model, tools …
	ToolGuard: enf.Session().Guard,
}, "refund order A-42")

A denied call is not an error: the reason is fed back to the model as the tool result, so it can explain or choose another route. Call one enf.Session() per conversation — maxCallsPerSession budgets are per session, not per process.

Skill-scoped tools (allowedTools)

A Skill may declare the tools its instructions are allowed to use. Because a Skill body is disclosed mid-conversation, this is enforced in the same session: tell the enforcer when a skill loads, and subsequent calls are confined to the union of the loaded skills' allowedTools.

sess := enf.Session()
base.ToolGuard = sess.Guard

// …in your load_skill handler, once the body goes to the model:
sess.NoteSkillLoaded(skill.Name, skill.AllowedTools)

A skill declaring no allowedTools restricts nothing. And a skill can only narrow: a tool the Agent never referenced, or that a Policy denies, stays out of reach — otherwise anyone who could write a Skill could escalate past a Policy.

Registry wire protocol

  • GET /v1/agents/{namespace}/{name}/config — one-shot resolved snapshot (JSON AgentConfig).
  • GET /v1/agents/{namespace}/{name}/watch — SSE stream; each data: event is a full AgentConfig snapshot (never a delta), keyed by configHash. Keepalive comments (: keepalive) flow every 25s.

See runtime-protocol.md in the main repository for the full protocol description.

Versioning

The SDK follows the Registry's wire contract. Until v1, minor versions may add fields (always backward-compatible JSON) but never remove or rename them.

License

Apache-2.0

Documentation

Overview

Package agentplane is the Go SDK for building Agent Plane runtimes.

An Agent Plane runtime never talks to the Kubernetes API server for its configuration: it pulls the fully resolved config from the Registry (Client.FetchConfig / Client.Watch) and reads only Secret values — model API keys, memory connection strings — through its own RBAC (package secrets). This package defines the Registry wire contract; the same types are used by the Registry server, so the contract cannot drift silently.

Subpackages provide optional reference building blocks a runtime can adopt piecemeal: agentloop (an OpenAI-compatible tool-calling loop), memory (conversation persistence for the Memory CRD), retriever (RAG retrieval for http-source KnowledgeBases), and policy (enforcement of the Policy and ToolPolicy CRDs at call time).

Index

Constants

View Source
const (
	ToolActionAllow = "allow"
	ToolActionDeny  = "deny"
)

Tool action values for ToolRule.Action and Policy.DefaultToolAction.

View Source
const PhaseReady = "Ready"

PhaseReady is the Agent phase in which the resolved config is complete and a runtime may serve traffic.

Variables

This section is empty.

Functions

This section is empty.

Types

type AccessRule added in v0.4.0

type AccessRule struct {
	Allow []string `json:"allow,omitempty"`
	Deny  []string `json:"deny,omitempty"`
}

AccessRule expresses allow/deny by resource name. An empty Allow list means "allow anything not explicitly denied"; a non-empty Allow list is exhaustive. Deny always wins over Allow.

type AgentConfig

type AgentConfig struct {
	Namespace string `json:"namespace"`
	Name      string `json:"name"`
	// ConfigHash is the Operator-computed hash of the resolved configuration.
	// Runtimes compare it to detect drift; Client.Watch dedups on it.
	ConfigHash string `json:"configHash"`
	// Phase is the Agent's lifecycle phase ("Ready", "Pending", …). A runtime
	// should only serve traffic when the phase is PhaseReady.
	Phase string `json:"phase"`
	// Spec is the effective Agent spec (AgentClass defaults applied), as raw
	// JSON. Most runtimes never need it — everything actionable is resolved
	// into the typed views below — but it is available for introspection.
	Spec json.RawMessage `json:"spec,omitempty"`
	// Prompt is the resolved system prompt from the Agent's PromptTemplate,
	// if one is referenced.
	Prompt *Prompt `json:"prompt,omitempty"`
	// Workflow is the resolved execution shape from the Agent's Workflow, if
	// one is referenced. Agent Plane never executes it — interpreting the step
	// graph is the runtime's job (see the SDK's examples/workflow-runner).
	Workflow  *Workflow       `json:"workflow,omitempty"`
	Model     *Model          `json:"model,omitempty"`
	Tools     []Tool          `json:"tools,omitempty"`
	Skills    []Skill         `json:"skills,omitempty"`
	Memories  []Memory        `json:"memories,omitempty"`
	Knowledge []KnowledgeBase `json:"knowledgeBases,omitempty"`
	// Policy is the effective authorization policy for this Agent, merged from
	// every Policy and ToolPolicy it references. It is advisory data on the
	// wire: the control plane rejects statically-detectable violations at apply
	// time, but call-time decisions (which tool this turn, how many times) can
	// only be made where the calls happen, so the runtime must enforce it. See
	// package policy for a ready-made enforcer.
	Policy *Policy `json:"policy,omitempty"`
}

AgentConfig is the fully resolved runtime configuration the Registry serves for one Agent. Every field a runtime needs to operate is resolved server-side (tool endpoints, skill content, secret coordinates); the runtime never reads the Agent Plane CRDs itself.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client talks to the Agent Plane Registry.

func NewClient

func NewClient(baseURL string, opts ...Option) *Client

NewClient returns a Registry client for the given base URL, e.g. "http://agentplane-registry.agentplane-system:9090".

func (*Client) FetchConfig

func (c *Client) FetchConfig(ctx context.Context, namespace, name string) (*AgentConfig, error)

FetchConfig returns a one-shot snapshot of the resolved config for an Agent (GET /v1/agents/{ns}/{name}/config).

func (*Client) Watch

func (c *Client) Watch(ctx context.Context, namespace, name string, onChange func(*AgentConfig)) error

Watch subscribes to the Registry's SSE stream for an Agent (GET /v1/agents/{ns}/{name}/watch) and invokes onChange for every config whose ConfigHash differs from the previously delivered one. Each event is a full snapshot, never a delta, so a dropped intermediate update always converges on the next event.

Watch blocks until ctx is cancelled, reconnecting with a fixed delay when the stream ends. onChange is called from Watch's goroutine; a slow onChange backpressures the stream, it is never called concurrently.

type KnowledgeBase

type KnowledgeBase struct {
	Name           string `json:"name"`
	Source         string `json:"source"`
	URI            string `json:"uri,omitempty"`
	EmbeddingModel string `json:"embeddingModel,omitempty"`
	SecretName     string `json:"secretName,omitempty"`
	SecretKey      string `json:"secretKey,omitempty"`
}

KnowledgeBase tells the runtime where a retrieval corpus lives (RAG). EmbeddingModel is resolved to its model name; Secret coordinates for accessing the source are served, never the value.

type Memory

type Memory struct {
	Name    string `json:"name"`
	Backend string `json:"backend"`
	// Namespace is a key prefix that scopes entries within the backend.
	Namespace  string `json:"namespace,omitempty"`
	SecretName string `json:"secretName,omitempty"`
	SecretKey  string `json:"secretKey,omitempty"`
}

Memory tells the runtime which conversation-memory backend to use and where its connection string lives. As with Model, only Secret coordinates are served — the runtime reads the DSN value itself.

type Model

type Model struct {
	Provider   string `json:"provider"`
	ModelName  string `json:"modelName"`
	Endpoint   string `json:"endpoint,omitempty"`
	SecretName string `json:"secretName,omitempty"`
	SecretKey  string `json:"secretKey,omitempty"`
}

Model tells the runtime which chat model to call and where to find the API key. SecretName/SecretKey are Secret *coordinates*: the Registry never serves the secret value itself — the runtime reads the Secret through its own Kubernetes RBAC (see package secrets).

type Option

type Option func(*Client)

Option configures a Client.

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

WithHTTPClient replaces the underlying *http.Client. The client is used for both snapshot fetches and long-lived Watch streams, so it must not set a global Timeout (use WithFetchTimeout-style per-call contexts instead).

func WithLogf

func WithLogf(logf func(format string, args ...any)) Option

WithLogf directs the client's human-readable progress lines (reconnects, decode skips) to a printf-style logger. Silent by default.

func WithReconnectDelay

func WithReconnectDelay(d time.Duration) Option

WithReconnectDelay sets the pause between Watch reconnect attempts (default 2s).

type Policy added in v0.4.0

type Policy struct {
	// Names of the Policy CRs this view was merged from, in resolution order.
	Sources []string `json:"sources,omitempty"`
	// Models restricts which Models the agent may use, by Model CR name.
	Models *AccessRule `json:"models,omitempty"`
	// Memory restricts which Memory backends the agent may use, by CR name.
	Memory *AccessRule `json:"memory,omitempty"`
	// MCP restricts which MCPServers the agent may reach, by MCPServer CR name.
	MCP *AccessRule `json:"mcp,omitempty"`
	// Tools restricts which Tools the agent may invoke, by Tool CR name.
	Tools *AccessRule `json:"tools,omitempty"`
	// Workflows restricts which Workflows the agent may run, by CR name.
	Workflows *AccessRule `json:"workflows,omitempty"`
	// ToolRules are the merged per-tool rules from the referenced ToolPolicy
	// CRs, in order. The first rule matching a tool name applies.
	ToolRules []ToolRule `json:"toolRules,omitempty"`
	// DefaultToolAction applies when no ToolRule matches: "allow" or "deny".
	// Empty means allow, matching the ToolPolicy CRD default. If any referenced
	// ToolPolicy defaults to deny, the merged default is deny.
	DefaultToolAction string `json:"defaultToolAction,omitempty"`
}

Policy is the effective authorization policy the Registry serves for an Agent: the union of every Policy CR it references (coarse allow/deny over classes of resources) plus every ToolPolicy CR (per-tool rules and call caps).

Merge semantics across multiple referenced CRs are deliberately conservative: allow lists intersect and deny lists union, so adding a Policy can only ever narrow what an Agent may do. Deny always beats allow.

type Prompt

type Prompt struct {
	// Name of the PromptTemplate the content was resolved from.
	Name string `json:"name"`
	// System is the system prompt text.
	System string `json:"system,omitempty"`
}

Prompt is the resolved system prompt of the Agent's PromptTemplate.

type Skill

type Skill struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	Content     string `json:"content,omitempty"`
	// AllowedTools names the Tools this skill is permitted to invoke. Empty means
	// the skill adds no restriction of its own.
	//
	// This is a *narrowing* signal, applied once the skill's body has been
	// disclosed: a runtime that loads a skill mid-conversation should from then on
	// confine tool calls to the union of the loaded skills' AllowedTools. It
	// cannot widen access — a tool the Agent never referenced, or that a Policy
	// denies, stays out of reach. See policy.Scope.
	AllowedTools []string `json:"allowedTools,omitempty"`
}

Skill is a resolved Skill: a markdown instruction pack whose body the runtime discloses to the model on demand.

type Tool

type Tool struct {
	Name string `json:"name"`
	// Type is "http" (POST JSON to Endpoint) or "mcp" (JSON-RPC tools/call).
	Type        string `json:"type"`
	Description string `json:"description,omitempty"`
	Endpoint    string `json:"endpoint,omitempty"`
	// MCPToolName is the tool's name on the MCP server when it differs from
	// Name (mcp tools only).
	MCPToolName string `json:"mcpToolName,omitempty"`
	// InputSchema is the tool's JSON-Schema parameters object.
	InputSchema json.RawMessage `json:"inputSchema,omitempty"`
}

Tool is a fully resolved tool definition a runtime can invoke without reading the Tool/MCPServer CRs itself.

type ToolRule added in v0.4.0

type ToolRule struct {
	// Tool matches a Tool by name; "*" matches any tool.
	Tool string `json:"tool"`
	// Action is "allow" or "deny" for the matched tool(s).
	Action string `json:"action"`
	// MaxCallsPerSession caps invocations within one agent session. Nil means
	// uncapped; 0 means the tool may not be called at all.
	MaxCallsPerSession *int32 `json:"maxCallsPerSession,omitempty"`
}

ToolRule is one merged per-tool rule from a ToolPolicy CR.

type Workflow added in v0.2.0

type Workflow struct {
	// Name of the Workflow CR the graph was resolved from.
	Name string `json:"name"`
	// Engine names the runtime engine the workflow is authored for
	// (e.g. "langgraph", "openai-agents", "crewai"). Free-form.
	Engine string `json:"engine,omitempty"`
	// Version is the semantic version of the workflow definition.
	Version string `json:"version,omitempty"`
	// Steps is the ordered step graph. Interpretation of Next is
	// engine-specific.
	Steps []WorkflowStep `json:"steps,omitempty"`
}

Workflow is the resolved execution shape of the Agent's Workflow CR: an engine-neutral step graph (e.g. planner → tool → reflect → finish). The control plane only declares it; how each step type executes is defined by the runtime/engine that consumes it.

type WorkflowStep added in v0.2.0

type WorkflowStep struct {
	// Name uniquely identifies the step within the workflow.
	Name string `json:"name"`
	// Type categorizes the step (e.g. "planner", "tool", "reflect", "finish").
	Type string `json:"type,omitempty"`
	// Next lists the names of steps that may follow this one.
	Next []string `json:"next,omitempty"`
}

WorkflowStep is a single node in a Workflow's step graph.

Directories

Path Synopsis
Package agentloop is a minimal, reusable agent execution loop.
Package agentloop is a minimal, reusable agent execution loop.
examples
workflow-runner command
Command workflow-runner is an example Agent Plane runtime that *interprets* the Agent's Workflow step graph instead of running one fixed loop.
Command workflow-runner is an example Agent Plane runtime that *interprets* the Agent's Workflow step graph instead of running one fixed loop.
Package memory is a minimal, dependency-free persistence layer for agent conversation history, backing the Memory CRD.
Package memory is a minimal, dependency-free persistence layer for agent conversation history, backing the Memory CRD.
Package policy enforces the Agent Plane Policy and ToolPolicy CRDs, and a Skill's allowedTools, inside a runtime.
Package policy enforces the Agent Plane Policy and ToolPolicy CRDs, and a Skill's allowedTools, inside a runtime.
Package retriever augments user queries with context retrieved from the KnowledgeBases the Registry serves (RAG).
Package retriever augments user queries with context retrieved from the KnowledgeBases the Registry serves (RAG).
Package secrets reads Secret values through the runtime's own Kubernetes RBAC.
Package secrets reads Secret values through the runtime's own Kubernetes RBAC.

Jump to

Keyboard shortcuts

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