Debugging empty-output sessions
Symptom: session status: completed but the chat-tool wrapper got back
nothing useful — empty summary, empty artifactIds, or the sentinel
"Session completed without producing a summarizable document."
Diagnostic checklist
Walk in order. Each step has a concrete fix.
1. Read aiSummary.summary from describe_session
The daemon synthesizes a useful diagnosis post-completion. The SSE
job-complete may have fired before aiSummary was ready, so the
chat-tool wrapper's summary field can be empty even when
describe_session has the right answer.
describe_session(sessionId) → check session.aiSummary.summary
If aiSummary.summary reads "completed without producing a
summarizable document" → continue to step 2 (the agent didn't emit).
If aiSummary.summary describes what actually happened → surface
that to the user. The job worked but the wrapper dropped the summary.
Phase 2 of the system-skills remodel made buildSessionJobResult
never return empty; if you're still seeing empty, you're on stale code.
2. Inspect agentBlocks[].toolCalls for complete
describe_session(sessionId) → session.agentBlocks[<last>].toolCalls
For an action with outputTo: response (or any outputTo), the
runtime auto-injects a complete tool. The agent MUST call it.
- If
completeIS in toolCalls → checkoutput: { response: "..." }for empty content. The agent called complete with nothing. Fix the prompt so the LLM puts the actual final text in thecompletearg. - If
completeis NOT in toolCalls → the agent never called the contracted exit. Fix: update the agent's prompt to explicitly instruct "When done, call thecompletetool with{response: '<the full final text>'}. This is how the FSM captures your output."
See @friday/agent-action-handshake for the full contract.
3. Check the agent prompt's instructions
The most common pattern that causes silent emptiness:
"output ONLY the finished markdown report below as your final
message. Do not add any preamble..."
This prompt tells the LLM to stream prose. With outputTo set, the
runtime expects the prose to come via complete(...) — not as a final
assistant message. Replace with:
"When you have the report, call the `complete` tool with
`{response: '<the full markdown report>'}`. Do not stream the report
as text — the runtime captures it from `complete`."
4. Confirm via test invocation
After updating the prompt, fire the job again. The new session should
show complete in toolCalls and a populated summary/artifactIds.
If still empty → recurse to step 2 with the new session's data, OR
load @friday/debugging-tool-loops (the agent might be stuck calling
search/fetch tools instead of finishing).
What NOT to do
- Do NOT migrate the job from Pattern A (
execution.strategy: sequential) to Pattern B (fsm:). Pattern A returns the final agent's text via the session natively. Migrating to Pattern B introduces the verycomplete-injection contract that you're fighting. - Do NOT add
create_artifactto the agent'stools:list as a workaround. Platform tools (includingcreate_artifact) auto-inject regardless oftools:. The agent already has it. The problem is the agent's prompt, not its tool surface. - Do NOT declare "platform bug" or "FSM wiring is broken." If you reach for that, you skipped step 2.
Cross-references
@friday/agent-action-handshake— theoutputTo+completeinjection contract.@friday/debugging-broken-jobs— the triage entry point.@friday/debugging-tool-loops— sibling skill for agent-stuck-in-tools.@friday/writing-workspace-jobs— Pattern A vs B; whatoutputTodoes.