Skip to main content

Implementing a New Tool

Tools process Parts as they flow through a pipeline. Most tools only care about one or two Part types (usually Blocks).

Using BaseTool

Build a tool.BaseTool and set handler function fields for the Part types you want to process. Parts you don't handle pass through unchanged. There are two families of handler.

For Block parts, set exactly ONE capability-typed handler: the parameter type bounds what the tool may write (immutability model, E-03):

  • Annotate(tool.BlockView) error: read-only; writes only overlays, annotations, and properties.
  • Produce(tool.VariantView) error: reads source, writes target.
  • Transform(tool.BlockView) (tool.EditPlan, error): a read-only edit producer: returns an edit plan, and the framework applier rewrites the source, rebasing surviving overlays, vaulting secrets, and bounds-checking, atomically. The flow's placement pass validates where a transformer may sit.

For the non-Block parts (Data, Media, Layer/Group start/end), set the untyped Handle*Fn fields, which use tool.PartHandler = func(part *model.Part) (*model.Part, error): these receive the streaming Part and type-assert the resource they care about.

package mytool

import (
"strings"

"github.com/neokapi/neokapi/core/model"
"github.com/neokapi/neokapi/core/tool"
)

func NewUppercaseTool() *tool.BaseTool {
t := &tool.BaseTool{
ToolName: "uppercase",
ToolDescription: "Converts source text to uppercase",
}
// Writes a target, so it sets Produce (the view bounds it to target writes).
t.Produce = func(v tool.VariantView) error {
if !v.Translatable() {
return nil
}
v.SetTargetText(model.LocaleEnglish, strings.ToUpper(v.SourceText()))
return nil
}
return t
}

Tool Categories

CategoryResponsibilityExamples
TransformModify content in-placecase change, search/replace, redaction
EnrichAdd metadata or overlayssegmentation, content-memory leveraging, AI translation, terminology
ValidateCheck quality without modifyingrule-based checks, word count, spell check
ConvertTransform representationsEncoding conversion, line break normalization

Overriding Process

If you need full control over the processing loop (for example, to accumulate state across many Parts, or to emit more Parts than you consume), define a named type that embeds tool.BaseTool and override Process directly:

type MyTool struct {
tool.BaseTool
}

func (t *MyTool) Process(ctx context.Context, in <-chan *model.Part, out chan<- *model.Part) error {
for {
select {
case <-ctx.Done():
return ctx.Err()
case part, ok := <-in:
if !ok {
return nil
}
// Custom processing logic
out <- part
}
}
}

Registration

Register your tool in a ToolRegistry, mapping a name to a factory:

reg := registry.NewToolRegistry()
reg.Register("uppercase", func() tool.Tool {
return NewUppercaseTool()
})

Use RegisterWithSchema instead to attach a parameter schema, and SetConfigFactory so a YAML step's config: reaches the tool; see Tool Authoring.

Built-in Tools

The framework's built-in tools are registered with their parameter schemas. The authoritative, generated list of what ships in the current build (every tool's name, description, and parameters) is the Tool Reference, rendered from those schemas so it always matches the build. This guide deliberately does not restate it; for how the built-ins map to the kinds of work above, see Tools.

Schema-Driven CLI Flags

All built-in tools use schema-driven CLI flags. Tool config structs use schema:"..." tags to auto-generate flags from the struct fields. Use schema:"-" to exclude a field from flag generation. The NewToolFromConfig pattern allows the flow engine to instantiate tools from YAML configuration by mapping config keys to struct fields automatically.

Registering Built-in Tools

All built-in tools can be registered into a registry at once, each with its parameter schema:

import (
"github.com/neokapi/neokapi/core/registry"
"github.com/neokapi/neokapi/core/tools"
)

toolReg := registry.NewToolRegistry()
tools.RegisterAll(toolReg)

Individual tools can also be constructed directly. Each takes a config struct (see the Tool Reference for every field):

// Segmentation with default SRX-like rules
segTool := tools.NewSegmentationTool(&tools.SegmentationConfig{})

// Rule-based check: configured via per-rule flags on RuleCheckConfig
checkTool := tools.NewRuleCheckTool(tools.NewRuleCheckConfig(model.LocaleID("fr")))

// Content-memory leverage with a custom fuzzy threshold and a memory provider
memoryTool := tools.NewMemoryLeverageTool(&tools.MemoryLeverageConfig{
TargetLocale: "fr",
FuzzyThreshold: 80, // 0-100
Memory: memoryProvider,
})

The terms package offers two library tools for embedding in Go, each taking a Terminology alongside its config. Neither is a registered flow tool: the tool a YAML flow or kapi exec names is term-check, and when a tool's schema declares that it requires a terms store, the runner appends term-lookup and term-enforce after it with the run's locales.

import "github.com/neokapi/neokapi/terms"

// Term lookup: scans source text and attaches terminology annotations
termLookupTool := terms.NewTermLookupTool(tb, terms.TermLookupConfig{
SourceLocale: "en",
TargetLocale: "fr",
})

// Term enforce: verifies translations use the preferred terminology
termEnforceTool := terms.NewTermEnforceTool(tb, terms.TermEnforceConfig{
SourceLocale: "en",
TargetLocale: "fr",
})