Skip to content

feat(shared): add ctx.metadata.clear() and ctx.metadata.delete(key) fixes NV-7501#10971

Open
ChmaraX wants to merge 2 commits intonextfrom
cursor/metadata-clear-delete-c7f5
Open

feat(shared): add ctx.metadata.clear() and ctx.metadata.delete(key) fixes NV-7501#10971
ChmaraX wants to merge 2 commits intonextfrom
cursor/metadata-clear-delete-c7f5

Conversation

@ChmaraX
Copy link
Copy Markdown
Contributor

@ChmaraX ChmaraX commented May 4, 2026

What changed

Added ctx.metadata.clear() and ctx.metadata.delete(key) methods to the agent context, enabling developers to reset or selectively remove conversation metadata from handlers.

Problem

Previously, there was no way to clear or delete individual metadata keys. The only workaround was manually setting every known key to null:

ctx.metadata.set('board', null);
ctx.metadata.set('deadline', null);
ctx.metadata.set('score', null);

Solution

Two new methods on ctx.metadata:

ctx.metadata.clear();          // wipe all keys
ctx.metadata.delete('board');  // remove a single key

Architecture: action discriminant on a single signal type

Rather than introducing separate top-level signal types (metadata_delete, metadata_clear), the new operations use an action discriminant within the existing metadata signal:

type MetadataSignal =
  | { type: 'metadata'; action?: 'set'; key: string; value: unknown }
  | { type: 'metadata'; action: 'delete'; key: string }
  | { type: 'metadata'; action: 'clear' };

Why this is better than separate signal types:

  • Signal stays MetadataSignal | TriggerSignal — no union expansion
  • All existing server-side filters (s.type === 'metadata') work unchanged
  • SIGNAL_TYPES stays ['metadata', 'trigger'] — no new constants needed
  • Backward compatible: action defaults to 'set' when omitted (existing SDKs unaffected)
  • Future operations (e.g. merge, increment) are new action values, not new top-level types

Changes

Framework SDK (packages/framework)

  • MetadataSignal is now a discriminated union on action (set | delete | clear)
  • AgentContext.metadata interface extended with delete(key) and clear()
  • AgentContextImpl pushes the appropriate action for each method

API server (apps/api)

  • SignalDto gains an optional action field; validator branches on it within type === 'metadata'
  • updateMetadata processes clear (reset to {}), delete (remove key), and set in order
  • validateMetadataSignalKeys skips value check for delete action
  • No changes needed to signal filtering or analytics counting

Tests

  • 4 new test cases covering delete, clear, mixed signal ordering, and flush-only clear
  • Existing metadata test assertions updated for action: 'set' field
  • All 316 framework tests pass

Linear Issue: NV-7501

Open in Web Open in Cursor 

What changed

This PR adds ctx.metadata.delete(key) and ctx.metadata.clear() methods to the agent context metadata API. Previously, clearing metadata required manually setting known keys to null. Now developers can remove individual keys or wipe all metadata with dedicated methods. These new operations emit metadata signals that integrate seamlessly with the existing batching and flushing pipeline.

Affected areas

framework: Extended AgentContext.metadata interface with delete(key) and clear() methods. Updated MetadataSignal type to a discriminated union supporting action: 'set' | 'delete' | 'clear', with 'set' as the default when omitted for backward compatibility.

api: Updated SignalDto validation to accept metadata signals with action variants. Modified updateMetadata to process clear (resets metadata to {}), delete (removes single key), and set operations in order.

Key technical decisions

  • Introduced discriminated MetadataSignal union (action: 'set' | 'delete' | 'clear') instead of separate signal types, reducing the overall Signal union complexity and simplifying server-side filtering logic.
  • New metadata operations queue as signals through the existing reply()/flush() pipeline rather than requiring a separate API path.
  • The action field defaults to 'set' when omitted, maintaining backward compatibility with existing code.

Testing

Added 4 new Vitest cases covering: metadata.delete() batched with reply, metadata.clear() batched with reply, mixed clear + set + delete operations preserving order, and metadata.clear() flushed after handler completion. All 316 framework tests pass.

…ixes NV-7501

Co-authored-by: Adam Chmara <adam.chmara1@gmail.com>
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 4, 2026

@netlify
Copy link
Copy Markdown

netlify Bot commented May 4, 2026

Deploy preview added

Name Link
🔨 Latest commit 4d40288
🔍 Latest deploy log https://app.netlify.com/projects/dashboard-v2-novu-staging/deploys/69f888ffe459e70008383066
😎 Deploy Preview https://deploy-preview-10971.dashboard-v2.novu-staging.co
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 37d41834-9c23-4d12-8260-29fb0b4fced5

📥 Commits

Reviewing files that changed from the base of the PR and between 9452d6f and 4d40288.

📒 Files selected for processing (6)
  • apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts
  • apps/api/src/app/agents/services/agent-conversation.service.ts
  • apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts
  • packages/framework/src/resources/agent/agent.context.ts
  • packages/framework/src/resources/agent/agent.test.ts
  • packages/framework/src/resources/agent/agent.types.ts

📝 Walkthrough

Walkthrough

The PR extends the metadata signal system to support three distinct action types (set, delete, clear), enabling agents to clear all metadata or remove individual keys in addition to setting values. Types, validators, and the signal processing pipeline are updated accordingly.

Changes

Metadata Signal Actions

Layer / File(s) Summary
Type Definitions
packages/framework/src/resources/agent/agent.types.ts
AgentContext.metadata now exposes delete(key) and clear() methods. MetadataSignal becomes a discriminated union: { type: 'metadata'; action?: 'set'; key; value }, { type: 'metadata'; action: 'delete'; key }, or { type: 'metadata'; action: 'clear' }.
DTO Validation
apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts
METADATA_ACTIONS constant added for ['set', 'delete', 'clear']. IsValidSignal.validate() now branches on signal.action (defaulting to 'set'): set requires valid key and defined value, delete requires only key, clear always passes. SignalDto extended with optional action field.
Framework Context
packages/framework/src/resources/agent/agent.context.ts
AgentContextImpl.metadata implementation now enqueues action-specific signal objects: set() produces { type: 'metadata', action: 'set', key, value }, delete(key) produces { type: 'metadata', action: 'delete', key }, clear() produces { type: 'metadata', action: 'clear' }.
Service Processing
apps/api/src/app/agents/services/agent-conversation.service.ts
UpdateMetadataParams.signals redefined to accept { type: 'metadata'; action?; key?; value? }[]. updateMetadata() applies conditional logic: action: 'clear' resets to {}, action: 'delete' removes merged[key], otherwise sets/replaces merged[key] = value. Signal activity content fallback: key ?? action ?? 'set'.
Signal Validation
apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts
executeSignals() filters metadata signals to keyed entries before validation. validateMetadataSignalKeys() now accepts optional value and action, requiring value only when action !== 'delete'.
Tests
packages/framework/src/resources/agent/agent.test.ts
Existing metadata tests updated to assert action: 'set' field. New tests verify ctx.metadata.delete(key) batches delete signals, ctx.metadata.clear() batches clear signals, mixed operations emit in order (clear, set, delete), and clear-only payloads flush correctly after handler completion.

Sequence Diagram

sequenceDiagram
    actor Agent
    participant AgentContext as Agent Context
    participant SignalQueue as Signal Queue
    participant Validator as Signal Validator
    participant Service as Metadata Service

    Agent->>AgentContext: metadata.set(key, value)
    AgentContext->>SignalQueue: enqueue {type: 'metadata', action: 'set', key, value}

    Agent->>AgentContext: metadata.delete(key)
    AgentContext->>SignalQueue: enqueue {type: 'metadata', action: 'delete', key}

    Agent->>AgentContext: metadata.clear()
    AgentContext->>SignalQueue: enqueue {type: 'metadata', action: 'clear'}

    Agent->>AgentContext: reply() / flush()
    AgentContext->>Validator: validate metadata signals
    Validator->>Validator: action='set': require key & value
    Validator->>Validator: action='delete': require key only
    Validator->>Validator: action='clear': always valid
    Validator-->>Service: valid signals
    Service->>Service: apply clear (reset to {})
    Service->>Service: apply delete (remove merged[key])
    Service->>Service: apply set (merged[key] = value)
    Service-->>Agent: updated metadata
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

@novu/shared

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title follows Conventional Commits format with valid type (feat) and scope (shared), uses imperative mood, and includes the Linear ticket reference (NV-7501) at the end as required.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 5/8 reviews remaining, refill in 22 minutes and 11 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/framework/src/resources/agent/agent.types.ts (1)

171-174: Treat the new AgentContext.metadata methods as a semver-significant API change.

Adding required members to a public exported interface can break downstream consumer mocks/implementations at compile time. Please make sure this ships under a release that allows breaking type-surface changes, or keep the addition backward-compatible.

As per coding guidelines, "packages/**/*: Treat all exported symbols in packages as public API; follow semver conventions with breaking changes requiring major bumps, new exports as minor versions, and fixes as patches."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/framework/src/resources/agent/agent.types.ts` around lines 171 -
174, You added required methods to the public AgentContext.metadata interface
(set, delete, clear), which is a semver-breaking type change; either revert them
or make the change backward-compatible by marking those members optional
(metadata.set?, metadata.delete?, metadata.clear?) and ensure any concrete
implementations (the classes/factories that construct AgentContext) provide
no-op or real implementations so consumers compiling against older mocks won't
break; alternatively, if you intend the breaking change, document it and ship
under a major-version bump per semver.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts`:
- Around line 55-60: The metadata_delete and metadata_clear branches currently
accept extra unrelated fields; update their validation to reject extra
properties by ensuring the signal object only contains the allowed keys: for
metadata_delete require signal.type === 'metadata_delete' AND
isValidMetadataSignalKey(signal.key) AND Object.keys(signal) is exactly
['type','key'], and for metadata_clear require signal.type === 'metadata_clear'
AND Object.keys(signal) is exactly ['type'] (do the same corresponding stricter
check in the other branch around lines 74-75). Use the existing
isValidMetadataSignalKey helper and the signal.type discriminator when
implementing the exact-key checks so the accepted shape matches
defaultMessage().

---

Nitpick comments:
In `@packages/framework/src/resources/agent/agent.types.ts`:
- Around line 171-174: You added required methods to the public
AgentContext.metadata interface (set, delete, clear), which is a semver-breaking
type change; either revert them or make the change backward-compatible by
marking those members optional (metadata.set?, metadata.delete?,
metadata.clear?) and ensure any concrete implementations (the classes/factories
that construct AgentContext) provide no-op or real implementations so consumers
compiling against older mocks won't break; alternatively, if you intend the
breaking change, document it and ship under a major-version bump per semver.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 286ff233-6f49-4c51-bbfd-b19df3319ff4

📥 Commits

Reviewing files that changed from the base of the PR and between b5365d7 and 9452d6f.

📒 Files selected for processing (8)
  • apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts
  • apps/api/src/app/agents/services/agent-conversation.service.ts
  • apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts
  • packages/framework/src/index.ts
  • packages/framework/src/resources/agent/agent.context.ts
  • packages/framework/src/resources/agent/agent.test.ts
  • packages/framework/src/resources/agent/agent.types.ts
  • packages/framework/src/resources/agent/index.ts

Comment on lines +55 to +60
if (signal.type === 'metadata_delete') {
return isValidMetadataSignalKey(signal.key);
}

if (signal.type === 'metadata_clear') {
return true;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject extra fields on metadata_delete and metadata_clear.

These branches currently accept payloads with unrelated fields like value, workflowId, or payload, so malformed signals still validate and the server silently ignores the extra data. Tighten the discriminator checks so the accepted shape matches the contract in defaultMessage().

Suggested fix
     if (signal.type === 'metadata_delete') {
-      return isValidMetadataSignalKey(signal.key);
+      return (
+        isValidMetadataSignalKey(signal.key) &&
+        signal.value === undefined &&
+        signal.workflowId === undefined &&
+        signal.to === undefined &&
+        signal.payload === undefined
+      );
     }
 
     if (signal.type === 'metadata_clear') {
-      return true;
+      return (
+        signal.key === undefined &&
+        signal.value === undefined &&
+        signal.workflowId === undefined &&
+        signal.to === undefined &&
+        signal.payload === undefined
+      );
     }

Also applies to: 74-75

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts` around lines 55 -
60, The metadata_delete and metadata_clear branches currently accept extra
unrelated fields; update their validation to reject extra properties by ensuring
the signal object only contains the allowed keys: for metadata_delete require
signal.type === 'metadata_delete' AND isValidMetadataSignalKey(signal.key) AND
Object.keys(signal) is exactly ['type','key'], and for metadata_clear require
signal.type === 'metadata_clear' AND Object.keys(signal) is exactly ['type'] (do
the same corresponding stricter check in the other branch around lines 74-75).
Use the existing isValidMetadataSignalKey helper and the signal.type
discriminator when implementing the exact-key checks so the accepted shape
matches defaultMessage().

…types

Replace MetadataSetSignal, MetadataDeleteSignal, MetadataClearSignal with
a single MetadataSignal union discriminated by an action field:

  { type: 'metadata', action?: 'set', key, value }
  { type: 'metadata', action: 'delete', key }
  { type: 'metadata', action: 'clear' }

This keeps Signal = MetadataSignal | TriggerSignal (no union expansion),
simplifies server-side filters (s.type === 'metadata' still catches all),
and is backward compatible (action defaults to 'set' when omitted).

Co-authored-by: Adam Chmara <adam.chmara1@gmail.com>
@ChmaraX ChmaraX marked this pull request as ready for review May 4, 2026 13:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants