Advanced

API Contract

Complete reference for the Shiro module interface — Go types, subprocess stdin/stdout protocol, and HTTP microservice API.

Module Interface

All Shiro modules — whether compiled-in Go, subprocess, or HTTP — ultimately implement this interface. Compiled Go modules must implement it directly.

go
// internal/modules/module.go

type Module interface {
    // Run executes the module for a single workflow step.
    // stepCtx contains resolved outputs from upstream steps.
    // step is the workflow.Step struct; extract Config via reflection.
    // Returns output map accessible as {{steps.<id>.output.*}} in downstream steps.
    Run(ctx context.Context, stepCtx interface{}, step interface{}) (map[string]interface{}, error)

    // Metadata returns the module's schema for documentation and validation.
    Metadata() ModuleMetadata
}

ModuleMetadata & SchemaField

go
type ModuleMetadata struct {
    Name         string                 `json:"name"`
    Description  string                 `json:"description"`
    InputSchema  map[string]SchemaField `json:"input_schema"`
    OutputSchema map[string]SchemaField `json:"output_schema"`
}

type SchemaField struct {
    Type        string      `json:"type"`        // "string" | "number" | "boolean" | "array" | "object"
    Description string      `json:"description"`
    Required    bool        `json:"required"`
    Default     interface{} `json:"default,omitempty"`
}

Copy these structs into your module package. Do not import them from internal/modules — that creates circular dependencies.

Registry

The registry maps module type names to Module instances. Compiled-in modules register themselves at startup via generated code in internal/cli/registry.go.

go
type Registry struct { /* ... */ }

func NewRegistry() *Registry

// Register adds a module by type name. Returns error if already registered.
func (r *Registry) Register(moduleType string, module Module) error

// Get retrieves a module by type name.
func (r *Registry) Get(moduleType string) (Module, error)

// List returns all registered type names.
func (r *Registry) List() []string

// RegisterHTTPModule adds an HTTP-backed module.
func (r *Registry) RegisterHTTPModule(moduleType string, config *HTTPModuleConfig) error

Registry config file format (auto-generated by shiro add module):

.shiro/modules/registry.yaml
modules:
  mymodule:
    name: mymodule
    type: builtin           # builtin | http | subprocess
    package: github.com/your-org/shiro-mymodule
    factory: NewMyModule    # exported constructor function name
    version: "1.0.0"
    description: "My module"
    source: https://github.com/your-org/shiro-mymodule
    added_at: "2025-01-01T00:00:00Z"

Subprocess Protocol

Subprocess modules receive a JSON request on stdin and must write a JSON response to stdout. The binary must be named shiro-<name> and placed in .shiro/plugins/ or on PATH.

Request (stdin)

json
{
  "action": "operation_name",          // step type or specific operation
  "config": {                          // step.config map
    "key": "value"
  },
  "context": {                         // resolved upstream step outputs
    "steps": {
      "step_id": { "output": { ... } }
    }
  }
}

Response (stdout)

json
{
  "output": {                          // returned as steps.<id>.output.*
    "result": "...",
    "success": true
  },
  "error": ""                          // non-empty string causes step failure
}

Metadata action

Shiro calls the binary with "action": "__metadata__" to fetch schema. Respond with a ModuleMetadata JSON object:

json
// Shiro sends:
{ "action": "__metadata__" }

// Binary responds:
{
  "name": "mymodule",
  "description": "Does useful things",
  "input_schema": {
    "key": { "type": "string", "description": "...", "required": true }
  },
  "output_schema": {
    "result": { "type": "string", "description": "..." }
  }
}

Go types

go
// SubprocessRequest — sent to the binary via stdin
type SubprocessRequest struct {
    Action  string                 `json:"action"`
    Config  map[string]interface{} `json:"config"`
    Context map[string]interface{} `json:"context,omitempty"`
}

// SubprocessResponse — read from the binary's stdout
type SubprocessResponse struct {
    Output map[string]interface{} `json:"output"`
    Error  string                 `json:"error,omitempty"`
}

HTTP Module API

HTTP modules are language-agnostic servers that implement two endpoints. Register them in the registry with type: http.

POST/run

Execute a step. Body is the same JSON as the subprocess stdin request.

json
// Request body
{
  "action": "operation",
  "config": { "key": "value" },
  "context": { "steps": { ... } }
}

// Response body (200 OK)
{
  "output": { "result": "..." },
  "error": ""
}
GET/metadata

Return module metadata. Response body is a ModuleMetadata JSON object.

json
// Response body (200 OK)
{
  "name": "mymodule",
  "description": "...",
  "input_schema": { ... },
  "output_schema": { ... }
}

Shiro supports multiple endpoints per HTTP module for load balancing — specify them as a list under endpoints in the registry.

AI Provider Interface

The ai.generate module resolves providers from .shiro/config.yaml. Built-in providers are openai and ollama.

go
// internal/ai/provider.go

type Provider interface {
    Generate(ctx context.Context, req *GenerateRequest) (*GenerateResponse, error)
    Stream(ctx context.Context, req *GenerateRequest) (<-chan StreamChunk, error)
    Close() error
}

type GenerateRequest struct {
    Model       string            `json:"model"`
    Messages    []Message         `json:"messages"`
    System      string            `json:"system,omitempty"`
    Temperature float64           `json:"temperature,omitempty"`
    MaxTokens   int               `json:"max_tokens,omitempty"`
    TopP        float64           `json:"top_p,omitempty"`
    Metadata    map[string]string `json:"metadata,omitempty"`
}

type Message struct {
    Role    string `json:"role"`    // "system" | "user" | "assistant"
    Content string `json:"content"`
}

type GenerateResponse struct {
    Content      string            `json:"content"`
    FinishReason string            `json:"finish_reason"`
    Usage        *Usage            `json:"usage,omitempty"`
    Metadata     map[string]string `json:"metadata,omitempty"`
}

type Usage struct {
    PromptTokens     int `json:"prompt_tokens"`
    CompletionTokens int `json:"completion_tokens"`
    TotalTokens      int `json:"total_tokens"`
}

type ProviderConfig struct {
    Type          string            `json:"type"`             // "ollama" | "openai"
    BaseURL       string            `json:"base_url"`
    APIKey        string            `json:"api_key,omitempty"`
    Model         string            `json:"model"`
    Headers       map[string]string `json:"headers,omitempty"`
    Timeout       int               `json:"timeout"`
    SkipTLSVerify bool              `json:"skip_tls_verify,omitempty"`
}