Revamp custom agents: DB-backed defaults, unified chat, and delegation#506
Conversation
…elegation Convert 6 hardcoded built-in agents (Assistant, Search, Sales, Docs, Connect, Automate) into database records seeded per-organization, giving users full control to edit, enable/disable, and reconfigure all agents. Introduce a partner agent system where any agent can designate other agents as delegation partners. At runtime, dynamic `partner_<name>` tools are generated via `createPartnerDelegationTool()`, replacing the old hardcoded sub-agent tools (crm_assistant, document_assistant, integration_assistant, web_assistant, workflow_assistant). Key changes: - Schema: add partnerAgentIds, maxSteps, timeoutMs, outputReserve, isSystemDefault, systemAgentSlug, roleRestriction fields to customAgents table - Seeding: system_defaults.ts templates + seed_system_defaults.ts for per-org initialization - Partner delegation: generic tool factory with context validation, budget checks, role access, sub-thread reuse - Unified chat: single chatWithAgent mutation replaces 3 separate entry points - Frontend: unified agent selector and mutation hooks, removed builtin/custom split - Cleanup: deleted 25 files of old builtin agent infrastructure (-2591 lines)
…gation UI Remove the isActive soft-delete pattern in favor of hard deletes and status-based queries. Refactor the chat agent from a pure router into a general-purpose assistant with direct knowledge base and web search access. Add delegation configuration page for managing agent-to-agent task routing.
…ity toggle Rename the "partner agent" concept to "delegation" throughout the codebase for clearer semantics — partnerAgentIds becomes delegateAgentIds with a backward-compatible fallback on read. Add a visibleInChat field so agents like the document assistant can be hidden from the chat selector while still being available through delegation. Disable chat input when no agents are published. Improve PPTX tool messaging to direct users to the Knowledge Base page with a direct link.
…for instructions - Add 'manual' mode to useAutoSave that tracks dirty state but only persists when save() is called explicitly or on unmount - Switch instructions tab to manual save, triggering on blur and value change instead of debounced auto-save - Filter inactive agents from delegation list - Add tests for useAutoSave hook
Greptile SummaryThis PR successfully consolidates agent architecture by replacing hardcoded built-in agents with DB-backed system defaults, eliminating ~2,600 lines of duplicated code. Key Changes:
Breaking Changes:
Migration Considerations:
Confidence Score: 4/5
|
| Filename | Overview |
|---|---|
| services/platform/convex/custom_agents/unified_chat.ts | New unified chat entry point consolidating three separate chat mutations into one |
| services/platform/convex/custom_agents/seed_system_defaults.ts | Idempotent seeding logic for system defaults with two-pass delegate resolution |
| services/platform/convex/agent_tools/delegation/create_delegation_tool.ts | Dynamic delegation tool factory replacing hardcoded sub-agent tools |
| services/platform/convex/custom_agents/schema.ts | Schema changes deprecating isActive, adding delegateAgentIds and system default fields; removes index by_org_active_status |
| services/platform/convex/custom_agents/mutations.ts | Mutation updates to support delegation and remove isActive checks; deletion now removes all versions |
| services/platform/convex/custom_agents/queries.ts | Query updates to use new indexes and filter by visibleInChat instead of isActive |
| services/platform/convex/lib/agent_chat/internal_actions.ts | Enhanced with dynamic delegation tool loading and instruction appending at runtime |
| services/platform/app/features/chat/components/agent-selector.tsx | Refactored to use DB-backed system defaults instead of hardcoded builtin types |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User sends message] --> B{Agent selected?}
B -->|No| C[Resolve default chat agent]
B -->|Yes| D[Use selected agentId]
C --> E[Query by systemAgentSlug='chat']
D --> F[Load active version by rootVersionId]
E --> F
F --> G[Check team access]
G --> H[Build agent config]
H --> I{Has delegateAgentIds?}
I -->|Yes| J[Load delegate agents from DB]
I -->|No| K[Create agent with tools]
J --> L[Generate delegation tools dynamically]
L --> M[Append delegation instructions]
M --> K
K --> N[Execute unified chat via startAgentChat]
N --> O{Agent uses delegation?}
O -->|Yes| P[Create sub-thread by delegate rootVersionId]
O -->|No| Q[Complete response]
P --> R[Run delegate via runAgentGeneration]
R --> Q
Last reviewed commit: f009d28
| // @deprecated — kept for existing documents, no longer written or read | ||
| partnerAgentIds: v.optional(v.array(v.id('customAgents'))), | ||
| maxSteps: v.optional(v.number()), | ||
| timeoutMs: v.optional(v.number()), |
There was a problem hiding this comment.
removed index by_org_active_status but isActive field still marked as optional instead of fully deprecated
| timeoutMs: v.optional(v.number()), | |
| // @deprecated — kept for existing documents, no longer written or read | |
| isActive: v.optional(v.boolean()), |
There was a problem hiding this comment.
The isActive field already has the deprecation comment on line 53: // @deprecated — kept for existing documents, no longer written or read. The suggested code is identical to what's already in the file. In Convex, schema fields with existing document data cannot be fully removed — keeping it as v.optional(v.boolean()) with the deprecation comment is the correct approach for Convex schemas.
If you agree with the changes and no further modifications are needed, please resolve this conversation.
| if (existing?.rootVersionId) { | ||
| slugToId.set(template.systemAgentSlug, existing.rootVersionId); | ||
| } else { | ||
| const agentId = await insertSystemAgent(ctx, organizationId, template); | ||
| slugToId.set(template.systemAgentSlug, agentId); | ||
| } |
There was a problem hiding this comment.
if an existing agent has no rootVersionId, it won't be included in the map but the code continues silently
check whether this is expected behavior for existing agents without rootVersionId
There was a problem hiding this comment.
Good catch. The original existing?.rootVersionId condition would fall through to insertSystemAgent when an existing agent lacked rootVersionId, creating a duplicate. Fixed by splitting the check: if the agent exists but has no rootVersionId, we backfill it (patch rootVersionId to existing._id) and use it in the map instead of inserting a duplicate. Typecheck passes.
If you agree with the changes and no further modifications are needed, please resolve this conversation.
| teamId: source.teamId, | ||
| sharedWithTeamIds: source.sharedWithTeamIds, | ||
| createdBy: source.createdBy, | ||
| delegateAgentIds: source.delegateAgentIds ?? source.partnerAgentIds, |
There was a problem hiding this comment.
backward compatibility: falls back to partnerAgentIds when delegateAgentIds is undefined
There was a problem hiding this comment.
This backward compatibility fallback is intentional and correct. The partnerAgentIds field is deprecated in the schema (marked with @deprecated comment) but existing documents may still have it set. The ?? fallback ensures those records are handled properly when copying version fields during duplication or draft creation. The same pattern is used consistently in config.ts line 59. No change needed.
If you agree with the changes and no further modifications are needed, please resolve this conversation.
| const allVersions = ctx.db | ||
| .query('customAgents') | ||
| .withIndex('by_root', (q) => q.eq('rootVersionId', args.customAgentId)); | ||
|
|
||
| for await (const version of allVersions) { |
There was a problem hiding this comment.
deletion now removes ALL versions of an agent (draft, active, archived) instead of just marking as inactive
verify this behavior change won't cause unintended data loss for users who expect soft deletion
There was a problem hiding this comment.
The hard-delete behavior is intentional — isActive is deprecated and the new versioning system uses status as the canonical lifecycle state. However, this flagged a real issue: associated webhooks in customAgentWebhooks were not being cleaned up, leaving dangling references. Fixed by deleting associated webhooks (via the by_agent index) before removing agent versions. The delegateAgentIds references in other agents are handled gracefully since loadDelegateAgents already tolerates missing agents.
If you agree with the changes and no further modifications are needed, please resolve this conversation.
| for await (const agent of systemChat) { | ||
| if (agent.status === 'active' && agent.rootVersionId) { | ||
| return agent.rootVersionId; | ||
| } | ||
| } |
There was a problem hiding this comment.
query returns only the first active agent with slug chat, but doesn't validate there's exactly one
if multiple active chat agents exist for an org (data inconsistency), this silently picks the first one
There was a problem hiding this comment.
Declining this suggestion. The "pick first active" pattern is intentional and used consistently across the codebase (also in seed_system_defaults.ts and human_input/mutations.ts). Multiple documents with the same systemAgentSlug are expected — they are versions of the same agent, all sharing the same rootVersionId. Since line 47 returns agent.rootVersionId, any active version resolves to the same root ID regardless of which one is picked first. The seeding function is idempotent and prevents duplicate root agents per slug per org, so the data inconsistency scenario described cannot occur through normal code paths.
If you agree with the changes and no further modifications are needed, please resolve this conversation.
| ); | ||
|
|
||
| const results: Doc<'customAgents'>[] = []; | ||
| for await (const agent of agents) { |
There was a problem hiding this comment.
added filter for visibleInChat to exclude agents marked as non-visible (e.g., document assistant used only via delegation)
There was a problem hiding this comment.
This is a descriptive comment summarizing the code's behavior, not a change request. The visibleInChat === false filter on line 74 is correctly placed: it only applies in the filterPublished === true branch, which is used by the chat page to select agents. The management UI calls listCustomAgents without filterPublished, so all agents (including non-visible ones) correctly appear there for configuration. The implementation is sound.
If you agree with the changes and no further modifications are needed, please resolve this conversation.
📝 WalkthroughWalkthroughThis PR consolidates the agent system from separate builtin and custom types into a unified custom agent model. Hard-coded agent definitions (chat, web, crm, document, integration, workflow) are removed and replaced with database-seeded system default agents. The PR eliminates separate chat mutations (chatWithAgent, chatWithBuiltinAgent, chatWithCustomAgent) in favor of a single unified chatWithAgent. Sub-agent tools (workflow_assistant, document_assistant, integration_assistant, crm_assistant) are removed and replaced with a delegation model where agents can delegate tasks to other agents using a delegate_ prefix naming convention. System agents are auto-seeded on first use with pre-configured instructions, tools, and delegate relationships. UI components and type systems are updated to reflect this unified architecture. Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
services/platform/convex/agent_tools/files/pptx_tool.ts (1)
239-250:⚠️ Potential issue | 🟡 MinorHarden SITE_URL handling to avoid malformed links.
If
SITE_URLis empty or ends with a trailing slash, the message can embed a relative or double-slash URL. Suggest trimming and falling back to a path-only URL when needed.🔧 Proposed fix
- const siteUrl = process.env.SITE_URL || ''; - const knowledgeUrl = `${siteUrl}/dashboard/${organizationId}/knowledge/documents`; + const siteUrl = (process.env.SITE_URL ?? '').replace(/\/$/, ''); + const knowledgePath = `/dashboard/${organizationId}/knowledge/documents`; + const knowledgeUrl = siteUrl ? `${siteUrl}${knowledgePath}` : knowledgePath;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@services/platform/convex/agent_tools/files/pptx_tool.ts` around lines 239 - 250, The code builds knowledgeUrl from process.env.SITE_URL into siteUrl and may produce malformed URLs if SITE_URL is empty or has a trailing slash; update the logic that computes siteUrl/knowledgeUrl (the variables siteUrl and knowledgeUrl and any code that uses organizationId) to normalize SITE_URL by trimming whitespace and any trailing slash and falling back to a path-only value (e.g., '/dashboard/<org>/knowledge/documents') when SITE_URL is empty; ensure knowledgeUrl is constructed by concatenating a single slash and organizationId (or by using a URL-join utility) so the returned message never contains double slashes or a relative URL.services/platform/app/features/chat/components/chat-input.tsx (1)
135-140:⚠️ Potential issue | 🟡 MinorDisable drag‑and‑drop uploads when the input is disabled.
FileUpload.DropZonestill accepts dropped files even whendisabled/isLoadingis true, so users can upload attachments they cannot send (e.g., no agents available). Consider guardingonFilesSelectedto no‑op in those states.Proposed fix
+ const handleFilesSelected = (files: File[]) => { + if (disabled || isLoading) return; + void uploadFiles(files); + }; + return ( <div {...restProps} className={cn('bg-background', restProps.className)}> <FileUpload.DropZone className="relative flex h-full min-h-0 flex-1 flex-col" - onFilesSelected={uploadFiles} + onFilesSelected={handleFilesSelected} clickable={false} >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@services/platform/app/features/chat/components/chat-input.tsx` around lines 135 - 140, FileUpload.DropZone currently invokes uploadFiles even when the input is disabled or isLoading; change its onFilesSelected prop to a guarded no-op when disabled || isLoading (e.g., pass undefined or a noop) so dropped files are ignored in those states. Locate the FileUpload.DropZone component in chat-input.tsx and replace onFilesSelected={uploadFiles} with a conditional handler that returns immediately when disabled or isLoading, leaving clickable behavior unchanged.services/platform/convex/custom_agents/mutations.ts (2)
282-299:⚠️ Potential issue | 🟠 MajorAdd organization membership validation in new mutations.
Both
updateCustomAgentVisibilityanddeleteCustomAgentauthenticate but don’t verify org membership, which can allow cross‑org changes for teamless agents. Insert the standard org‑membership check beforehasTeamAccess.Based on learnings: Enforce the established authorization flow in mutation handlers: authenticate → getOrganizationMember → perform operation, and do not introduce authorizeRls() into mutation handlers.
Also applies to: 303-317
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@services/platform/convex/custom_agents/mutations.ts` around lines 282 - 299, The handlers updateCustomAgentVisibility and deleteCustomAgent authenticate users but skip org-membership checks; add a call to getOrganizationMember(ctx, authUser) immediately after obtaining authUser and before calling hasTeamAccess (and throw if it returns falsy) to enforce organization membership for teamless agents; keep the existing hasTeamAccess check and do not add authorizeRls() to these mutation handlers.
352-359:⚠️ Potential issue | 🟠 MajorPrevent duplicating system‑default flags on copies.
duplicateCustomAgentcopiesisSystemDefault/systemAgentSlugviacopyVersionFields, which can create multiple “system default” agents and collide on slugs if a system default is duplicated. Clear those fields (or block duplication) on copies.🔧 Suggested fix
const newAgentId = await ctx.db.insert('customAgents', { ...copyVersionFields(source), + isSystemDefault: false, + systemAgentSlug: undefined, name: newName, displayName: newDisplayName, createdBy: String(authUser._id), versionNumber: 1, status: 'draft', });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@services/platform/convex/custom_agents/mutations.ts` around lines 352 - 359, duplicateCustomAgent currently copies system-default flags through copyVersionFields, which can create multiple system defaults and slug collisions; update the insert in duplicateCustomAgent (the code that calls ctx.db.insert('customAgents', { ...copyVersionFields(source), name: newName, displayName: newDisplayName, ... })) to explicitly clear or override the system-default fields (e.g., set isSystemDefault to false and systemAgentSlug to null/undefined) when creating the new record so copies are never marked as system defaults and cannot reuse the system slug.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@services/platform/app/features/chat/components/agent-selector.tsx`:
- Around line 80-95: The sort repeatedly calls allAgents.find inside system.sort
(see the aAgent/bAgent lookups), which is inefficient; before sorting, build a
lookup map (e.g., agentByKey) keyed by (agent.rootVersionId ?? agent._id) and
also account for the default-chat mapping (systemAgentSlug === 'chat') so the
default chat option can be resolved from the map, then replace the
allAgents.find calls in the system.sort comparator with O(1) lookups from
agentByKey and compute aOrder/bOrder from SYSTEM_SLUG_ORDER with the same 99
fallback.
In `@services/platform/app/features/chat/components/thinking-animation.tsx`:
- Around line 67-74: The delegate display logic using
toolName.startsWith('delegate_') should handle snake_case and empty slugs: when
extracting the slug from toolName (the slice after 'delegate_'), normalize it by
replacing underscores and dashes with spaces (or split on /[-_]/) before
capitalizing each word to build agentDisplayName, and if the resulting slug is
empty or only whitespace fall back to a safe default (e.g., the original
toolName or a literal like "Delegate") so displayText is never blank; update the
block that computes agentDisplayName to perform these checks and normalization.
In
`@services/platform/app/features/custom-agents/hooks/__tests__/use-auto-save.test.ts`:
- Around line 241-254: Add a unit test to cover the skip-save behavior when
override data equals the last saved value: render useAutoSave (useAutoSave) with
initial data and onSave, mutate to a new value and call result.current.save() to
create lastSavedRef, then call result.current.save(override) with an override
object equal to that last saved value and assert onSave was not called again
(expect onSave call count unchanged) and status remains appropriate; reference
the save method, lastSavedRef behavior, and onSave mock in your new test.
- Around line 99-123: Rename the test description string in the failing it(...)
block from "does not save when data reverts to the last saved value" to a
clearer name like "saves when reverting to a different value, skips duplicate
saves" to reflect both behaviors demonstrated (save when reverting to a
different lastSavedRef and no save for duplicate value), keeping the test body
and assertions for useAutoSave and onSave unchanged.
In
`@services/platform/app/routes/dashboard/`$id/custom-agents/$agentId/delegation.tsx:
- Around line 72-96: toggleAgent currently reads agent.delegateAgentIds and
selectedSet at call time which can be stale during rapid toggles; change to use
an atomic/functional update and optimistic mutation to avoid races: compute the
new delegate list from the latest state (use a ref or a state updater that
receives previous value, or use react-query's onMutate to apply an optimistic
update) and ensure subsequent toggles operate against that updated value (or
disable UI while updateAgent.mutateAsync is in-flight). Update references in
toggleAgent, updateAgent, saveWithStatus, selectedSet and agent.delegateAgentIds
to use the chosen functional/optimistic pattern so each toggle bases its next
array on the current/previous value rather than stale reads.
In
`@services/platform/app/routes/dashboard/`$id/custom-agents/$agentId/instructions.tsx:
- Around line 139-143: The onValueChange handlers call form.setValue(...) then
immediately call save(form.getValues()), which risks missing the new value if
setValue timing changes; instead compute the updated payload locally and pass it
to save (e.g., let updated = { ...form.getValues(), modelPreset: val } and call
save(updated)) and still call form.setValue(...) to keep the form state in sync;
apply the same change to the other identical handler that sets a form value then
calls save (the other onValueChange around the model setting at the later
block).
In `@services/platform/convex/custom_agents/internal_queries.ts`:
- Around line 15-29: The validation for args.rootVersionIds is incorrect:
replace v.array(v.string()) with v.array(v.id('customAgents')) so the handler's
args.rootVersionIds are Convex document IDs and you can remove the
toId<'customAgents'>(rootId) cast when querying the customAgents index; keep
organizationId as v.string() since it's an external auth identifier. Ensure the
args block in the handler uses v.array(v.id('customAgents')) and update any
usage of toId in the loop (e.g., where you build the by_root_status query on
customAgents) to use rootId directly.
In `@services/platform/convex/custom_agents/mutations.ts`:
- Around line 311-323: The deletion currently queries versions by
eq('rootVersionId', args.customAgentId) which fails if callers passed a non-root
version; instead derive the true root id from the fetched rootAgent (e.g., use
rootAgent.rootVersionId if present, otherwise args.customAgentId) and use that
normalized root id when building the allVersions cursor (the variable
allVersions and the withIndex('by_root') / eq('rootVersionId', ...) call) so all
child versions of the root are targeted for deletion.
In `@services/platform/convex/custom_agents/seed_system_defaults.ts`:
- Around line 86-91: The current logic inserts a new agent when an existing
record has no rootVersionId, causing duplicates; update the branch around
slugToId.set so that if existing is truthy but existing.rootVersionId is falsy
you set slugToId.set(template.systemAgentSlug, existing._id) as a fallback
instead of calling insertSystemAgent, and only call insertSystemAgent when
existing is completely absent; reference the symbols existing, rootVersionId,
existing._id, slugToId, template.systemAgentSlug, and insertSystemAgent when
applying the change.
In `@services/platform/convex/custom_agents/unified_chat.ts`:
- Around line 79-119: After authenticating via authComponent.getAuthUser and
before resolving or checking the agent (resolveAgentId, activeVersionQuery,
hasTeamAccess), validate the caller is a member of args.organizationId by
calling the standard getOrganizationMember(ctx, authUser._id,
args.organizationId) pattern and rejecting if not a member; then continue with
loading the activeVersion and team-access checks (use getUserTeamIds and
hasTeamAccess as before). Do not introduce authorizeRls() into this mutation
handler—follow authenticate → getOrganizationMember → perform operation flow.
- Around line 143-158: Before calling startAgentChat, fetch the thread by
args.threadId (e.g., using the same data-access used elsewhere in
unified_chat.ts), verify its organizationId equals args.organizationId, and
throw or return an authorization error if they differ; alternatively, add the
same check at the top of startAgentChat to ensure every caller is protected.
Make the check happen immediately after loading the conversation/thread and
before any inserts/patches or scheduling, referencing startAgentChat,
args.threadId and args.organizationId so the call is gated by the organizationId
equality check.
---
Outside diff comments:
In `@services/platform/app/features/chat/components/chat-input.tsx`:
- Around line 135-140: FileUpload.DropZone currently invokes uploadFiles even
when the input is disabled or isLoading; change its onFilesSelected prop to a
guarded no-op when disabled || isLoading (e.g., pass undefined or a noop) so
dropped files are ignored in those states. Locate the FileUpload.DropZone
component in chat-input.tsx and replace onFilesSelected={uploadFiles} with a
conditional handler that returns immediately when disabled or isLoading, leaving
clickable behavior unchanged.
In `@services/platform/convex/agent_tools/files/pptx_tool.ts`:
- Around line 239-250: The code builds knowledgeUrl from process.env.SITE_URL
into siteUrl and may produce malformed URLs if SITE_URL is empty or has a
trailing slash; update the logic that computes siteUrl/knowledgeUrl (the
variables siteUrl and knowledgeUrl and any code that uses organizationId) to
normalize SITE_URL by trimming whitespace and any trailing slash and falling
back to a path-only value (e.g., '/dashboard/<org>/knowledge/documents') when
SITE_URL is empty; ensure knowledgeUrl is constructed by concatenating a single
slash and organizationId (or by using a URL-join utility) so the returned
message never contains double slashes or a relative URL.
In `@services/platform/convex/custom_agents/mutations.ts`:
- Around line 282-299: The handlers updateCustomAgentVisibility and
deleteCustomAgent authenticate users but skip org-membership checks; add a call
to getOrganizationMember(ctx, authUser) immediately after obtaining authUser and
before calling hasTeamAccess (and throw if it returns falsy) to enforce
organization membership for teamless agents; keep the existing hasTeamAccess
check and do not add authorizeRls() to these mutation handlers.
- Around line 352-359: duplicateCustomAgent currently copies system-default
flags through copyVersionFields, which can create multiple system defaults and
slug collisions; update the insert in duplicateCustomAgent (the code that calls
ctx.db.insert('customAgents', { ...copyVersionFields(source), name: newName,
displayName: newDisplayName, ... })) to explicitly clear or override the
system-default fields (e.g., set isSystemDefault to false and systemAgentSlug to
null/undefined) when creating the new record so copies are never marked as
system defaults and cannot reuse the system slug.
…dge and web search Replace the boolean knowledgeEnabled toggle with a four-way retrieval mode for both knowledge base and web search. In "context" or "both" modes, RAG and web results are automatically injected into the agent's structured context before generation, rather than relying solely on tool calls. - Add retrievalModeValidator and knowledgeMode/webSearchMode schema fields - Replace syncRagSearchTool with syncRetrievalModes in mutations - Add context injection paths in generate_response for knowledge and web - New query_web_context helper for crawled page retrieval - Update knowledge UI from toggle to radio group, add web search mode on tools tab - Backward-compatible: derives mode from legacy fields for existing agents
Allow users to enable/disable structured response markers (conclusion, key points, details, etc.) per agent via the instructions tab. Defaults to enabled for backward compatibility.
… agent instructions
Remove the beforeContextHook and integrations/system-info context injection
pipeline in favor of template variables (e.g. {{organization.name}},
{{current_time}}) resolved at runtime within system instructions. This
simplifies context management and gives agent creators explicit control
over what dynamic data appears in their prompts.
- Add resolve_template_variables module with tests
- Remove beforeContextHook, integrationsInfo, contextFeatures, and system info injection
- Make hasIntegrations optional in schema for backward compatibility
- Add template variables reference UI to instructions tab
- Replace `as ModelPreset` type casts with Zod parsing
…ead of listing all Prevents new custom agent fields from being silently dropped when creating versions — copyVersionFields now spreads the source document and deletes the version-meta keys rather than enumerating every content field.
Summary
custom_agentsrecords with asystemDefaultflag, eliminating per-type boilerplate (removed ~2,600 lines of duplicated agent mutations, internal actions, and config)unified_chatpath instead of separate per-agent chat/mutation/action filesuseAutoSave— instructions tab now saves on blur/value change instead of debounced auto-save, with explicitsave()API and testsTest plan
useAutoSavehook tests🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Improvements
Documentation