Skip to content

feat(ts): add persistent_memory and user_id support#124

Merged
sawradip merged 2 commits into
mainfrom
fix/ts-persistent-memory
May 2, 2026
Merged

feat(ts): add persistent_memory and user_id support#124
sawradip merged 2 commits into
mainfrom
fix/ts-persistent-memory

Conversation

@sawradip
Copy link
Copy Markdown
Contributor

@sawradip sawradip commented May 1, 2026

Summary

  • Add userId and persistentMemory to RunAgentConfig interface
  • Add user_id and persistent_memory to ExecutionRequest interface
  • Store fields in RunAgentClient with env var fallback (RUNAGENT_USER_ID)
  • Add getUserId() and getPersistentMemory() getter methods
  • Conditionally include both fields in REST and WebSocket request payloads (only when set)

Brings the TypeScript SDK in line with the Rust and C# reference implementations per sdk_checklist.md.

Test plan

  • Verify getUserId() returns configured user ID (explicit or env var)
  • Verify getPersistentMemory() returns configured flag (defaults to false)
  • Verify user_id appears in REST POST body only when set
  • Verify persistent_memory appears in REST POST body only when true
  • Verify both fields appear in WebSocket request payload when set
  • Verify fields are omitted when not configured
  • Run npm run build to confirm TypeScript compiles

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • User identity tracking: Configure and retrieve user IDs for agent execution and streaming, allowing runs to be associated with a specific user.
    • Persistent memory control: Enable or disable persistent memory on a per-execution basis to control state retention across runs.
    • Unified API support: These options are supported for both REST and WebSocket-based agent execution and streaming.

- Add userId and persistentMemory to RunAgentConfig interface
- Add user_id and persistent_memory to ExecutionRequest interface
- Store fields in RunAgentClient with env var fallback (RUNAGENT_USER_ID)
- Add getUserId() and getPersistentMemory() getter methods
- Conditionally include user_id and persistent_memory in REST request body
- Conditionally include user_id and persistent_memory in WebSocket request payload

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 1, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8476166a-e242-4a37-a26f-b85168e641c8

📥 Commits

Reviewing files that changed from the base of the PR and between 1a53cff and 38e9b97.

📒 Files selected for processing (3)
  • runagent-ts/src/client/index.ts
  • runagent-ts/src/rest/index.ts
  • runagent-ts/src/websocket/base.ts
✅ Files skipped from review due to trivial changes (1)
  • runagent-ts/src/rest/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • runagent-ts/src/websocket/base.ts
  • runagent-ts/src/client/index.ts

📝 Walkthrough

Walkthrough

RunAgent client now captures optional userId and boolean persistentMemory (from config or env), exposes them via getters, and passes them into both REST (RestClient.runAgent) and WebSocket (BaseWebSocketClient.runStream) request payloads as user_id and persistent_memory.

Changes

User identity & persistent-memory flow

Layer / File(s) Summary
Data Shape
runagent-ts/src/types/index.ts
Added optional userId?: string and persistentMemory?: boolean to RunAgentConfig; added optional user_id?: string and persistent_memory?: boolean to ExecutionRequest.
Client state & parsing
runagent-ts/src/client/index.ts
Added parseEnvBool() helper; introduced private _userId and _persistentMemory initialized from config or environment (RUNAGENT_USER_ID, RUNAGENT_PERSISTENT_MEMORY); added public getters getUserId() and getPersistentMemory().
Request construction (REST)
runagent-ts/src/rest/index.ts
RestClient.runAgent signature extended with userId?: string, persistentMemory?: boolean; request body built as Record<string, unknown> and conditionally includes user_id and persistent_memory before casting to JsonValue.
Request construction (WebSocket)
runagent-ts/src/websocket/base.ts
BaseWebSocketClient.runStream signature extended with userId?: string, persistentMemory?: boolean; ExecutionRequest sent over socket conditionally sets user_id and persistent_memory.
Wiring / Invocation
runagent-ts/src/client/index.ts
executeRun and executeStream now pass stored _userId and _persistentMemory into restClient.runAgent(...) and socketClient.runStream(...) respectively.

Sequence Diagram(s)

sequenceDiagram
    participant Client as RunAgentClient
    participant REST as RestClient
    participant Socket as BaseWebSocketClient
    participant Server as RunAgent API

    Client->>Client: read config / env -> _userId, _persistentMemory
    Client->>REST: runAgent(agentId, entrypoint, options, userId, persistentMemory)
    REST->>Server: POST /execute {..., user_id?, persistent_memory?}
    Server-->>REST: 200 / response
    REST-->>Client: ApiResponse

    Client->>Socket: runStream(agentId, entrypoint, options, userId, persistentMemory)
    Socket->>Server: WS send {..., user_id?, persistent_memory?}
    Server-->>Socket: stream events
    Socket-->>Client: async events
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Sawra/sdk fix ts #90 — Overlaps changes to the TypeScript client WebSocket and client code; may conflict in the same classes (BaseWebSocketClient, client/index.ts).

Poem

🐰
I parsed the bits from env and config,
threaded user and memory without a schtick,
through REST and sockets I nimbly hop,
storing seeds so context won't stop,
hopping on—persistent dreams, quick! 🥕

🚥 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 clearly and concisely summarizes the main changes: adding support for persistent_memory and user_id fields to the TypeScript SDK.
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.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ts-persistent-memory

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: 0/1 reviews remaining, refill in 60 minutes.

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

Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@runagent-ts/src/rest/index.ts`:
- Around line 62-75: The REST handler builds requestData and currently uses a
truthy check (if (userId)) which drops explicitly set empty strings; change that
to a nullish check (if (userId != null)) so user_id is included when caller sets
"" but excluded only when null/undefined, and apply the same change pattern to
the analogous logic in websocket/base.ts to keep REST and WS behavior
consistent; update the reference to requestData.user_id and ensure any similar
check for persistentMemory uses the same nullish pattern if intended.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 312fe65c-865f-450a-882c-04e775de3b53

📥 Commits

Reviewing files that changed from the base of the PR and between 2c0de38 and 1a53cff.

📒 Files selected for processing (4)
  • runagent-ts/src/client/index.ts
  • runagent-ts/src/rest/index.ts
  • runagent-ts/src/types/index.ts
  • runagent-ts/src/websocket/base.ts

Comment on lines +62 to +75
const requestData: Record<string, unknown> = {
entrypoint_tag: entrypointTag,
input_args: inputArgs,
input_kwargs: inputKwargs,
timeout_seconds: timeoutSeconds,
async_execution: false,
} as JsonValue;
};

if (userId) {
requestData.user_id = userId;
}
if (persistentMemory) {
requestData.persistent_memory = persistentMemory;
}
Copy link
Copy Markdown

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

Use a nullish check for userId.

if (userId) drops an explicitly configured empty string, so the field can disappear even when the caller set it. Switching to a null/undefined check keeps the request body aligned with the stored config; please mirror the same behavior in websocket/base.ts so REST/WS stay consistent.

🔧 Proposed fix
-      if (userId) {
+      if (userId != null) {
         requestData.user_id = userId;
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const requestData: Record<string, unknown> = {
entrypoint_tag: entrypointTag,
input_args: inputArgs,
input_kwargs: inputKwargs,
timeout_seconds: timeoutSeconds,
async_execution: false,
} as JsonValue;
};
if (userId) {
requestData.user_id = userId;
}
if (persistentMemory) {
requestData.persistent_memory = persistentMemory;
}
const requestData: Record<string, unknown> = {
entrypoint_tag: entrypointTag,
input_args: inputArgs,
input_kwargs: inputKwargs,
timeout_seconds: timeoutSeconds,
async_execution: false,
};
if (userId != null) {
requestData.user_id = userId;
}
if (persistentMemory) {
requestData.persistent_memory = persistentMemory;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@runagent-ts/src/rest/index.ts` around lines 62 - 75, The REST handler builds
requestData and currently uses a truthy check (if (userId)) which drops
explicitly set empty strings; change that to a nullish check (if (userId !=
null)) so user_id is included when caller sets "" but excluded only when
null/undefined, and apply the same change pattern to the analogous logic in
websocket/base.ts to keep REST and WS behavior consistent; update the reference
to requestData.user_id and ensure any similar check for persistentMemory uses
the same nullish pattern if intended.

@sawradip
Copy link
Copy Markdown
Contributor Author

sawradip commented May 1, 2026

Code Review — TypeScript SDK persistent_memory / user_id (fix/ts-persistent-memory)

The overall structure is correct: fields are added to config/types, stored in the client, threaded through to REST and WebSocket callers, and getters are provided. Two real bugs need fixing before merge.


Bug 1 — persistent_memory: false can never be sent (REST + WebSocket)

Both rest/index.ts and websocket/base.ts use:

if (persistentMemory) {
  requestData.persistent_memory = persistentMemory;
}

Because persistentMemory is boolean | undefined, the value false is falsy and is silently dropped — treated identically to undefined. A caller who sets persistentMemory: false to disable a server-side default of true will have the field omitted entirely. The correct guard is:

if (persistentMemory !== undefined) {
  requestData.persistent_memory = persistentMemory;
}

The Go SDK avoids this because its zero value for bool is false and persistentMemory is always present — only "" for userId is guarded. TypeScript has a three-state situation (true / false / undefined) that requires an explicit !== undefined check. The PR description says "only when true", but that makes false unrepresentable over the wire.


Bug 2 — No RUNAGENT_PERSISTENT_MEMORY env var support

The Go SDK (PR #123) reads RUNAGENT_PERSISTENT_MEMORY from the environment with full parse-bool logic. This SDK only reads RUNAGENT_USER_ID. The two SDKs should be symmetric. Add env var fallback for persistentMemory:

// helper
const parseEnvBool = (name: string): boolean | undefined => {
  const val = getEnvVar(name);
  if (val === undefined) return undefined;
  return val.toLowerCase() === 'true' || val === '1';
};

// in constructor
this._persistentMemory = config.persistentMemory ?? parseEnvBool('RUNAGENT_PERSISTENT_MEMORY') ?? false;

What looks good

  • RunAgentConfig additions (userId?: string, persistentMemory?: boolean) are correctly typed as optional.
  • ExecutionRequest additions (user_id?: string, persistent_memory?: boolean) match the snake_case API contract.
  • _userId = config.userId ?? getEnvVar('RUNAGENT_USER_ID') env fallback is correct and consistent with the Go SDK.
  • getUserId(): string | undefined and getPersistentMemory(): boolean return types are correct.
  • if (userId) guard (skips empty string) is consistent with Go's userId != "" check.
  • Both executeRun and executeStream in client/index.ts correctly forward this._userId and this._persistentMemory to the underlying clients.
  • No any introduced; Record<string, unknown> in the REST path is the right approach.
  • Removing the as JsonValue cast in favor of the typed typedRequestData intermediary is a genuine type-safety improvement.
  • The ExecutionRequest interface mutation in the WebSocket path is valid since user_id/persistent_memory are now declared optional on the interface.

Summary: request changes for two issues — (1) use !== undefined guard for persistent_memory so false can be sent over the wire, and (2) add RUNAGENT_PERSISTENT_MEMORY env var support to match the Go SDK reference implementation.

…upport

- Fix: use `persistentMemory !== undefined` instead of `if (persistentMemory)` so
  `false` can be sent over the wire (REST + WebSocket)
- Add RUNAGENT_PERSISTENT_MEMORY env var support with parseEnvBool helper,
  matching the Go SDK reference implementation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sawradip
Copy link
Copy Markdown
Contributor Author

sawradip commented May 2, 2026

Re-review after 38e9b97 — everything looks correct, approving in spirit (can't self-approve).

persistentMemory !== undefined guard (REST + WebSocket): Both paths now correctly send persistent_memory: false over the wire when the caller explicitly sets it. The old if (persistentMemory) would have silently dropped false due to it being falsy — the fix is exactly right and is consistent between the two transports.

parseEnvBool helper: Handles all cases correctly:

  • Absent env var → undefined (lets the ?? false constructor fallback apply)
  • "true"/"TRUE"/"1"true
  • "false" and anything else → false

Return type boolean | undefined fits the ?? false chain in the constructor perfectly.

Constructor wiring: config.persistentMemory ?? parseEnvBool('RUNAGENT_PERSISTENT_MEMORY') ?? false follows the same precedence pattern as every other env var in the file (explicit config → env var → default). Correct.

One pre-existing minor note (not introduced by this PR): if (userId) is still a truthy check rather than !== undefined, so an empty-string userId would be silently dropped. Worth a follow-up cleanup but it predates 38e9b97 and is out of scope here.

All changes in 38e9b97 are correct. Ready to merge.

@sawradip sawradip merged commit 3d7c91f into main May 2, 2026
2 checks passed
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