Molecule SDK Reference
The Molecule SDK provides the types, interfaces, and helpers for building molecule plugins in Go. Molecules can provide discovery, MCP tools, skills, visualizations, and shipped agents.
Installation
go get github.com/sixdegree-ai/molecule-sdk
Requirements:
- Go 1.24 or later
Core Interfaces
Molecule
The base interface that all molecules must implement:
type Molecule interface {
// GetMetadata returns the molecule's identity and capabilities.
GetMetadata(ctx context.Context) (*MoleculeMetadata, error)
// Configure sets up the molecule with per-capability configuration.
Configure(ctx context.Context, config *MoleculeConfig) error
// Shutdown gracefully stops the molecule.
Shutdown(ctx context.Context) error
}
Discoverer
For molecules that discover entities. Extends Molecule:
type Discoverer interface {
Molecule
// Discover executes discovery and streams results via a channel.
Discover(ctx context.Context, opts *DiscoveryOptions) (<-chan *DiscoveryResult, error)
}
ToolProvider
For molecules that provide MCP tools. Extends Molecule:
type ToolProvider interface {
Molecule
// ListTools returns the available MCP tools.
ListTools(ctx context.Context) ([]ToolDefinition, error)
// CallTool executes an MCP tool and returns the result.
CallTool(ctx context.Context, req *ToolCallRequest) (*ToolCallResponse, error)
}
VisualizationProvider
For molecules that render visualizations. Extends Molecule:
type VisualizationProvider interface {
Molecule
// ListVisualizations returns the available visualizations.
ListVisualizations(ctx context.Context) ([]VisualizationDefinition, error)
// RenderVisualization renders a visualization for an entity.
RenderVisualization(ctx context.Context, req *VisualizationRenderRequest) (*VisualizationRenderResponse, error)
}
WebhookHandler
Optional interface for real-time event handling:
type WebhookHandler interface {
// HandleWebhook processes an incoming webhook event.
HandleWebhook(ctx context.Context, req *WebhookRequest) (*WebhookResponse, error)
}
FullMolecule
Convenience interface for molecules that implement all capabilities:
type FullMolecule interface {
Discoverer
ToolProvider
VisualizationProvider
ConfigWidgetProvider
}
BaseMolecule
Embed BaseMolecule in your struct to get default implementations for GetMetadata, Configure, and Shutdown:
type MyMolecule struct {
*molecule.BaseMolecule
}
func NewMyMolecule() *MyMolecule {
return &MyMolecule{
BaseMolecule: molecule.NewBaseMolecule(&molecule.MoleculeMetadata{
Namespace: "molecules.sixdegree.ai",
Name: "my-molecule",
Version: "1.0.0",
DisplayName: "My Molecule",
Description: "Discovers entities from my service",
Discovery: &molecule.DiscoveryCapability{
Supported: true,
Modes: []molecule.DiscoveryMode{molecule.DiscoveryModePolling},
EntityTypes: []molecule.EntityTypeDefinition{
{
Group: "entities.sixdegree.ai",
Version: "v1",
Kind: "MyResource",
Description: "A resource from my service",
},
},
},
}),
}
}
Data Structures
Entity
Follows a Kubernetes-inspired structure:
type Entity struct {
APIVersion string `json:"apiVersion"` // e.g., "entities.sixdegree.ai/v1"
Kind string `json:"kind"` // e.g., "GithubRepository"
Metadata EntityMetadata `json:"metadata"`
Spec map[string]any `json:"spec"`
Status *EntityStatus `json:"status,omitempty"`
}
type EntityMetadata struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
UID string `json:"uid,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Annotations map[string]string `json:"annotations,omitempty"`
}
Relation
type Relation struct {
Namespace string `json:"namespace"`
RelationType string `json:"relation"` // e.g., "OWNS", "DEPLOYS"
Source EntityReference `json:"source"`
Target EntityReference `json:"target"`
Labels map[string]string `json:"labels,omitempty"`
Spec map[string]any `json:"spec,omitempty"`
Weight *float64 `json:"weight,omitempty"` // For graph algorithms
}
type EntityReference struct {
APIVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Name string `json:"name"`
Namespace string `json:"namespace"`
}
MoleculeMetadata
type MoleculeMetadata struct {
Namespace string `json:"namespace"`
Name string `json:"name"`
Version string `json:"version"`
DisplayName string `json:"display_name"`
Description string `json:"description,omitempty"`
// Categories this molecule belongs to (use Category constants from categories.go)
Categories []Category `json:"categories,omitempty"`
Author *AuthorInfo `json:"author,omitempty"`
License string `json:"license,omitempty"`
// Structured capabilities
Discovery *DiscoveryCapability `json:"discovery,omitempty"`
MCP *MCPCapability `json:"mcp,omitempty"`
Visualizations *VisualizationCapability `json:"visualizations,omitempty"`
HomepageURL string `json:"homepage_url,omitempty"`
DocsURL string `json:"docs_url,omitempty"`
Logo []byte `json:"logo,omitempty"`
// Skills registers named prompt skills in the environment's skill registry
// when the molecule is configured. The AI agent can discover them via
// search_skills/get_skill tools.
Skills []SkillDefinition `json:"skills,omitempty"`
// Agents lists default agent recipes this molecule ships. When the molecule
// is configured, the platform creates a TaskDefinition for each entry,
// scoped to this molecule. See "Shipped Agents" below.
Agents []AgentTemplate `json:"agents,omitempty"`
}
Shipped Agents
A molecule can declare default agents alongside its discovery and tools. Each AgentTemplate becomes a TaskDefinition in any environment where the molecule is installed. They appear under /agents ready to fire ad-hoc or attach triggers to.
type AgentTemplate struct {
Name string `json:"name"` // shown in /agents
Description string `json:"description,omitempty"`
Goal string `json:"goal"` // supports {{dotted.path}}
Model string `json:"model,omitempty"` // empty = env default
Autonomy string `json:"autonomy,omitempty"` // observe | suggest | act | act_destructive
Scope *AgentScopeTemplate `json:"scope,omitempty"` // narrows access
BlastRadius *AgentBlastTemplate `json:"blast_radius,omitempty"` // hard ceilings
Triggers []AgentTriggerTemplate `json:"triggers,omitempty"` // default event/cron triggers
}
type AgentScopeTemplate struct {
Molecules []string `json:"molecules,omitempty"` // FQP allowlist (the molecule's own FQP is auto-added)
Tools []string `json:"tools,omitempty"` // tool-name allowlist
EntityTypes []string `json:"entity_types,omitempty"`
Selectors []AgentResourceSelector `json:"selectors,omitempty"` // record-level scoping
}
type AgentBlastTemplate struct {
MaxToolCalls int `json:"max_tool_calls,omitempty"`
MaxTokens int `json:"max_tokens,omitempty"`
MaxCostUSD float64 `json:"max_cost_usd,omitempty"`
MaxWallClockSeconds int `json:"max_wall_clock_seconds,omitempty"`
}
type AgentTriggerTemplate struct {
Name string `json:"name"`
EventType string `json:"event_type,omitempty"` // event-driven (XOR with Cron)
Filter map[string]string `json:"filter,omitempty"` // dotted-path equality
Cron string `json:"cron,omitempty"` // scheduled (XOR with EventType)
}
The agent's goal supports {{dotted.path}} placeholders. For event-fired triggers, the placeholders resolve against the event payload; for ad-hoc runs, against the user-supplied context map.
Example: a GitHub molecule shipping a critical-issue triage agent.
agents := []molecule.AgentTemplate{
{
Name: "Triage critical GitHub issues",
Description: "Summarize critical issues and tag the on-call.",
Goal: "An issue was just opened: {{payload.issue.title}}. Summarize and post a triage comment.",
Autonomy: "suggest",
Scope: &molecule.AgentScopeTemplate{
Tools: []string{"github_add_comment", "github_close_issue"},
},
BlastRadius: &molecule.AgentBlastTemplate{
MaxToolCalls: 10,
MaxTokens: 50000,
MaxCostUSD: 0.50,
MaxWallClockSeconds: 300,
},
Triggers: []molecule.AgentTriggerTemplate{
{
Name: "On critical issue",
EventType: "github.issue.opened",
Filter: map[string]string{"payload.labels": "critical"},
},
},
},
}
Lifecycle on Install / Uninstall / Upgrade
- Install: the platform upserts a
TaskDefinitionby(source_molecule_fqp_base, name). The molecule's FQP is auto-added toscope.molecules. - User edits: the first edit to a shipped definition stamps
user_edited_at. Subsequent install-time upserts skip the row, so user changes survive molecule upgrades. - Uninstall: only molecule-shipped, unedited definitions are removed. Anything a user touched is preserved.
The Name field is the stable upsert key. Keep names stable across molecule versions or you'll churn user-visible rows.
DiscoveryOptions
Controls how discovery runs:
type DiscoveryOptions struct {
// IncrementalFrom limits discovery to changes since this time.
// If nil, performs a full discovery.
IncrementalFrom *time.Time `json:"incremental_from,omitempty"`
// Filters restricts discovery scope.
Filters map[string]string `json:"filters,omitempty"`
// DryRun validates without persisting entities.
DryRun bool `json:"dry_run"`
}
DiscoveryResult
Streamed back from Discover():
type DiscoveryResult struct {
Entity *Entity `json:"entity,omitempty"`
Relation *Relation `json:"relation,omitempty"`
Error *DiscoveryError `json:"error,omitempty"`
Progress int `json:"progress"` // 0-100
Message string `json:"message,omitempty"`
}
ToolDefinition
Describes an MCP tool:
type ToolDefinition struct {
Name string `json:"name"`
Description string `json:"description"`
InputSchema json.RawMessage `json:"input_schema"`
// EntityTypes for late tool disclosure — tool is only revealed to the LLM
// after one of these entity types is discovered in the ontology.
// Empty means the tool is always available (bootstrap behaviour).
EntityTypes []string `json:"entity_types,omitempty"`
// EntityParams maps tool input parameter names to entity attribute paths.
// The platform resolves these from the ontology before dispatching the call,
// so the LLM only needs to pass entity_id.
// Supported path prefixes: "attributes.<key>", "name", "id".
// Example: {"org_id": "attributes.org_id", "project_id": "attributes.project_id"}
EntityParams map[string]string `json:"entity_params,omitempty"`
}
Builders
The SDK provides builder helpers to reduce boilerplate.
EntityBuilder
entity := molecule.NewEntityBuilder("entities.sixdegree.ai/v1", "GithubRepository").
Name("api-service").
Namespace("default").
Label("language", "go").
Label("visibility", "private").
Annotation("source-url", "https://github.com/org/api-service").
Spec("url", "https://github.com/org/api-service").
Spec("language", "Go").
Spec("stars", 42).
Build()
RelationBuilder
relation := molecule.NewRelationBuilder("OWNS").
Namespace("default").
From("entities.sixdegree.ai/v1", "GithubOrganization", "default", "my-org").
To("entities.sixdegree.ai/v1", "GithubRepository", "default", "api-service").
Label("role", "owner").
Build()
You can also use entities directly:
relation := molecule.NewRelationBuilder("CONTRIBUTES_TO").
Namespace("default").
FromEntity(userEntity).
ToEntity(repoEntity).
Build()
NewEntityRef
Helper for creating entity references:
ref := molecule.NewEntityRef("GithubRepository", "api-service", "default")
// → EntityReference{APIVersion: "entities.sixdegree.ai/v1", Kind: "GithubRepository", ...}
ResultChannel
Helper for streaming discovery results:
func (m *MyMolecule) Discover(ctx context.Context, opts *molecule.DiscoveryOptions) (<-chan *molecule.DiscoveryResult, error) {
rc := molecule.NewResultChannel(100) // buffered channel
go func() {
defer rc.Close()
rc.SendProgress(0, "Starting discovery...")
// Discover entities
repos, err := m.fetchRepos(ctx)
if err != nil {
rc.SendError("FETCH_FAILED", err.Error())
return
}
for i, repo := range repos {
entity := molecule.NewEntityBuilder("entities.sixdegree.ai/v1", "MyResource").
Name(repo.Name).
Namespace(m.Config().Namespace).
Spec("url", repo.URL).
Build()
rc.SendEntity(entity)
rc.SendProgress(((i + 1) * 100) / len(repos), fmt.Sprintf("Discovered %d/%d", i+1, len(repos)))
}
}()
return rc.Chan(), nil
}
Configuration Helpers
Reading Settings
func (m *MyMolecule) Configure(ctx context.Context, config *molecule.MoleculeConfig) error {
// Store config via BaseMolecule
m.BaseMolecule.Configure(ctx, config)
// Read required settings
token, err := molecule.RequireCapabilitySetting[string](config.Discovery, "token")
if err != nil {
return err
}
// Read optional settings with defaults
pageSize := molecule.GetCapabilitySetting(config.Discovery, "page_size", 100)
org := molecule.GetCapabilitySetting(config.Discovery, "organization", "")
// Check which capabilities are enabled
if molecule.IsCapabilityEnabled(config, molecule.CapabilityDiscovery) {
m.setupDiscovery(token, org, pageSize)
}
if molecule.IsCapabilityEnabled(config, molecule.CapabilityMCP) {
m.setupMCPTools(token)
}
return nil
}
Config Schema
Define a JSON Schema for your configuration so the UI can render a configuration form:
Discovery: &molecule.DiscoveryCapability{
Supported: true,
ConfigSchema: json.RawMessage(`{
"type": "object",
"required": ["token"],
"properties": {
"token": {
"type": "string",
"title": "API Token",
"description": "Authentication token for the service",
"x-sixdegree": {"widget": "secret"}
},
"organization": {
"type": "string",
"title": "Organization",
"description": "Organization to discover resources from"
},
"page_size": {
"type": "integer",
"title": "Page Size",
"default": 100,
"minimum": 10,
"maximum": 1000
}
}
}`),
},
MCP Tools
Defining Tools
MCP: &molecule.MCPCapability{
Supported: true,
Tools: []molecule.ToolDefinition{
{
Name: "my_search",
Description: "Search for resources in my service",
InputSchema: json.RawMessage(`{
"type": "object",
"required": ["query"],
"properties": {
"query": {"type": "string", "description": "Search query"}
}
}`),
EntityTypes: []string{"entities.sixdegree.ai/v1/MyResource"},
},
},
},
Handling Tool Calls
func (m *MyMolecule) CallTool(ctx context.Context, req *molecule.ToolCallRequest) (*molecule.ToolCallResponse, error) {
switch req.Name {
case "my_search":
query, _ := req.Arguments["query"].(string)
results, err := m.client.Search(ctx, query)
if err != nil {
return &molecule.ToolCallResponse{
Success: false,
Error: err.Error(),
}, nil
}
return &molecule.ToolCallResponse{
Success: true,
Result: map[string]any{"results": results},
}, nil
default:
return &molecule.ToolCallResponse{
Success: false,
Error: fmt.Sprintf("unknown tool: %s", req.Name),
}, nil
}
}
Webhooks
Handling Webhook Events
func (m *MyMolecule) HandleWebhook(ctx context.Context, req *molecule.WebhookRequest) (*molecule.WebhookResponse, error) {
switch req.EventType {
case "resource.created", "resource.updated":
return &molecule.WebhookResponse{
Acknowledged: true,
TriggerDiscovery: true, // triggers incremental discovery
}, nil
case "resource.deleted":
var payload struct {
ResourceName string `json:"resource_name"`
}
json.Unmarshal(req.Payload, &payload)
return &molecule.WebhookResponse{
Acknowledged: true,
AffectedEntities: []molecule.EntityReference{
molecule.NewEntityRef("MyResource", payload.ResourceName, "default"),
},
}, nil
default:
return &molecule.WebhookResponse{Acknowledged: true}, nil
}
}
Discovery Modes
Declare supported discovery modes in metadata:
Discovery: &molecule.DiscoveryCapability{
Supported: true,
Modes: []molecule.DiscoveryMode{
molecule.DiscoveryModePolling, // interval-based
molecule.DiscoveryModeWebhook, // event-driven
molecule.DiscoveryModeBoth, // both simultaneously
},
},
Next Steps
- Building Molecules: Step-by-step guide to creating a molecule
- Building Standalone MCP Servers: Adding standalone AI tools
- Building Visualizations: Creating dynamic visualizations
- Testing Guide: Testing your molecule