Skip to content

6.12. HITL Client

The HITL Client talks to the sw4rm.hitl.HitlService service. Use it to:

  • Escalate decisions to human operators when policy or risk requires it.
  • Provide structured context and candidate actions for review.
  • Capture the human decision payload and rationale.

6.12.1. Service Overview

The service exposes one RPC:

  • Decide(HitlInvocation) -> HitlDecision

Invocations are often generated by the Scheduler and carried in an Envelope payload. The HITL service provides the decision side of that workflow.

HitlReasonType values

Value Description
HITL_REASON_UNSPECIFIED Default/unknown reason
CONFLICT Agent or policy conflict
SECURITY_APPROVAL Security-sensitive approval required
TASK_ESCALATION Task needs human escalation
MANUAL_OVERRIDE Manual override requested
WORKTREE_OVERRIDE Worktree switch requires approval
DEBATE_DEADLOCK Debate or negotiation is deadlocked
TOOL_PRIVILEGE_ESCALATION Tool privilege escalation
CONNECTOR_APPROVAL Connector action requires approval

HitlInvocation fields

Field Type Description
reason_type HitlReasonType Escalation reason
context bytes Serialized context (often JSON)
proposed_actions list[string] Candidate actions for the operator
priority int Priority hint for operator queues

HitlDecision fields

Field Type Description
action string Selected action
decision_payload bytes Optional serialized payload
rationale string Human rationale or notes

6.12.2. Constructors

Python

HitlClient(channel: grpc.Channel)

  • channel: A gRPC channel connected to the HitlService endpoint.

JavaScript/TypeScript

new HITLClient(options: ClientOptions)

  • options.address: host:port for the HitlService endpoint.
  • Optional: deadlineMs, retry, userAgent, interceptors, errorMapper.

Rust

HitlClient::new(endpoint: &str) -> Result<HitlClient>

  • endpoint: Full gRPC URL (for example, http://host:port).

6.12.3. Key Methods

decide

Python decide(invocation: dict) -> HitlDecision

JavaScript/TypeScript decide(inv: HitlInvocation): Promise<HitlDecision>

Rust decide(&mut self, reason_type: i32, context: Vec<u8>, proposed_actions: Vec<String>, priority: i32) -> Result<HitlDecisionResponse>

Response fields

  • action (string): Selected action.
  • decision_payload (bytes): Optional decision payload.
  • rationale (string): Human rationale or notes.

6.12.4. Usage Examples

import grpc
import json
from sw4rm.clients import HitlClient

channel = grpc.insecure_channel("HITL_HOST:HITL_PORT")
client = HitlClient(channel)

invocation = {
    "reason_type": "SECURITY_APPROVAL",
    "context": json.dumps({
        "operation": "deploy",
        "environment": "prod",
        "change_id": "chg-42",
    }).encode("utf-8"),
    "proposed_actions": ["approve", "reject"],
    "priority": 5,
}

decision = client.decide(invocation)
print(decision.action, decision.rationale)
import { HITLClient } from '@sw4rm/js-sdk';

const client = new HITLClient({
  address: 'HITL_HOST:HITL_PORT',
  deadlineMs: 20000,
});

const encoder = new TextEncoder();
const decision = await client.decide({
  reason_type: 'SECURITY_APPROVAL',
  context: encoder.encode(JSON.stringify({
    operation: 'deploy',
    environment: 'prod',
    change_id: 'chg-42',
  })),
  proposed_actions: ['approve', 'reject'],
  priority: 5,
});

console.log(decision.action, decision.rationale);
use sw4rm_sdk::clients::HitlClient;
use sw4rm_sdk::proto::sw4rm::common::HitlReasonType;

#[tokio::main]
async fn main() -> sw4rm_sdk::Result<()> {
    let mut client = HitlClient::new("http://HITL_HOST:HITL_PORT").await?;

    let context = serde_json::json!({
        "operation": "deploy",
        "environment": "prod",
        "change_id": "chg-42",
    });

    let decision = client
        .decide(
            HitlReasonType::SecurityApproval as i32,
            serde_json::to_vec(&context)?,
            vec!["approve".to_string(), "reject".to_string()],
            5,
        )
        .await?;

    println!("action={} rationale={}", decision.action, decision.rationale);
    Ok(())
}

6.12.5. HITL Absence Policy

Deployments without a HITL component MUST define a policy for how to proceed when human decisions are required. Per spec §15.3:

  • Deny-by-default (RECOMMENDED for security-sensitive deployments): Reject the operation that triggered the escalation.

  • Threshold-based auto-decision: Apply automatic decisions based on configurable score thresholds when confidence is sufficient.

The Scheduler MUST document and log which fallback was applied. Components MUST NOT block indefinitely waiting for human input when no HITL component is available.

6.12.6. HITL Unavailability During Negotiation Timeout

When a negotiation timeout fires and requires HITL escalation (e.g., DEBATE_DEADLOCK), but the HITL component is unavailable, the Scheduler MUST handle the situation per spec §15.4:

  1. Detect HITL unavailability: The Scheduler MUST detect unavailability within a bounded time (RECOMMENDED: 5 seconds) through health checks, connection failures, or response timeouts.

  2. Apply fallback policy: The Scheduler MUST apply the configured hitl_unavailable_policy. Valid policy values:

Policy Behavior
DENY_BY_DEFAULT Abort the negotiation with error_code=hitl_unavailable. RECOMMENDED default for security-sensitive deployments.
AUTO_DECIDE_THRESHOLD If the highest-scoring proposal exceeds a configured auto-approve threshold, accept it automatically; otherwise abort.
EXTEND_TIMEOUT Extend the negotiation timeout by a configured duration (RECOMMENDED: 1x original) and retry HITL escalation. Maximum retry count RECOMMENDED: 3.
  1. Log and notify: The Scheduler MUST log the HITL unavailability event with negotiation context and the fallback action taken. Implementations SHOULD emit an alert to operators.

  2. Preserve audit trail: The decision record MUST indicate that the decision was made via fallback policy due to HITL unavailability, including the policy applied and timestamp.

Working Examples

For complete runnable examples demonstrating HITL usage:

6.12.7. Error Handling

  • Python raises RuntimeError if protobuf stubs are missing. Run make protos.
  • JavaScript/TypeScript methods reject with gRPC errors (wrapped as Sw4rmError).
  • Rust returns Result<T>; handle transport errors and status codes via ?.