Keep document bodies out of the cacheable system prompt

buildDocumentsSection was inlining up to 5 document bodies at 2000
chars each into the system prompt. Any edit to any of the listed
documents between wakes invalidated the Anthropic prompt cache — the
prefix that buildTaskPrompt spent real effort to keep stable.

Emit metadata only (id, type, title, author, status, version,
updatedAt) and teach agents to GET the single-document endpoint when
they actually need the body. The single-doc route did not exist, so
add it; update the API doc block in the system prompt accordingly.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-19 19:14:14 -04:00
1 parent 3a02b4d0a6
commit 9c65291973
3 files changed
+35 -14

No files matched your search

+18
View File
@@ -118,6 +118,24 @@ export function registerFileRoutes(server: AgentApiServer) {
server.json(res, 200, docs);
});
server.addRoute("GET", "/api/companies/:companyId/documents/:documentId", (_req, res, params, token) => {
if (params.companyId !== token.companyId) {
server.json(res, 403, { error: "Company mismatch" });
return;
}
const documentId = params.documentId;
if (!documentId) {
server.json(res, 400, { error: "Missing documentId" });
return;
}
const doc = db.getDocumentForUpdate(documentId, token.companyId);
if (!doc) {
server.json(res, 404, { error: "Document not found in this company" });
return;
}
server.json(res, 200, doc);
});
server.addRoute("PATCH", "/api/companies/:companyId/documents/:documentId", (_req, res, params, token, body) => {
if (params.companyId !== token.companyId) {
server.json(res, 403, { error: "Company mismatch" });
@@ -18,6 +18,12 @@ Body: {
### List Company Documents
GET /api/companies/{{companyId}}/documents
### Fetch a Single Document (by id)
GET /api/companies/{{companyId}}/documents/{documentId}
Returns the full document record including `content`. The system prompt
only lists document metadata to keep the cacheable prefix stable — call
this endpoint when you need the actual text.
### Update Document (with conflict detection)
PATCH /api/companies/{{companyId}}/documents/{documentId}
Body: {
+11 -14
View File
@@ -146,23 +146,20 @@ interface DocumentsSectionCtx {
export function buildDocumentsSection(ctx: DocumentsSectionCtx): string | null {
if (ctx.recentDocuments.length === 0) return null;
const docLines = ctx.recentDocuments.slice(0, 5).map((d) => {
// Intentionally emit metadata only. Inlining document content (even
// truncated to 2000 chars × 5 documents) invalidates the Anthropic
// prompt cache on every wake where any listed document is edited —
// the exact opposite of what buildTaskPrompt's cache-aware layout
// promises. Agents fetch the body with
// GET /api/companies/{companyId}/documents/{id}
// when they actually need it.
const docLines = ctx.recentDocuments.slice(0, 10).map((d) => {
const author = d.authorAgentId ? (ctx.allAgents.find(a => a.id === d.authorAgentId)?.name ?? "Unknown") : "System";
const header = `- [${d.type}] ${d.title} — by ${author}, status: ${d.status}, updated: ${d.updatedAt}`;
if (d.content && d.content.length > 0) {
const contentLimit = 2000;
const excerpt = d.content.length > contentLimit
? d.content.slice(0, contentLimit) + "..."
: d.content;
return `${header}\n Content:\n ${excerpt.split("\n").join("\n ")}`;
}
return header;
return `- [${d.id}] ${d.type}: "${d.title}" — by ${author}, status: ${d.status}, v${d.version}, updated ${d.updatedAt}`;
});
return `# Recent Documents
${docLines.join("\n\n")}
Reference these documents when relevant to your current work. Create new documents for important work artifacts.
return `# Recent Documents (metadata only — fetch content via GET /api/companies/{companyId}/documents/{id})
${docLines.join("\n")}
`;
}