Documentation
¶
Overview ¶
Package packtrail is the public, embeddable entry point to the packtrail durable workflow engine. packtrail orchestrates declarative YAML flow graphs — task, fanout, fanin, choice and signal nodes — with crash-durable state backed only by NATS (Core + JetStream + KV + Message Scheduler).
packtrail is ecosystem-agnostic: nodes are executed through a pluggable Invoker, so any project can drive its own services (an agent caller, an HTTP client, a NATS request/reply worker) while inheriting durability, retries, fan-in policies, conditional routing, signals and timers. A built-in "nats-task" Invoker (pkg/protocol request/reply) is always registered.
nc, _ := nats.Connect(nats.DefaultURL)
srv, _ := packtrail.New(nc,
packtrail.WithFlowsDir("flows"),
packtrail.WithInvoker("agent", myInvoker), // your ecosystem's transport
packtrail.WithResultCache(), // idempotent retries
)
id, _ := srv.Start(ctx, "research-pipeline", nil)
srv.Run(ctx) // blocks: engine + indexer
The Server does not own the *nats.Conn it is given; the caller connects and closes it.
Index ¶
- Constants
- Variables
- func ValidateFlowDef(defs ...FlowDef) error
- type Branch
- type DeadLetter
- type EdgeDef
- type Event
- type Execution
- type FlowDef
- type FlowGraph
- type GraphEdge
- type GraphNode
- type GraphRule
- type Handler
- type Invoker
- type InvokerFunc
- type NodeDef
- type Option
- func WithArchive(retention time.Duration) Option
- func WithAsyncInvoker(kind string, exec invoker.Invoker, opts ...asyncqueue.Option) Option
- func WithDefaultTimeout(d time.Duration) Option
- func WithDrainTimeout(d time.Duration) Option
- func WithFlow(yamlDoc []byte) Option
- func WithFlowDef(f FlowDef) Option
- func WithFlowsDir(dir string) Option
- func WithHistory(retention time.Duration) Option
- func WithInvoker(kind string, inv invoker.Invoker) Option
- func WithLeaseTTL(d time.Duration) Option
- func WithMaxConcurrency(n int) Option
- func WithMaxDeliver(n int) Option
- func WithMaxDocumentBytes(n int) Option
- func WithMaxPayloadBytes(n int) Option
- func WithNamespace(prefix string) Option
- func WithOwnerID(id string) Option
- func WithReconcileActive(cronExpr string) Option
- func WithReconcileFull(cronExpr string) Option
- func WithResultCache() Option
- func WithResultCacheTTL(d time.Duration) Option
- func WithSignalRetention(d time.Duration) Option
- func WithStallRedrive(d time.Duration) Option
- type Request
- type Result
- type RetryPolicy
- type RuleDef
- type Server
- func (s *Server) ArchiveTerminal(ctx context.Context) (int, error)
- func (s *Server) ByFlow(ctx context.Context, flow string) ([]string, error)
- func (s *Server) ByFlowEvents(ctx context.Context, flow string) ([]Event, error)
- func (s *Server) ByFlowEventsLimit(ctx context.Context, flow string, limit int) ([]Event, error)
- func (s *Server) ByStatus(ctx context.Context, status string) ([]string, error)
- func (s *Server) ByStatusEvents(ctx context.Context, status string) ([]Event, error)
- func (s *Server) ByStatusEventsLimit(ctx context.Context, status string, limit int) ([]Event, error)
- func (s *Server) Cancel(ctx context.Context, execID, reason string) error
- func (s *Server) Close()
- func (s *Server) CompleteActivity(ctx context.Context, execID, node string, attempt int, res Result) error
- func (s *Server) CompleteActivityWithGeneration(ctx context.Context, execID, node string, generation uint64, attempt int, ...) error
- func (s *Server) DeadLetterCount(ctx context.Context) (uint64, error)
- func (s *Server) FlowGraph(ctx context.Context, name string) (*FlowGraph, error)
- func (s *Server) Flows() []string
- func (s *Server) GCIndex(ctx context.Context) (int, error)
- func (s *Server) Get(ctx context.Context, execID string) (*Execution, error)
- func (s *Server) Handle(ctx context.Context, subject string, h Handler) error
- func (s *Server) History(ctx context.Context, execID string, limit int) ([]Event, error)
- func (s *Server) Init(ctx context.Context) error
- func (s *Server) List(ctx context.Context) ([]string, error)
- func (s *Server) ListFlows(ctx context.Context) ([]string, error)
- func (s *Server) ListFunc(ctx context.Context, fn func(id string) error) error
- func (s *Server) RecentDeadLetters(ctx context.Context, limit int) ([]DeadLetter, error)
- func (s *Server) Reconcile(ctx context.Context) error
- func (s *Server) ReconcileActive(ctx context.Context) error
- func (s *Server) RedriveStalled(ctx context.Context) (int, error)
- func (s *Server) Results(ctx context.Context, execID string) (json.RawMessage, error)
- func (s *Server) Resume(ctx context.Context, execID string) error
- func (s *Server) Run(ctx context.Context) error
- func (s *Server) ScheduleFlow(ctx context.Context, name, flow, cronExpr string, payload json.RawMessage) error
- func (s *Server) Signal(ctx context.Context, execID, name string, payload json.RawMessage) error
- func (s *Server) SignalWithID(ctx context.Context, execID, name, idempotencyKey string, ...) error
- func (s *Server) Start(ctx context.Context, flow string, payload json.RawMessage) (string, error)
- func (s *Server) StartWithID(ctx context.Context, execID, flow string, payload json.RawMessage) (string, error)
- func (s *Server) WatchEvents(ctx context.Context) (<-chan Event, error)
- type Status
- type TaskRequest
- type TaskResponse
Constants ¶
const ( StatusOK = invoker.StatusOK StatusError = invoker.StatusError StatusRetry = invoker.StatusRetry StatusPending = invoker.StatusPending )
Invocation outcome statuses.
const ( TaskOK = protocol.StatusOK TaskError = protocol.StatusError TaskRetry = protocol.StatusRetry )
Built-in nats-task worker response statuses (the string values a Handler sets on a TaskResponse). For Invoker implementations, use the Status* constants.
const ( ExecRunning = store.StatusRunning ExecWaiting = store.StatusWaiting ExecCompleted = store.StatusCompleted ExecFailed = store.StatusFailed ExecCancelled = store.StatusCancelled )
Execution statuses.
const NATSTaskKind = natstask.Kind
NATSTaskKind is the invoker kind of the always-registered built-in transport.
Variables ¶
var ErrNotFound = store.ErrNotFound
ErrNotFound is returned by Get when an execution does not exist.
Functions ¶
func ValidateFlowDef ¶ added in v0.1.0
ValidateFlowDef validates one or more FlowDefs against the full flow-graph rules — node/edge structure, a unique start node, choice defaults, fan-in join policy, retry bounds, etc. — without a NATS connection, so a builder can verify programmatic flows offline (e.g. in a `validate` command) and catch the same errors New would raise at startup. It returns the first validation error (which already names the offending flow).
Types ¶
type Branch ¶
type Branch struct {
Node string `json:"node"`
Status string `json:"status"`
Generation uint64 `json:"generation,omitempty"`
Attempt int `json:"attempt"`
Error string `json:"error,omitempty"`
}
Branch is the control state of a single fan-out branch; a completed branch's result appears under results.<branch> in Server.Results.
type DeadLetter ¶ added in v0.1.0
type DeadLetter = store.DeadLetter
DeadLetter is a durable record of a work item a durable consumer gave up on (Term'd) — a terminal error or an exhausted delivery cap. Kind is the source consumer ("work", "schedule", "signal" or "async").
type Event ¶
type Event struct {
ExecID string `json:"exec_id"`
Flow string `json:"flow"`
Status string `json:"status"`
Node string `json:"node"`
Error string `json:"error,omitempty"`
Revision uint64 `json:"revision"`
Time time.Time `json:"time"`
}
Event is a flow execution transition, suitable for a live activity feed.
type Execution ¶
type Execution struct {
ID string `json:"id"`
Flow string `json:"flow"`
Status string `json:"status"`
CurrentNode string `json:"current_node"`
NodeGeneration uint64 `json:"node_generation,omitempty"`
Attempt int `json:"attempt"`
Outputs []string `json:"outputs,omitempty"` // node ids with a stored output, in settle order
OutputVersions map[string]string `json:"output_versions,omitempty"` // committed versioned output keys, per node
Branches map[string]Branch `json:"branches,omitempty"`
Signals []string `json:"signals,omitempty"` // received-but-unconsumed signal names
WaitSignal string `json:"wait_signal,omitempty"`
Error string `json:"error,omitempty"`
UpdatedAt time.Time `json:"updated_at"`
}
Execution is a read-only snapshot of a flow instance's control state. Payloads live in the data plane and are not carried on the snapshot — read them with Server.Results, which assembles {input, results, signals, branches, last_node}.
type FlowDef ¶ added in v0.0.2
type FlowDef struct {
Version string // "1.0"; empty is accepted for legacy parity with YAML
Name string
Nodes []NodeDef
Edges []EdgeDef
}
FlowDef is a programmatic flow definition. It mirrors the YAML schema and can be passed to WithFlowDef instead of writing YAML.
type FlowGraph ¶
type FlowGraph struct {
Version string `json:"version,omitempty"`
Name string `json:"name"`
Nodes []GraphNode `json:"nodes"`
Edges []GraphEdge `json:"edges"`
}
FlowGraph is the static structure of a flow, for visualisation. It is published to a KV registry at startup so observability tools can render a flow without its source YAML.
type GraphNode ¶
type GraphNode struct {
ID string `json:"id"`
Type string `json:"type"` // task | fanout | fanin | choice | signal
Invoker string `json:"invoker,omitempty"`
Target string `json:"target,omitempty"`
Branches []string `json:"branches,omitempty"`
WaitFor []string `json:"wait_for,omitempty"`
JoinPolicy string `json:"join_policy,omitempty"`
Rules []GraphRule `json:"rules,omitempty"`
OnError string `json:"on_error,omitempty"`
SignalName string `json:"signal_name,omitempty"`
OnTimeout string `json:"on_timeout,omitempty"`
}
GraphNode is one node of a FlowGraph. Fields are type-specific; empty ones are omitted.
type GraphRule ¶
type GraphRule struct {
When string `json:"when,omitempty"`
Default bool `json:"default,omitempty"`
To string `json:"to"`
}
GraphRule is one routing rule of a choice node.
type Invoker ¶
Invoker executes a single node invocation. Implement it to plug in a transport for your ecosystem.
type NodeDef ¶ added in v0.0.2
type NodeDef struct {
ID string
Type string // "task" | "fanout" | "fanin" | "choice" | "signal"
// task
Invoker string
Target string
Subject string
Timeout time.Duration
Retry *RetryPolicy
// fanout
Branches []string
// fanin
WaitFor []string
JoinPolicy string // "all" | "any" | "quorum:N"
// choice
Rules []RuleDef
OnError string // "" routes eval errors to the default rule; "fail" fails the execution
// signal
SignalName string
OnTimeout string
}
NodeDef is a single node in a FlowDef.
type Option ¶
type Option func(*config)
Option configures a Server. Pass options to New.
func WithArchive ¶ added in v0.1.0
WithArchive enables execution archival: completed executions are swept out of the hot executions bucket into a cold archive bucket that retains them for roughly retention before they expire. This bounds the hot bucket — and the List/Keys and full-reconcile scans over it — by in-flight volume rather than all-time volume. The sweep runs on the full-reconcile schedule (see WithReconcileFull), so pair the two. Failed executions are left hot so they remain resumable. Without this option the hot bucket retains every execution.
func WithAsyncInvoker ¶ added in v0.1.0
WithAsyncInvoker registers an asynchronous Invoker under kind. Unlike WithInvoker, exec does not run on the engine's critical path: a flow node selecting this kind is dispatched to a durable JetStream work-queue (the engine parks the execution) and exec is run later by an in-process worker, with at-least-once delivery. Use it for slow nodes — an agent call, an HTTP request — so they never hold an engine slot. exec is an ordinary synchronous Invoker returning StatusOK/StatusError/StatusRetry; the durability, retries and completion are handled for you. opts tune the worker and its stream (see the asyncqueue package). It may be passed multiple times for distinct kinds.
Delivery to exec is at-least-once: a job redelivered after a worker crash (invoked, but not yet settled and acked) runs exec again. For a target whose side effects must not fire twice, enable WithResultCache — it dedups the worker's execution as well — or make the target idempotent.
func WithDefaultTimeout ¶
WithDefaultTimeout sets the invocation timeout used when a node omits one (default 30s).
func WithDrainTimeout ¶ added in v0.1.0
WithDrainTimeout sets how long a graceful shutdown waits for in-flight work items to settle and ack before aborting the stragglers (default 30s). When Run returns because its context was cancelled, the engine stops accepting new work and drains what is already running within this window — so a clean restart does not abandon in-flight invocations to redelivery (which would double-fire naturally non-idempotent targets). Stragglers exceeding the window are cancelled and their work redelivers. A hard crash is unaffected (it always relies on redelivery).
func WithFlow ¶
WithFlow registers a single flow from its YAML document. It may be passed multiple times.
func WithFlowDef ¶ added in v0.0.2
WithFlowDef registers a single flow from a Go struct. It may be passed multiple times and combined with WithFlow / WithFlowsDir.
func WithFlowsDir ¶
WithFlowsDir loads every *.yaml / *.yml flow definition in dir.
func WithHistory ¶ added in v0.1.0
WithHistory enables the durable per-execution history: every state transition is also appended, best-effort, to a `<namespace>-history` stream retained for the given duration, and Server.History returns an execution's ordered step-by-step trace. Without it, transition events live only in the short-retention events stream that feeds the visibility indexes. The trace is observability, not operational truth.
func WithInvoker ¶
WithInvoker registers an Invoker under kind, the value a flow node selects via its `invoker:` field. The built-in "nats-task" kind is always registered and may be overridden by passing WithInvoker("nats-task", ...). It may be passed multiple times for distinct kinds; duplicate kinds are rejected by New.
func WithLeaseTTL ¶
WithLeaseTTL sets the per-execution ownership lease TTL (default 30s). A crashed instance's executions become available to others after roughly this.
func WithMaxConcurrency ¶
WithMaxConcurrency caps how many work items this instance processes at once (default 64).
func WithMaxDeliver ¶ added in v0.1.0
WithMaxDeliver caps how many times a single message is delivered before the engine dead-letters it instead of redelivering forever (default 10). It bounds the blast radius of a message that can never succeed (e.g. its flow or node was removed) or one that keeps hitting a transient fault. The cap applies to all three of the engine's durable consumers: the work stream (failing the execution with a descriptive reason), the fired-schedule stream (a removed-flow cron tick or persistently-failing reconcile), and the signal stream. A terminal error on any of them is dead-lettered immediately, regardless of this cap. A non-positive value is treated as the default — the cap cannot be disabled, or a poisoned message could redeliver forever. (The async invoker worker has its own asyncqueue.WithMaxDeliver, which does allow an explicit opt-out.)
func WithMaxDocumentBytes ¶ added in v0.1.0
WithMaxDocumentBytes caps the serialized size of an execution's control document (default 768 KiB, store.DefaultMaxDocumentBytes). The document is small control metadata, but a very wide fanout (one BranchState per branch) or a large transient outbox can grow it toward NATS's 1 MiB ceiling; a write that would exceed the limit is rejected with a typed error (and, on the fanout path, fails the node with a clear reason) instead of an opaque NATS publish error. Pass a negative value to disable the guard; zero keeps the default.
func WithMaxPayloadBytes ¶ added in v0.1.0
WithMaxPayloadBytes caps the size of an execution's payload (default 512 KiB, store.DefaultMaxPayloadBytes). The payload grows as task results, branch results and signal payloads are merged in; a transition that would exceed the limit fails the execution with a clear reason instead of producing an opaque KV write error. The default leaves headroom below NATS's 1 MiB max message size for the rest of the execution document. Pass a negative value to disable the guard; zero keeps the default.
func WithNamespace ¶
WithNamespace sets the resource prefix for every NATS bucket, stream, subject and durable consumer (default "packtrail"). Give each independent deployment a distinct namespace to let them share a NATS cluster without colliding.
func WithOwnerID ¶
WithOwnerID sets this instance's ownership-lease owner id. Defaults to a random id; only set it if you need a stable, distinct id per instance.
func WithReconcileActive ¶ added in v0.1.0
WithReconcileActive installs the recurring active-set reconcile: a cheap pass over only the in-flight (running/waiting) executions, on the given 6-field cron expression ("sec min hour dom mon dow"), e.g. "0 */5 * * * *". Its cost is independent of accumulated terminal executions, so it is safe to run often. It fixes the common drift where a finished execution is still indexed as active, but cannot recover an execution missing from the index entirely.
func WithReconcileFull ¶ added in v0.1.0
WithReconcileFull installs the recurring full reconcile: an authoritative scan of every execution on the given 6-field cron expression, e.g. "0 0 * * * *" for hourly. It is the deep backstop that recovers index entries the active pass cannot see, and it also runs low-frequency maintenance such as reclaiming consumed scheduler firings; its cost grows with total execution volume, so schedule it well below the active cadence. Without either option the indexer still runs but no periodic reconcile is scheduled.
func WithResultCache ¶
func WithResultCache() Option
WithResultCache enables idempotent invocation: every node result is cached by (execution, node, attempt) in a KV bucket, so a re-invocation returns the cached result instead of running the node again. Node invocation is otherwise at-least-once — a work item redelivered after a crash, or a lease taken over while an instance is paused, can run the same node twice (the execution-doc CAS fences state, not external side-effects). Enable this (or make targets idempotent) whenever an invocation has a side effect that must not run twice. The cache covers both invocation paths: the engine-side dispatch (including an async node's StatusPending, so a redelivered work item re-parks instead of dispatching a second job) and the async worker's execution of the target (under a separate keyspace in the same bucket, so a redelivered job serves the completed result instead of re-firing the side effect). Cache entries expire after a TTL (default 24h — see WithResultCacheTTL): an entry is only ever consulted during the redelivery window of its own attempt, so retaining it longer would just grow the bucket without bound.
func WithResultCacheTTL ¶ added in v0.1.0
WithResultCacheTTL enables the result cache with a custom entry TTL. Entries need only outlive the redelivery window of a single attempt (ack wait × delivery cap), so the 24h default is already generous; raise it if your redelivery horizon is unusually long. A negative TTL disables expiry (the bucket then grows without bound — the pre-TTL behaviour). Implies WithResultCache.
func WithSignalRetention ¶ added in v0.1.0
WithSignalRetention sets how long the signals stream retains messages (its MaxAge) — the window during which an undelivered signal survives an engine outage before it is dropped. A positive duration sets that window; a negative value disables the age limit (retain until the stream's other limits); zero keeps the default (7 days). Raise it if executions may wait for a signal through a longer outage than a week.
func WithStallRedrive ¶ added in v0.1.0
WithStallRedrive sets the quiet-time threshold after which the stall watchdog re-drives an active execution (default 5× the engine ack wait; a negative value disables the watchdog). The watchdog runs after each reconcile-active pass (see WithReconcileActive — without that schedule it only runs via Server.RedriveStalled), and it heals executions whose driving work item was lost in a crash window: still active, quiet past the threshold, not inside a scheduled retry backoff, and not lease-held by a live instance. Set the threshold above your longest legitimate quiet period; a false positive is state-safe (guarded transitions) but duplicates an invocation within the at-least-once contract.
type RetryPolicy ¶ added in v0.0.2
RetryPolicy controls task retries for a NodeDef.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is an embeddable packtrail engine instance: it runs the work consumer, visibility indexer and (optionally) reconciliation, and can host built-in nats-task workers in the same process.
Construction is pure (see New); every NATS resource is provisioned by Init — explicitly for fail-fast startups, or implicitly by the first operation that needs it.
func New ¶
New builds a Server against an existing NATS connection. It parses and validates the configured flows and options but performs no NATS I/O: every bucket and stream is provisioned by Init, which the first operation that needs NATS (Start, Run, Get, …) calls implicitly with its own context. Call Init yourself at startup to surface provisioning errors fail-fast.
func (*Server) ArchiveTerminal ¶ added in v0.1.0
ArchiveTerminal sweeps terminal, non-resumable executions (completed or cancelled) out of the hot bucket into the cold archive; failed executions stay hot so they remain resumable. It is a no-op unless archival is enabled (see WithArchive). The full-reconcile schedule runs it automatically; it is exported for manual or test-driven sweeps.
func (*Server) ByFlowEvents ¶ added in v0.0.2
ByFlowEvents returns a summary event for every execution belonging to flow, read directly from the visibility index without a per-execution round-trip.
func (*Server) ByFlowEventsLimit ¶ added in v0.1.0
ByFlowEventsLimit is ByFlowEvents capped at limit entries (0 = no cap). The same arbitrary-subset caveat as ByStatusEventsLimit applies.
func (*Server) ByStatus ¶
ByStatus returns the ids of executions currently indexed under status. The index is eventually consistent (best-effort visibility).
func (*Server) ByStatusEvents ¶ added in v0.0.2
ByStatusEvents returns a summary event for every execution currently indexed under status, read directly from the visibility index without a per-execution round-trip. The index is eventually consistent; use Get for authoritative state.
func (*Server) ByStatusEventsLimit ¶ added in v0.1.0
func (s *Server) ByStatusEventsLimit(ctx context.Context, status string, limit int) ([]Event, error)
ByStatusEventsLimit is ByStatusEvents capped at limit entries (0 = no cap). Since KV keys have no inherent order the cap yields an arbitrary subset, not an ordered page; it is a guardrail against an unbounded transfer.
func (*Server) Cancel ¶ added in v0.1.0
Cancel transitions a running or waiting execution to the terminal cancelled state with an optional reason (stored on the execution's error field). It is idempotent and stale-safe: cancelling an already-terminal execution is a no-op, and any in-flight work — pending retries, fanin joins, signal waits, or an async activity later settled via CompleteActivity — no-ops once the execution is cancelled. A cancelled execution is terminal and, unlike a failed one, cannot be resumed.
func (*Server) Close ¶
func (s *Server) Close()
Close drains any registered task workers. It does not close the NATS connection, which the caller owns.
func (*Server) CompleteActivity ¶
func (s *Server) CompleteActivity(ctx context.Context, execID, node string, attempt int, res Result) error
CompleteActivity settles an asynchronous activity using the legacy attempt-only identity. Prefer CompleteActivityWithGeneration when Request.Generation is available so stale completions from earlier node visits cannot settle a later legal cycle or resume.
func (*Server) CompleteActivityWithGeneration ¶ added in v0.1.0
func (s *Server) CompleteActivityWithGeneration( ctx context.Context, execID, node string, generation uint64, attempt int, res Result, ) error
CompleteActivityWithGeneration settles an asynchronous activity for a specific node visit generation. Use Request.Generation from the original asynchronous dispatch to prevent stale completions from earlier legal cycles or resumes settling a later visit with the same node/attempt.
func (*Server) DeadLetterCount ¶ added in v0.1.0
DeadLetterCount returns the durable number of dead-letter records retained in the dead-letter stream (bounded by its ~30-day retention). It is the operational signal that poisoned work is being dropped; a non-zero, growing count warrants investigation.
func (*Server) GCIndex ¶ added in v0.1.0
GCIndex prunes index entries whose execution has expired out of the archive. It is a no-op unless archival is enabled. The full-reconcile schedule runs it automatically; it is exported for manual or test-driven runs.
func (*Server) Get ¶
Get returns a snapshot of an execution, or ErrNotFound. The execution KV is the source of truth; read it (not the indexes) for correctness decisions.
func (*Server) Handle ¶
Handle registers a built-in nats-task worker for subject (NATS wildcards allowed, e.g. "tasks.triage.*") in this process. The namespace prefix is prepended automatically, so the worker subscribes to "<namespace>.tasks.triage.*". ctx is the worker's lifetime: every handler invocation derives its context from it, so cancelling ctx cancels in-flight handlers. The subscription itself is drained when Run returns or Close is called.
func (*Server) History ¶ added in v0.1.0
History returns an execution's ordered transition trace (oldest first, up to limit; non-positive = a generous default). It requires WithHistory — otherwise it returns nothing — and records expire after the configured retention.
func (*Server) Init ¶ added in v0.1.0
Init provisions every NATS resource the Server needs — KV buckets, streams, the flow-graph registry — and builds the engine on top of them. It is idempotent and safe for concurrent use, and a failed attempt leaves the Server unprovisioned so a later call retries. Calling it explicitly at startup gives fail-fast provisioning errors; otherwise the first operation that needs NATS calls it implicitly, bounded by that operation's ctx.
func (*Server) List ¶
List returns the execution ids in the hot bucket. With archival enabled (see WithArchive) this is the active set plus recently-completed executions, not every execution ever — archived executions are still readable via Get but are not listed here. Without archival it is every execution.
func (*Server) ListFlows ¶
ListFlows returns the names of every flow in the registry. Unlike Flows() (the flows this Server instance loaded), this reads the shared KV registry, so an observer process that loaded no flows still sees them.
func (*Server) ListFunc ¶ added in v0.1.0
ListFunc streams the hot-bucket execution ids to fn instead of materialising them into a slice, so a large active set can be rendered incrementally and a caller can stop early by returning a sentinel error (returned as-is). It covers the same set as List.
func (*Server) RecentDeadLetters ¶ added in v0.1.0
RecentDeadLetters returns up to limit of the most recent dead-letter records, oldest-first (limit <= 0 uses a sane default). Use it to inspect what poisoned work was dropped and why, without scanning logs.
func (*Server) Reconcile ¶
Reconcile rebuilds the visibility indexes from the source of truth with a full scan of every execution. It is the authoritative deep backstop; its cost grows with total execution volume, so schedule it sparingly (the scheduled reconcile already runs it periodically — see Run).
func (*Server) ReconcileActive ¶ added in v0.1.0
ReconcileActive re-asserts the indexes for only the in-flight (running or waiting) executions. It is cheap enough to run frequently and fixes the common drift where a finished execution is still indexed as active; it does not catch active executions missing from the index entirely, for which Reconcile is the backstop.
func (*Server) RedriveStalled ¶ added in v0.1.0
RedriveStalled scans the in-flight executions the visibility index knows of and re-drives any that look stranded — active, quiet past the stall threshold (WithStallRedrive; default 5× the engine ack wait), outside any scheduled retry backoff, and with no live ownership lease. It heals executions whose committed follow-on messages were never flushed (a crash between the outbox commit and its publish) without a manual Resume. The reconcile-active schedule runs it automatically after each index pass; it is exported for manual or test-driven runs. It returns how many executions were re-driven.
func (*Server) Results ¶ added in v0.1.0
Results assembles an execution's data-plane view — {"input": <start payload>, "results": {<node>: <output>, …}, "signals": {<name>: <payload>, …}} — the same context document invokers and choice rules see. Entries of an archived execution are dropped by the archive sweep, so Results of an archived id returns only what remains.
func (*Server) Resume ¶
Resume revives a failed execution, re-running the node it failed on with a fresh retry budget (the durable payload is preserved). Only failed executions can be resumed. It is durable: any running engine for the namespace picks up the resumed work.
func (*Server) Run ¶
Run starts the engine, the visibility indexer and (if configured) the reconciliation schedule, and blocks until ctx is cancelled. Registered task workers are drained on return.
func (*Server) ScheduleFlow ¶
func (s *Server) ScheduleFlow(ctx context.Context, name, flow, cronExpr string, payload json.RawMessage) error
ScheduleFlow installs a recurring schedule named name that starts flow on the given 6-field cron expression ("sec min hour dom mon dow"). Reusing name replaces the schedule.
Timing semantics (the JetStream Message Scheduler's, NATS 2.12+): the cron expression is evaluated in UTC, not the server's or the caller's local time zone. Ticks that would have fired while the NATS server was down are skipped, not replayed — after recovery the schedule resumes at its next occurrence, so downtime spanning N ticks starts zero executions for them, never N.
func (*Server) SignalWithID ¶ added in v0.1.0
func (s *Server) SignalWithID( ctx context.Context, execID, name, idempotencyKey string, payload json.RawMessage, ) error
SignalWithID sends an external signal with a caller-supplied idempotency key. Reusing the same key for the same execution/signal within the signal stream's duplicate window collapses ambiguous publish retries into one stream entry.
func (*Server) Start ¶
Start creates a new execution of flow with the given initial payload and returns its (freshly minted) id. The payload must be a JSON object (the keyed context that task/branch/signal results merge into); nil or empty defaults to {}, and a non-object is rejected with an error.
func (*Server) StartWithID ¶ added in v0.1.0
func (s *Server) StartWithID(ctx context.Context, execID, flow string, payload json.RawMessage) (string, error)
StartWithID is an idempotent Start keyed by a caller-supplied execution id (an idempotency key): the first call creates the execution, and any retry with the same id and the same arguments returns that id without creating a duplicate. Use it to make Start safe to retry — e.g. key it on a domain id so a timed-out Start does not spawn a second execution. First-write wins, and reuse is checked: a call whose flow or payload differs from what the id is already bound to returns an error instead of silently reporting the existing execution as its own — retries must replay byte-identical arguments. The id must match [A-Za-z0-9_-]{1,128}. Like Start, the payload must be a JSON object (or empty).
func (*Server) WatchEvents ¶
WatchEvents streams execution transitions as they happen. It delivers events published after the call (an ephemeral consumer with DeliverNew); load current state via Get/ByStatus first, then apply events live. The channel is closed when ctx is cancelled.
type TaskRequest ¶
type TaskRequest = protocol.TaskRequest
TaskRequest is the envelope delivered to a nats-task handler.
type TaskResponse ¶
type TaskResponse = protocol.TaskResponse
TaskResponse is the envelope a nats-task handler returns.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
packtrail-ui
command
Command packtrail-ui is a read-only observability dashboard for a packtrail deployment.
|
Command packtrail-ui is a read-only observability dashboard for a packtrail deployment. |
|
examples
|
|
|
approval
command
Command approval demonstrates human-in-the-loop control: a signal node parks the execution until an external approval arrives (with a timeout fallback), Cancel abandons an execution an operator no longer wants, and WithHistory records a durable step-by-step trace queryable with Server.History.
|
Command approval demonstrates human-in-the-loop control: a signal node parks the execution until an external approval arrives (with a timeout fallback), Cancel abandons an execution an operator no longer wants, and WithHistory records a durable step-by-step trace queryable with Server.History. |
|
async
command
Command async demonstrates long-running work off the engine's critical path.
|
Command async demonstrates long-running work off the engine's critical path. |
|
custom-invoker
command
Command custom-invoker demonstrates packtrail's defining feature: the pluggable Invoker seam.
|
Command custom-invoker demonstrates packtrail's defining feature: the pluggable Invoker seam. |
|
embedded
command
Command embedded shows packtrail running as a single microservice: the engine and the task workers live in one process, importing only the public github.com/henomis/packtrail package.
|
Command embedded shows packtrail running as a single microservice: the engine and the task workers live in one process, importing only the public github.com/henomis/packtrail package. |
|
worker
command
Command worker is a tiny external task service for the research-pipeline flow, demonstrating the pkg/protocol request/reply contract without importing the packtrail engine at all.
|
Command worker is a tiny external task service for the research-pipeline flow, demonstrating the pkg/protocol request/reply contract without importing the packtrail engine at all. |
|
internal
|
|
|
dsl
Package dsl parses and validates Packtrail Flow Definitions (YAML) and exposes graph-walk helpers used by the runtime.
|
Package dsl parses and validates Packtrail Flow Definitions (YAML) and exposes graph-walk helpers used by the runtime. |
|
names
Package names centralises every NATS resource name Packtrail uses — KV buckets, streams, subject prefixes and durable consumer names — derived from a single namespace prefix.
|
Package names centralises every NATS resource name Packtrail uses — KV buckets, streams, subject prefixes and durable consumer names — derived from a single namespace prefix. |
|
natstest
Package natstest starts an embedded, in-process nats-server with JetStream enabled for use in tests.
|
Package natstest starts an embedded, in-process nats-server with JetStream enabled for use in tests. |
|
rules
Package rules compiles and evaluates the boolean expressions used by choice nodes.
|
Package rules compiles and evaluates the boolean expressions used by choice nodes. |
|
runtime
Package runtime is the packtrail execution engine: it walks flow graphs, invokes nodes through a pluggable Invoker, and drives fanout/fanin, choice and signal nodes.
|
Package runtime is the packtrail execution engine: it walks flow graphs, invokes nodes through a pluggable Invoker, and drives fanout/fanin, choice and signal nodes. |
|
scheduler
Package scheduler wraps the NATS JetStream Message Scheduler.
|
Package scheduler wraps the NATS JetStream Message Scheduler. |
|
signal
Package signal carries external signals to waiting executions.
|
Package signal carries external signals to waiting executions. |
|
store
Package store wraps the NATS JetStream KV buckets and streams that hold all Packtrail state: executions, ownership leases, visibility indexes and the domain event stream.
|
Package store wraps the NATS JetStream KV buckets and streams that hold all Packtrail state: executions, ownership leases, visibility indexes and the domain event stream. |
|
visibility
Package visibility maintains the eventually-consistent status/flow indexes (spec §9).
|
Package visibility maintains the eventually-consistent status/flow indexes (spec §9). |
|
Package invoker defines packtrail's agnostic node-invocation contract.
|
Package invoker defines packtrail's agnostic node-invocation contract. |
|
asyncqueue
Package asyncqueue is packtrail's durable asynchronous Invoker: it makes any ordinary synchronous Invoker run off the engine's critical path with at-least-once delivery.
|
Package asyncqueue is packtrail's durable asynchronous Invoker: it makes any ordinary synchronous Invoker run off the engine's critical path with at-least-once delivery. |
|
natstask
Package natstask is packtrail's built-in Invoker: a NATS request/reply caller that speaks the pkg/protocol envelope.
|
Package natstask is packtrail's built-in Invoker: a NATS request/reply caller that speaks the pkg/protocol envelope. |
|
pkg
|
|
|
protocol
Package protocol defines the public NATS request/reply contract between the Packtrail engine and the remote services that implement workflow tasks.
|
Package protocol defines the public NATS request/reply contract between the Packtrail engine and the remote services that implement workflow tasks. |