Step-by-step instructions for the most common changes contributors make. Each
recipe assumes you've already worked through
onboarding.md.
git checkout main
git pull
git checkout -b feature/<short-topic>
dotnet build --configuration Release -warnaserror
dotnet testConfirm a clean baseline before editing anything.
Personas are pure data — no code change required.
- Create
personas/<id>.yamlwith the schema from the Personas section of the README. Required fields:id,name,backend,systemPrompt. - Save the file. The
YamlPersonaRepositorywatcher debounces ~500 ms and reloads automatically. - Verify:
curl http://localhost:5xxx/v1/personasshould include your new id;GET /v1/personas/<id>should return the full record. - If the file is invalid, the previous snapshot is kept and the failure is logged — check the API console for the parse error.
Tip: copy
personas/explorer.yamlas a starting point.
Use this when you want to support a new provider (e.g., a hypothetical
bedrock).
- Domain: add the new id to the
BackendIdvalue object insrc/CloudEngAgent.Domain/Backends/. - Configuration shape: add a strongly-typed options record under
src/CloudEngAgent.Infrastructure/Backends/(mirror the existingOpenAiBackendOptions,AnthropicBackendOptions, etc.). - Adapter: implement the new chat client adapter in the same folder.
Resolve the API key (if any) through
IBackendSecretResolver— never read the env var or config directly. - Factory wiring: register the adapter in the
IChatClientFactoryimplementation so aBackendIdof your new value resolves to your client. - Composition: bind the options section in
ServiceCollectionExtensions.cs(Backends:<id>). - Docs: add a row to the "Supported backends" table in
README.mdand include a sampleBackends:<id>block. - Tests: add unit tests for option binding and secret resolution; add an integration smoke test if the SDK supports an offline mode.
The WorkflowEngine:Mode = Auto heuristic only auto-detects Azure backends
today (because key-based backends ship with placeholder ApiKeyRef values).
Document the explicit WorkflowEngine:Mode=Real opt-in for your backend.
MCP tools live in src/CloudEngAgent.Mcp.Server/Tools/. Tools must remain
read-only — the safety model documented in the
MCP Server section is enforced by review.
- Define the tool method using the MCP server attributes already used by
sibling tools (look at
list_tables.cs/describe_table.cs). - Validate identifiers: any
schema/table/databaseargument must pass the allow-list regex^[A-Za-z_][A-Za-z0-9_]{0,127}$and be present inINFORMATION_SCHEMAbefore being bracket-quoted into SQL. - Use
SqlParameterfor every value. Never string-concatenate values into SQL. - Cap result sizes as appropriate (see the 100-row hard cap on
sample_rows). - Add tests in
tests/CloudEngAgent.Mcp.Server.Tests/covering: valid happy path, invalid identifier (rejection), and missing connection string (clearInvalidParamserror). - Document the new tool in the "Tool catalog" table in
README.md.
- Add a minimal-API mapping in
src/CloudEngAgent.Api/Endpoints/next to the existingMapXxxEndpointsextension methods. - Wire it in
Program.csalongside the others (look forapp.MapPersonaEndpoints()-style calls). - Keep the endpoint thin: validate inputs, call into a handler in
CloudEngAgent.Application, return the result. Don't put domain logic in the endpoint. - If the endpoint mutates state, apply the write rate-limit policy; for
reads use the read policy (
RateLimiting:WritePermitsPerMinute/ReadPermitsPerMinute). - Add tests in
tests/CloudEngAgent.Api.Tests/usingWebApplicationFactory. Cover the auth path (unauthenticated → 401 in non-Development environments) and the happy path. - Update the "API surface (v1)" table in
README.md.
The mapping from internal RunEvent to AG-UI SSE events lives in
src/CloudEngAgent.Api/Sse/. The mapping table in the README must stay in
sync with the writer — update both in the same PR.
When changing the writer:
- Preserve event ordering guarantees (e.g.,
STEP_FINISHEDbeforeSTEP_STARTEDon a handoff). - Keep frames flushable individually — never buffer multiple events into a single SSE write without a deliberate reason.
- Add or update a writer unit test that asserts the byte-level output of the affected branch.
- Define the key under the matching
*Optionsrecord (or add a new options record if introducing a new section). - Bind the section in
ServiceCollectionExtensions.csorProgram.cs. - Add validation if the value is required in non-Development environments.
The convention in this codebase is fail fast at startup with a clear
InvalidOperationExceptionrather than crashing later. - Update the "Configuration" highlights block and any per-feature section in
README.md. - If the key is a secret, route it through
IBackendSecretResolverinstead of reading it directly.
- Make your model change in
src/CloudEngAgent.Infrastructure/Persistence/. - Add a migration (the design-time factory in the project supports this):
dotnet ef migrations add <DescriptiveName> \ --project src/CloudEngAgent.Infrastructure \ --startup-project src/CloudEngAgent.Api
- Inspect the generated migration. If it does anything destructive (drops a column, changes a key), call it out in the PR description and add a data-preserving step.
- Apply locally and run the
Infrastructure.Testsintegration suite (Docker required) to confirm round-trip behaviour. - Migrations are applied at runtime; you do not need to commit a
database updateartifact. Just commit the generated migration files.
- Console logging is your friend. The default development log level surfaces backend selection, persona reloads, MCP server discovery, and SSE token issuance. Skim the startup log first when something behaves oddly.
- Reproduce CI locally with the same commands the workflow runs (see
CONTRIBUTING.md→ First build & test). - Stub vs Real workflow engine: most "but it works in dev" surprises are
caused by the engine silently falling back to
Stub. SetWorkflowEngine:Mode=Realto force the real path and surface missing configuration. /readyzis your DB canary. A 503 there means the EF Core context can't reach SQL Server — fix the connection string before debugging higher layers.- MCP discovery failures appear as warnings, not errors, by design.
Check the API console for
HttpMcpToolRegistrylog lines if a tool you expect isn't showing up inListToolsAsync.
- Match the surrounding code. Conventions (file layout, naming, DI patterns) are intentionally consistent across the projects.
- Prefer small, reviewable PRs over large drops. Open a draft early and iterate.
- Ask in the PR. A two-line "should this live in Application or Infrastructure?" comment saves a re-review later.