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
kindsof 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
readyuntil 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] 53. 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"