diff --git a/instructions/copilot-sdk-java.instructions.md b/instructions/copilot-sdk-java.instructions.md
new file mode 100644
index 000000000..bf18a3c5a
--- /dev/null
+++ b/instructions/copilot-sdk-java.instructions.md
@@ -0,0 +1,756 @@
+---
+applyTo: '**.java, **/pom.xml'
+description: 'This file provides guidance on building Java applications using GitHub Copilot SDK for Java.'
+name: 'GitHub Copilot SDK Java Instructions'
+---
+
+## Core Principles
+
+- The SDK is in technical preview and may have breaking changes
+- Requires Java 17 or later
+- Requires GitHub Copilot CLI installed and in PATH
+- Uses `CompletableFuture` for all async operations
+- Implements `AutoCloseable` for resource cleanup (try-with-resources)
+
+## Installation
+
+### Maven
+
+```xml
+
+ com.github
+ copilot-sdk-java
+ ${copilot-sdk-java.version}
+
+```
+
+### Gradle
+
+```groovy
+implementation "com.github:copilot-sdk-java:${copilotSdkJavaVersion}"
+```
+
+## Client Initialization
+
+### Basic Client Setup
+
+```java
+try (var client = new CopilotClient()) {
+ client.start().get();
+ // Use client...
+}
+```
+
+### Client Configuration Options
+
+When creating a CopilotClient, use `CopilotClientOptions`:
+
+- `cliPath` - Path to CLI executable (default: "copilot" from PATH)
+- `cliArgs` - Extra arguments prepended before SDK-managed flags
+- `cliUrl` - URL of existing CLI server (e.g., "localhost:8080"). When provided, client won't spawn a process
+- `port` - Server port (default: 0 for random, only when `useStdio` is false)
+- `useStdio` - Use stdio transport instead of TCP (default: true)
+- `logLevel` - Log level: "error", "warn", "info", "debug", "trace" (default: "info")
+- `autoStart` - Auto-start server on first request (default: true)
+- `autoRestart` - Auto-restart on crash (default: true)
+- `cwd` - Working directory for the CLI process
+- `environment` - Environment variables for the CLI process
+- `gitHubToken` - GitHub token for authentication
+- `useLoggedInUser` - Use logged-in `gh` CLI auth (default: true unless token provided)
+- `onListModels` - Custom model list handler for BYOK scenarios
+
+```java
+var options = new CopilotClientOptions()
+ .setCliPath("/path/to/copilot")
+ .setLogLevel("debug")
+ .setAutoStart(true)
+ .setAutoRestart(true)
+ .setGitHubToken(System.getenv("GITHUB_TOKEN"));
+
+try (var client = new CopilotClient(options)) {
+ client.start().get();
+ // Use client...
+}
+```
+
+### Manual Server Control
+
+For explicit control:
+```java
+var client = new CopilotClient(new CopilotClientOptions().setAutoStart(false));
+client.start().get();
+// Use client...
+client.stop().get();
+```
+
+Use `forceStop()` when `stop()` takes too long.
+
+## Session Management
+
+### Creating Sessions
+
+Use `SessionConfig` for configuration. The permission handler is **required**:
+
+```java
+var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setStreaming(true)
+ .setTools(List.of(...))
+ .setSystemMessage(new SystemMessageConfig()
+ .setMode(SystemMessageMode.APPEND)
+ .setContent("Custom instructions"))
+ .setAvailableTools(List.of("tool1", "tool2"))
+ .setExcludedTools(List.of("tool3"))
+ .setProvider(new ProviderConfig().setType("openai"))
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+```
+
+### Session Config Options
+
+- `sessionId` - Custom session ID
+- `clientName` - Application name
+- `model` - Model name ("gpt-5", "claude-sonnet-4.5", etc.)
+- `reasoningEffort` - "low", "medium", "high", "xhigh"
+- `tools` - Custom tools exposed to the CLI
+- `systemMessage` - System message customization
+- `availableTools` - Allowlist of tool names
+- `excludedTools` - Blocklist of tool names
+- `provider` - Custom API provider configuration (BYOK)
+- `streaming` - Enable streaming response chunks (default: false)
+- `workingDirectory` - Session working directory
+- `mcpServers` - MCP server configurations
+- `customAgents` - Custom agent configurations
+- `agent` - Pre-select agent by name
+- `infiniteSessions` - Infinite sessions configuration
+- `skillDirectories` - Skill SKILL.md directories
+- `disabledSkills` - Skills to disable
+- `configDir` - Config directory path
+- `hooks` - Session lifecycle hooks
+- `onPermissionRequest` - **REQUIRED** permission handler
+- `onUserInputRequest` - User input handler
+- `onEvent` - Event handler registered before session creation
+
+All setters return `SessionConfig` for method chaining.
+
+### Resuming Sessions
+
+```java
+var session = client.resumeSession(sessionId, new ResumeSessionConfig()
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+```
+
+### Session Operations
+
+- `session.getSessionId()` - Get session identifier
+- `session.send(prompt)` / `session.send(MessageOptions)` - Send message, returns message ID
+- `session.sendAndWait(prompt)` / `session.sendAndWait(MessageOptions)` - Send and wait for response (60s timeout)
+- `session.sendAndWait(options, timeoutMs)` - Send and wait with custom timeout
+- `session.abort()` - Abort current processing
+- `session.getMessages()` - Get all events/messages
+- `session.setModel(modelId)` - Switch to a different model
+- `session.log(message)` / `session.log(message, "warning", false)` / `session.log(message, "error", false)` - Log to session timeline with level `"info"`, `"warning"`, or `"error"`
+- `session.close()` - Clean up resources
+
+## Event Handling
+
+### Event Subscription Pattern
+
+Use `CompletableFuture` for waiting on session events:
+
+```java
+var done = new CompletableFuture();
+
+session.on(event -> {
+ if (event instanceof AssistantMessageEvent msg) {
+ System.out.println(msg.getData().content());
+ } else if (event instanceof SessionIdleEvent) {
+ done.complete(null);
+ }
+});
+
+session.send(new MessageOptions().setPrompt("Hello"));
+done.get();
+```
+
+### Type-Safe Event Handling
+
+Use the typed `on()` overload for compile-time safety:
+
+```java
+session.on(AssistantMessageEvent.class, msg -> {
+ System.out.println(msg.getData().content());
+});
+
+session.on(SessionIdleEvent.class, idle -> {
+ done.complete(null);
+});
+```
+
+### Unsubscribing from Events
+
+The `on()` method returns a `Closeable`:
+
+```java
+var subscription = session.on(event -> { /* handler */ });
+// Later...
+subscription.close();
+```
+
+### Event Types
+
+Use pattern matching (Java 17+) for event handling:
+
+```java
+session.on(event -> {
+ if (event instanceof UserMessageEvent userMsg) {
+ // Handle user message
+ } else if (event instanceof AssistantMessageEvent assistantMsg) {
+ System.out.println(assistantMsg.getData().content());
+ } else if (event instanceof AssistantMessageDeltaEvent delta) {
+ System.out.print(delta.getData().deltaContent());
+ } else if (event instanceof ToolExecutionStartEvent toolStart) {
+ // Tool execution started
+ } else if (event instanceof ToolExecutionCompleteEvent toolComplete) {
+ // Tool execution completed
+ } else if (event instanceof SessionStartEvent start) {
+ // Session started
+ } else if (event instanceof SessionIdleEvent idle) {
+ // Session is idle (processing complete)
+ } else if (event instanceof SessionErrorEvent error) {
+ System.err.println("Error: " + error.getData().message());
+ }
+});
+```
+
+### Event Error Handling
+
+Control how errors in event handlers are handled:
+
+```java
+// Set a custom error handler
+session.setEventErrorHandler(ex -> {
+ logger.error("Event handler error", ex);
+});
+
+// Or set the error propagation policy
+session.setEventErrorPolicy(EventErrorPolicy.SUPPRESS_AND_LOG_ERRORS);
+```
+
+## Streaming Responses
+
+### Enabling Streaming
+
+Set `streaming(true)` in SessionConfig:
+
+```java
+var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setStreaming(true)
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+```
+
+### Handling Streaming Events
+
+Handle both delta events (incremental) and final events:
+
+```java
+var done = new CompletableFuture();
+
+session.on(event -> {
+ switch (event) {
+ case AssistantMessageDeltaEvent delta ->
+ // Incremental text chunk
+ System.out.print(delta.getData().deltaContent());
+ case AssistantReasoningDeltaEvent reasoningDelta ->
+ // Incremental reasoning chunk (model-dependent)
+ System.out.print(reasoningDelta.getData().deltaContent());
+ case AssistantMessageEvent msg ->
+ // Final complete message
+ System.out.println("\n--- Final ---\n" + msg.getData().content());
+ case AssistantReasoningEvent reasoning ->
+ // Final reasoning content
+ System.out.println("--- Reasoning ---\n" + reasoning.getData().content());
+ case SessionIdleEvent idle ->
+ done.complete(null);
+ default -> { }
+ }
+});
+
+session.send(new MessageOptions().setPrompt("Tell me a story"));
+done.get();
+```
+
+Note: Final events (`AssistantMessageEvent`, `AssistantReasoningEvent`) are ALWAYS sent regardless of streaming setting.
+
+## Custom Tools
+
+### Defining Tools
+
+Use `ToolDefinition.create()` with JSON Schema parameters and a `ToolHandler`:
+
+```java
+var tool = ToolDefinition.create(
+ "get_weather",
+ "Get weather for a location",
+ Map.of(
+ "type", "object",
+ "properties", Map.of(
+ "location", Map.of("type", "string", "description", "City name")
+ ),
+ "required", List.of("location")
+ ),
+ invocation -> {
+ String location = (String) invocation.getArguments().get("location");
+ return CompletableFuture.completedFuture("Sunny in " + location);
+ }
+);
+
+var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setTools(List.of(tool))
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+```
+
+### Type-Safe Tool Arguments
+
+Use `getArgumentsAs()` for deserialization into a typed record or class:
+
+```java
+record WeatherArgs(String location, String unit) {}
+
+var tool = ToolDefinition.create(
+ "get_weather",
+ "Get weather for a location",
+ Map.of(
+ "type", "object",
+ "properties", Map.of(
+ "location", Map.of("type", "string"),
+ "unit", Map.of("type", "string", "enum", List.of("celsius", "fahrenheit"))
+ ),
+ "required", List.of("location")
+ ),
+ invocation -> {
+ var args = invocation.getArgumentsAs(WeatherArgs.class);
+ return CompletableFuture.completedFuture(
+ Map.of("temp", 72, "unit", args.unit(), "location", args.location())
+ );
+ }
+);
+```
+
+### Overriding Built-In Tools
+
+```java
+var override = ToolDefinition.createOverride(
+ "built_in_tool_name",
+ "Custom description",
+ Map.of("type", "object", "properties", Map.of(...)),
+ invocation -> CompletableFuture.completedFuture("custom result")
+);
+```
+
+### Tool Return Types
+
+- Return any JSON-serializable value (String, Map, List, record, POJO)
+- The SDK automatically serializes the return value and sends it back to the CLI
+
+### Tool Execution Flow
+
+When Copilot invokes a tool, the client automatically:
+1. Deserializes the arguments
+2. Runs your handler function
+3. Serializes the return value
+4. Responds to the CLI
+
+## Permission Handling
+
+### Required Permission Handler
+
+A permission handler is **mandatory** when creating or resuming sessions:
+
+```java
+// Approve all requests (for development/testing)
+new SessionConfig()
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+
+// Custom permission logic
+new SessionConfig()
+ .setOnPermissionRequest((request, invocation) -> {
+ if ("dangerous-action".equals(request.getKind())) {
+ return CompletableFuture.completedFuture(
+ new PermissionRequestResult().setKind(PermissionRequestResultKind.DENIED)
+ );
+ }
+ return CompletableFuture.completedFuture(
+ new PermissionRequestResult().setKind(PermissionRequestResultKind.APPROVED)
+ );
+ })
+```
+
+## User Input Handling
+
+Handle user input requests from the agent:
+
+```java
+new SessionConfig()
+ .setOnUserInputRequest((request, invocation) -> {
+ System.out.println("Agent asks: " + request.getQuestion());
+ String answer = scanner.nextLine();
+ return CompletableFuture.completedFuture(
+ new UserInputResponse()
+ .setAnswer(answer)
+ .setWasFreeform(true)
+ );
+ })
+```
+
+## System Message Customization
+
+### Append Mode (Default - Preserves Guardrails)
+
+```java
+var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setSystemMessage(new SystemMessageConfig()
+ .setMode(SystemMessageMode.APPEND)
+ .setContent("""
+
+ - Always check for security vulnerabilities
+ - Suggest performance improvements when applicable
+
+ """))
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+```
+
+### Replace Mode (Full Control - Removes Guardrails)
+
+```java
+var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setSystemMessage(new SystemMessageConfig()
+ .setMode(SystemMessageMode.REPLACE)
+ .setContent("You are a helpful assistant."))
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+```
+
+## File Attachments
+
+Attach files to messages using `Attachment`:
+
+```java
+session.send(new MessageOptions()
+ .setPrompt("Analyze this file")
+ .setAttachments(List.of(
+ new Attachment("file", "/path/to/file.java", "My File")
+ ))
+);
+```
+
+## Message Delivery Modes
+
+Use the `mode` property in `MessageOptions`:
+
+- `"enqueue"` - Queue message for processing (default)
+- `"immediate"` - Process message immediately
+
+```java
+session.send(new MessageOptions()
+ .setPrompt("...")
+ .setMode("enqueue")
+);
+```
+
+## Convenience: Send and Wait
+
+Use `sendAndWait()` to send a message and block until the assistant responds:
+
+```java
+// With default 60-second timeout
+AssistantMessageEvent response = session.sendAndWait("What is 2+2?").get();
+System.out.println(response.getData().content());
+
+// With custom timeout
+AssistantMessageEvent response = session.sendAndWait(
+ new MessageOptions().setPrompt("Write a long story"),
+ 120_000 // 120 seconds
+).get();
+```
+
+## Multiple Sessions
+
+Sessions are independent and can run concurrently:
+
+```java
+var session1 = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+
+var session2 = client.createSession(new SessionConfig()
+ .setModel("claude-sonnet-4.5")
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+
+session1.send(new MessageOptions().setPrompt("Hello from session 1"));
+session2.send(new MessageOptions().setPrompt("Hello from session 2"));
+```
+
+## Bring Your Own Key (BYOK)
+
+Use custom API providers via `ProviderConfig`:
+
+```java
+// OpenAI
+var session = client.createSession(new SessionConfig()
+ .setProvider(new ProviderConfig()
+ .setType("openai")
+ .setBaseUrl("https://api.openai.com/v1")
+ .setApiKey("sk-..."))
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+
+// Azure OpenAI
+var session = client.createSession(new SessionConfig()
+ .setProvider(new ProviderConfig()
+ .setType("azure")
+ .setAzure(new AzureOptions()
+ .setEndpoint("https://my-resource.openai.azure.com")
+ .setDeployment("gpt-4"))
+ .setBearerToken("..."))
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+).get();
+```
+
+## Session Lifecycle Management
+
+### Listing Sessions
+
+```java
+var sessions = client.listSessions().get();
+for (var metadata : sessions) {
+ System.out.println("Session: " + metadata.getSessionId());
+}
+```
+
+### Deleting Sessions
+
+```java
+client.deleteSession(sessionId).get();
+```
+
+### Checking Connection State
+
+```java
+var state = client.getState();
+```
+
+### Lifecycle Event Subscription
+
+```java
+AutoCloseable subscription = client.onLifecycle(event -> {
+ System.out.println("Lifecycle event: " + event);
+});
+// Later...
+subscription.close();
+```
+
+## Error Handling
+
+### Standard Exception Handling
+
+```java
+try {
+ var session = client.createSession(new SessionConfig()
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+ ).get();
+ session.sendAndWait("Hello").get();
+} catch (ExecutionException ex) {
+ Throwable cause = ex.getCause();
+ System.err.println("Error: " + cause.getMessage());
+} catch (Exception ex) {
+ System.err.println("Error: " + ex.getMessage());
+}
+```
+
+### Session Error Events
+
+Monitor `SessionErrorEvent` for runtime errors:
+
+```java
+session.on(SessionErrorEvent.class, error -> {
+ System.err.println("Session Error: " + error.getData().message());
+});
+```
+
+## Connectivity Testing
+
+Use `ping()` to verify server connectivity:
+
+```java
+var response = client.ping("test message").get();
+```
+
+## Status and Authentication
+
+```java
+// Get CLI version and protocol info
+var status = client.getStatus().get();
+
+// Check authentication status
+var authStatus = client.getAuthStatus().get();
+
+// List available models
+var models = client.listModels().get();
+```
+
+## Resource Cleanup
+
+### Automatic Cleanup with try-with-resources
+
+ALWAYS use try-with-resources for automatic disposal:
+
+```java
+try (var client = new CopilotClient()) {
+ client.start().get();
+ try (var session = client.createSession(new SessionConfig()
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) {
+ // Use session...
+ }
+}
+// Resources automatically cleaned up
+```
+
+### Manual Cleanup
+
+If not using try-with-resources:
+
+```java
+var client = new CopilotClient();
+try {
+ client.start().get();
+ // Use client...
+} finally {
+ client.stop().get();
+}
+```
+
+## Best Practices
+
+1. **Always use try-with-resources** for `CopilotClient` and `CopilotSession`
+2. **Always provide a permission handler** - it is required for `createSession` and `resumeSession`
+3. **Use `CompletableFuture`** properly - call `.get()` to block, or chain with `.thenApply()`/`.thenCompose()`
+4. **Use `sendAndWait()`** for simple request-response patterns instead of manual event handling
+5. **Handle `SessionErrorEvent`** for robust error handling
+6. **Use pattern matching** (switch with sealed types) for event handling
+7. **Enable streaming** for better UX in interactive scenarios
+8. **Close event subscriptions** (`Closeable`) when no longer needed
+9. **Use `SystemMessageMode.APPEND`** to preserve safety guardrails
+10. **Provide descriptive tool names and descriptions** for better model understanding
+11. **Handle both delta and final events** when streaming is enabled
+12. **Use `getArgumentsAs()`** for type-safe tool argument deserialization
+
+## Common Patterns
+
+### Simple Query-Response
+
+```java
+try (var client = new CopilotClient()) {
+ client.start().get();
+
+ try (var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) {
+
+ var response = session.sendAndWait("What is 2+2?").get();
+ System.out.println(response.getData().content());
+ }
+}
+```
+
+### Event-Driven Conversation
+
+```java
+try (var client = new CopilotClient()) {
+ client.start().get();
+
+ try (var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) {
+
+ var done = new CompletableFuture();
+
+ session.on(AssistantMessageEvent.class, msg ->
+ System.out.println(msg.getData().content()));
+
+ session.on(SessionIdleEvent.class, idle ->
+ done.complete(null));
+
+ session.send(new MessageOptions().setPrompt("What is 2+2?"));
+ done.get();
+ }
+}
+```
+
+### Multi-Turn Conversation
+
+```java
+try (var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)).get()) {
+
+ var response1 = session.sendAndWait("What is the capital of France?").get();
+ System.out.println(response1.getData().content());
+
+ var response2 = session.sendAndWait("What is its population?").get();
+ System.out.println(response2.getData().content());
+}
+```
+
+### Tool with Complex Return Type
+
+```java
+record UserInfo(String id, String name, String email, String role) {}
+
+var tool = ToolDefinition.create(
+ "get_user",
+ "Retrieve user information",
+ Map.of(
+ "type", "object",
+ "properties", Map.of(
+ "userId", Map.of("type", "string", "description", "User ID")
+ ),
+ "required", List.of("userId")
+ ),
+ invocation -> {
+ String userId = (String) invocation.getArguments().get("userId");
+ return CompletableFuture.completedFuture(
+ new UserInfo(userId, "John Doe", "john@example.com", "Developer")
+ );
+ }
+);
+```
+
+### Session Hooks
+
+```java
+var session = client.createSession(new SessionConfig()
+ .setModel("gpt-5")
+ .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
+ .setHooks(new SessionHooks()
+ .setOnPreToolUse((input, invocation) -> {
+ System.out.println("About to execute tool: " + input);
+ var decision = new PreToolUseHookOutput().setKind("allow");
+ return CompletableFuture.completedFuture(decision);
+ })
+ .setOnPostToolUse((output, invocation) -> {
+ System.out.println("Tool execution complete: " + output);
+ return CompletableFuture.completedFuture(null);
+ }))
+).get();
+```