Skip to content

Commit 527fd10

Browse files
MorabbinCopilot
andcommitted
Add memory configuration API to Go, Node, Python, .NET, and Java SDKs
Mirror the Rust session memory surface across the remaining SDK languages: session create and resume accept an optional memory configuration carrying a required `enabled` flag, omitted from the wire when unset. Adds the type, the create/resume wiring, clone and serialization coverage, and README docs per language. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 74c0b28 commit 527fd10

28 files changed

Lines changed: 589 additions & 0 deletions

dotnet/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,20 @@ When enabled, sessions emit compaction events:
410410
- `SessionCompactionStartEvent` - Background compaction started
411411
- `SessionCompactionCompleteEvent` - Compaction finished (includes token counts)
412412

413+
## Memory
414+
415+
Sessions can opt into persistent memory, allowing the agent to read and write memory across turns. Memory is configured per session and applies to both `CreateSessionAsync` and `ResumeSessionAsync`.
416+
417+
```csharp
418+
var session = await client.CreateSessionAsync(new SessionConfig
419+
{
420+
Model = "gpt-5",
421+
Memory = new MemoryConfiguration { Enabled = true }
422+
});
423+
```
424+
425+
When `Memory` is left unset, no memory configuration is sent and the runtime default applies.
426+
413427
## Advanced Usage
414428

415429
### Manual Server Control

dotnet/src/Client.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig config, Cance
935935
InstructionDirectories: config.InstructionDirectories,
936936
PluginDirectories: config.PluginDirectories,
937937
LargeOutput: config.LargeOutput,
938+
Memory: config.Memory,
938939
Canvases: config.Canvases,
939940
RequestCanvasRenderer: config.RequestCanvasRenderer,
940941
RequestExtensions: config.RequestExtensions,
@@ -1131,6 +1132,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
11311132
InstructionDirectories: config.InstructionDirectories,
11321133
PluginDirectories: config.PluginDirectories,
11331134
LargeOutput: config.LargeOutput,
1135+
Memory: config.Memory,
11341136
Canvases: config.Canvases,
11351137
RequestCanvasRenderer: config.RequestCanvasRenderer,
11361138
RequestExtensions: config.RequestExtensions,
@@ -2322,6 +2324,7 @@ internal record CreateSessionRequest(
23222324
IList<string>? InstructionDirectories = null,
23232325
IList<string>? PluginDirectories = null,
23242326
LargeToolOutputConfig? LargeOutput = null,
2327+
MemoryConfiguration? Memory = null,
23252328
#pragma warning disable GHCP001
23262329
IList<CanvasDeclaration>? Canvases = null,
23272330
bool? RequestCanvasRenderer = null,
@@ -2409,6 +2412,7 @@ internal record ResumeSessionRequest(
24092412
IList<string>? InstructionDirectories = null,
24102413
IList<string>? PluginDirectories = null,
24112414
LargeToolOutputConfig? LargeOutput = null,
2415+
MemoryConfiguration? Memory = null,
24122416
#pragma warning disable GHCP001
24132417
IList<CanvasDeclaration>? Canvases = null,
24142418
bool? RequestCanvasRenderer = null,

dotnet/src/Types.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2364,6 +2364,18 @@ public sealed class LargeToolOutputConfig
23642364
public string? OutputDirectory { get; set; }
23652365
}
23662366

2367+
/// <summary>
2368+
/// Configuration for session memory.
2369+
/// </summary>
2370+
public sealed class MemoryConfiguration
2371+
{
2372+
/// <summary>
2373+
/// Whether memory is enabled for the session.
2374+
/// </summary>
2375+
[JsonPropertyName("enabled")]
2376+
public bool Enabled { get; set; }
2377+
}
2378+
23672379
/// <summary>
23682380
/// GitHub repository metadata to associate with a cloud session.
23692381
/// </summary>
@@ -2458,6 +2470,7 @@ protected SessionConfigBase(SessionConfigBase? other)
24582470
Hooks = other.Hooks;
24592471
InfiniteSessions = other.InfiniteSessions;
24602472
LargeOutput = other.LargeOutput;
2473+
Memory = other.Memory;
24612474
McpServers = other.McpServers is not null
24622475
? (other.McpServers is Dictionary<string, McpServerConfig> dict
24632476
? new Dictionary<string, McpServerConfig>(dict, dict.Comparer)
@@ -2805,6 +2818,12 @@ protected SessionConfigBase(SessionConfigBase? other)
28052818
/// </summary>
28062819
public LargeToolOutputConfig? LargeOutput { get; set; }
28072820

2821+
/// <summary>
2822+
/// Configuration for session memory. When set, controls whether the
2823+
/// session can read and write persistent memory.
2824+
/// </summary>
2825+
public MemoryConfiguration? Memory { get; set; }
2826+
28082827
/// <summary>
28092828
/// Optional event handler registered on the session before the session.create / session.resume
28102829
/// RPC is issued, ensuring early events are delivered.

dotnet/test/Unit/CloneTests.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
9797
DisabledSkills = ["skill1"],
9898
PluginDirectories = ["/plugins"],
9999
LargeOutput = new LargeToolOutputConfig { Enabled = true, MaxSizeBytes = 2048, OutputDirectory = "/tmp/out" },
100+
Memory = new MemoryConfiguration { Enabled = true },
100101
OnExitPlanModeRequest = static (_, _) => Task.FromResult(new ExitPlanModeResult()),
101102
OnAutoModeSwitchRequest = static (_, _) => Task.FromResult(AutoModeSwitchResponse.No),
102103
};
@@ -129,6 +130,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
129130
Assert.Equal(original.DisabledSkills, clone.DisabledSkills);
130131
Assert.Equal(original.PluginDirectories, clone.PluginDirectories);
131132
Assert.Same(original.LargeOutput, clone.LargeOutput);
133+
Assert.Same(original.Memory, clone.Memory);
132134
Assert.Same(original.OnExitPlanModeRequest, clone.OnExitPlanModeRequest);
133135
Assert.Same(original.OnAutoModeSwitchRequest, clone.OnAutoModeSwitchRequest);
134136
}
@@ -402,12 +404,14 @@ public void ResumeSessionConfig_Clone_CopiesPluginDirectoriesAndLargeOutput()
402404
{
403405
PluginDirectories = ["/resume/plugins"],
404406
LargeOutput = largeOutput,
407+
Memory = new MemoryConfiguration { Enabled = true },
405408
};
406409

407410
var clone = original.Clone();
408411

409412
Assert.Equal(original.PluginDirectories, clone.PluginDirectories);
410413
Assert.Same(original.LargeOutput, clone.LargeOutput);
414+
Assert.Same(original.Memory, clone.Memory);
411415
}
412416

413417
[Fact]

dotnet/test/Unit/SerializationTests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,58 @@ public void SessionRequests_CanSerializePluginDirectoriesAndLargeOutput_WithSdkO
267267
Assert.Equal("/tmp/large-output", resumeLargeOutput.GetProperty("outputDir").GetString());
268268
}
269269

270+
[Fact]
271+
public void SessionRequests_CanSerializeMemory_WithSdkOptions()
272+
{
273+
var options = GetSerializerOptions();
274+
275+
var createRequestType = GetNestedType(typeof(CopilotClient), "CreateSessionRequest");
276+
var createRequest = CreateInternalRequest(
277+
createRequestType,
278+
("SessionId", "session-id"),
279+
("Memory", new MemoryConfiguration { Enabled = true }));
280+
281+
var createJson = JsonSerializer.Serialize(createRequest, createRequestType, options);
282+
using var createDocument = JsonDocument.Parse(createJson);
283+
var createRoot = createDocument.RootElement;
284+
Assert.True(createRoot.GetProperty("memory").GetProperty("enabled").GetBoolean());
285+
286+
var resumeRequestType = GetNestedType(typeof(CopilotClient), "ResumeSessionRequest");
287+
var resumeRequest = CreateInternalRequest(
288+
resumeRequestType,
289+
("SessionId", "session-id"),
290+
("Memory", new MemoryConfiguration { Enabled = false }));
291+
292+
var resumeJson = JsonSerializer.Serialize(resumeRequest, resumeRequestType, options);
293+
using var resumeDocument = JsonDocument.Parse(resumeJson);
294+
var resumeRoot = resumeDocument.RootElement;
295+
Assert.False(resumeRoot.GetProperty("memory").GetProperty("enabled").GetBoolean());
296+
}
297+
298+
[Fact]
299+
public void SessionRequests_OmitMemory_WhenUnset()
300+
{
301+
var options = GetSerializerOptions();
302+
303+
var createRequestType = GetNestedType(typeof(CopilotClient), "CreateSessionRequest");
304+
var createRequest = CreateInternalRequest(
305+
createRequestType,
306+
("SessionId", "session-id"));
307+
308+
var createJson = JsonSerializer.Serialize(createRequest, createRequestType, options);
309+
using var createDocument = JsonDocument.Parse(createJson);
310+
Assert.False(createDocument.RootElement.TryGetProperty("memory", out _));
311+
312+
var resumeRequestType = GetNestedType(typeof(CopilotClient), "ResumeSessionRequest");
313+
var resumeRequest = CreateInternalRequest(
314+
resumeRequestType,
315+
("SessionId", "session-id"));
316+
317+
var resumeJson = JsonSerializer.Serialize(resumeRequest, resumeRequestType, options);
318+
using var resumeDocument = JsonDocument.Parse(resumeJson);
319+
Assert.False(resumeDocument.RootElement.TryGetProperty("memory", out _));
320+
}
321+
270322
[Fact]
271323
public void CreateSessionRequest_CanSerializeEnableSessionTelemetry_WithSdkOptions()
272324
{

go/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,30 @@ When enabled, sessions emit compaction events:
485485
- `session.compaction_start` - Background compaction started
486486
- `session.compaction_complete` - Compaction finished (includes token counts)
487487

488+
## Memory
489+
490+
Sessions can opt in to the memory feature, which lets the agent persist and recall
491+
information across turns. Provide a `MemoryConfiguration` on session create or resume;
492+
when omitted, the runtime default applies.
493+
494+
```go
495+
// Enable memory for a session
496+
session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{
497+
Model: "gpt-5",
498+
Memory: &copilot.MemoryConfiguration{
499+
Enabled: true,
500+
},
501+
})
502+
503+
// Disable memory for a session
504+
session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{
505+
Model: "gpt-5",
506+
Memory: &copilot.MemoryConfiguration{
507+
Enabled: false,
508+
},
509+
})
510+
```
511+
488512
## Custom Providers
489513

490514
The SDK supports custom OpenAI-compatible API providers (BYOK - Bring Your Own Key), including local providers like Ollama. When using a custom provider, you must specify the `Model` explicitly.

go/client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
647647
req.DisabledSkills = config.DisabledSkills
648648
req.InfiniteSessions = config.InfiniteSessions
649649
req.LargeOutput = config.LargeOutput
650+
req.Memory = config.Memory
650651
req.GitHubToken = config.GitHubToken
651652
req.RemoteSession = config.RemoteSession
652653
req.Cloud = config.Cloud
@@ -983,6 +984,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
983984
req.DisabledSkills = config.DisabledSkills
984985
req.InfiniteSessions = config.InfiniteSessions
985986
req.LargeOutput = config.LargeOutput
987+
req.Memory = config.Memory
986988
req.GitHubToken = config.GitHubToken
987989
req.RemoteSession = config.RemoteSession
988990
req.Canvases = config.Canvases

go/client_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,70 @@ func TestSessionRequests_PluginDirectoriesAndLargeOutput(t *testing.T) {
605605
})
606606
}
607607

608+
func TestSessionRequests_Memory(t *testing.T) {
609+
t.Run("create includes memory in JSON when enabled", func(t *testing.T) {
610+
req := createSessionRequest{Memory: &MemoryConfiguration{Enabled: true}}
611+
data, err := json.Marshal(req)
612+
if err != nil {
613+
t.Fatalf("Failed to marshal: %v", err)
614+
}
615+
var m map[string]any
616+
if err := json.Unmarshal(data, &m); err != nil {
617+
t.Fatalf("Failed to unmarshal: %v", err)
618+
}
619+
expected := map[string]any{"enabled": true}
620+
if !reflect.DeepEqual(m["memory"], expected) {
621+
t.Errorf("Expected memory %v, got %v", expected, m["memory"])
622+
}
623+
})
624+
625+
t.Run("resume includes memory in JSON when disabled", func(t *testing.T) {
626+
req := resumeSessionRequest{SessionID: "s1", Memory: &MemoryConfiguration{Enabled: false}}
627+
data, err := json.Marshal(req)
628+
if err != nil {
629+
t.Fatalf("Failed to marshal: %v", err)
630+
}
631+
var m map[string]any
632+
if err := json.Unmarshal(data, &m); err != nil {
633+
t.Fatalf("Failed to unmarshal: %v", err)
634+
}
635+
expected := map[string]any{"enabled": false}
636+
if !reflect.DeepEqual(m["memory"], expected) {
637+
t.Errorf("Expected memory %v, got %v", expected, m["memory"])
638+
}
639+
})
640+
641+
t.Run("create omits memory when nil", func(t *testing.T) {
642+
req := createSessionRequest{}
643+
data, err := json.Marshal(req)
644+
if err != nil {
645+
t.Fatalf("Failed to marshal: %v", err)
646+
}
647+
var m map[string]any
648+
if err := json.Unmarshal(data, &m); err != nil {
649+
t.Fatalf("Failed to unmarshal: %v", err)
650+
}
651+
if _, ok := m["memory"]; ok {
652+
t.Errorf("Expected memory to be omitted")
653+
}
654+
})
655+
656+
t.Run("resume omits memory when nil", func(t *testing.T) {
657+
req := resumeSessionRequest{SessionID: "s1"}
658+
data, err := json.Marshal(req)
659+
if err != nil {
660+
t.Fatalf("Failed to marshal: %v", err)
661+
}
662+
var m map[string]any
663+
if err := json.Unmarshal(data, &m); err != nil {
664+
t.Fatalf("Failed to unmarshal: %v", err)
665+
}
666+
if _, ok := m["memory"]; ok {
667+
t.Errorf("Expected memory to be omitted")
668+
}
669+
})
670+
}
671+
608672
func TestCreateSessionRequest_Agent(t *testing.T) {
609673
t.Run("includes agent in JSON when set", func(t *testing.T) {
610674
req := createSessionRequest{Agent: "test-agent"}

go/types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,12 @@ type InfiniteSessionConfig struct {
841841
BufferExhaustionThreshold *float64 `json:"bufferExhaustionThreshold,omitempty"`
842842
}
843843

844+
// MemoryConfiguration configures the memory feature for a session.
845+
type MemoryConfiguration struct {
846+
// Enabled controls whether the memory feature is enabled for this session.
847+
Enabled bool `json:"enabled"`
848+
}
849+
844850
// LargeToolOutputConfig configures handling of large tool outputs. When a tool
845851
// produces output exceeding the configured size, the output is written to a
846852
// temp file and a reference is returned to the model instead of the full
@@ -1028,6 +1034,9 @@ type SessionConfig struct {
10281034
// output exceeding the configured size, the output is written to a temp file
10291035
// and a reference is returned to the model instead of the full payload.
10301036
LargeOutput *LargeToolOutputConfig
1037+
// Memory configures the memory feature for the session. When omitted, the
1038+
// runtime default applies.
1039+
Memory *MemoryConfiguration
10311040
// OnEvent is an optional event handler that is registered on the session before
10321041
// the session.create RPC is issued. This guarantees that early events emitted
10331042
// by the CLI during session creation (e.g. session.start) are delivered to the
@@ -1409,6 +1418,9 @@ type ResumeSessionConfig struct {
14091418
// output exceeding the configured size, the output is written to a temp file
14101419
// and a reference is returned to the model instead of the full payload.
14111420
LargeOutput *LargeToolOutputConfig
1421+
// Memory configures the memory feature for the session. When omitted, the
1422+
// runtime default applies.
1423+
Memory *MemoryConfiguration
14121424
// GitHubToken is an optional per-session GitHub token used for authentication.
14131425
// When provided, the session authenticates as the token's owner instead of
14141426
// using the global client-level auth.
@@ -1725,6 +1737,7 @@ type createSessionRequest struct {
17251737
DisabledSkills []string `json:"disabledSkills,omitempty"`
17261738
InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"`
17271739
LargeOutput *LargeToolOutputConfig `json:"largeOutput,omitempty"`
1740+
Memory *MemoryConfiguration `json:"memory,omitempty"`
17281741
Commands []wireCommand `json:"commands,omitempty"`
17291742
RequestElicitation *bool `json:"requestElicitation,omitempty"`
17301743
RequestMCPApps *bool `json:"requestMcpApps,omitempty"`
@@ -1805,6 +1818,7 @@ type resumeSessionRequest struct {
18051818
DisabledSkills []string `json:"disabledSkills,omitempty"`
18061819
InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"`
18071820
LargeOutput *LargeToolOutputConfig `json:"largeOutput,omitempty"`
1821+
Memory *MemoryConfiguration `json:"memory,omitempty"`
18081822
Commands []wireCommand `json:"commands,omitempty"`
18091823
RequestElicitation *bool `json:"requestElicitation,omitempty"`
18101824
RequestMCPApps *bool `json:"requestMcpApps,omitempty"`

java/src/main/java/com/github/copilot/SessionRequestBuilder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess
135135
request.setInstructionDirectories(config.getInstructionDirectories());
136136
request.setPluginDirectories(config.getPluginDirectories());
137137
request.setLargeOutput(config.getLargeOutput());
138+
request.setMemory(config.getMemory());
138139
request.setDisabledSkills(config.getDisabledSkills());
139140
request.setConfigDirectory(config.getConfigDirectory());
140141
config.getEnableConfigDiscovery().ifPresent(request::setEnableConfigDiscovery);
@@ -262,6 +263,7 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo
262263
request.setInstructionDirectories(config.getInstructionDirectories());
263264
request.setPluginDirectories(config.getPluginDirectories());
264265
request.setLargeOutput(config.getLargeOutput());
266+
request.setMemory(config.getMemory());
265267
request.setDisabledSkills(config.getDisabledSkills());
266268
request.setInfiniteSessions(config.getInfiniteSessions());
267269
request.setModelCapabilities(config.getModelCapabilities());

0 commit comments

Comments
 (0)