Put it in an agent
You'll end with an agent that acts on Datagoat's answers only when it should: alone when the engine stands behind it, through a person when it doesn't, and never on a refusal. Datagoat supplies the chance, its reasons and a signed Verdict; the action and what a mistake costs are yours.
The gate, in order
- State. Act only on
answered.refusedandnot_yettake your fallback path; never retry a refusal (The answer state). - Band. Ask with
band: true. Each case carriesband,band_reasonandmax_autonomy(The band). - Your threshold. Within what the band allows, compare
pwith a bar set by what a wrong action costs. Cheap and reversible: a lower bar. Expensive or permanent: a higher bar, or always a person. - A person before the side effect. Pause, show the case,
p, the reasons with theirrange.textand the Verdict'sverdict_id; act only on approval. - Act once. Give the action an idempotency key built from the Verdict (
verdict_idplus the action's name), so a retry after the pause cannot act twice. - Record it. Acting through a lever:
dg_attest. When you learn the outcome:dg_report_outcomes(Prove it).
state |
band |
max_autonomy |
The agent may |
|---|---|---|---|
refused or not_yet |
none | none | nothing from this answer; fallback |
answered |
refuse |
L0 |
nothing for this case |
answered |
escalate |
L1 |
draft a suggestion for a person |
answered |
act |
L2 |
act after a person approves |
answered |
act |
L3 |
act, then queue it for review |
People (subject_kind: "person") are decision support: a person reviews every decision, whatever
the band says, and the ask needs acknowledge_decision_support: true.
In code
out = dg.ask({"churn": yesno("churned", outcome_is_desirable=False)}, band=True,
dataset_id="sample:telco_churn", entity_column="account_id", subject_kind="org",
cases={"ids": ["acct_0001"]})
a = out["answers"]["churn"]
if a["state"] != "answered":
fallback()
else:
case = a["cases"][0]
key = f'{a["verdicts"][0]["verdict"]["verdict_id"]}:retention_call'
if case["band"] == "act" and case["max_autonomy"] == "L3":
book_call(case, idempotency_key=key); queue_review(case)
elif case["band"] == "act" and case["max_autonomy"] == "L2":
if ask_person(case) == "approve": book_call(case, idempotency_key=key)
elif case["band"] == "escalate":
suggest_to_person(case)
In AgentFactory
An HTTP request step (nango.http@5, the datagoat connection) posts to /v1/ask with
"band": true. A router reads steps.<ask>.output.body.answers.<id>.state, then
…cases[0].band. act with L2 goes through a human approval step before the step that acts;
escalate posts the case and its reasons to a channel; everything else ends quietly.
In LangGraph and n8n
- LangGraph: call
interrupt()with the case,pand the reasons before the node that acts; resume with the person's decision. - n8n: HTTP Request → IF on
state→ Switch onband→ a Wait node that resumes on a webhook → the node that acts, carrying the idempotency key.
The agent skill for this page: datagoat-gate.
Next
Keep the model honest over time: Run it.