Skip to content
View PabloMickaelQuevedo's full-sized avatar
🐱
Senior Software Engineer · Technical Leader
🐱
Senior Software Engineer · Technical Leader

Block or report PabloMickaelQuevedo

Block user

Prevent this user from interacting with your repositories and sending you notifications. Learn more about blocking users.

You must be logged in to block users.

Content in all repositories owned by your account will be closed.
Maximum 250 characters. Please don’t include any personal information such as legal names or email addresses. Markdown is supported. This note will only be visible to you.
Report abuse

Contact GitHub support about this user’s behavior. Learn more about reporting abuse.

Report abuse

Pablo Mickael Quevedo

Senior Software Engineer · Technical Leader
Distributed systems · Payments · Industrial IoT · Agent tooling for engineering teams

.NET 10 C# Clean Architecture, DDD, CQRS Kubernetes, ArgoCD, Helm Oracle, PostgreSQL, MongoDB


I spent 8 years and 8 months assembling product on a factory line. Today I build the platform that monitors that same shop floor: machine telemetry over MQTT, real-time OEE, and a rules engine the operations team reconfigures without asking anyone for a deploy.

I know which questions the operator asks because I used to be the operator. That is the part of my background that explains most about how I work — I never had to learn the business, I already was the business.

I moved into tech through the help desk in November 2021 and pushed my first commit in August 2022. Four years later I answer technically for the cross-cutting systems of a group that runs manufacturing, retail, banking, consortium and insurance.

Engineering with agents · Scope · What I build · Open source · How I decide · Stack · Trajectory


Engineering With Agents

This is the most relevant thing I do today, and it is not writing code with an assistant. I built the engineering toolchain my area uses to work with agents — and it stopped being mine. It is now an internal product any engineer installs with one command.

The house tools became commands the agent operates. The house standards became instructions it loads on its own. The knowledge of the estate became a queryable base that updates without me. And the daily friction became the input of a cycle that produces new tooling.

Distributed skills 11 — one per tool or operational domain
Automatic hooks 5, paired for the two operating systems the team runs
Command-line tools 8, ~2,540 lines — service desk, database, logs, identity, versioning, standards
Knowledge base 681 application records, plus 20 code-standard rules
Tests 3,798 lines — the toolchain has more test than CLI
flowchart TB
    CLI["<b>Tools as CLIs</b><br/>8 commands<br/><i>GitLab · database · logs<br/>service desk · identity</i>"]
    STD["<b>Standards the machine consumes</b><br/>11 skills · 5 hooks · 20 rules<br/><i>one neutral source,<br/>thin per-vendor adapters</i>"]
    MAP["<b>The estate map</b><br/>681 application records<br/><i>scanned, self-updating</i>"]

    CLI --> KIT
    STD --> KIT
    MAP --> KIT

    KIT["<b>One command installs and updates all of it</b><br/><i>idempotent · rewrites what changed version<br/>· never touches the user's own content</i>"]

    KIT --> CYC

    CYC(["<b>The cycle that feeds itself</b><br/>friction &rarr; problem log &rarr; detected pattern &rarr; new skill<br/><i>the only step I do by hand is approving</i>"])

    CYC -.->|"generates the next tool,<br/>the next skill, the next record"| KIT

    classDef piece fill:#512BD4,stroke:#512BD4,color:#fff
    classDef hub fill:transparent,stroke:#888,color:#888
    class CLI,STD,MAP piece
    class KIT,CYC hub
Loading

Three decisions hold the design up

Knowledge lives once; the adapter is disposable. What a tool does, what trap it has, what the house standard is — that lives in a single layer. The format a specific AI product consumes lives in a thin one beside it. Swapping vendors tomorrow means writing a sibling adapter, not rewriting years of operational knowledge. Agent tooling is the fastest-moving market there is; binding the company's knowledge to one vendor's file format is buying a migration with a date on it.

Synchronization, not installation. The command is not a first-run command — it runs as many times as needed. Toolchain parts that changed version get rewritten; the user's own content is never touched. That separation is what makes one command serve both install and update, and what made it safe to distribute to the team. It stopped being called "install" precisely because the wrong name invited the wrong use.

Write access graded by blast radius. The whole toolchain belongs to the team and anyone may propose a change. But the pieces whose failure travels far — the commit hooks, the context common to every session, the code standards, the commands that write to sensitive systems, the synchronizer itself — require review from whoever leads. Not hierarchy by status: a bug in a lookup skill inconveniences whoever ran it; a bug in the commit hook reaches every machine on the team on its next run.

What keeps the standards from rotting

Standards written for humans have a known problem: nobody reads them at the moment it matters. Standards written for an agent have a quieter one — they keep looking correct after they become wrong. A document teaching a method whose signature changed breaks nothing. It just teaches the wrong thing, to everyone, indefinitely.

So there is a project that consumes every API the standards cite, and compiling is the assertion. If a standard describes a method that no longer exists, that is not a doc quietly going stale — it is a red build. Paired with automatic dependency updates and a scheduled run, because the drift never originates from a commit in the standards repo: it originates from a package changing out there.

The cycle, end to end — one real case

The best example of the cycle closing was not one problem. It was three that did not look like the same problem.

First symptom. A package install failed saying it could not resolve the host. Right after, every curl started answering HTTP 200 — with the HTML of a login page in the body. I went through "the package manager broke" and "DNS issue" before landing on the cause: the network edge appliance session had expired and the captive portal was intercepting everything, answering 200. About eight attempts. The wrong hypothesis was reasonable — the first error genuinely was name resolution.

Second symptom, days later. A mobile build could not resolve a dependency, while curl against the same host worked. That makes no sense until you find out the same appliance does selective TLS inspection — intercepting one vendor's artifact repository and not the language's public one — and that each tool picks its own truststore: curl uses the OS one, where the corporate CA is installed; the build uses the JDK's, where it is not. Three attempts.

Third symptom. The app froze on "no connection" in the emulator and looked like an app bug. I killed two framework hypotheses before finding it: the OS runs an HTTPS network-validation probe, the probe failed against the corporate CA, and the system flagged the network as partial connectivity. Without the validated-network flag, the app's library was only repeating what the system said. About ten attempts.

The crossing. Three incidents, three symptoms with no apparent kinship — name resolution, a build, emulator connectivity. None of them alone revealed the pattern. Reading the accumulated log did: it is one appliance with three faces. An entire class of problem, not three accidents.

What it became. A network skill the agent loads on its own when a symptom from that family shows up. It describes the three faces, explains why curl works and the build does not, and carries the procedure for each case — including where the exported CA lives, so it goes into the right truststore.

Cost before and after. Across the three incidents: twenty-plus attempts and a day lost to a diagnosis that went through package manager, DNS, framework and app bug — none of them the cause. Today the symptom is recognized in the first message of the conversation, with the procedure attached.

And one case where the cycle deliberately produced nothing

This one matters more, because it shows the judgment step is real.

Common commands kept failing with "command not found", including basic system utilities. It looked like a missing package. The real cause was the shell PATH collapsing now and then, losing even the default system directories. It happened twelve times, across five projects, over a month.

Twelve recurrences is exactly the pattern that makes the system propose a skill. I reviewed it and decided it does not become one. A skill delivers knowledge to an agent; the problem here is not missing knowledge, it is a broken environment. A skill saying "when PATH disappears, re-export PATH" would automate living with a defect instead of fixing it — and worse, would make the defect look solved.

The entry stays in the log, marked reviewed, which is what it should be: a known diagnosis waiting for a fix at the source. Not every recurring friction is a tooling opportunity. Some are just debt waiting for an owner, and telling the two apart is the one thing in the cycle that cannot be automated.

Why this counts as engineering

Because the bottleneck moved. When implementing gets cheap, the value migrates to whoever can define the problem, verify the result, and organize the context the work happens in.

And there is a consequence I did not predict: the agent fails exactly where the documentation fails. Where the convention is implicit, where the rule lives in someone's head, where "that's just how we do it here" was never written down — that is where the output comes out wrong. Working this way forced me to make explicit what I should already have made explicit for people. The quality gain for the team came along for free, and it was not the stated goal.


Scope

319 applications in my area
128 under my direct technical responsibility
118 repositories containing my code
32 systems where I am the principal author

Sole author of 5 of those 32; among the top three contributors in 65 more repositories.

Measure Value
My own code alive at HEAD 112,461 lines, measured with git blame
Long ownership 20 systems maintained for three years or more
Reach 19 · 15 · 12 systems consuming the payments hub, document management and the email service
Shared libraries contributor to 27, principal author of 8
Open source 5 public packages
Automated tests two thirds of the repositories I touch have a suite

Measured across the estate and my own repositories, as of August 2026.


What I Build

Payments and antifraud · 17 repositories

A paid order is not a released order. Between the two sits a sequence of checks — acquirer confirmation, antifraud, a decision engine, sometimes a human. Which steps apply depends on the combination of company, business, operation type, channel, country and branch, plus the payment method used. That space grows every time the group opens a new business.

Multi-acquirer orchestration where a core decides the path and each payment method is a satellite service that only knows its own part: card, Pix, boleto, voucher, link, digital wallet. A transaction state machine with transitions written as an explicit ordered chain. A historical risk dossier — windowed counting, recurrence, fuzzy address comparison, blocklist crossing. Refunds with proportional apportionment and idempotency by source identifier. Revenue from e-commerce, the app and 200+ stores flows through it, plus the group's own financial products.

The decision: the verification policy of each channel is data, not code. Changing a whole business's antifraud policy stopped requiring a release. The cost is real — configuration became critical system state and needs care of its own. I took it because a wrong configuration is reversible in minutes and a wrong deploy is not.

Industrial monitoring · 5 repositories

OEE platform reading machine telemetry off the shop floor. Ingestion over MQTT — a device protocol, not an application one: small messages, persistent connection, tolerance for bad networks. Time series in TimescaleDB with retention windows and continuous aggregates. Real-time panel over SignalR. A PWA with local state that keeps operating with the network down and reconciles afterward. Back end and front end, same author.

The decision: offline was a requirement decided before the client was written, not a second-phase feature. Shop-floor connectivity drops — interference, uneven coverage between sheds, equipment competing for spectrum. A system that needs a connection to record a reading gets worked around with paper, and then the data feeding the indicator is fiction.

Commissioning · 7 repositories

Sales commission for five channels that pay differently: physical retail, e-commerce, wholesale, B2B, and an operation in another country. From a distance the five rules are the same thing. Up close they diverge in everything that matters — calculation base, when it is assessed, what retroacts, how apportionment treats a team, what happens when a sale is cancelled after closing.

The decision: one implementation per channel, not a parameterized engine. Parameterizing a formula produces a programming language badly disguised as configuration. It aged better than I expected, for a reason I had not anticipated: the largest repository in the area went through 29 different authors, and independent, readable code survived that. Each person changed the channel they needed without understanding the other four.

Integrations · from the ERP outward

Integrating is not moving data, it is translating models — and deciding what to do when the translation is not exact. SAP as ERP, with internal SDKs standardizing authentication, OData and sales orders. Apple GSX, DEP and Trade In behind technical assistance and buyback. Zendesk for support, Emarsys and Oracle Responsys for marketing.

The decision: one translator per external platform, not a generic bus. A single bus looks like savings and becomes a queue: every team waits on one team, and one platform's incompatibility contaminates the shared model.

From Windows servers to the cluster · in progress

Migrating a corporate estate from IIS on Windows servers to containers on Kubernetes. Worth being precise about what was already solved: the estate already had automated delivery — GitLab CI building, testing and publishing, separate environments, working rollback. What was legacy was not the delivery process, it was where the application runs.

What actually changes: reproducible environments, CronJobs instead of Windows Schedule, and monitoring the team can read without filing a ticket. 33 applications on the cluster, 194 still on Windows servers — 12 of the migrated ones are mine. I am a competent consumer of that platform, not its owner: the cluster, the base charts and the GitOps pipeline are the DevOps team's work.


Open Source

Five .NET libraries extracted from systems already running in production, then generalized. This is the part of my work anyone can read today, with no NDA involved.

Package What it solves Version Downloads
PMQ.Notifications Broken business rules as accumulated data instead of exceptions NuGet Downloads
PMQ.Mediator CQRS mediator with pipeline behaviors, streaming and FluentValidation NuGet Downloads
PMQ.ErrorHandling Centralized ASP.NET Core error handling with RFC 9457 responses NuGet Downloads
PMQ.Identity Provider-agnostic authentication — external OIDC or self-issued JWT NuGet Downloads
PMQ.Domain DDD building blocks — entities, aggregate roots, value objects, domain events NuGet Downloads

They are not five unrelated utilities

They snap together into one request lifecycle — the fit is the thesis:

flowchart TB
    REQ(["HTTP request"]) --> ID

    subgraph EDGE ["&nbsp;Edge&nbsp;"]
        ID["PMQ.Identity<br/><i>who is calling</i><br/>OIDC or self-issued JWT"]
    end

    ID --> MED

    subgraph APP ["&nbsp;Application&nbsp;"]
        MED["PMQ.Mediator<br/><i>dispatches the use case</i>"]
        VAL{"validation<br/>pipeline"}
        HND["handler<br/><i>one use case</i>"]
        MED --> VAL
        VAL -->|"valid"| HND
    end

    subgraph DOMAIN ["&nbsp;Domain&nbsp;"]
        DOM["PMQ.Domain<br/><i>entity · aggregate<br/>value object · event</i>"]
        NOT["PMQ.Notifications<br/><i>the rule complains here,<br/>accumulating</i>"]
        DOM -->|"rule broken"| NOT
    end

    HND --> DOM
    VAL -.->|"reads"| NOT
    VAL -->|"invalid:<br/>never executes"| ERR

    subgraph OUT ["&nbsp;Output&nbsp;"]
        ERR["PMQ.ErrorHandling<br/><i>standard response</i><br/>RFC 9457"]
    end

    HND --> OK(["200 · result"])
    ERR --> PROB(["4xx · problem details<br/>with every error"])

    classDef pkg fill:#512BD4,stroke:#512BD4,color:#fff
    classDef plain fill:transparent,stroke:#888,color:#888
    class ID,MED,DOM,NOT,ERR pkg
    class VAL,HND plain
Loading

The notification the domain accumulates is the same one the mediator pipeline reads before executing, and the same one the error handler turns into an HTTP response. Domain events publish after the commit — a handler must never observe a fact the transaction rolled back.

The design decision behind them

A violated business rule is an expected outcome, not exceptional control flow. Entities accumulate their failures instead of throwing — which costs less, and reports every problem at once instead of only the first:

var order = Order.Create(request.Items);          // never throws

if (order.IsInvalid)
{
    notificationContext.AddFrom(order, NotificationType.BusinessRule);   // → HTTP 422
    return Guid.Empty;
}

Exceptions stay where they belong: programming errors.

What the client gets back

One request, every broken rule — instead of discovering them one deploy at a time:

{
  "title": "A business rule validation failed.",
  "status": 422,
  "errors": [
    { "field": "Items", "message": "Item must be at most 200 characters." },
    { "field": "Items", "message": "Provide at least one item." }
  ],
  "traceId": "00-079d247677d55f7d8427fd421daec5f0-a773dd27f85d9bdb-01"
}

Extracted after the third case, never on the expectation of reuse — an abstraction built in anticipation is almost always wrong.


How I Decide

Five positions I hold, each with the price I agreed to pay for it.

Rules as data when the rule belongs to the business. Whenever a rule changes at the speed of the business, it leaves the code and becomes configuration — the channel risk policy, the factory downtime engine. The cost: configuration becomes critical system state and needs care of its own. The limit: in commissioning I decided the opposite, because there what varies is the formula, not which steps of a known sequence apply. Knowing where the rule stops applying is part of the rule.

Extract after the third case. Only the third use shows what is essential and what was an accident of the first. All 8 shared libraries I own and all 5 public packages came out of code that was already running in more than one place. The cost: duplication lives longer than it looks comfortable to leave it.

Derived state, never assigned. When an aggregate's state depends on its parts, that state is calculated, not written. Writing status = completed creates the chance for the status to lie. The cost: a recalculation function. The return: an entire category of support bug disappears.

Idempotency on everything coming from outside. External integrations resend, callbacks arrive out of order, robots reprocess queues. Any operation reachable by a third party must answer the same thing when called twice.

Say what I don't know. A profile with no declared limits forces the reader to find the limits themselves, and the first discovery contaminates everything that came before it. I would rather the limit be written down before anyone has to ask.


Stack

The level is the part that matters here. Plenty of people list twenty technologies without distinguishing what ran in production from what was a tutorial, so the distinction is explicit. Presence means a declared dependency in a project file, not a mention in code.

Applied in production

Level of presence
.NET / C# every repository of mine · 224 of the estate's 319 apps · versions 2.2 through 10
Oracle · EF Core · Dapper 57% · 54% · 21% of my repositories
xUnit · FluentValidation 65% · 54% of my repositories
React and TypeScript 37 apps in the estate · 3 products of mine with back and front
Angular 14 apps in the estate — maintenance, not new architecture
MongoDB · PostgreSQL · TimescaleDB 6% · 4% · one system, telemetry time series
Kubernetes · ArgoCD · Helm · Docker 11% of my repos · 33 estate apps on the cluster
GitLab CI delivery for the whole estate, legacy and new
MQTT · SignalR one system each — machine telemetry, real-time panel
Keycloak · OIDC · JWT estate authentication
Polly 6% of repos — retry and circuit policies
Declared limits — what I know without production to prove it
Technology Status
Broker messaging (RabbitMQ, Kafka) knowledge, no production. Does not appear in a single repository of mine. It is the gap I would close first
gRPC knowledge, no production system
Event sourcing studied, never applied

Two more, from the projects themselves: I have no mileage with TimescaleDB at scale or advanced tuning, and I have never operated a Kubernetes cluster during a real incident — when something breaks at the infrastructure level, I am not the one who fixes it.


Outside Work

Two games, both private repositories, both built for family rather than for a store. They are here for what they prove.

Onde está o Gato — 2D Android game, Godot 4 and C#. The game rules do not know the engine: the domain is a pure C# library with no line of Godot in it, and the engine project is a thin layer that reads it and draws it. Most of the game is testable without a GPU, and the whole suite runs headless in seconds — which matters, because I develop on a dual-core Pentium G4560 where opening an Android emulator to check a progression rule is not viable in practice, and what is not viable in practice nobody checks.

Reino do Ritmo — musical game for Roblox, in Luau. Same decision, different stack: the core is pure Luau with no game, workspace, Instance or task, holding economy, rarity rolls, offline income and streaks. Keeping it pure took three layers — a linter configured against the standard library instead of Roblox's, a CI guard against os.time, tick and math.random, and the suite running outside Roblox, which is the one that actually proves it.

The same architectural decision surviving C#/Godot and Luau/Roblox is the best evidence I have that the principle does not depend on the stack. What did not translate is documented with the same honesty — identity, repositories and domain events gain nothing in a single-aggregate save file, and pretending otherwise is how pattern translation turns into cargo cult.


Trajectory

Period Role Duration
Apr 2013 – Nov 2021 Production Assembler 8 y 8 m
Nov 2021 – Jul 2022 IT Help Desk Assistant 9 m
Jul 2022 – Nov 2025 Backend Software Engineer (.NET) 3 y 4 m
Nov 2025 – present Senior Software Engineer · Technical Leader

Thirteen years at the same group, four of them as an engineer. First commit on 10 August 2022. In the four years since, the scope grew in three steps: from a feature, to a whole service with back and front, to platform and standards other teams consume.

Education — Postgraduate lato sensu in Web Development (UNIFAEL) · Technologist in Systems Analysis and Development (UNINTER). Six certifications between October 2021 and December 2022, concentrated in the career transition.


LinkedIn Email NuGet

Novo Hamburgo, RS · Brazil

Popular repositories Loading

  1. PabloMickaelQuevedo PabloMickaelQuevedo Public

    3

  2. PMQ.Notifications PMQ.Notifications Public

    A package for managing notifications and business rules in .NET applications, making it easy to handle messages, errors, and domain rules in a centralized and strongly-typed way. Designed to fit CQ…

    C# 2

  3. PMQ.ErrorHandling PMQ.ErrorHandling Public

    A comprehensive ASP.NET Core error handling library that provides standardized error responses, automatic exception filtering, and business rule notification processing with built-in localization s…

    C# 2

  4. PMQ.Identity PMQ.Identity Public

    Provider-agnostic identity module for authentication and authorization in ASP.NET Core applications. Supports External (OIDC/JWT — e.g. Keycloak, Auth0) and Local (self-issued JWT — e.g. ASP.NET Id…

    C# 1

  5. PMQ.Mediator PMQ.Mediator Public

    A lightweight mediator pattern implementation for .NET applications with FluentValidation integration, request/response pipeline behaviors, streaming support, and notification publishing.

    C# 1

  6. AspNetCore-Developer-Roadmap AspNetCore-Developer-Roadmap Public

    Forked from MoienTajik/AspNetCore-Developer-Roadmap

    Roadmap to becoming an ASP.NET Core developer in 2022