Session M6-01 lab: Build a traced change readiness assistant¶
Duration: 120 minutes
Difficulty: Advanced
Prerequisites: Sessions M1-01 to M1-03, M2-01 to M2-04, M3-01 to M3-05 and the Session M6-01 trainer content
Objective¶
Complete a TypeScript CLI that uses the GitHub Copilot SDK to assess deterministic synthetic change records. Your application must stream its response and call one tool limited to readiness lookups. It must also deny a restricted lookup, record failures, pass focused tests, and export traces through an OpenTelemetry Collector to Jaeger.
Final deliverables¶
Submit or demonstrate:
- a working CLI under
sdk-readiness-assistant/; - client and session cleanup on every exit path;
- streamed parent-agent output without duplicate final text;
- the typed readiness tool over the supplied synthetic data;
- permission handling plus a pre-tool policy that visibly denies
RESTRICTED-0001; - failed-tool, error, and session-end hook records;
- passing tests and type checks;
- one Jaeger trace for a permitted assessment;
- one Jaeger trace or trace event showing the denied action;
- terminal output for the unknown-change failure path.
Time plan¶
| Part | Work | Time |
|---|---|---|
| 1 | Preflight and baseline | 15 min |
| 2 | Client, session, and streaming | 20 min |
| 3 | Readiness tool and policy | 30 min |
| 4 | Failure hooks and cleanup | 20 min |
| 5 | Tests | 15 min |
| 6 | Collector, Jaeger, and final checks | 20 min |
Required preflight¶
Work from:
Authenticated GitHub Copilot SDK access is required. You also need:
- Node.js and npm versions accepted by
package.json; - Docker Engine;
- Docker Compose;
- permission to bind the local ports declared in the Compose file.
Stop if any item is unavailable. Do not switch to BYOK, another model provider, a remote session, or a different language.
Run the local checks:
Some focused tests should fail because you have not completed the reliability
controls yet. The project must compile far enough to show those gaps. Use the
instructor-provided authentication check in src/live-demo.ts before starting the
live exercises. If client.getAuthStatus() reports that the user is not
authenticated, stop.
Project map¶
Use the names present in the starter project. The expected layout is:
sdk-readiness-assistant/
├── package.json
├── compose.yaml
├── otel-collector-config.yaml
├── fixtures/
│ └── change-requests.json
├── src/
│ ├── live-demo.ts
│ ├── change-store.ts
│ ├── permission-policy.ts
│ ├── hook-decisions.ts
│ └── types.ts
└── tests/
├── change-store.test.ts
├── permission-policy.test.ts
└── hook-decisions.test.ts
Do not add external data sources or new runtime dependencies.
Part 1: Establish lifecycle and streaming (20 minutes)¶
Goal¶
Start the client, create one local session, stream parent-agent output, and close the client on every exit path.
Steps¶
- Open the application entry point and lifecycle module.
- Create
CopilotClientwith the supplied telemetry endpoint. - Start the client before creating the session.
- Create one session with streaming enabled.
- Subscribe to
assistant.message_delta. - Print only parent-agent deltas.
- Keep final message events for audit or tests. Do not print the same response again.
- Put client shutdown in
finally. - Keep per-session state keyed by the session ID.
Use this lifecycle shape:
const client = new CopilotClient(clientOptions);
await client.start();
let session;
try {
session = await client.createSession(sessionConfig);
subscribeToOutput(session);
await session.sendAndWait({ prompt });
} finally {
await session?.disconnect();
await client.stop();
}
Run:
Checkpoint 1¶
- The client starts before session creation.
- The session disconnects after success and thrown errors.
- The client stops after success and thrown errors.
- Parent text streams once.
- Per-session state is not global shared state.
Part 2: Complete the readiness tool and policy (30 minutes)¶
Goal¶
Expose deterministic readiness data through one typed tool. Permit normal lookups and deny the restricted synthetic record before the handler runs.
Steps¶
- Open the supplied change records in
fixtures/change-requests.json. - Do not edit the record IDs or expected verdicts.
- Complete the
lookup_change_requesttool with its supplied JSON Schema. - Validate the change ID inside the handler.
- Return the matching evidence, gaps, and verdict.
- Return the fixed
invalid-inputornot-foundresult for bad IDs. - Add
onPermissionRequest. - Approve only the supplied readiness tool request.
- Reject unknown request kinds and unrelated tools.
- Add
onPreToolUse. -
Deny IDs beginning with
RESTRICTED-with this reason: -
Record whether the handler ran. The denial test must check that it did not.
Do not use an approve-all helper. You need to show that unrelated requests are rejected while the readiness lookup is permitted.
Run the focused tool and policy tests:
Checkpoint 2¶
Demonstrate:
CR-1042returnsreadywith no gaps;CR-2087returnsneeds-reviewwith fixed gaps;- malformed or unknown IDs return fixed results without invention;
- a normal lookup is permitted;
RESTRICTED-0001is denied with a visible reason;- the denied request never enters the tool handler.
Part 3: Add failure, error, and end hooks (20 minutes)¶
Goal¶
Record the expected failure paths and clear session state at the end.
Steps¶
- Complete
onPostToolUseFailure. - Record the tool name and failed result.
- Add context that tells the agent to report unknown data instead of inventing a change record.
- Complete
onErrorOccurred. - Store the original error.
- Retry once only when the SDK marks the error recoverable.
- Abort unrecoverable errors and show a short user notification.
- Complete
onSessionEnd. - Record the end reason and timestamp.
- Delete state for the ended session.
- Keep cleanup safe if called more than once.
Do not treat a denied tool call as an SDK error. It is a policy result.
Run:
Checkpoint 3¶
The audit record distinguishes:
- policy denial;
- failed tool result;
- recoverable or unrecoverable SDK error;
- normal or abnormal session end.
Session state is empty after the end hook.
Part 4: Finish the automated tests (15 minutes)¶
Goal¶
Test the control path without depending on model wording or a live trace backend.
Complete or repair tests for:
- a known ready change;
- a known blocked change;
- malformed and unknown change IDs;
- denied
RESTRICTED-0001lookup; - handler non-execution after denial;
- one retry for a recoverable error;
- abort for an unrecoverable error;
- session state cleanup;
- streamed deltas rendered once;
- client shutdown after an exception.
Mock the SDK at the boundaries the starter provides. Do not call a live model from unit tests.
Run:
If package.json defines linting, run:
Checkpoint 4¶
All tests pass twice without data or snapshot changes.
Part 5: Start tracing and run the application (20 minutes)¶
Goal¶
Export one connected trace through the local Collector and inspect it in Jaeger.
Steps¶
- Review
compose.yamlandotel-collector-config.yaml. - Check the SDK OTLP HTTP endpoint is:
- Start the local stack:
- Wait until the Collector and Jaeger services are healthy.
- Run
npm run demo. - Check that
CR-1042streams a ready assessment. - Check that
bad-idreturns the fixed invalid-input result. - Check that
RESTRICTED-0001prints the denial reason. - Check that the session disconnects and the client stops.
- Open the Jaeger UI at
http://localhost:16686. - Select the service name configured by the project.
- Find the three most recent runs.
- Open the denied run and check that no lookup handler work follows the policy denial.
- Locate the permitted and invalid-input paths.
Stop the local stack when the trainer tells you to:
Checkpoint 5¶
Show the trainer:
- the permitted terminal run;
- the visible denied action;
- the invalid-input result;
- one connected Jaeger trace;
- the session end reason;
- passing tests and type checks.
Final checks¶
- Authenticated GitHub Copilot SDK preflight passed.
- Node.js, npm, Docker, and Docker Compose preflight passed.
- The client starts before session creation and stops on every exit path.
- The session streams parent output without duplicate final text.
- The readiness tool reads only deterministic synthetic data.
- Permission handling rejects unrelated requests.
- The pre-tool hook denies
RESTRICTED-0001before handler execution. - The denial reason is visible.
- Failed-tool, error, and session-end hooks record distinct outcomes.
- Per-session state is cleared.
- Tests pass.
- Type checks pass.
- The Collector and Jaeger receive traces.
- A Jaeger trace shows the denied path.
- No excluded feature or external business system was added.
Troubleshooting¶
| Problem | Fix |
|---|---|
client.getAuthStatus() reports unauthenticated |
Stop. Check the GitHub Copilot sign-in and permitted network path. Do not use BYOK or another provider. |
CopilotClient starts but session creation fails |
Check the installed package lock, supported Node.js version, and current GitHub Copilot access. |
| Output is duplicated | Print assistant.message_delta as live output and keep the final message for records only. |
| Restricted data reaches the handler | Match the exact tool name and inspect parsed toolArgs.requestId in onPreToolUse. |
| Unknown IDs produce an invented answer | Return a failed tool result and add failure-hook context that forbids invention. |
| Tests call the live model | Mock the SDK session boundary and assert deterministic events or decisions. |
| Collector cannot bind port 4318 | Stop the conflicting process only if you may do so, or change the supplied local configuration consistently. |
| Jaeger is empty | Check container health, the Collector export pipeline, service name, and Jaeger time range. |
| Session state remains after completion | Delete the session ID in onSessionEnd and keep outer cleanup in finally. |
Solution reference¶
After completing all checkpoints, compare your work with:
Review these areas in order:
src/live-demo.tsfor client, session, streaming, and hooks;src/change-store.tsfor the deterministic lookup;src/permission-policy.tsfor permission decisions;src/hook-decisions.tsfor prompt and pre-tool policy;tests/for stable control-path assertions;compose.yamlandotel-collector-config.yamlfor trace routing.
Do not copy the solution before the checkpoints. Compare its controls, test results, and traces with yours.
Official references¶
Checked on October 5, 2026: