Skip to content

Authoring Plugins in Go

Jon edited this page Sep 5, 2026 · 1 revision

Authoring Plugins in Go

Go plugins are compiled binaries located in ~/.ash/plugins/. They provide fast execution, low memory overhead, and strong type safety.

Requirements for Ash Go Plugins

Every Ash plugin must fulfill the following contract:

  1. AI Capability Discovery (--ai-docs):
    • Output structured JSON defining capabilities, arguments, return schemas, and usage hints.
  2. Standard CLI Flags:
    • --version / -v: Print <plugin_name> <version> and exit 0.
    • --help / -h: Print human-readable usage instructions and exit 0.
  3. Structured Logging (slog):
    • Inspect ASH_LOG_FILE, ASH_LOG_FORMAT (json or text), and ASH_VERBOSE.
    • Use log/slog to append structured logs with an EID (Event/Error ID) key.
  4. Lifecycle & Signal Trapping:
    • Trap SIGINT / SIGTERM and listen for context.Context cancellation for graceful exit.

Using the plugin.Plugin Interface

Ash provides a standard plugin lifecycle package in internal/plugin:

package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log/slog"
	"ash/internal/plugin"
)

type MyPlugin struct{}

func (p *MyPlugin) Name() string { return "my_plugin" }
func (p *MyPlugin) Version() string { return "1.0.0" }

func (p *MyPlugin) AIDocs() string {
	docs := map[string]any{
		"Capabilities": []string{"Describe what the plugin does."},
		"Arguments": map[string]string{
			"--option": "Describe flag options.",
			"--ai-docs": "Print AI documentation",
			"--version": "Print version",
			"--help": "Print help",
		},
		"Return format": map[string]string{
			"status": "success or error",
			"result": "the output value",
		},
	}
	b, _ := json.MarshalIndent(docs, "", "  ")
	return string(b)
}

func (p *MyPlugin) Run(ctx context.Context, args []string, stdout, stderr io.Writer, logger *slog.Logger) int {
	logger.Debug("running my_plugin", "args", args, "EID", "myPl01a")
	
	flags := flag.NewFlagSet("my_plugin", flag.ContinueOnError)
	opt := flags.String("option", "default", "Sample option")
	if err := flags.Parse(args); err != nil {
		logger.Error("failed parsing args", "error", err, "EID", "myPl02b")
		return 1
	}

	select {
	case <-ctx.Done():
		return 130
	default:
	}

	fmt.Fprintf(stdout, "Processed: %s\n", *opt)
	return 0
}

func main() {
	plugin.Main(&MyPlugin{})
}

Directory Structure and Makefile

Place your plugin in its own directory with a dedicated Makefile:

internal/app/ash_bootstrap/plugins_src/my_plugin/
├── Makefile
├── README.md
└── main.go

Example Makefile:

PLUGIN_NAME := my_plugin
BUILD_DIR ?= ../../../../../bin/plugins
OUTPUT_PATH ?= $(BUILD_DIR)/$(PLUGIN_NAME)

.PHONY: all build test lint clean

all: build

build:
	@mkdir -p "$(BUILD_DIR)"
	@go build -o "$(OUTPUT_PATH)" .

test:
	@go test -v ./...

lint:
	@go vet ./...
	@golangci-lint run .

clean:
	@rm -f "$(OUTPUT_PATH)"

Clone this wiki locally