fix(logging): record structured abort errors in turn logs (#572)

Co-authored-by: Letta Code <noreply@letta.com>
This commit is contained in:
Cameron
2026-03-11 23:45:27 -07:00
committed by GitHub
parent 6ecd78e294
commit a6b1a43ec5
2 changed files with 16 additions and 1 deletions

View File

@@ -1222,6 +1222,7 @@ export class LettaBot implements AgentSession {
const maxRepeatedBashFailures = 3;
let lastEventType: string | null = null;
let abortedWithMessage = false;
let turnError: string | undefined;
const parseAndHandleDirectives = async () => {
if (!response.trim()) return;
@@ -1346,6 +1347,7 @@ export class LettaBot implements AgentSession {
log.error(`Agent stuck in tool loop (${msgTypeCounts['tool_call']} calls), aborting`);
session?.abort().catch(() => {});
response = '(Agent got stuck in a tool loop and was stopped. Try sending your message again.)';
turnError = `tool loop abort after ${msgTypeCounts['tool_call'] || 0} tool calls`;
abortedWithMessage = true;
break;
}
@@ -1402,6 +1404,7 @@ export class LettaBot implements AgentSession {
log.error(`Stopping run after repeated Bash command failures (${repeatedBashFailureCount}) for: ${bashCommand}`);
session?.abort().catch(() => {});
response = `(I stopped after repeated CLI command failures while running: ${bashCommand}. The command path appears mismatched. Please confirm Bluesky CLI commands are available, then resend your request.)`;
turnError = `repeated Bash failure abort (${repeatedBashFailureCount}x): ${bashCommand}`;
abortedWithMessage = true;
break;
}
@@ -1652,7 +1655,7 @@ export class LettaBot implements AgentSession {
events,
output: output || response,
durationMs: Math.round(performance.now() - t0),
error: lastErrorDetail?.message,
error: turnError ?? lastErrorDetail?.message,
}).catch(() => {});
}
}

View File

@@ -111,6 +111,8 @@ describe('result divergence guard', () => {
allowedTools: [],
maxToolCalls: 100,
});
const writeTurn = vi.fn(async () => {});
(bot as any).turnLogger = { write: writeTurn };
const abort = vi.fn(async () => {});
const adapter = {
@@ -152,6 +154,10 @@ describe('result divergence guard', () => {
expect(abort).toHaveBeenCalled();
const sentTexts = adapter.sendMessage.mock.calls.map(([payload]) => payload.text as string);
expect(sentTexts.some(text => text.includes('repeated CLI command failures'))).toBe(true);
expect(writeTurn).toHaveBeenCalledTimes(1);
expect(writeTurn).toHaveBeenCalledWith(expect.objectContaining({
error: 'repeated Bash failure abort (3x): lettabot bluesky post --text "hi" --agent Bot',
}));
});
it('stops consuming stream and avoids retry after explicit tool-loop abort', async () => {
@@ -160,6 +166,8 @@ describe('result divergence guard', () => {
allowedTools: [],
maxToolCalls: 1,
});
const writeTurn = vi.fn(async () => {});
(bot as any).turnLogger = { write: writeTurn };
const adapter = {
id: 'mock',
@@ -207,6 +215,10 @@ describe('result divergence guard', () => {
expect(runSession).toHaveBeenCalledTimes(1);
const sentTexts = adapter.sendMessage.mock.calls.map(([payload]) => payload.text);
expect(sentTexts).toEqual(['(Agent got stuck in a tool loop and was stopped. Try sending your message again.)']);
expect(writeTurn).toHaveBeenCalledTimes(1);
expect(writeTurn).toHaveBeenCalledWith(expect.objectContaining({
error: 'tool loop abort after 1 tool calls',
}));
});
it('does not deliver reasoning text from error results as the response', async () => {