Problem — a failed LLM call leaves no audit trace
Field report 2026-07-22: an agent whose tool schema the provider rejected failed every task with a gateway 400 — but the audit stream showed nothing. EmitLLMCall is wired only to the AfterLLMCall success hook (forge-cli/runtime/runner.go); the error path just logs "agent loop error" to the pod (runner.go:2461). Result: the Security Events screen showed healthy llm_call rows from before the break and then silence — the one signal that explains "agent up, all tasks failing" lived only in kubectl logs.
Change
Emit an llm_call_failed audit event from the LLM error path (naming per the <entity>_<verb> convention, joining llm_call / llm_call_cancelled):
model, provider, duration_ms as on llm_call
fields.error: bounded (e.g. first 512 bytes) of the provider/gateway error — enough to carry "Property keys should match pattern …" into the audit stream
fields.status: upstream HTTP status when known
- outcome:
error
Cross-repo registration (contract)
- security-next: add
llm_call_failed to models/audit_event_model.go::AuditEventTypes
- console-next: typed label + row summary (render the bounded error like other failure rows)
Paired PRs per the workspace contract rule.
Problem — a failed LLM call leaves no audit trace
Field report 2026-07-22: an agent whose tool schema the provider rejected failed every task with a gateway 400 — but the audit stream showed nothing.
EmitLLMCallis wired only to theAfterLLMCallsuccess hook (forge-cli/runtime/runner.go); the error path just logs"agent loop error"to the pod (runner.go:2461). Result: the Security Events screen showed healthyllm_callrows from before the break and then silence — the one signal that explains "agent up, all tasks failing" lived only inkubectl logs.Change
Emit an
llm_call_failedaudit event from the LLM error path (naming per the<entity>_<verb>convention, joiningllm_call/llm_call_cancelled):model,provider,duration_msas onllm_callfields.error: bounded (e.g. first 512 bytes) of the provider/gateway error — enough to carry "Property keys should match pattern …" into the audit streamfields.status: upstream HTTP status when knownerrorCross-repo registration (contract)
llm_call_failedtomodels/audit_event_model.go::AuditEventTypesPaired PRs per the workspace contract rule.