Skip to main content
Unlisted page
This page is unlisted. Search engines will not index it, and only users having a direct link can access it.

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 TaskDefinition by (source_molecule_fqp_base, name). The molecule's FQP is auto-added to scope.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