Skip to content

R4c (governance): implement DEFER decision (resumable executor) #211

Description

@naveen-kurra

Requirement (governance framework, MUST)

The policy engine MUST be capable of producing one of five decisions: ALLOW, DENY, MODIFY, STEP_UP, or DEFER.

Focus of this issue: DEFER.

Current state

Not implemented. Guardrail hits are terminal — a violation errors out the current execution:

forge-core/runtime/skill_guardrails.go:160
  return "", fmt.Errorf("tool output blocked by skill guardrail")

There is no primitive to pause the executor, hand off to a human or external decision-maker, and resume when a decision arrives.

Gap

R4-DEFER expects the engine to be able to say "I cannot decide this — pause and await a human/other authority". The executor loop (forge-core/runtime/loop.go:LLMExecutor.Execute) is a stateful step-machine but not resumable across process restarts, and the A2A tasks state model doesn't have a deferred state.

Proposed implementation

Architectural — the biggest gap in the R4 set.

  1. Add TaskStateDeferred to the A2A task-status enum.
  2. Add DecisionDefer to PolicyDecision (see R4a (governance): generalize MODIFY decision beyond cli_execute output #209) with fields:
    • DeferTo string — target (e.g., "human:oncall", "channel:slack:#approvals")
    • Timeout time.Duration — how long to wait before auto-DENY
    • ContextForApprover string — the decision context serialized for the approver
  3. Executor loop learns to:
    • Persist current step state to MemoryStore (already stores conversation; extend to store the pending tool call + args)
    • Emit task_deferred audit event
    • Poll (or subscribe) for an approve/reject signal via new endpoint POST /tasks/{id}/decisions
    • Resume execution when a decision arrives OR auto-DENY on timeout
  4. Delivery: reuse existing channel adapters (Slack/Telegram/Teams) to notify approvers with the deferred-context payload; approver replies via emoji or a signed URL that hits POST /tasks/{id}/decisions.

Effort

~2 weeks (executor resumability is the hard part).

Acceptance criteria

  • TaskStateDeferred recognized in A2A responses
  • DecisionDefer returnable from any guardrail hook
  • Executor persists step state on Defer; ctx returns without terminating the task
  • POST /tasks/{id}/decisions accepts {decision, approver, note} and resumes the executor
  • Timeout auto-DENYs and audits
  • Slack + Telegram notify handlers wired to receive deferred-context payloads
  • Tests: pause → external approve → resume; pause → timeout → deny
  • Docs: docs/security/defer-decisions.md

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestforge-cliAffects the forge-cli command-line tool (init, run, build, mcp commands)forge-coreAffects the forge-core library (runtime, security, types, llm, mcp, auth)

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions