Code morphology

Patterns an agent can patch in one turn

These are structural choices, not style nits. Each one either removes a hop, closes a set of outcomes, or makes an invalid state unrepresentable. The code samples use a payments withdrawal because that is the reference implementation; the pattern is the shape, not the domain.

Two examples

FundsService.cs · one of many files
Hop chain
// Controller maps a contract to a DTO, then a façade, then a command.
public class FundsService : IFundsService
{
    private readonly IMediator _mediator;
    private readonly IMapper _mapper;

    public async Task<ResultDto> WithdrawAsync(RequestDto dto)
    {
        var command = _mapper.Map<WithdrawCommand>(dto);
        return _mapper.Map<ResultDto>(
            await _mediator.Send(command));
    }
}
Handler, validator, repository, and exception middleware are not in this window. Token cost 100%
Features/Withdraw.cs · complete operation
One file
// Request, closed outcomes, rules, and HTTP mapping stay together.
public static class Withdraw
{
    public sealed record Request(Guid AccountId, decimal Amount);

    public abstract record Outcome
    {
        public sealed record Ok(Money Balance) : Outcome;
        public sealed record Fail(Status Status, string Message) : Outcome;
    }

    public static async Task<IResult> Handle(Request req, AppDbContext db)
    {
        var outcome = await Execute(req, db);
        return outcome switch
        {
            Outcome.Ok ok => Results.Ok(ok),
            Outcome.Fail fail => Results.Ok(fail),
            _ => throw new InvalidOperationException(outcome.GetType().Name)
        };
    }
}
Missing a status is a compile break, not a silent 500. Files touched 45%

Six patterns

Closed outcomes

One vocabulary on the wire

Publish the closed set on Playbooks. Map to HTTP once at the edge. Do not invent a second enum in middleware.

return new Outcome.Fail(Status.Insufficient, "Balance is below the amount.");
Strong types

Parse at the boundary

Identifiers and money are readonly record structs. Provider names go through TryParse, not a string that later fails in a factory.

public readonly record struct Money(decimal Amount)
{
    public bool IsPositive => Amount > 0;
}
Copy, do not share

A few duplicated lines beat a helper maze

Shared validation helpers couple operations that should change independently. Copy the amount check. Changing decline rules on one command must not silently break another.

Explicit construction

No IServiceProvider in the use case

Primary constructors list real dependencies. Operation-local routes construct the data context in the lambda so forgotten AddScoped is not a runtime surprise.

Header as contract

JSON examples next to the code

Happy path and every status in a comment at the top of the file. Those tokens are cheaper than searching a wiki after the agent has already guessed.

Do not add

Ceremony the agent will misuse

  • Mediator, automappers, fluent validator pipelines
  • Repository / unit-of-work / factory interfaces for a single implementation
  • Exception flow for expected business outcomes
  • Feature-level constructor DI when the route can construct the context