From 63593aa7bfa4d7c35c968a157c5d42cfba39b5fe Mon Sep 17 00:00:00 2001 From: "[._.]/ Adam Eivy" Date: Tue, 28 Jul 2026 08:01:24 -0700 Subject: [PATCH] feat(cos): capture detailed agent feedback --- .changelog/NEXT.md | 2 + client/src/components/cos/tabs/AgentCard.jsx | 74 ++++++++++++------- .../components/cos/tabs/AgentCard.test.jsx | 58 +++++++++++++++ docs/features/chief-of-staff.md | 2 +- 4 files changed, 110 insertions(+), 26 deletions(-) diff --git a/.changelog/NEXT.md b/.changelog/NEXT.md index 8542e11af1..f25104a4ff 100644 --- a/.changelog/NEXT.md +++ b/.changelog/NEXT.md @@ -10,4 +10,6 @@ ## Changed +- **Chief of Staff feedback now captures why an agent helped or missed the mark.** Add detail to positive, negative, or neutral ratings so future CoS learning has the context needed to improve. + - **Branch reconciler prioritizes recognized work branches.** The in-flight set handed to the coordinator agent is now ordered by branch prefix — `claim/`, `cos/`, `next/`, `feature/`, `fix/`, and other conventional prefixes are reconciled ahead of unrecognized/ad-hoc branches — so a bounded run spends its budget on real deliverables first. Both `/` and `-` separators match. diff --git a/client/src/components/cos/tabs/AgentCard.jsx b/client/src/components/cos/tabs/AgentCard.jsx index 9e642c0cdf..8a4598c2b3 100644 --- a/client/src/components/cos/tabs/AgentCard.jsx +++ b/client/src/components/cos/tabs/AgentCard.jsx @@ -137,7 +137,7 @@ export default function AgentCard({ agent, onPause, onKill, onDelete, onResume, }, [agent.btwMessages]); const [submittingFeedback, setSubmittingFeedback] = useState(false); const [showFeedbackComment, setShowFeedbackComment] = useState(false); - const [feedbackComment, setFeedbackComment] = useState(''); + const [feedbackComment, setFeedbackComment] = useState(agent.feedback?.comment || ''); // Determine if this is a system agent (health check, etc.) const isSystemAgent = agent.taskId?.startsWith('sys-') || agent.id?.startsWith('sys-'); @@ -161,7 +161,7 @@ export default function AgentCard({ agent, onPause, onKill, onDelete, onResume, if (result?.success) { setFeedbackState(rating); setShowFeedbackComment(false); - setFeedbackComment(''); + setFeedbackComment(result.agent?.feedback?.comment || ''); toast.success(`Feedback recorded: ${rating}`); onFeedbackChange?.(result.agent); } @@ -840,42 +840,66 @@ export default function AgentCard({ agent, onPause, onKill, onDelete, onResume, > - {!feedbackState && ( - - )} + {feedbackState && ( - - {feedbackState === 'positive' ? 'Thanks for the feedback!' : 'We\'ll improve'} + + {feedbackState === 'positive' ? 'Thanks for the feedback!' : feedbackState === 'negative' ? 'We\'ll improve' : 'Feedback recorded'} )} {/* Comment input */} - {showFeedbackComment && !feedbackState && ( -
+ {showFeedbackComment && ( +
setFeedbackComment(e.target.value)} - placeholder="Optional: add a comment..." + placeholder="What made this work well or poorly?" className="flex-1 px-2 py-1 text-sm bg-port-bg border border-port-border rounded text-white placeholder-gray-500 focus:outline-hidden focus:border-port-accent min-h-[32px]" maxLength={200} /> - + {feedbackState ? ( + + ) : ( +
+ + + +
+ )}
)}
diff --git a/client/src/components/cos/tabs/AgentCard.test.jsx b/client/src/components/cos/tabs/AgentCard.test.jsx index 0223f2b715..7b1d164ca9 100644 --- a/client/src/components/cos/tabs/AgentCard.test.jsx +++ b/client/src/components/cos/tabs/AgentCard.test.jsx @@ -55,4 +55,62 @@ describe('AgentCard feedback', () => { ); await waitFor(() => expect(onFeedbackChange).toHaveBeenCalledWith(updatedAgent)); }); + + it('records a negative rating with the detail needed to improve future work', async () => { + const user = userEvent.setup(); + const updatedAgent = { + ...agent, + feedback: { + rating: 'negative', + comment: 'The implementation did not include tests.', + submittedAt: '2026-07-13T12:00:00.000Z' + } + }; + api.submitCosAgentFeedback.mockResolvedValue({ success: true, agent: updatedAgent }); + + render( + + + + ); + + await user.click(screen.getByRole('button', { name: 'Add feedback comment' })); + await user.type(screen.getByPlaceholderText('What made this work well or poorly?'), updatedAgent.feedback.comment); + await user.click(screen.getByRole('button', { name: 'Needs work' })); + + expect(api.submitCosAgentFeedback).toHaveBeenCalledWith( + agent.id, + { rating: 'negative', comment: updatedAgent.feedback.comment }, + { silent: true } + ); + }); + + it('lets a quick rating receive a follow-up detail', async () => { + const user = userEvent.setup(); + const ratedAgent = { + ...agent, + feedback: { rating: 'positive', submittedAt: '2026-07-13T12:00:00.000Z' } + }; + const detailedAgent = { + ...ratedAgent, + feedback: { ...ratedAgent.feedback, comment: 'The focused test coverage was useful.' } + }; + api.submitCosAgentFeedback.mockResolvedValue({ success: true, agent: detailedAgent }); + + render( + + + + ); + + await user.click(screen.getByRole('button', { name: 'Add feedback detail' })); + await user.type(screen.getByPlaceholderText('What made this work well or poorly?'), detailedAgent.feedback.comment); + await user.click(screen.getByRole('button', { name: 'Save detail' })); + + expect(api.submitCosAgentFeedback).toHaveBeenCalledWith( + agent.id, + { rating: 'positive', comment: detailedAgent.feedback.comment }, + { silent: true } + ); + }); }); diff --git a/docs/features/chief-of-staff.md b/docs/features/chief-of-staff.md index 966868f9ca..df8a029a68 100644 --- a/docs/features/chief-of-staff.md +++ b/docs/features/chief-of-staff.md @@ -21,7 +21,7 @@ Autonomous agent manager that watches task files, spawns sub-agents, and maintai 6. **Self-Improvement**: Can analyze performance and suggest prompt/config improvements 7. **Script Generation**: Creates automation scripts for repetitive tasks 8. **Report Generation**: Daily summaries of completed work -9. **Durable Agent Feedback**: Completion notifications accept quick ratings, while the Agents tab keeps a filterable queue of loaded runs that still need feedback after a notification expires +9. **Durable Agent Feedback**: Completion notifications accept quick ratings, while the Agents tab keeps a filterable queue of loaded runs that still need feedback after a notification expires. Feedback details can be attached to helpful, unhelpful, or neutral ratings so learning has actionable context. ## Task File Format