Project & Story context assets — slice 2: lazy summarisation#111
Conversation
There was a problem hiding this comment.
Pull request overview
Adds the “lazy summarisation” pipeline for context assets so long text bodies can be compressed via a BYOK-resolved Laravel AI agent before being used in downstream prompts.
Changes:
- Introduces
ContextSummariseragent + prompt (prompts/context-summariser.md) and a BYOK-awareContextCompressorthat returns aSummariseResult. - Adds
SummariseContextItemJoband wires dispatch fromContextItemWriter(long text bodies) andAssetUploader(some file uploads). - Adds/updates Pest feature tests covering dispatch decisions and job outcomes (Ready/Skipped/Failed).
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Feature/ContextItem/WriterDispatchesSummariseTest.php | New tests asserting writer/uploader dispatch + skipped semantics for short bodies. |
| tests/Feature/ContextItem/SummariseContextItemJobTest.php | New tests for job outcomes (no creds, null creator, ready summary, failed callback). |
| tests/Feature/ContextItem/ContextItemWriterTest.php | Updates expectation to reflect “short text => Skipped” behavior. |
| tests/Feature/ContextItem/AssetUploaderTest.php | Adds Bus::fake() setup for new job dispatch behavior. |
| prompts/context-summariser.md | New summariser prompt instructions for plain-text ≤1,000 char summaries. |
| app/Services/Context/ContextItemWriter.php | Dispatches summarisation job for long text bodies; marks short bodies skipped. |
| app/Services/Context/AssetUploader.php | Dispatches summarisation job for certain file MIME types over a threshold. |
| app/Services/Ai/SummariseResult.php | New value object describing summarisation outcome. |
| app/Services/Ai/ContextCompressor.php | New BYOK provider binding + summarisation invocation/cleanup logic. |
| app/Jobs/SummariseContextItemJob.php | New queued job that runs compressor and persists summary status/results. |
| app/Ai/Agents/ContextSummariser.php | New Laravel AI agent for generating plain-text context summaries. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if ($this->shouldSummariseFile($file)) { | ||
| SummariseContextItemJob::dispatch($item->getKey()); | ||
| } |
There was a problem hiding this comment.
Fixed in 3f2a84d — AssetUploader no longer dispatches SummariseContextItemJob for file uploads. You were right: the compressor would have read metadata.extracted_text (unset), marked Skipped, and effectively poisoned the row. File items now land Skipped at upload time. When the PDF/text extractor lands, it can flip to Pending and dispatch the job itself.
| $threshold = (int) config('specify.context.assets.summary_threshold_chars', 2000); | ||
|
|
||
| return $file->getSize() >= $threshold; |
There was a problem hiding this comment.
Made moot by the same change in 3f2a84d — the chars-vs-bytes mismatch is gone because shouldSummariseFile() was removed entirely. If/when file extraction lands and we re-introduce dispatch, it will live in the extractor and gate on extracted-text length (chars), not raw file size.
| $item->forceFill([ | ||
| 'summary_status' => $result->status->value, | ||
| 'summary' => $result->status === ContextItemSummaryStatus::Ready ? $result->summary : null, | ||
| 'summary_error' => $result->status === ContextItemSummaryStatus::Failed ? $result->error : null, |
There was a problem hiding this comment.
Fixed in 3f2a84d — the job now persists summary_error for both Skipped and Failed (only Ready clears it). summary_status is the source of truth for outcome; summary_error is the audit trail (skip reason or failure cause). New regression in SummariseContextItemJobTest::"job marks item Skipped when creator has no BYOK creds and records the reason" confirms the reason survives.
3f2a84d to
346b442
Compare
Adds ContextSummariser (laravel/ai), ContextCompressor (BYOK-aware resolver — no creds → Skipped, never throws), and the queued SummariseContextItemJob. AssetUploader and ContextItemWriter dispatch the job for text-mime/long-text bodies; short bodies are marked Skipped so bodyForContext() falls back to the truncated raw body. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- AssetUploader no longer dispatches SummariseContextItemJob for files. ContextCompressor::resolveBody() for File-typed items reads metadata['extracted_text'] which the uploader doesn't populate, so the job would have marked the item Skipped on first run. File extraction (PDF/text) is a separate concern; when that lands the extractor will flip status to Pending and dispatch from there. - File items now land summary_status=Skipped at upload (was Pending), matching the actual behaviour of the compressor today. - SummariseContextItemJob now persists summary_error for both Skipped and Failed outcomes — skip reasons were silently dropped before. summary_status is the source of truth for outcome; the column is the audit trail. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Slice 2 changed file uploads to land at summary_status=skipped (no extraction pipeline yet) but the class-level docblock still claimed "summary_status=pending so that worker picks them up later." Update the docstring to match — and document the future-extractor handoff. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
346b442 to
8debda2
Compare
Summary
Stacked on #110 (slice 1). Adds the BYOK-resolved summariser path so long bodies get compressed before they hit the AI prompt.
ContextSummariser(laravel/ai agent, plain-text output, prompt atprompts/context-summariser.md).ContextCompressor(resolves provider directly from a User's BYOK creds; no creds →Skipped, never throws).SummariseResultvalue object +SummariseContextItemJob(queued; failure path marksFailedwith the error).AssetUploaderandContextItemWriterdispatch the job for text-mime files / long text bodies; short bodies are markedSkippedsobodyForContext()falls back to the truncated raw body.Test plan
🤖 Generated with Claude Code