Problem
needs_human is a normal, resumable agent outcome: the agent has reached a decision that requires human authority. Today its state transition is coupled to hitl.enabled.
For example, the fixer only accepts needs_human as HITL control flow when hitl.enabled is true. With HITL disabled, the same structured outcome falls back to manual_intervention and the loop is paused as a failure-like condition. This conflates two different semantics:
- expected control flow: the agent intentionally requests a human decision;
- operational failure: Looper, a tool, or an agent contract cannot continue correctly.
It also makes transport configuration an authority over the meaning of the agent's structured result. hitl.enabled currently controls more than delivery: prompts, state transitions, notifications, GitHub/Feishu transport activity, and some git lifecycle behavior are tied together.
Authority
A valid structured needs_human result from the active agent execution is the authority for entering awaiting_human. Transport configuration is not authority for reclassifying that result as a failure.
Desired behavior
State semantics and automated external side effects should be independent:
| Agent outcome |
hitl.enabled |
Required result |
valid needs_human |
false |
Persist the ask and atomically park the loop as awaiting_human; perform no automated external HITL side effects |
valid needs_human |
true |
Persist and park as awaiting_human, then run the configured notification/answer transport |
| Looper/tool/protocol failure |
either |
Follow the normal failure or operational-hold path; never masquerade as awaiting_human |
When hitl.enabled is false, “no automated external HITL side effects” means:
- no GitHub WIP push or draft PR created for an ask;
- no GitHub ask comment or awaiting-human label;
- no Feishu decision card or inbox polling;
- no automated HITL notification/outreach;
- no automatic answer polling or transport-driven requeue.
The required local/durable state changes still occur: persist the question and resume metadata, cancel the active queue item, interrupt the current run, clear next_run_at, and expose awaiting_human through the control plane.
An explicit operator response through POST /api/v1/loops/{seq}/respond should remain available regardless of hitl.enabled. It should store the answer, requeue the loop, and resume the prior agent session when compatible.
Scope
- Apply the same state contract to the existing fixer and worker human-ask paths.
- Make the agent contract capable of producing a valid human ask independently of transport enablement.
- Keep outbound notification and answer transports gated by
hitl.enabled.
- Preserve the existing
awaiting_human ask/answer metadata and resume flow.
- Document that a disabled transport can leave a loop silently parked until an operator discovers it through the control plane and responds explicitly.
Non-goals
- Do not add another loop or queue status.
- Do not redesign every existing use of
manual_intervention in this issue.
- Do not add a new transport, inference layer, gate, or persisted authority field.
- Do not globally freeze every other loop associated with the same PR.
- Do not change the policy for deciding whether reviewer feedback is in scope; this issue only defines what happens after an agent returns
needs_human.
Acceptance criteria
Trade-offs
A loop can become silently parked when transport side effects are disabled. That is intentional: awaiting_human remains visible in durable state and the control plane, while deployments that want proactive delivery can enable HITL transports. This is preferable to silently changing a valid human-decision request into an unrelated failure classification.
The existing hitl.enabled name becomes broader than its remaining responsibility. Keep it for compatibility in this change and document it as the gate for automated HITL outreach/transports rather than adding another configuration concept.
Problem
needs_humanis a normal, resumable agent outcome: the agent has reached a decision that requires human authority. Today its state transition is coupled tohitl.enabled.For example, the fixer only accepts
needs_humanas HITL control flow whenhitl.enabledis true. With HITL disabled, the same structured outcome falls back tomanual_interventionand the loop is paused as a failure-like condition. This conflates two different semantics:It also makes transport configuration an authority over the meaning of the agent's structured result.
hitl.enabledcurrently controls more than delivery: prompts, state transitions, notifications, GitHub/Feishu transport activity, and some git lifecycle behavior are tied together.Authority
A valid structured
needs_humanresult from the active agent execution is the authority for enteringawaiting_human. Transport configuration is not authority for reclassifying that result as a failure.Desired behavior
State semantics and automated external side effects should be independent:
hitl.enabledneeds_humanfalseawaiting_human; perform no automated external HITL side effectsneeds_humantrueawaiting_human, then run the configured notification/answer transportawaiting_humanWhen
hitl.enabledis false, “no automated external HITL side effects” means:The required local/durable state changes still occur: persist the question and resume metadata, cancel the active queue item, interrupt the current run, clear
next_run_at, and exposeawaiting_humanthrough the control plane.An explicit operator response through
POST /api/v1/loops/{seq}/respondshould remain available regardless ofhitl.enabled. It should store the answer, requeue the loop, and resume the prior agent session when compatible.Scope
hitl.enabled.awaiting_humanask/answer metadata and resume flow.Non-goals
manual_interventionin this issue.needs_human.Acceptance criteria
hitl.enabled=false, a valid fixerneeds_humanresult ends in loop statusawaiting_human, not queue status/failure kindmanual_intervention.hitl.enabled=false, a valid worker ask sentinel follows the sameawaiting_humancontract./respondcan answer and resume anawaiting_humanloop while automated HITL side effects are disabled.hitl.enabled=true, existing configured transport delivery and polling behavior continues to work.awaiting_humanmerely because an operator may need to inspect them.Trade-offs
A loop can become silently parked when transport side effects are disabled. That is intentional:
awaiting_humanremains visible in durable state and the control plane, while deployments that want proactive delivery can enable HITL transports. This is preferable to silently changing a valid human-decision request into an unrelated failure classification.The existing
hitl.enabledname becomes broader than its remaining responsibility. Keep it for compatibility in this change and document it as the gate for automated HITL outreach/transports rather than adding another configuration concept.