toolcompose¶
Composition layer providing filtered tool collections and skill-based workflows. This repository enables building higher-level abstractions over tools.
Packages¶
| Package | Purpose |
|---|---|
set | Filtered tool collections with predicates |
skill | Skill-based workflow composition |
Motivation¶
- Group related tools into logical collections
- Enable skill-based workflows for common tasks
- Provide filtered views without duplicating tools
- Support composition patterns for complex operations
set Package¶
The set package provides filtered tool collections using predicates.
Core Responsibilities¶
- Create filtered views of tool registries
- Apply namespace, tag, and custom predicates
- Chain filters for complex selections
- Export filtered sets in multiple formats
Example¶
import (
"github.com/jonwraymond/toolcompose/set"
"github.com/jonwraymond/toolfoundation/adapter"
)
tools := []*adapter.CanonicalTool{
{Namespace: "github", Name: "create_issue", Tags: []string{"issues"}, InputSchema: &adapter.JSONSchema{Type: "object"}},
{Namespace: "github", Name: "add_labels", Tags: []string{"issues"}, InputSchema: &adapter.JSONSchema{Type: "object"}},
}
ts, _ := set.NewBuilder("github-issues").
FromTools(tools).
WithNamespace("github").
WithTags([]string{"issues"}).
WithPolicy(set.DenyTags("danger")).
Build()
ids := ts.IDs()
Built-in Predicates¶
| Predicate | Description |
|---|---|
NamespaceFilter(ns...) | Match any namespace |
TagsAny(tags...) | Match tools with any tag |
TagsAll(tags...) | Match tools with all tags |
TagsNone(tags...) | Match tools with none of the tags |
CategoryFilter(cats...) | Match any category |
AllowIDs(ids...) | Allow only listed IDs |
DenyIDs(ids...) | Exclude listed IDs |
skill Package¶
The skill package provides skill-based workflow composition.
Features¶
- Define declarative skills from tool steps
- Deterministic planning (sorted by step ID)
- Guardrails for max steps and allowed tool IDs
- Execution via a pluggable runner
Example¶
import (
"context"
"github.com/jonwraymond/toolcompose/skill"
"github.com/jonwraymond/toolexec/run"
)
sk := skill.Skill{
Name: "create-issue",
Steps: []skill.Step{
{ID: "create", ToolID: "github:create_issue", Inputs: map[string]any{"title": "Bug report"}},
{ID: "label", ToolID: "github:add_labels", Inputs: map[string]any{"labels": []string{"bug"}}},
},
}
plan, _ := skill.NewPlanner().Plan(sk)
type runAdapter struct{ exec run.Runner }
func (r runAdapter) Run(ctx context.Context, step skill.Step) (any, error) {
res, err := r.exec.Run(ctx, step.ToolID, step.Inputs)
if err != nil {
return nil, err
}
return res.Output, nil
}
runner := run.NewRunner()
_, err := skill.Execute(context.Background(), plan, runAdapter{exec: runner})
Skill Composition¶
flowchart TB
Input["Skill Inputs"] --> Step1["Step 1: create_issue"]
Step1 --> Cond{"Has labels?"}
Cond -->|Yes| Step2["Step 2: add_labels"]
Cond -->|No| Output
Step2 --> Output["Skill Output"] Diagram¶
Key Design Decisions¶
- Deterministic sets: Toolset listing is sorted and repeatable
- Filter + policy: Filters reduce candidates, policies enforce access
- Declarative skills: Skills are pure definitions, not executors
- Pluggable runners: Execution integrates with any tool runner