Conversation
- Create DuplicateAgentRequestSchema in schemas.ts - Validate newAgentId as required string (2-256 chars, URL-safe) - Validate newAgentName as optional string (1-255 chars) - Response returns AgentWithinContextOfProjectSelectSchema - Add comprehensive schema validation tests for boundary cases - Tests cover valid IDs, invalid characters, length boundaries, reserved names Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implemented duplicateAgent() function in agents data access layer to copy an
agent and all its relationships while preserving data integrity.
Features:
- Copy agent record with new ID and name (defaults to '{originalName} (Copy)')
- Copy all sub-agents preserving original IDs
- Copy all agent-scoped function tools with new IDs
- Copy all agent-scoped context configs
- Copy all sub-agent relations (transfer/delegate)
- Copy all sub-agent tool relations with toolPolicies and headers
- Copy all sub-agent function tool relations with remapped function tool IDs
- Copy all sub-agent external agent relations
- Copy all sub-agent team agent relations
- Copy all sub-agent data component relations
- Copy all sub-agent artifact component relations
- Explicitly exclude triggers from duplication scope
- All operations preserve original timestamps
Tests:
- Comprehensive unit tests covering all entity types
- Tests for basic duplication with default and custom names
- Tests for error handling when source agent not found
- Tests for all relationship types
- Tests for comprehensive duplication with multiple relationships
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Added POST /{tenantId}/projects/{projectId}/agents/{agentId}/duplicate endpoint
- Validates newAgentId (required) and newAgentName (optional) via DuplicateAgentRequestSchema
- Returns 201 Created with full agent definition on success
- Returns 404 if original agent not found
- Returns 409 Conflict if newAgentId already exists
- Returns 400 Bad Request for invalid ID format
- Returns 403 Forbidden if user lacks project access (via requireProjectPermission middleware)
- Defaults newAgentName to '{originalName} (Copy)' if not provided
- Added comprehensive integration tests for all success and error scenarios
- Updated OpenAPI snapshot
- All tests, typecheck, and lint pass
- Added 'Duplicate agent' menu item to agent card dropdown - Positioned between 'Edit' and 'Delete' menu items - Added Copy icon from lucide-react - Added isDuplicateOpen state for modal control - Added placeholder for DuplicateAgentModal (to be implemented in US-005) - Added unit tests for AgentItemMenu component - All typecheck, lint, and format checks pass Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ents in agent duplication **Bug:** When duplicating an agent with data/artifact components, the components appeared duplicated in BOTH the original and copied agents due to missing agentId filter in getFullAgentDefinitionInternal. **Root Cause:** The query for data/artifact component relations only filtered by tenantId and subAgentId, not agentId. Since sub-agents can have the same IDs across different agents (preserved during duplication), this caused the query to return relations from ALL agents with that sub-agent ID. **Fix:** - Added projectId and agentId filters to data component relation queries (line 475-477) - Added projectId and agentId filters to artifact component relation queries (line 484-486) **Testing:** - Added regression test: "should not cause cross-contamination of data/artifact components" - Verifies both agents show exactly 1 of each component (not duplicated) - Verifies database has exactly 2 relations (1 per agent, not mixed) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…micity **Critical Fix**: The duplicateAgent function performed 10+ database operations without transaction wrapping, risking partial duplications on failure. **Risk**: If any operation failed midway (network timeout, constraint violation), the database would contain an orphaned partial agent with inconsistent state. **Fix**: - Wrapped entire duplicateAgent operation in db.transaction() - All database operations now use transaction context (tx) - On failure: automatic rollback, no orphaned records - On success: all operations committed atomically **Testing**: - All 15 duplication tests pass - Typecheck passes Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…r performance
**Performance Issue**: Sequential for...of loops with individual awaits created
N+1 query pattern. Agent with 10 sub-agents + 5 tools + 12 relations = 27+
sequential database round-trips (40-1400ms total latency).
**Fix**: Convert all sequential inserts to batch inserts using Drizzle's
multi-value INSERT support.
**Before** (Sequential):
```typescript
for (const sub of subs) {
await tx.insert(subAgents).values({...}); // 10 queries
}
```
**After** (Batched):
```typescript
await tx.insert(subAgents).values(
subs.map(sub => ({...})) // 1 query
);
```
**Performance Improvement**:
- Before: 27+ sequential queries (~40-1400ms)
- After: 9 batched queries (~10-50ms)
- **Up to 28x faster** for large agents
**Changes**:
- Batch insert sub-agents (10 → 1 query)
- Batch insert function tools (5 → 1 query)
- Batch insert context configs (n → 1 query)
- Batch insert all relation types (12 → 6 queries)
- Batch insert data/artifact components (4 → 2 queries)
**Testing**:
- All 15 duplication tests pass
- Typecheck passes
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
**Dead Code Removed**: subAgentIdMapping was created but never used. **Why it existed**: Likely copied from functionToolIdMapping pattern, but sub-agent IDs are intentionally preserved (not remapped) during duplication, making the mapping unnecessary. **Comparison**: - functionToolIdMapping: NEEDED (function tools get new IDs) - subAgentIdMapping: NOT NEEDED (sub-agents keep original IDs) **Changes**: - Removed unused Record<string, string> declaration - Removed identity mapping assignment (sourceId -> sourceId) - Added comment clarifying IDs are preserved **Testing**: - All 15 duplication tests pass - Typecheck passes Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The agent-item-menu and duplicate-agent-modal tests were placeholder tests that didn't render components or test any actual behavior: - agent-item-menu.test.tsx: Created empty div, asserted true === true - duplicate-agent-modal.test.tsx: Only tested object properties and string interpolation Real test coverage exists in: - packages/agents-core/src/__tests__/data-access/duplicateAgent.test.ts (14 tests) - agents-api/src/__tests__/manage/agent-duplicate.test.ts (8 tests) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 3e94ec9 The changes in this PR will be included in the next version bump. This PR includes changesets to release 10 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @sarah-inkeep's task —— View job Code Review CompleteThis PR implements a comprehensive duplicate agent feature across all layers of the system. The implementation follows the repository's patterns well and includes proper testing, UI components, and API documentation. Here's my detailed review: ✅ Strong PointsArchitectural Design
Implementation Quality
🔍 Areas for ConsiderationPerformance & Scalability
Security & Authorization
Data Consistency
Error Handling
API Design
UI/UX Considerations
📝 Minor Code Quality Issues
🎯 Architectural ConsiderationsFuture Extensibility
Audit & Observability
Documentation Completeness
🏆 Overall AssessmentThis is a well-implemented feature that demonstrates strong understanding of the codebase architecture. The implementation correctly follows the repository's patterns for:
The performance optimizations using batch operations and the atomic transaction wrapper show thoughtful engineering. The identified considerations are primarily about edge cases and potential scalability improvements rather than fundamental issues. Recommendation: Approve with the understanding that the identified performance and security considerations can be addressed in follow-up iterations as usage patterns emerge. |
Add immediate loading toast feedback when duplicating an agent to improve UX. The loading toast appears when the user clicks "Duplicate" and smoothly transitions to either a success or error toast when the operation completes. This provides better visibility than just the button text change, especially for operations that may take a few seconds due to the transaction overhead. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
| // PostgreSQL: code 23505 | ||
| // Doltgres/MySQL: errno 1062 or message contains "duplicate primary key" | ||
| const isDuplicateKey = | ||
| errorCode === '23505' || | ||
| topLevelMessage.includes('duplicate primary key') || | ||
| topLevelMessage.includes('duplicate key') || | ||
| topLevelMessage.includes('errno 1062') || | ||
| causeMessage.includes('duplicate primary key') || | ||
| causeMessage.includes('duplicate key') || | ||
| causeMessage.includes('errno 1062'); |
There was a problem hiding this comment.
I want to do a refactor after you merge this functionality to better handle DB specific error code catching.
| {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} | ||
|
|
||
| <APIPage document={"index"} webhooks={[]} operations={[{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agent","method":"post"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agent/{agentId}","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agent/{agentId}","method":"delete"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agent/{agentId}","method":"put"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents","method":"post"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/full","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-artifact-components","method":"post"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-artifact-components/agent/{subAgentId}","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-artifact-components/agent/{subAgentId}/component/{artifactComponentId}","method":"delete"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-artifact-components/agent/{subAgentId}/component/{artifactComponentId}/exists","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-artifact-components/component/{artifactComponentId}/agents","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-data-components","method":"post"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-data-components/agent/{subAgentId}","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-data-components/agent/{subAgentId}/component/{dataComponentId}","method":"delete"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-data-components/agent/{subAgentId}/component/{dataComponentId}/exists","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-data-components/component/{dataComponentId}/agents","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agents/{subAgentId}/related","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{id}","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{id}","method":"delete"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{id}","method":"put"}]} showTitle={true} /> No newline at end of file | ||
| <APIPage document={"index"} webhooks={[]} operations={[{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agent","method":"post"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agent/{agentId}","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agent/{agentId}","method":"delete"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agent/{agentId}","method":"put"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents","method":"post"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/duplicate","method":"post"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/full","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-artifact-components","method":"post"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-artifact-components/agent/{subAgentId}","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-artifact-components/agent/{subAgentId}/component/{artifactComponentId}","method":"delete"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-artifact-components/agent/{subAgentId}/component/{artifactComponentId}/exists","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-artifact-components/component/{artifactComponentId}/agents","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-data-components","method":"post"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-data-components/agent/{subAgentId}","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-data-components/agent/{subAgentId}/component/{dataComponentId}","method":"delete"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-data-components/agent/{subAgentId}/component/{dataComponentId}/exists","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agent-data-components/component/{dataComponentId}/agents","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/sub-agents/{subAgentId}/related","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{id}","method":"get"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{id}","method":"delete"},{"path":"/manage/tenants/{tenantId}/projects/{projectId}/agents/{id}","method":"put"}]} showTitle={true} /> No newline at end of file |
There was a problem hiding this comment.
Just to confirm, this change was generated from the script and fumadocs package, yes?
| } from '../ui/dialog'; | ||
| import { Form } from '../ui/form'; | ||
|
|
||
| const duplicateAgentSchema = z.object({ |
There was a problem hiding this comment.
This schema doesn't match the API. The API has a max character restriction. Can we derive the duplicateAgentSchema from the schema exported from agents-core? Then we will be able to have our frontend and backend schema validation automatically in sync.
| const form = useForm<DuplicateAgentFormData>({ | ||
| resolver: zodResolver(duplicateAgentSchema), | ||
| defaultValues: { | ||
| newAgentName: `${agentName} (Copy)`, |
There was a problem hiding this comment.
It looks like this might be duplicate default value for newAgentName? other value is defined in manage/routes/agent.ts (https://github.com/inkeep/agents/pull/1663/changes#diff-dd956509456b777e1f3f68474c6ce67478761c28396fdabb7e576c33ded6e7a4R299) and described in a third location here: https://github.com/inkeep/agents/pull/1663/changes#diff-a6b7ad468243ec559f66f3a1cb18b172f224383d7a2f31a15a6c3657f20103d3R2204-R2212 agents-core/validation/schemas.ts
| .describe( | ||
| 'Name for the duplicated agent. Defaults to "{originalName} (Copy)" if not provided.' | ||
| ), | ||
| }) |
There was a problem hiding this comment.
can we make newAgentName schema inherit from the original agentName schema so that they remain in sync?
| id: newAgentId, | ||
| tenantId, | ||
| projectId, | ||
| name: newAgentName || `${sourceAgent.name} (Copy)`, |
There was a problem hiding this comment.
default name is defined yet again at the data access layer. I think we need to reduce default name definitions to one location.
| ); | ||
| } | ||
|
|
||
| const sourceFunctionTools = await tx |
There was a problem hiding this comment.
optimization that should be done within this PR. Parallel data fetches for all resources, otherwise this operation will take longer than it needs to.
There was a problem hiding this comment.
Additionally, parallel inserts if at all possible, there will need to be some sequencing to make sure dependencies exist before inserting some types of resources.
Additionally, think forward into the future, how will maintaining this duplication functionality have to change if we add other types of resources. I'm worried that we will add a new feature like "Agent Skills" and forget to include it in the duplication functionality. The challenge here is that all duplicated resources as explicitly defined instead of dynamically discovered.
amikofalvy
left a comment
There was a problem hiding this comment.
need to look at schema re-use, parallel db executions when possible, and making it easier to maintain this functionality when we add new agent resource types in the near and far future.
…nt cross-contamination Function tools now get their own copy of the underlying function record, so modifying code or input schema in a duplicated agent no longer affects the original.
|
Claude finished @sarah-inkeep's task in 6m 18s —— View job PR Review Summary1 Key Finding | Risk: Medium 🟠
|
| Location | Issue | Reason Excluded |
|---|---|---|
agent.ts:307-332 |
Duplicate key error detection is complex with multiple string checks | Works correctly; refactoring is nice-to-have, flagged for future DB error utility |
duplicate-agent-modal.tsx:46 |
Default name (Copy) defined in UI |
Only one location now (previous review addressed), acceptable for UI-specific default |
Discarded as invalid or not applicable
| Location | Issue | Reason Excluded |
|---|---|---|
agents.ts:1012 |
Context config ID reference issue | False positive - Context configs are duplicated with preserved IDs but assigned to new agentId; lookup uses (tenantId, projectId, agentId, id) so this works correctly |
agents.ts:1105 |
Potential unsafe cast on functionIdMapping | Type guard .filter() on line 1223 handles null cases; mapping is built synchronously before use |
|
Claude finished @sarah-inkeep's task in 3m 42s —— View job PR Review Summary1 Key Finding | Risk: Medium 🟠
|
| Prior Feedback | Status |
|---|---|
| Schema re-use (@amikofalvy) | ✅ DuplicateAgentRequestSchema now inherits from AgentInsertSchema.shape |
| Default name duplication (@amikofalvy) | ✅ Reduced to single location in UI defaultValues |
| Parallel data fetches (@amikofalvy) | ✅ Phase 1 uses Promise.all for all 10 entity types |
| Frontend schema sync (@amikofalvy) | ✅ UI imports DuplicateAgentRequestSchema from @inkeep/agents-core/client-exports |
💡 APPROVE WITH SUGGESTIONS
Summary: This is a well-implemented feature with proper transaction handling, comprehensive test coverage (926 lines of unit tests + 306 lines of integration tests), correct tenant isolation, and good schema reuse. Most prior feedback from @amikofalvy has been addressed - parallel fetches are implemented, schema is properly reused from agents-core, and default name is consolidated. The main remaining gap is that triggers (an agent-scoped entity) are not duplicated, which should be addressed before merge to ensure complete agent copies. The implementation is otherwise solid and follows repository patterns well.
Other Findings (3)
Potentially valid
(minor or low confidence - not blocking)
| Location | Issue | Reason Excluded |
|---|---|---|
agent.ts:307-332 |
Duplicate key error detection uses multiple string checks | Works correctly; @amikofalvy already noted desire for future DB error utility refactor |
duplicate-agent-modal.tsx:46 |
Default name (Copy) in UI |
Single location now (proper place for UI-specific defaults) |
Discarded as invalid or not applicable
| Location | Issue | Reason Excluded |
|---|---|---|
agents.ts:1138 |
Context config ID reference issue | False positive - Context configs are duplicated with preserved IDs but assigned to new agentId; lookup uses compound key (tenantId, projectId, agentId, id) so this works correctly |
Allow users to duplicate an agent in the same project as the original agent. https://linear.app/inkeep/issue/PRD-5932/allow-users-to-duplicate-and-modify-existing-agents-in-ui
https://www.loom.com/share/ae08b9b6d8d14af79045464aebc0d208