dataflow

package
v1.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package dataflow is a deterministic node-graph execution engine: HTTP calls, filters, data transforms, database queries, and similar steps that don't need an LLM turn to run. It complements pkg/workflow (a multi-agent DAG where every node is a Prompt) rather than replacing it — a dataflow graph can invoke a pkg/workflow.Definition as a single "subworkflow" node (see builtin.go) when a step genuinely needs judgment, without either engine reimplementing the other's execution model.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AsString

func AsString(v any) string

AsString coerces a resolved parameter value (a literal, or an expression's evaluation result) to a string: a string passes through unchanged, nil becomes "", anything else (a number or bool an expression evaluated to) is formatted with fmt.Sprint - the same loose coercion JS itself would apply putting a non-string into a string context.

func BoolParam

func BoolParam(params map[string]any, name string, fallback bool) bool

BoolParam reads the boolean parameter name from params, or returns fallback when it is absent or not a boolean.

func IntParam

func IntParam(params map[string]any, name string, fallback int) int

IntParam reads the integer parameter name from params, accepting an int or a float64 (which is what JSON decoding produces), or returns fallback otherwise.

func RegisterBuiltins

func RegisterBuiltins(registry *Registry)

RegisterBuiltins registers the "agent", "query", "tools" and "subworkflow" node types on registry — the node kinds that bridge into the LLM side (a single scoped agent turn, or a full pkg/workflow multi-agent chain) rather than running deterministic code. Callers still need to register whatever deterministic node types (http_request, filter, ...) their graph uses.

agent/query/tools together replace what used to be one catch-all "agent" node (automation-app-pages.md §37.9): agent is pure identity (which local persona), query is the task that actually runs against a connected agent's identity, and tools is a reusable, individually-toggleable group of capability nodes a query may call. This is a breaking change to the old single-node shape - its "prompt"/"tools" parameters moved to query.

func ResolveParam

func ResolveParam(ctx context.Context, rt *Runtime, params map[string]any, key string, item Item, itemIndex int) (any, error)

ResolveParam is ResolveValue(ctx, rt, params[key], item, itemIndex) - the common case of resolving one top-level parameter.

func ResolveValue

func ResolveValue(ctx context.Context, rt *Runtime, raw any, item Item, itemIndex int) (any, error)

ResolveValue is dataflow's equivalent of n8n's getNodeParameter: a node's own Execute calls this (as many times as it wants, for whichever items it wants - Execute's own signature never changes) instead of reading a raw parameter directly, to get expression support for free. raw is returned unresolved when it isn't a string, doesn't start with "=" (mirrors n8n's own isExpression() convention, and this SDK's pre-existing "set" node's own hand-rolled version of the same idea), or when rt/rt.Expr is nil (expressions not wired up by the caller - not an error, just "off").

Bindings available inside the expression: $json (item), $itemIndex, $now/$today (native JS Date), $node(name) - a callable returning {json, all} for a node that has already executed (see Runtime.NodeOutput) - and $vars (the caller's organization-scoped named values, Tier 3.2 - see Runtime.Vars/VarsMap). Deliberately no $prevNode: that needs per-item upstream source (NodeResult.InputSource, Tier 1.3), which the engine only knows after a node returns, not during its call - exposing it would mean changing Execute's signature, which this design avoids. $node('Name') covers the actual need.

func StringListParam

func StringListParam(params map[string]any, name string) []string

StringListParam reads a comma-separated PropString parameter as a trimmed, non-empty string slice - the same plain-string convention NodeProperty's own doc comment describes for a value only known at runtime per-tenant (an agent's available tool names), where a real multi-select has nowhere to source its options from statically. Returns nil (not an empty, non-nil slice) when the parameter is absent or blank, so callers can use its zero value to mean "no override" - see agentNode's use of this for its optional "tools" parameter.

func StringParam

func StringParam(params map[string]any, name, fallback string) string

StringParam reads the string parameter name from params, or returns fallback when it is absent or not a string.

func Validate

func Validate(def Definition) error

Validate checks graph structure only (unique node IDs, connections that target real nodes, no cycles, Tools/Agent references that are structurally sound) — it does not need a Registry, mirroring pkg/workflow.Validate. Per-node parameter validation, and the one Tools check that needs a node type's real metadata (a target can't be a Trigger or Logic node - see validateToolTargetTypes), happens in Run, once each node's executor is resolved.

Types

type AgentCaller

type AgentCaller interface {
	Ask(ctx context.Context, agentSlug, prompt string, tools []string, graphTools []ToolSpec) (string, error)
}

AgentCaller runs a single prompt through a scoped agent turn — what the built-in "agent" node type (see builtin.go) delegates to. The concrete implementation (e.g. wrapping sdk.Client.Ask) lives with the caller, not here, so dataflow never imports pkg/sdk directly.

agentSlug and tools are both optional per-node overrides of the job's own default agent/tool set - empty/nil means "behave exactly as before this parameter existed" (reuse the job's shared session, full conversation continuity across nodes). A non-default value asks the implementation for an isolated, freshly-scoped turn instead (e.g. sdk.Client.Ask's own create-register-submit-close session), since a node explicitly asking for a different agent or tool set is asking to delegate to something other than "continue the same conversation." tools is a list of tool *names* already available to resolve, not new tool definitions - see StringListParam, which is how agentNode reads its own "tools" property.

graphTools is this node's own Tools resolved into real, invocable ToolSpecs by BuildAgentTools (see tool_adapter.go) - nil/empty for every agent node that sets no Tools, which is every graph saved before this parameter existed. An implementation with no way to register a dynamic tool for this one turn should fail loudly on a non-empty graphTools rather than silently running the turn without them - the same posture already established for tools above.

type Base

type Base struct {
	// contains filtered or unexported fields
}

Base provides the boilerplate most NodeExecutor implementations share — embed it and override Execute (and ValidateParameters when a node has required parameters).

func NewBase

func NewBase(description NodeDescription) Base

NewBase creates the Base of a node executor from its description, so that the executor only has to implement Execute (and ValidateParameters when a node has required parameters).

func (Base) Description

func (b Base) Description() NodeDescription

Description returns the description the Base was created with.

func (Base) ValidateParameters

func (b Base) ValidateParameters(map[string]any) error

ValidateParameters is a no-op default; nodes with required parameters should override it.

type Definition

type Definition struct {
	Name        string `json:"name" yaml:"name"`
	Description string `json:"description,omitempty" yaml:"description,omitempty"`
	Nodes       []Node `json:"nodes" yaml:"nodes"`
	// PinnedData freezes a node's output to a fixed set of items instead of
	// actually running it - Run checks this (keyed by node ID) before
	// invoking a node's own Execute, mirroring n8n's IPinData. Kept
	// separate from each Node's own Parameters, since "frozen test output"
	// is a different concept from real node configuration - a pinned
	// node's ValidateParameters/Execute (and even its registry lookup) are
	// never called at all, so a graph can be tested with a node whose real
	// config is incomplete, or whose type isn't implemented yet.
	PinnedData map[string][]Item `json:"pinned_data,omitempty" yaml:"pinned_data,omitempty"`
}

Definition is a node graph: which node types run, their parameters, and how each node's output routes to downstream nodes.

type DisplayCondition

type DisplayCondition struct {
	Field  string   `json:"field"`
	Equals []string `json:"equals"`
}

DisplayCondition gates a property's visibility on another property's current value — the simplest useful subset of n8n's displayOptions.show (no hide, no operators beyond equality-in-a-set), sufficient for every current node's conditional fields (e.g. a database node's operation picker gating which of query/args apply).

type ExpressionEvaluator

type ExpressionEvaluator interface {
	Eval(ctx context.Context, source string, bindings map[string]any) (any, error)
}

ExpressionEvaluator is satisfied by *pkg/dataflow/expr.Pool as-is (no changes needed there) - an interface here rather than a hard dependency, the same "compose through an interface" split Runtime already uses for Secrets/Agent/Subworkflow, so core dataflow never needs to import the expr package.

type Item

type Item map[string]any

Item is one record flowing through the graph — the dataflow analog of a single JSON object. A node's input/output is always a slice of Item, never a single bare value, so merge/fan-out behave uniformly across node types.

type ItemSource

type ItemSource struct {
	Node string `json:"node,omitempty"`
	Port string `json:"port,omitempty"`
}

ItemSource identifies where one received input item came from - which upstream node produced it and on which output port. A zero value (Node == "") means the item came from this run's own seed input (Run's `input` parameter), not from another node.

type Node

type Node struct {
	ID          string              `json:"id" yaml:"id"`
	Type        string              `json:"type" yaml:"type"`
	Parameters  map[string]any      `json:"parameters,omitempty" yaml:"parameters,omitempty"`
	Connections map[string][]string `json:"connections,omitempty" yaml:"connections,omitempty"`
	// RetryOnFail/MaxTries/WaitBetweenTriesMS/OnError (Tier 3.1) are real
	// production behavior - not test/dev overrides like PinnedData - so
	// they're siblings of Parameters/Connections here rather than a
	// separate top-level map, mirroring n8n's own INode shape exactly.
	// RetryOnFail off (the default, and every graph saved before this field
	// existed) means Execute is called exactly once, today's existing
	// behavior. MaxTries is meaningful only when RetryOnFail is true, and
	// clamped 2-5 by the engine; WaitBetweenTriesMS is clamped 0-5000.
	RetryOnFail        bool `json:"retry_on_fail,omitempty" yaml:"retry_on_fail,omitempty"`
	MaxTries           int  `json:"max_tries,omitempty" yaml:"max_tries,omitempty"`
	WaitBetweenTriesMS int  `json:"wait_between_tries_ms,omitempty" yaml:"wait_between_tries_ms,omitempty"`
	// OnError decides what happens once every retry attempt (or the single
	// attempt, if RetryOnFail is off) has failed: "" (unset, the default -
	// today's existing behavior: NodeResult.Success=false, downstream
	// dependents skipped), "continueRegularOutput" (proceed with empty
	// output as if nothing happened), or "continueErrorOutput" (route a
	// single synthesized {"error": message} item onto a new "error" port,
	// letting a downstream branch handle the failure explicitly). Retry and
	// OnError are independent: a node can continue-on-error without ever
	// retrying, or retry a few times and still stop the run.
	OnError string `json:"on_error,omitempty" yaml:"on_error,omitempty"`
	// Tools lists the IDs of other nodes this node (which must be type
	// "query" or "tools" - enforced by Validate) may invoke as LLM-callable
	// tools during its turn, in addition to (never instead of) whatever it's
	// wired to via Connections. A node named here that has no real
	// Connections of its own stays dormant - the engine never schedules it
	// eagerly (see topologicalLevels) - and only runs when the query's LLM
	// actually calls it (see ResolveTools). A node that's both wired into
	// Connections *and* named in some query's Tools keeps running in its
	// normal sequential position too - dual-use, purely additive
	// (automation-app-pages.md §37.9). A target of type "tools" is expanded
	// recursively rather than called directly - see ResolveTools - letting
	// several "query" nodes share the same reusable, individually-toggleable
	// tool group instead of each repeating the same list of IDs.
	Tools []string `json:"tools,omitempty" yaml:"tools,omitempty"`
	// Agent is the ID of the "agent"-type node providing this "query" node's
	// persona - required on every "query" node (enforced by Validate), never
	// set on any other type. A plain ID reference rather than a Connections
	// edge, same reasoning as Tools above: it's a structural relationship
	// ("which identity do I run as"), not data flow, so the referenced agent
	// node is looked up directly rather than needing to run/be scheduled at
	// all.
	Agent string `json:"agent,omitempty" yaml:"agent,omitempty"`
}

Node is one step in the graph. Type selects the NodeExecutor (registered by name in a Registry, e.g. "http_request", "if", "agent"). Connections maps an output port name to the IDs of nodes that receive items emitted on that port — most node types only ever emit on "main"; conditional nodes (if/switch) emit on named ports like "true"/"false" instead.

type NodeDescription

type NodeDescription struct {
	Type        string         `json:"type"`
	Name        string         `json:"name"`
	Description string         `json:"description"`
	Category    string         `json:"category"`
	Properties  []NodeProperty `json:"properties,omitempty"`
	// IsTrigger marks a node type that can act as a graph's entry point (has
	// no meaningful input - Run already seeds every node with no
	// predecessors, trigger or not) and carries its own activation config
	// (e.g. a schedule) rather than transforming upstream data. An authoring
	// UI uses this to render trigger nodes distinctly (no input handle,
	// grouped separately) without hardcoding a type-name list.
	IsTrigger bool `json:"isTrigger,omitempty"`
	// IsTestable marks a node type whose executor implements
	// TestableExecutor — an authoring UI uses this to show a "Test
	// connection" affordance without hardcoding a type-name list. Set by
	// Registry.Types() via a type assertion, never by a node's own
	// Description() implementation.
	IsTestable bool `json:"isTestable,omitempty"`
}

NodeDescription is metadata about a node type, for catalogs/authoring UIs (an agent building a Definition can be handed a list of these).

type NodeExecutor

type NodeExecutor interface {
	Execute(ctx context.Context, rt *Runtime, input []Item, params map[string]any) (Output, error)
	Description() NodeDescription
	ValidateParameters(params map[string]any) error
}

NodeExecutor is what every node type implements — a thin adapter between the graph engine and one deterministic operation (an HTTP call, a filter, a database query, ...). Implementations should not hold per-run state; Runtime carries whatever per-run dependencies (secrets, callers) a node needs.

type NodeProperty

type NodeProperty struct {
	Name        string               `json:"name"`
	DisplayName string               `json:"displayName"`
	Type        NodePropertyType     `json:"type"`
	Required    bool                 `json:"required,omitempty"`
	Default     any                  `json:"default,omitempty"`
	Placeholder string               `json:"placeholder,omitempty"`
	Description string               `json:"description,omitempty"`
	Options     []NodePropertyOption `json:"options,omitempty"`
	DisplayIf   *DisplayCondition    `json:"displayIf,omitempty"`
}

NodeProperty describes one parameter field for an authoring UI's form renderer. Options a node can only resolve at runtime (a tenant's connector accounts, an agent's configured personas) have no place to live here — Description() is a stateless method — so such a field stays a plain string property; the UI can't render a dynamic dropdown for it.

type NodePropertyOption

type NodePropertyOption struct {
	Label string `json:"label"`
	Value string `json:"value"`
}

NodePropertyOption is one choice offered for a node property that is picked from a list: the label shown and the value stored.

type NodePropertyType

type NodePropertyType string

NodePropertyType is the set of parameter field widgets an authoring UI needs to render a node's Properties as a real form instead of raw JSON. Deliberately a small, flat subset — not a port of n8n's ~20-type INodeProperties system, which exists to serve thousands of third-party nodes. A node whose parameters don't fit this shape (a nested sub-workflow definition, a free-form key/value map with user-defined keys) should leave Properties empty rather than force a bad fit; the authoring UI falls back to raw JSON for those, which is correct, not a shortfall.

const (
	PropString    NodePropertyType = "string"
	PropText      NodePropertyType = "text" // multiline string
	PropNumber    NodePropertyType = "number"
	PropBoolean   NodePropertyType = "boolean"
	PropOptions   NodePropertyType = "options"   // single-select dropdown, static Options list
	PropJSON      NodePropertyType = "json"      // free-form object/array, still authored as JSON text
	PropSecretRef NodePropertyType = "secretRef" // a named secret's value is resolved by the caller, never authored inline - see dataflow.SecretResolver
)

type NodeResult

type NodeResult struct {
	ID      string `json:"id"`
	Type    string `json:"type"`
	Success bool   `json:"success"`
	Skipped bool   `json:"skipped,omitempty"`
	// Pinned is true when this result came from Definition.PinnedData
	// rather than a real Execute call - lets a trace/UI consumer tell
	// "this node actually ran" apart from "this used frozen test data".
	Pinned bool `json:"pinned,omitempty"`
	// Continued is true when this node failed (Success=false) but its own
	// OnError setting (Tier 3.1) let the run proceed anyway rather than
	// stopping - kept distinct from Success so a trace/UI consumer can
	// still tell "this genuinely failed" apart from "this failed but the
	// run kept going", the same distinction n8n's own execution view makes.
	// Run's own downstream-skip/routing logic treats Continued the same as
	// a real success (see engine.go).
	Continued bool `json:"continued,omitempty"`
	// Attempts is how many times Execute was actually called for this node
	// - 1 unless RetryOnFail was set and at least one attempt failed.
	Attempts int `json:"attempts,omitempty"`
	// Input/InputSource are exactly what this node received and, index for
	// index, where each item came from - populated by Run's own scheduling
	// loop (see engine.go), never by the node's own Execute, so every node
	// type gets this for free with no interface change.
	Input       []Item       `json:"input,omitempty"`
	InputSource []ItemSource `json:"input_source,omitempty"`
	// Output is the same single-port view every existing consumer already
	// reads (unchanged) - OutputByPort is the real per-port breakdown Output
	// collapses away (see executeNode).
	Output       []Item            `json:"output,omitempty"`
	OutputByPort map[string][]Item `json:"output_by_port,omitempty"`
	Error        string            `json:"error,omitempty"`
	StartedAt    time.Time         `json:"started_at"`
	EndedAt      time.Time         `json:"ended_at"`
	Duration     time.Duration     `json:"duration"`
}

NodeResult is the recorded outcome of one node's execution within a Run.

type Options

type Options struct {
	MaxParallel int
}

Options tunes Run's execution. MaxParallel caps how many nodes within one level run concurrently; 0 defaults to 4 (mirrors pkg/workflow.Options).

type Output

type Output struct {
	Ports map[string][]Item
}

Output is what a node execution produces, split by output port. Use Main(items) for the common single-port case.

func Main

func Main(items []Item) Output

Main wraps items as a single-port ("main") Output — the shape almost every node type other than a conditional (if/switch) needs.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry maps node type names to their executors. A Run needs a Registry covering every Type referenced by the Definition being executed.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates an empty registry of node executors, safe for concurrent use.

func (*Registry) Get

func (r *Registry) Get(nodeType string) (NodeExecutor, error)

Get returns the executor registered for nodeType, or an error when there is none.

func (*Registry) Register

func (r *Registry) Register(nodeType string, executor NodeExecutor)

Register adds executor for the node type nodeType, replacing the one that was registered for it.

func (*Registry) Types

func (r *Registry) Types() []NodeDescription

Types lists every registered node type's description, for authoring surfaces (e.g. a tool the Inbox Agent uses to see what's available).

type Result

type Result struct {
	Name      string                `json:"name"`
	Success   bool                  `json:"success"`
	StartedAt time.Time             `json:"started_at"`
	EndedAt   time.Time             `json:"ended_at"`
	Duration  time.Duration         `json:"duration"`
	Results   map[string]NodeResult `json:"results"`
	// Order is execution order, in the same level-batched order Run used —
	// mirrors pkg/workflow.Result.Order.
	Order []string `json:"order"`
}

Result is the outcome of a full graph Run.

func Run

func Run(ctx context.Context, def Definition, registry *Registry, rt *Runtime, input []Item, opts Options) (Result, error)

Run executes def against registry, seeding every node with no predecessors with input, and returns once every reachable node has run (or been skipped because a predecessor failed).

type Runtime

type Runtime struct {
	Secrets     SecretResolver
	Agent       AgentCaller
	Subworkflow SubworkflowRunner
	// Expr enables expression resolution (ResolveParam/ResolveValue) for any
	// node that opts in - nil (the default for a caller that doesn't set it,
	// and for every existing Runtime{} literal/nil Runtime already in use)
	// means expressions are off, not an error; nodes calling ResolveParam
	// against a nil-Expr Runtime just get their literal values back
	// unresolved.
	Expr ExpressionEvaluator
	// Vars is the caller's own organization-scoped named values (Tier 3.2's
	// "Variables"), exposed to expressions as $vars.NAME - see VarsMap.
	// Unlike Secrets, this is a plain map rather than a lazy resolver
	// interface: Variables aren't secret, so the caller resolves the whole
	// set once up front (e.g. before a run starts) instead of one name at a
	// time per access. nil (the default) means no variables are configured,
	// not an error - $vars.ANYTHING then just resolves to undefined.
	Vars map[string]string
	// contains filtered or unexported fields
}

Runtime carries the per-run dependencies node executors need, injected by the caller (seshat-backend/seshat-server for multi-tenant secret resolution, internal/automation for the agent/subworkflow callers) rather than owned by this package — dataflow itself never stores a credential or holds an sdk.Client. This is the same "compose through an interface, don't couple bounded contexts" split already used for iam/automation in seshat-ai's seshat-server.

func (*Runtime) NodeAccessor

func (rt *Runtime) NodeAccessor() func(string) map[string]any

NodeAccessor returns the Go closure bound as the JS-callable $node(name) function - exported so a node type building its own bindings map (e.g. filter/if/switch, which evaluate a whole-field expression rather than going through ResolveValue) can offer $node too without duplicating this logic. A nil rt still returns a working (always-empty) accessor so ResolveValue's own nil-rt short-circuit is the only nil check callers need.

func (*Runtime) NodeOutput

func (rt *Runtime) NodeOutput(name string) ([]Item, bool)

NodeOutput returns the named node's Output ("main"-collapsed, same shape as NodeResult.Output) once Run has recorded it - only for a node that has actually finished executing, mirroring n8n's own $('NodeName') execution- order requirement. ok is false before that (including for a name that will never exist). Safe to call from concurrently-running node executions - Run itself is the only writer, under its own lock.

func (*Runtime) VarsMap

func (rt *Runtime) VarsMap() map[string]string

VarsMap returns rt.Vars, or an empty (never nil) map for a nil Runtime or a Runtime with no Vars set - so a binding map can always assign rt.VarsMap() to "$vars" without a separate nil check, mirroring NodeAccessor's own nil-safety. Exported so a node type building its own bindings map (e.g. filter/if/switch, see nodes/logic.go) can offer $vars too without duplicating this logic.

type SecretResolver

type SecretResolver interface {
	Resolve(ctx context.Context, ref string) (string, error)
}

SecretResolver resolves an opaque reference (e.g. a ConnectorAccount ID, or a provider-setting ID) to the secret value a node needs — a database password, an API key, an OAuth token. nil is valid: a graph with no node that needs a secret never calls it.

type SubworkflowRunner

type SubworkflowRunner interface {
	Run(ctx context.Context, def workflow.Definition) (workflow.Result, error)
}

SubworkflowRunner executes a pkg/workflow.Definition — what the built-in "subworkflow" node type delegates to, for a step that genuinely needs a multi-agent chain (triage -> draft -> verify) rather than a single prompt.

type TestableExecutor

type TestableExecutor interface {
	TestConnection(ctx context.Context, rt *Runtime, params map[string]any) error
}

TestableExecutor is an optional capability a node type may implement to verify its own secretRef-based connection (a SQL Ping, a Redis PING, a Mongo Ping, an Elasticsearch root GET) without requiring the rest of its parameters (query, key, operation, ...) to be filled in or valid — the Go-idiomatic equivalent of n8n's per-node testedBy fallback, since a dataflow secret carries no "credential type" to hang a declarative ICredentialType.test off of (see DataflowSecret in seshat-server: an opaque name/value pair, nothing more). A node type that doesn't implement this simply has no "Test connection" affordance — Registry.Types() sets NodeDescription.IsTestable via a type assertion against this interface, so nothing needs a hardcoded type-name list anywhere.

type ToolSpec

type ToolSpec struct {
	// Name is the target node's own ID - stable, unique within the graph,
	// and already meaningful to whoever built it (unlike a synthesized
	// name), so it doubles as the tool's name the LLM sees.
	Name        string
	Description string
	// Schema is a JSON-schema object (the same shape sdk.Tool's
	// InputSchema/schema.FromMap expects: {"type":"object","properties":
	// {...},"required":[...]}), built from the target node's own
	// NodeDescription.Properties.
	Schema map[string]any
	// Invoke runs the target node for real with args overriding its own
	// static Parameters (LLM-supplied keys win), and returns its output
	// serialized as JSON text - the same "records, not a single scalar"
	// shape every other dataflow node produces.
	Invoke func(ctx context.Context, args map[string]any) (string, error)
}

ToolSpec is one node exposed as an LLM-callable tool - the plain-data bridge between a graph's Tools reference and a real registrable tool (e.g. pkg/sdk.Tool). Deliberately has no dependency on pkg/sdk or internal/tools - dataflow stays a deterministic engine package; turning a ToolSpec into a real sdk.Tool (internal/tools/registry.NewBuilder(...)) happens at the AgentCaller implementation's own call site, mirroring how internal/tools/system/mcp/wrapper.go wraps an external MCP tool schema.

func ResolveTools

func ResolveTools(def Definition, registry *Registry, rt *Runtime, nodeID string) ([]ToolSpec, error)

ResolveTools resolves nodeID's own Tools list into real, invocable ToolSpecs - called by queryNode.Execute (see builtin.go) before asking rt.Agent. Returns nil (not an error) for a node with no Tools set, the common case for a query with no tools at all.

A "tools"-type target is expanded recursively instead of becoming a ToolSpec itself (its own Tools list's members do, minus whatever its "disabled" parameter names) - this is how several "query" nodes share one reusable, individually-toggleable capability group instead of each repeating the same list of IDs (automation-app-pages.md §37.9).

Directories

Path Synopsis
Package expr evaluates JS boolean/value expressions (n8n's "{{ $json.x }}" convention, already used by seshat-backend's inbox event filter) for dataflow nodes that need per-item logic (filter/if/set/switch) — unlike event_filter.go's one-VM-per-call approach (fine for a single check per inbound message), a dataflow node may evaluate the same expression against many items in one run, so this pool reuses goja runtimes and caches compiled bytecode across evaluations.
Package expr evaluates JS boolean/value expressions (n8n's "{{ $json.x }}" convention, already used by seshat-backend's inbox event filter) for dataflow nodes that need per-item logic (filter/if/set/switch) — unlike event_filter.go's one-VM-per-call approach (fine for a single check per inbound message), a dataflow node may evaluate the same expression against many items in one run, so this pool reuses goja runtimes and caches compiled bytecode across evaluations.
Package nodes provides the generic, deterministic node types every dataflow graph can use regardless of tenant/product (http_request, filter, if, switch, set, merge, wait) — no credentials, no multi-tenant concerns.
Package nodes provides the generic, deterministic node types every dataflow graph can use regardless of tenant/product (http_request, filter, if, switch, set, merge, wait) — no credentials, no multi-tenant concerns.
database
Package database provides deterministic node executors for querying external databases from a dataflow graph — Postgres, MySQL, SQLite (pkg/database/sql, three thin drivers sharing one executor), Redis, and MongoDB (native drivers), and Elasticsearch (its own REST API, no client SDK needed).
Package database provides deterministic node executors for querying external databases from a dataflow graph — Postgres, MySQL, SQLite (pkg/database/sql, three thin drivers sharing one executor), Redis, and MongoDB (native drivers), and Elasticsearch (its own REST API, no client SDK needed).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL