Skip to content

refactor(sdk): introduce session-based architecture - #19180

Merged
mbleigh merged 17 commits into
mainfrom
sdk-05-refactor-session
Feb 20, 2026
Merged

refactor(sdk): introduce session-based architecture#19180
mbleigh merged 17 commits into
mainfrom
sdk-05-refactor-session

Conversation

@mbleigh

@mbleigh mbleigh commented Feb 16, 2026

Copy link
Copy Markdown
Contributor

This change introduces the GeminiCliSession class to manage conversation state, separating it from the main GeminiCliAgent. This allows for cleaner session management and enables session resumption.

BREAKING CHANGE: The sendStream method on GeminiCliAgent has been removed. Use agent.session().sendStream() instead.

This change introduces the GeminiCliSession class to manage conversation state, separating it from the main GeminiCliAgent. This allows for cleaner session management and enables session resumption.

BREAKING CHANGE: The `sendStream` method on `GeminiCliAgent` has been removed. Use `agent.session().sendStream()` instead.
@mbleigh
mbleigh requested a review from a team as a code owner February 16, 2026 00:57
@gemini-cli

gemini-cli Bot commented Feb 16, 2026

Copy link
Copy Markdown
Contributor

Hi there! Thank you for your contribution to Gemini CLI.

To improve our contribution process and better track changes, we now require all pull requests to be associated with an existing issue, as announced in our recent discussion and as detailed in our CONTRIBUTING.md.

This pull request is being closed because it is not currently linked to an issue. Once you have updated the description of this PR to link an issue (e.g., by adding Fixes #123 or Related to #123), it will be automatically reopened.

How to link an issue:
Add a keyword followed by the issue number (e.g., Fixes #123) in the description of your pull request. For more details on supported keywords and how linking works, please refer to the GitHub Documentation on linking pull requests to issues.

Thank you for your understanding and for being a part of our community!

@gemini-cli gemini-cli Bot closed this Feb 16, 2026
@gemini-cli

gemini-cli Bot commented Feb 16, 2026

Copy link
Copy Markdown
Contributor

Hi @mbleigh, thank you so much for your contribution to Gemini CLI! We really appreciate the time and effort you've put into this.

We're making some updates to our contribution process to improve how we track and review changes. Please take a moment to review our recent discussion post: Improving Our Contribution Process & Introducing New Guidelines.

Key Update: Starting January 26, 2026, the Gemini CLI project will require all pull requests to be associated with an existing issue. Any pull requests not linked to an issue by that date will be automatically closed.

Thank you for your understanding and for being a part of our community!

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @mbleigh, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant architectural refactor to the SDK by implementing a session-based approach for managing conversations. The core GeminiCliAgent now focuses on creating and resuming conversation sessions, delegating the actual conversation logic and state management to a new GeminiCliSession class. This change improves modularity, clarifies responsibilities, and lays the groundwork for robust session persistence and resumption features, enhancing the overall developer experience and flexibility of the SDK.

Highlights

  • Introduced GeminiCliSession class: A new GeminiCliSession class has been introduced to encapsulate conversation state, tool/skill management, and the sendStream method, separating these concerns from the main GeminiCliAgent.
  • Refactored GeminiCliAgent: The GeminiCliAgent has been refactored to act as a factory for GeminiCliSession instances, providing session() and resumeSession() methods. It no longer directly handles conversation streaming or internal configuration management.
  • Session Resumption Capability: The new architecture enables session resumption, allowing users to pick up previous conversations. The GeminiCliAgent now includes a resumeSession() method to facilitate this.
  • Breaking Change: sendStream method moved: The sendStream method has been removed from GeminiCliAgent. Consumers must now call agent.session().sendStream() or (await agent.resumeSession()).sendStream().
  • Updated Documentation and Tests: The SDK_DESIGN.md documentation and all relevant integration tests have been updated to reflect the new session-based API and usage patterns.
Changelog
  • packages/core/src/index.ts
    • Exported createSessionId function.
  • packages/core/src/utils/session.ts
    • Added createSessionId function to generate new UUIDs for sessions.
  • packages/sdk/SDK_DESIGN.md
    • Updated documentation to reflect the new session() and resumeSession() methods on GeminiCliAgent.
    • Removed sendStream from GeminiCliAgent examples.
    • Added session: GeminiCliSession to the SessionContext interface.
  • packages/sdk/src/agent.integration.test.ts
    • Updated integration tests to use the new agent.session().sendStream() pattern.
    • Added a new integration test for resumeSession functionality.
    • Removed previous tests related to dynamic instructions directly on the agent.
  • packages/sdk/src/agent.ts
    • Refactored GeminiCliAgent to remove direct sendStream implementation.
    • Introduced session() method to create new GeminiCliSession instances.
    • Added resumeSession() method to load and resume existing conversation sessions from storage.
    • Removed internal Config initialization and tool/skill loading logic, delegating it to GeminiCliSession.
  • packages/sdk/src/index.ts
    • Exported the new session.js module.
  • packages/sdk/src/session.ts
    • Added new file defining the GeminiCliSession class.
    • Implemented GeminiCliSession to manage individual conversation state, including Config initialization, tool/skill loading, dynamic instruction handling, and the sendStream method.
    • Included logic for resuming conversations from stored data within the session.
  • packages/sdk/src/skills.integration.test.ts
    • Updated skill integration tests to use agent.session().sendStream().
  • packages/sdk/src/tool.integration.test.ts
    • Updated tool integration tests to use agent.session().sendStream().
  • packages/sdk/src/types.ts
    • Moved SystemInstructions and GeminiCliAgentOptions interfaces from agent.ts.
    • Added GeminiCliSession type import.
    • Added an optional session?: GeminiCliSession property to the SessionContext interface.
  • packages/sdk/test-data/agent-dynamic-instructions.json
    • Updated test data to reflect changes in dynamic instruction handling.
  • packages/sdk/test-data/agent-resume-session.json
    • Added new test data for session resumption scenarios.
  • packages/sdk/test-data/agent-static-instructions.json
    • Updated test data.
  • packages/sdk/test-data/skill-dir-success.json
    • Updated test data.
  • packages/sdk/test-data/skill-root-success.json
    • Updated test data.
  • packages/sdk/test-data/tool-catchall-error.json
    • Updated test data.
  • packages/sdk/test-data/tool-error-recovery.json
    • Updated test data.
  • packages/sdk/test-data/tool-success.json
    • Updated test data.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request introduces a session-based architecture, separating conversation state from the main GeminiCliAgent into a new GeminiCliSession class, which improves modularity and enables session resumption. A critical security concern has been identified: the default configuration of the policy engine within the new GeminiCliSession class defaults to allowing all tool calls. This could lead to Remote Code Execution (RCE) if an agent is compromised via prompt injection, and it is recommended to change this default to a more secure 'deny-by-default' stance. Additionally, errors in dynamic instruction loading are silently ignored, and there is an inconsistent type definition for SessionContext in the public API.

Comment thread packages/sdk/src/session.ts Outdated
Comment thread packages/sdk/src/session.ts
Comment thread packages/sdk/src/types.ts Outdated
@mbleigh mbleigh reopened this Feb 16, 2026
@github-actions

github-actions Bot commented Feb 16, 2026

Copy link
Copy Markdown

Size Change: +1.69 kB (+0.01%)

Total Size: 24.6 MB

Filename Size Change
./bundle/gemini.js 24.5 MB +1.69 kB (+0.01%)
ℹ️ View Unchanged
Filename Size
./bundle/sandbox-macos-permissive-open.sb 890 B
./bundle/sandbox-macos-permissive-proxied.sb 1.31 kB
./bundle/sandbox-macos-restrictive-open.sb 3.36 kB
./bundle/sandbox-macos-restrictive-proxied.sb 3.56 kB
./bundle/sandbox-macos-strict-open.sb 4.82 kB
./bundle/sandbox-macos-strict-proxied.sb 5.02 kB

compressed-size-action

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@gemini-cli gemini-cli Bot added the status/need-issue Pull requests that need to have an associated issue. label Feb 16, 2026
@gemini-cli

gemini-cli Bot commented Feb 17, 2026

Copy link
Copy Markdown
Contributor

Hi there! Thank you for your contribution to Gemini CLI.

To improve our contribution process and better track changes, we now require all pull requests to be associated with an existing issue, as announced in our recent discussion and as detailed in our CONTRIBUTING.md.

This pull request is being closed because it is not currently linked to an issue. Once you have updated the description of this PR to link an issue (e.g., by adding Fixes #123 or Related to #123), it will be automatically reopened.

How to link an issue:
Add a keyword followed by the issue number (e.g., Fixes #123) in the description of your pull request. For more details on supported keywords and how linking works, please refer to the GitHub Documentation on linking pull requests to issues.

Thank you for your understanding and for being a part of our community!

@gemini-cli gemini-cli Bot closed this Feb 17, 2026
@mbleigh mbleigh reopened this Feb 17, 2026
Comment thread packages/sdk/src/agent.ts
const registry = this.config.getToolRegistry();
const messageBus = this.config.getMessageBus();
async resumeSession(sessionId?: string): Promise<GeminiCliSession> {
const cwd = this.options.cwd || process.cwd();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It'd be interesting to try and bind a session to a specific file path. Otherwise resuming from a different one would be a bit odd

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The sessions are essentially bound to a specific file path, because they are loaded from the project temp dir. So it can only resume a session for the same cwd path as implemented now.

Comment thread packages/sdk/src/agent.ts Outdated
registry.registerTool(sdkTool);
}
}
const chatsDir = path.join(storage.getProjectTempDir(), 'chats');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There should be options for this BUT I'd imagine we'd want to utilize the storage object directly. Does it make sense to surface the normal dir that's used by the CLI?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, good callout. Added some new methods to storage to do this more directly.

Comment thread packages/sdk/src/agent.ts Outdated
// Sort by mtime desc
fileStats.sort((a, b) => b.mtime - a.mtime);

let targetFile: { filePath: string } | undefined;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Feels odd to surface target file as a json array. After sorting above might just be worth mapping the file paths down to their final file path for readability

Comment thread packages/sdk/src/agent.ts Outdated
prompt_id: sessionId,
});
} catch (_e) {
// Ignore parse errors

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hmm, feels like a debug log should be here. I'd imagine not finding your chat history would be super frustrating otherwise

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Adding a TODO for this to come back to when I do logging.

Comment thread packages/sdk/src/agent.ts Outdated
targetFile = fileStats[0];
}

const content = await fs.promises.readFile(targetFile.filePath, 'utf8');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Instead of re-reading the file path (since we already did) it might be worth eagerly returning above instead of breaking

private readonly tools: Array<Tool<any>>;
private readonly skillRefs: SkillReference[];
private readonly instructions: SystemInstructions | undefined;
private client: GeminiClient | undefined;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What is a session without a client?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Client is initialized async based on the config, so it can't be required. But it's effectively required.

Comment thread packages/sdk/src/session.ts Outdated
this.tools = options.tools || [];
this.skillRefs = options.skills || [];

const initialMemory =

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should we be throwing if the instructions aren't a string? Feels like falling back to empty string will bite a lot of people

Comment thread packages/sdk/src/session.ts Outdated

const loadPromises = this.skillRefs.map(async (ref) => {
try {
if (ref.type === 'dir' || ref.type === 'root') {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Left a comment in another PR around using "user" and "workspace" to align with existing CLI verbiage

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I actually cleaned this up there but this is a straggler - the type here is not 'user' or 'workspace' since it's allowing passing in arbitrary directories. But the 'dir' and 'root' distinction doesn't matter because the loader will automatically detect and handle nested directories. Once we have in-memory skills we'll add a second, non-"dir" type here.

}
} catch (e) {
// eslint-disable-next-line no-console
console.error(`Failed to load skills from ${ref.path}:`, e);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

For SDK's we should probably have a dedicated logger stack instead of relying on console

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will do this in a followup as it's a bit of a big change to tack in here.

}

// Re-register ActivateSkillTool if we have skills
const skillManager = this.config.getSkillManager();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'll stop reviewing this bit, had a few other comments in other PRs about surfacing a lot of these bits in the core CLI instead of re-implementing

@mbleigh
mbleigh force-pushed the sdk-05-refactor-session branch from 766b2cf to 2118700 Compare February 18, 2026 01:14
@mbleigh
mbleigh force-pushed the sdk-05-refactor-session branch from 2118700 to 33e4c12 Compare February 18, 2026 01:25
Comment thread packages/sdk/src/session.ts Outdated

const loadPromises = this.skillRefs.map(async (ref) => {
try {
if (ref.type === 'dir' || ref.type === 'root') {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I actually cleaned this up there but this is a straggler - the type here is not 'user' or 'workspace' since it's allowing passing in arbitrary directories. But the 'dir' and 'root' distinction doesn't matter because the loader will automatically detect and handle nested directories. Once we have in-memory skills we'll add a second, non-"dir" type here.

Comment thread packages/sdk/src/agent.ts Outdated
prompt_id: sessionId,
});
} catch (_e) {
// Ignore parse errors

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Adding a TODO for this to come back to when I do logging.

Comment thread packages/sdk/src/agent.ts Outdated
registry.registerTool(sdkTool);
}
}
const chatsDir = path.join(storage.getProjectTempDir(), 'chats');

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, good callout. Added some new methods to storage to do this more directly.

Comment thread packages/sdk/src/agent.ts
const registry = this.config.getToolRegistry();
const messageBus = this.config.getMessageBus();
async resumeSession(sessionId?: string): Promise<GeminiCliSession> {
const cwd = this.options.cwd || process.cwd();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The sessions are essentially bound to a specific file path, because they are loaded from the project temp dir. So it can only resume a session for the same cwd path as implemented now.

private readonly tools: Array<Tool<any>>;
private readonly skillRefs: SkillReference[];
private readonly instructions: SystemInstructions | undefined;
private client: GeminiClient | undefined;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Client is initialized async based on the config, so it can't be required. But it's effectively required.

return await loadSkillsFromDir(ref.path);
}
} catch (e) {
// eslint-disable-next-line no-console

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
// eslint-disable-next-line no-console
// TODO: refactor this to use a proper logger interface
// eslint-disable-next-line no-console

}
} catch (e) {
// eslint-disable-next-line no-console
console.error(`Failed to load skills from ${ref.path}:`, e);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will do this in a followup as it's a bit of a big change to tack in here.

@gemini-cli

gemini-cli Bot commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

Hi there! Thank you for your contribution to Gemini CLI.

To improve our contribution process and better track changes, we now require all pull requests to be associated with an existing issue, as announced in our recent discussion and as detailed in our CONTRIBUTING.md.

This pull request is being closed because it is not currently linked to an issue. Once you have updated the description of this PR to link an issue (e.g., by adding Fixes #123 or Related to #123), it will be automatically reopened.

How to link an issue:
Add a keyword followed by the issue number (e.g., Fixes #123) in the description of your pull request. For more details on supported keywords and how linking works, please refer to the GitHub Documentation on linking pull requests to issues.

Thank you for your understanding and for being a part of our community!

@gemini-cli gemini-cli Bot closed this Feb 18, 2026
@mbleigh mbleigh reopened this Feb 19, 2026
@mbleigh
mbleigh enabled auto-merge February 20, 2026 00:38
@mbleigh
mbleigh added this pull request to the merge queue Feb 20, 2026
Merged via the queue into main with commit f1c0a69 Feb 20, 2026
27 checks passed
@mbleigh
mbleigh deleted the sdk-05-refactor-session branch February 20, 2026 00:57
spencer426 pushed a commit that referenced this pull request Feb 20, 2026
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
spencer426 pushed a commit that referenced this pull request Feb 20, 2026
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
tripleyoung pushed a commit to tripleyoung/gemini-cli that referenced this pull request Feb 27, 2026
)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
tripleyoung pushed a commit to tripleyoung/gemini-cli that referenced this pull request Feb 27, 2026
)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
liamhelmer pushed a commit to badal-io/gemini-cli that referenced this pull request Mar 12, 2026
)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
cocosheng-g pushed a commit that referenced this pull request May 6, 2026
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@sripasg sripasg added the size/l A large sized PR label Jun 2, 2026
software-0ficial pushed a commit to software-0ficial/gemini-cli that referenced this pull request Jul 9, 2026
)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l A large sized PR status/need-issue Pull requests that need to have an associated issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants