Skip to content

⚡ perf: Exclude Agent Version History from Default getAgent Reads - #57

Closed
devin-ai-integration[bot] wants to merge 1 commit into
devfrom
devin/1789719329-agent-versions-projection
Closed

devin-ai-integration[bot] wants to merge 1 commit into
devfrom
devin/1789719329-agent-versions-projection

Conversation

@devin-ai-integration

Copy link
Copy Markdown

Summary

Every agent chat request read the agent document twice with no projection (canAccessAgentFromBody → getAgent, then buildOptions → loadAgent → getAgent), so the unbounded versions[] snapshot array (one full agent copy per edit, never trimmed by updateAgent) was transferred and BSON-decoded on every request, even though the chat path only reads versions.length. The same happened in the ~30 other getAgent({ id }) callers that only need metadata for permission checks.

getAgent now excludes versions by default, and the chat hot path resolves the agent through the existing getAgentWithVersionCount aggregate ($size → version), so the reported version is unchanged while the per-request payload drops from (1 + N versions) × agentSize to ~1 × agentSize per read. Only the v1 update/revert handlers, which actually read agent.versions, request the full document explicitly.

How it works

-async function getAgent(searchParameter, projection?) {
+async function getAgent(searchParameter, projection = { versions: 0 }) {
   return await Agent.findOne(searchParameter, projection).lean();
 }
POST /api/agents/chat
  canAccessAgentFromBody  → getAgentWithVersionCount({ id })   # was getAgent (full doc)
  buildOptions → loadAgent → deps.getAgent = db.getAgentWithVersionCount
                             agent.version ??= agent.versions?.length ?? 0

loadAgent / loadAddedAgent keep the getAgent dep name but its result is now typed Agent & { version?: number; versions?: unknown[] }, and the version count is taken from version when present (falling back to versions.length for any injected dep still returning the full doc). build.js, client.js and addedConvo.js wire db.getAgentWithVersionCount into that dep.

Callers that need history pass an explicit empty projection: db.getAgent({ id }, {}) in updateAgentHandler (result may be returned as updatedAgent with version = versions.length) and revertAgentVersionHandler.

Expected win: for an agent edited N times, each chat request read moves from O(N × agentSize) to O(agentSize) — e.g. 100 edits × 20 KB prompt ≈ 4 MB → ~40 KB per request across the two reads.

Change Type

  • Bug fix (non-breaking change which fixes an issue)

Testing

  • packages/data-schemas: jest src/methods/agent.spec.ts (178 pass; assertions inspecting version history now pass {} to getAgent)
  • packages/api: tsc --noEmit; jest src/agents/load.spec.ts src/agents/__tests__/load.spec.ts (43 pass; the integration spec now wires getAgentWithVersionCount, mirroring production)
  • api: jest on v1.spec.js, __tests__/v1.spec.js, canAccessAgentFromBody.spec.js, addedConvo.spec.js, client.test.js, initialize.spec.js, resume.spec.js, steer.spec.js (679 pass)
  • eslint on changed files

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • My changes do not introduce new warnings
  • Local unit tests pass with my changes

Link to Devin session: https://app.devin.ai/sessions/28b7509f3db5434b88aa77685f0ae419
Open in Devin Desktop: https://app.devin.ai/desktop/session/28b7509f3db5434b88aa77685f0ae419?variant=devin
Requested by: @berry-13

getAgent now defaults its projection to { versions: 0 }; loadAgent/loadAddedAgent
and canAccessAgentFromBody use getAgentWithVersionCount so the version count stays
exact without transferring the unbounded versions array on every chat request.
Callers that need history (v1 update/revert handlers) request it explicitly.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@github-actions
github-actions Bot changed the base branch from main to dev September 18, 2026 08:23
@github-actions

Copy link
Copy Markdown

👋 Thanks for the contribution! LibreChat merges all changes into dev first — main only moves at release time — so this pull request's base branch was switched from main to dev automatically.

Nothing is needed from you; your commits, reviews and discussion are unchanged. If the diff now shows files you did not touch, rebase onto dev:

git remote add upstream https://github.com/berry-13/LibreChat.git
git fetch upstream dev
git rebase upstream/dev
git push --force-with-lease

Maintainers: apply the target: main label and restore the base branch if this one genuinely belongs on main.

@github-actions

Copy link
Copy Markdown

Lighthouse CI failed. The last 80 log lines contain the measured budgets and assertion failures.

│ 22      │ 'http://localhost:3080/api/mcp/servers'                                                                         │ 2955.2509999999893 │ 4221.230999999971  │ 200    │
│ 23      │ 'http://localhost:3080/api/permissions/mcpServer/effective/all'                                                 │ 2955.712999999989  │ 3715.505999999994  │ 200    │
│ 24      │ 'http://localhost:3080/api/prompts/groups?limit=10'                                                             │ 2958.158999999985  │ 4232.472000000009  │ 200    │
│ 25      │ 'http://localhost:3080/api/keys?name=openAI'                                                                    │ 3181.396999999997  │ 3887.29899999997   │ 200    │
│ 26      │ 'http://localhost:3080/api/presets'                                                                             │ 3181.6929999999993 │ 3900.3250000000116 │ 200    │
│ 27      │ 'http://localhost:3080/api/tags'                                                                                │ 3181.898999999976  │ 3900.57699999999   │ 200    │
│ 28      │ 'http://localhost:3080/api/share/link/16390000-0000-4000-8000-000000000001'                                     │ 3182.773999999976  │ 4221.514999999985  │ 200    │
│ 29      │ 'http://localhost:3080/api/messages/16390000-0000-4000-8000-000000000001'                                       │ 3182.975000000006  │ 4393.88499999998   │ 200    │
│ 30      │ 'http://localhost:3080/api/files/config'                                                                        │ 3183.8649999999907 │ 4157.264999999985  │ 200    │
│ 31      │ 'http://localhost:3080/api/agents/tools/web_search/auth'                                                        │ 3184.459999999992  │ 6925.550999999978  │ 200    │
│ 32      │ 'http://localhost:3080/api/endpoints/token-config'                                                              │ 3184.93299999999   │ 4414.410999999993  │ 200    │
│ 33      │ 'http://localhost:3080/api/agents/tools/calls?conversationId=16390000-0000-4000-8000-000000000001'              │ 3185.149999999994  │ 4730.358999999997  │ 200    │
│ 34      │ 'http://localhost:3080/api/agents/chat/status/16390000-0000-4000-8000-000000000001?generationProtocolVersion=2' │ 4485.064999999973  │ 4739.963999999978  │ 200    │
└─────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴────────────────────┴────────────────────┴────────┘

Inspect .lighthouse HTML/JSON and e2e/lighthouse/README.md. Reuse loaded user/config data; overlap independent reads without bypassing authorization.

┌─────────┬────────────────────────────┬──────────────────────┬───────┐
│ (index) │ audit                      │ median               │ limit │
├─────────┼────────────────────────────┼──────────────────────┼───────┤
│ 0       │ 'largest-contentful-paint' │ 4516.701             │ 4500  │
│ 1       │ 'cumulative-layout-shift'  │ 0.016891964035548546 │ 0.1   │
│ 2       │ 'total-blocking-time'      │ 197.9910000000009    │ 500   │
└─────────┴────────────────────────────┴──────────────────────┴───────┘

  1) [chrome] › e2e/lighthouse/load.spec.ts:10:5 › serial database latency stays within web-vitals budgets 

    Error: Median largest-contentful-paint must stay within 4500

    expect(received).toBeLessThanOrEqual(expected)

    Expected: <= 4500
    Received:    4516.701

       at audit.ts:159

      157 |   console.table(measured);
      158 |   for (const { audit, median, limit } of measured) {
    > 159 |     expect(median, `Median ${audit} must stay within ${limit}`).toBeLessThanOrEqual(limit);
          |                                                                 ^
      160 |   }
      161 |   return results;
      162 | }
        at auditPage (/home/runner/work/LibreChat/LibreChat/e2e/lighthouse/audit.ts:159:65)
        at /home/runner/work/LibreChat/LibreChat/e2e/lighthouse/load.spec.ts:33:19

    attachment #1: screenshot (image/png) ──────────────────────────────────────────────────────────
    e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/test-failed-1.png
    ────────────────────────────────────────────────────────────────────────────────────────────────

    Error Context: e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/error-context.md

    attachment #3: trace (application/zip) ─────────────────────────────────────────────────────────
    e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/trace.zip
    Usage:

        npx playwright show-trace e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/trace.zip

    ────────────────────────────────────────────────────────────────────────────────────────────────


🤖: global teardown has been started
2026-09-18 08:27:23 �[32minfo�[39m: �[32mMongo Connection options�[39m
2026-09-18 08:27:23 �[32minfo�[39m: �[32m{�[39m
�[32m  "bufferCommands": false�[39m
�[32m}�[39m
🤖:  ✅  Connected to Database
🤖:  ✅  Found user in Database
🤖:  ✅  Deleted 1 convos & 2 messages
🤖:  ✅  Deleted user from Database
2026-09-18 08:27:24 �[31merror�[39m: �[31mIndex build failed for "Conversation": Operation interrupted because client was closed�[39m
🤖: global teardown has been started
2026-09-18 08:27:24 �[32minfo�[39m: �[32mMongo Connection options�[39m
2026-09-18 08:27:24 �[32minfo�[39m: �[32m{�[39m
�[32m  "bufferCommands": false�[39m
�[32m}�[39m
🤖:  ✅  Connected to Database
🤖:  ⚠️  User not found in Database
  1 failed
    [chrome] › e2e/lighthouse/load.spec.ts:10:5 › serial database latency stays within web-vitals budgets 

Open the full run

@berry-13 berry-13 closed this Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant