Sparsi workflows provide built-in mechanisms for self-healing. Some
deterministic ops have a fixable failure mode — a JSON blob that's almost
right, or a schema violation a language model could plausibly correct.
Rather than hand-coding a retry path, Sparsi provides a repair wrapper
(library.WithRepair) that uses a bounded LLM pass to recover
deterministic logic.
Per Run, the wrapper executes up to max_attempts repair cycles
(default 3). Non-repairable errors propagate unchanged — the LLM is only
invoked when your inner op deliberately escalates.
Run is called normally.nil, the wrapper returns nil. Done.*library.ErrRepairable, the wrapper forwards Prompt (sandwiched by configured PromptPrefix / PromptSuffix) to the LLM.UnmarshalRepair on the LLM's response.SetInputField and re-runs the inner op.max_attempts.
Repair retries re-execute Run, so your inner op must be idempotent or pure.
Repair traces are captured by library.WithReasoningLog when enabled.
*ErrRepairable; implement UnmarshalRepairTwo interfaces, both on pointer receivers. The inner op decides what's repairable; the input type decides how to deserialise the LLM's response.
type TicketRaw struct{ Text string }
// RepairableInput — invoked with the LLM's raw response. Convert it into
// the op's input value. JSON/XML/regex/strconv — your call.
func (t *TicketRaw) UnmarshalRepair(response string) error {
t.Text = stripCodeFences(response)
return nil
}
type ParseTicketOp struct {
Raw *TicketRaw `dag:"input"`
Result TicketInput `dag:"output"`
}
func (op *ParseTicketOp) Run(_ context.Context) error {
var parsed TicketInput
if err := json.Unmarshal([]byte(op.Raw.Text), &parsed); err != nil {
return &library.ErrRepairable{
Prompt: "Correct this JSON to match the schema below:\n…",
Cause: err,
}
}
op.Result = parsed
return nil
}
init(); wire identicallyFrom the graph builder's perspective, a repair-wrapped vertex looks exactly like the inner op — same input / output field names. The repair config is set at registration time and can be overridden per-vertex via params.
library.RegisterWithRepair(
"ParseTicketRepair",
func() *ParseTicketOp { return &ParseTicketOp{} },
library.RepairConfig{
InputField: "Raw",
MaxAttempts: 3,
PromptPrefix: "You are a strict JSON corrector. Output corrected JSON only.\n\n",
},
)
Vertex params: max_attempts, provider (default
claude), model (default claude-sonnet-4-6),
max_tokens (default 2048), and the same credential routing
params (credential_ref, client_factory_id) as ordinary AI ops.
WithRepair is not a replacement for input validation — it's a recovery path for the cases you've decided are worth a small LLM round-trip to fix.
A two-stage repair workflow: a JSON parser fixes malformed input; a routing validator corrects business-rule violations by round-tripping through XML. Both wire-format paths exercised in one graph.
Enable WithReasoningLog(ctx) in your driver to capture every repair
decision — attempt number, prompt, model response, success/failure — alongside the
normal slog timing and token output.