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

  1. State. Act only on answered. refused and not_yet take your fallback path; never retry a refusal (The answer state).
  2. Band. Ask with band: true. Each case carries band, band_reason and max_autonomy (The band).
  3. Your threshold. Within what the band allows, compare p with 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.
  4. A person before the side effect. Pause, show the case, p, the reasons with their range.text and the Verdict's verdict_id; act only on approval.
  5. Act once. Give the action an idempotency key built from the Verdict (verdict_id plus the action's name), so a retry after the pause cannot act twice.
  6. 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, p and the reasons before the node that acts; resume with the person's decision.
  • n8n: HTTP Request → IF on state → Switch on band → 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.