Skip to contents

dagriculture is a pure, value-oriented graph library. It is designed to manage graph structures, topological dependencies, structural validity, and task planning. It does not natively orchestrate runtime execution or manage side effects; rather, it tracks structural states (new, ready, blocked) and enforces declarative dependency rules.

Core Concepts

  • Registry: Defines the allowable kinds of nodes in a graph.
  • Graph: The main immutable data structure carrying nodes, edges, and gates. Operations return a new graph and bump the internal $version.
  • Nodes & Edges: Represent units of computation (or data) and their dependencies.
  • Gates: Explicit structural blockers attached to edges, which prevent downstream nodes from becoming ready until the gate is resolved.
  • Planning: Topologically sorting and tracking the structural readiness of nodes.

Installation

pak::pak("sims1253/dagriculture")

Basic Workflow

1. Define a Registry

First, establish a registry to strictly define what kind of nodes are allowed in your graphs.

library(dagriculture)

reg <- dagri_registry(
  dagri_kind("input", output_type = "data.frame"),
  dagri_kind("model"),
  dagri_kind("report")
)

2. Build the Graph

Start with an empty graph, and add nodes and edges. Because dagriculture uses value semantics, every mutating function returns a new updated graph instance.

g <- dagri_graph(reg)

g <- g |>
  dagri_add_node(id = "data", kind = "input", label = "Raw Data") |>
  dagri_add_node(id = "fit", kind = "model", label = "Statistical Fit") |>
  dagri_add_node(id = "plot", kind = "report", label = "Final Plot") |>
  dagri_add_edge(from = "data", to = "fit", id = "e_data_fit") |>
  dagri_add_edge(from = "fit", to = "plot", id = "e_fit_plot")

# The internal version tracks changes
g$version
#> [1] 5

3. Add a Gate

You can place a “gate” on an edge to represent a manual or external block—for instance, requiring human approval before the statistical fit proceeds.

g <- dagri_add_gate(g, edge = "e_data_fit", id = "approval_gate")

4. Compute State and Plan

Use dagri_recompute_state() to evaluate the graph topologically and determine which nodes are structurally ready and which are blocked.

g_state <- dagri_recompute_state(g)

# The 'data' node is a root and has no blockers
dagri_eligible(g_state)
#> [1] "data"

# The 'fit' node is blocked by the gate; the 'plot' node is blocked because 'fit' is blocked.
dagri_blocked(g_state)
#> $fit
#> [1] "gate"
#> 
#> $plot
#> [1] "upstream_blocked"

We can generate a structural plan to see how dependencies shake out:

plan <- dagri_plan(g_state)
plan$targets
#> [1] "data" "fit"  "plot"
plan$blocked
#> $fit
#> [1] "gate"
#> 
#> $plot
#> [1] "upstream_blocked"
plan$external_blocked
#> named list()
plan$pending_gates
#> [1] "approval_gate"

Planner-visible external holds can be layered onto planning without mutating the graph:

dagri_plan(
  g_state,
  external_holds = list(fit = "manual_review")
)$external_blocked
#> $fit
#> [1] "manual_review"
#> 
#> $plot
#> [1] "manual_review"

5. Resolve the Gate

Once the external condition is met, you can resolve the gate. Computing the state again reveals that the downstream dependencies are now unblocked.

g_unblocked <- dagri_resolve_gate(g_state, id = "approval_gate") |>
  dagri_recompute_state()

# Everything is unblocked and ready for execution
dagri_eligible(g_unblocked)
#> [1] "data" "fit"  "plot"
dagri_blocked(g_unblocked)
#> named list()

Querying Topology

dagriculture includes standard helpers for topological traversal and queries.

dagri_upstream(g_unblocked, "plot")
#> [1] "fit"
dagri_descendants(g_unblocked, "data")
#> [1] "fit"  "plot"
dagri_leaves(g_unblocked)
#> [1] "plot"
dagri_topo_order(g_unblocked)
#> [1] "data" "fit"  "plot"