What Is Agentic Governance? (The Definition That Actually Ships)

“Agentic governance” is being used to mean two different things, and the gap between them is where most production incidents happen.

The first meaning — the one that fills enterprise whitepapers and framework documents — describes governance as a set of principles: accountability structures, ethical guidelines, oversight committees, policy documents for how AI should behave. It’s governance as intention.

The second meaning describes governance as a runtime enforcement layer: the software infrastructure that controls what AI agents are actually allowed to do at execution time, independent of what the agent’s own reasoning might suggest. It’s governance as enforcement.

The first kind of agentic governance tells you what your agents should do. The second kind determines what they can do. Most organizations investing in AI have the first kind. Far fewer have the second.

Agentic governance is the set of runtime policies and enforcement mechanisms that control what autonomous AI agents are permitted to access, spend, output, and execute — enforced at the infrastructure layer, evaluated before each action, independent of the agent’s own reasoning. It is distinct from AI governance frameworks (which define principles and accountability structures) and from AI observability (which records what agents did). Agentic governance is the enforcement layer between an agent’s intent and the real-world consequences of its actions. Without it, agents can reason correctly within their training and still take actions that violate safety, compliance, or cost constraints — because nothing stops them.

Why does the definition matter?

Because the wrong definition leads to the wrong investment.

Organizations that treat agentic governance as a framework exercise produce documentation. They define acceptable use policies, establish accountability chains, and write AI ethics principles. These are not worthless — they establish intent and can satisfy some regulatory requirements. But a framework document has no mechanism to stop an agent from routing customer PII to an external API. It has no mechanism to terminate a session that is burning $500 in API costs per minute. It has no mechanism to require human approval before an agent issues a database update.

The agent doesn’t read the governance document. It executes code.

The organizations that treat agentic governance as a runtime enforcement capability build systems that actually control agent behavior. The difference shows up in production, when something goes wrong that the framework didn’t anticipate — which is to say, regularly.

What makes a system “agentic”?

Before defining governance, it helps to be precise about what makes an AI system agentic, because the governance requirements scale directly with agentic capability.

A traditional LLM application is stateless and bounded: a user sends a prompt, the model generates a response, the interaction ends. The blast radius of a bad output is limited to that single response.

An agentic system is different in four ways that matter for governance:

Autonomy. Agents make sequences of decisions without human review at each step. They determine what to do next based on intermediate results, not just the original instruction.

Tool access. Agents don’t just generate text — they take actions. They call APIs, query databases, write files, send messages, trigger workflows. The blast radius of an agentic error is not a bad sentence — it’s a database write, a sent email, a processed transaction.

Persistence. Agents maintain context across steps and, increasingly, across sessions. They accumulate state. A governance failure that compounds across a multi-step workflow is categorically different from a single bad response.

Delegation. In multi-agent architectures, agents spawn and direct other agents. A single governance failure can propagate through a hierarchy of sub-agents before any human reviews it.

These four properties — autonomy, tool access, persistence, and delegation — are what make agentic systems powerful. They’re also what make agentic governance necessary. Each property multiplies the potential consequence of a governance gap.

What does runtime agentic governance actually enforce?

Runtime agentic governance operates across five enforcement domains. Each maps to a category of real production risk.

What is access governance for AI agents?

An agent’s capabilities are defined by what systems it can access: which databases, which APIs, which file paths, which external services. Without access governance, any tool in an agent’s definition is available to it in every context. With access governance, tool access is scoped to the session, the user, the task type, and the data classification of what’s being processed.

The enforcement question: is this agent permitted to invoke this tool, on this data, in this context?

What is cost governance for AI agents?

Agentic systems run in loops. A session that’s expected to consume $0.10 in tokens can, under the right (or wrong) conditions, consume $10 or $100. Without cost governance, the only ceiling is the API provider’s rate limits. With cost governance, a per-session token budget terminates the session before it exceeds its threshold — not after.

The enforcement question: has this session exceeded its authorized spend limit?

What is content governance for AI agents?

Agents process and generate content containing sensitive information: PII, financial data, healthcare records, confidential business information. Content governance intercepts inputs and outputs, applies classification and filtering rules, and blocks transmissions that violate data handling policies before they execute — not after they’re logged.

The enforcement question: does this content violate data handling policy for this context?

What is quality governance for AI agents?

An agent that is highly confident can still be wrong. Quality governance sets enforcement thresholds: outputs below a defined confidence level are flagged, held for review, or blocked rather than delivered. For high-stakes applications — medical information, legal advice, financial guidance — this is the difference between real enforcement and hope.

The enforcement question: does this output meet the quality threshold required for this use case?

What is operational governance for AI agents?

Beyond individual actions, agents can exhibit patterns that require intervention: retry loops that should trigger circuit breakers, sessions that should escalate to human review, operations that require approval. Operational governance defines the behavioral envelope within which agents are permitted to run.

The enforcement question: does this agent’s current behavior pattern require escalation or termination?

What agentic governance is not

Agentic governance is not observability. Observability records what your agents did. Governance controls what they’re allowed to do. You can have perfect observability — full traces, every tool call logged, complete cost data — and zero governance. You’ll know exactly what went wrong after it goes wrong. You won’t have stopped it. Observability and governance are complementary layers, not alternatives.

Agentic governance is not prompting. Writing “do not access customer financial records” in a system prompt is not governance. It’s an instruction. Agents can fail to follow instructions — due to context length limitations, adversarial injection, model drift, or simply because the edge case wasn’t anticipated. Governance operates at the infrastructure layer, not the prompt layer. It enforces regardless of what the model’s reasoning concludes.

Agentic governance is not testing. Evaluation suites, red-teaming, and pre-production testing reduce the probability of bad behavior. They do not eliminate it, and they do not intervene when novel edge cases appear in production. Runtime governance is the enforcement layer that catches what testing didn’t cover.

Agentic governance is not compliance documentation. Policy frameworks, acceptable use documents, and AI ethics guidelines establish intent and create accountability. They are not enforcement mechanisms. An agent does not consult your governance policy before making an API call.

Why the framework-first approach leaves a gap

Most enterprise AI governance investment today goes into framework governance: defining principles, establishing accountability structures, creating oversight bodies. This investment is not wasted — it’s necessary for organizational alignment and regulatory positioning.

But framework governance has a specific and predictable failure mode: it governs human decisions about AI systems, not the systems themselves.

A governance framework can establish that “agents must not process PII outside approved data regions.” A runtime governance policy can actually enforce that constraint. The framework creates the rule. The runtime layer makes the rule real.

The gap between framework governance and runtime governance is where most production AI incidents live. An agent violates a data handling policy — not because the policy didn’t exist, but because there was no enforcement mechanism between the policy document and the agent’s execution.

According to the AI Risk Management Framework published by NIST (AI RMF 1.0), effective AI governance requires both governing structures and technical controls. The technical controls — the enforcement layer — are what framework-only approaches consistently underinvest in.

The architecture of runtime agentic governance

Runtime agentic governance has three architectural requirements that distinguish it from both observability and framework governance.

Infrastructure-layer enforcement. Policies must operate at the infrastructure layer, not inside the agent’s code. An agent cannot be relied upon to govern itself — not because agents are malicious, but because their reasoning can fail, and because agent code changes frequently while governance requirements change on a different cadence. The governance layer must be independent of the agent’s own logic.

Pre-execution evaluation. Policies must evaluate before actions execute, not after they’re logged. Post-hoc detection of governance violations is valuable for learning; it is not governance. Governance is the intercepting layer between intent and consequence.

Framework agnosticism. Most organizations run agents across multiple frameworks — LangChain, CrewAI, LlamaIndex, custom Python. Runtime governance must apply consistently across all of them, enforcing the same policies regardless of what framework built the agent underneath.

How Waxell handles this

How Waxell handles this: Waxell is built around the runtime definition of agentic governance. The execution tracing layer instruments agents across any framework in three lines of SDK code, capturing the full execution graph: LLM calls, tool invocations, external API requests, token usage, costs. On top of that observability layer, runtime governance policies evaluate before each tool call and output — enforcing access scope, cost limits, content filtering, quality thresholds, and operational circuit breakers at the infrastructure layer, independent of the agent’s code. Policies are defined once and enforced across every agent regardless of framework. Enforcement records are embedded in the execution trace, creating the audit trail that compliance requires.

Agentic governance in practice: three scenarios

A financial services firm deploys a document analysis agent that reads customer financial records and generates summaries. Framework governance defines that the agent should not transmit account numbers externally. Runtime governance enforces a content policy that intercepts any outbound request containing detected account number patterns before the API call executes. The framework states the rule. The runtime layer makes it real.

A healthcare platform’s patient intake agent begins a session that enters an unexpected loop — repeatedly re-querying a symptom database due to a parsing edge case that didn’t appear in testing. Cost governance with a per-session token budget terminates the session automatically when it exceeds its threshold. Without the policy, the loop runs to timeout.

An enterprise deploys 12 agents across customer service, document processing, and internal IT functions. A quarterly compliance audit requires evidence that agents operated within data handling constraints. Waxell’s enforcement records — embedded in each execution trace — document every policy evaluation: what was checked, what triggered, what was blocked, what was allowed. The audit produces enforcement documentation, not just logs.

Frequently Asked Questions

What is agentic governance?
Agentic governance is the set of runtime policies and enforcement mechanisms that control what autonomous AI agents are permitted to do at execution time — including what tools they can access, how much they can spend, what content they can transmit, and when they must escalate to human review. It operates at the infrastructure layer, evaluated before each agent action, independent of the agent’s own reasoning. This is distinct from AI governance frameworks (which define principles and accountability structures) and from AI observability (which records what agents did). Agentic governance is enforcement, not documentation.

What is the difference between agentic governance and AI governance?
AI governance broadly refers to the policies, frameworks, and oversight structures that guide responsible AI development and deployment. Agentic governance is a specific subset focused on autonomous AI agents — systems that take sequences of actions and use tools to interact with real-world systems. Agentic governance requires runtime enforcement because agents have a broader action surface than static LLM applications: they call APIs, write to databases, and execute workflows. Framework documents can describe what agents should do; runtime governance enforces what they can do.

What is the difference between agentic governance and AI observability?
Observability gives you visibility into what your agents did: traces, logs, cost data, tool call records. Governance determines what your agents are allowed to do: it enforces policies at runtime, before actions execute. You can have complete observability and zero governance — you’ll know exactly what went wrong after the fact, but you won’t have prevented it. Waxell provides both in a unified data model, so enforcement events appear in the same execution trace as observability events.

Why can’t I govern AI agents with system prompts?
System prompts are instructions — they tell the agent what to do. They’re not enforcement mechanisms. An agent can fail to follow a system prompt instruction due to context window limitations, adversarial prompt injection, model drift, or simply unanticipated edge cases. Runtime governance operates at the infrastructure layer, outside the agent’s reasoning loop. It enforces regardless of what the model concludes — which is why it’s governance rather than guidance.

What policies does agentic governance cover?
Runtime agentic governance covers five enforcement domains: access governance (which tools and systems the agent can invoke), cost governance (per-session and cumulative spend limits), content governance (PII filtering, data classification, output screening), quality governance (confidence thresholds and output quality gates), and operational governance (circuit breakers, escalation triggers, session termination conditions). Each domain maps to a category of production risk that framework governance can describe but cannot enforce.

How is agentic governance different from AI guardrails?
“Guardrails” typically refers to input/output filtering for LLM responses — checking that outputs don’t contain harmful content or meet quality thresholds. This is content governance, which is one component of agentic governance. But agentic governance is broader: it also covers tool access control, cost enforcement, operational circuit breakers, and compliance audit trails. Guardrails address what an agent says; governance addresses what an agent does. Agents with tool access require governance across the full action surface, not just the output surface.

Sources

Bipolar and Sleep Deprivation: What Actually Happens

Originally published at steadyline.app

Sleep deprivation is one of the strongest and most consistent triggers for manic episodes in bipolar disorder. Research shows that even a single night of significantly reduced sleep can initiate hypomanic symptoms, and two consecutive nights of poor sleep substantially increases the risk of a full mood episode. The mechanism is neurological, not psychological: sleep loss destabilizes the circadian and neurotransmitter systems that are already vulnerable in bipolar disorder.

I need to tell you something that took me far too long to learn.

When I don’t sleep, what happens to me is not what happens to most people. Most people get tired. They’re groggy, they need coffee, they push through the day and crash early that night. Their system self-corrects.

Mine doesn’t. When I lose sleep, something different happens. Something that feels, at first, like the opposite of tiredness. And that’s exactly what makes it dangerous.

What actually happens: the cascade

Here’s the sequence I’ve experienced and tracked enough times to describe it with precision.

Hours 0 to 16 (the first bad night): Nothing obvious.

You slept 4 hours instead of 7. You feel a little off, maybe slightly foggy in the morning, but it clears. By midday you feel fine. Maybe even better than fine. There’s a subtle sharpness, a low-grade buzz of energy that doesn’t match the sleep deficit. If you’re not paying attention, and you’re probably not, this passes without notice.

This is where it starts. Not with a dramatic crash. With a misleading uptick.

Hours 16 to 40 (the day after, heading into night two): The divergence.

This is where the bipolar brain separates from the neurotypical response. A neurotypical brain responds to sleep debt with increasing sleepiness. The homeostatic sleep drive pushes harder, you get drowsy earlier, you sleep longer the next night. System corrects.

In bipolar, research suggests the homeostatic response can malfunction. Instead of getting sleepier, you may get more alert. Dopamine and norepinephrine spike in response to sleep deprivation, and in a brain already prone to mood dysregulation, that spike doesn’t just wake you up. It energizes you. It lifts your mood. It makes you feel productive, clear, capable.

Your brain is breaking, and it feels like your brain is finally working.

If you sleep normally on night two, you usually recover. The system resets. This is why one bad night is survivable. It’s the second night that matters.

Hours 40 to 72 (two bad nights in a row): The 48-hour window.

This is the danger zone. Two consecutive nights of short sleep, under 5 or 6 hours, is enough to destabilize the entire system in someone with bipolar. I’ve lived this pattern enough times to describe it from the inside.

Your thoughts speed up. Not anxiously. They feel fluid, connected, brilliant. You have ideas and they all seem urgent. You’re talking more, texting more, planning more. Your mood is elevated but your patience is thin. You’re euphoric and irritable at the same time, which makes no sense unless you’ve been there.

This is where the 48-hour rule becomes critical. Any decision that feels urgent at this point should wait. Because your judgment is compromised, and the urgency itself is a symptom.

Beyond 72 hours: The episode.

If the sleep deficit continues uncorrected, the cascade becomes a full mood episode. For me, this has always been mania or hypomania, though some people cascade into depression or mixed states. The early warning signs are fully present by this point. Decreased sleep need, grandiosity, rapid speech, impulsive behavior. But you don’t see them because you feel amazing. And feeling amazing after not sleeping is the most reliable sign that something is very wrong.

Why this isn’t just “being tired”

When someone without bipolar loses sleep, the effects are broadly linear. More sleep debt equals more tiredness. The response is proportional and predictable.

With bipolar, the response is nonlinear. Sleep loss doesn’t just make you tired. It triggers a cascade through your circadian system that can flip your neurochemistry into a different mode entirely. The clinical literature treats sleep deprivation as one of the most reliable experimental triggers for mania. Researchers have literally used sleep deprivation to induce hypomania in controlled settings. That’s how strong the connection is.

Here’s what that means practically. If you have bipolar and you pull an all-nighter for work, you haven’t just lost sleep. You’ve potentially initiated a process that takes days to play out and weeks to recover from. The all-nighter itself might feel fine. The episode that follows two days later will not.

I’ve seen this in my own tracking data. The worst mood episodes I’ve logged were almost always preceded by a cluster of short sleep nights. Not always dramatically short. Sometimes just 5 hours for three nights running. Enough to feel like “not a big deal” in the moment. Enough to start the cascade.

The paradox: why sleep loss feels productive

This is the cruelest trick of bipolar neurobiology.

The initial hours of sleep deprivation produce a neurochemical response that feels exactly like enhanced performance. You feel sharper. Tasks that usually bore you suddenly feel engaging. You’re writing faster, thinking faster, connecting ideas faster. The productivity is real, in the sense that output increases. But the judgment behind it degrades. Your data would show the pattern even though your feelings insist otherwise.

I’ve written code at 3 AM during these stretches that I was absolutely certain was brilliant. Some of it actually was. Most of it was not. The problem isn’t that sleep-deprived creativity is always wrong. It’s that you can’t tell the difference in the moment. And the cost of being wrong, a full episode, is vastly higher than the benefit of one productive night.

The productivity trap is particularly vicious because it creates a reinforcement loop. You lose sleep, you feel productive, you stay up to keep being productive, you lose more sleep, and the cycle accelerates. By the time the productivity curdles into irritability and chaos, you’re deep enough in the cascade that self-correction is much harder.

What I do now: the protocol

This is what I’ve built for myself after getting burned enough times.

I treat two bad nights as a medical event. Not a lifestyle inconvenience. Not something to push through. A signal that requires active response. The same way you’d respond to a 102-degree fever. You probably don’t call your doctor for a single bad night. Two consecutive bad nights? Call.

I track sleep every single day. Not just hours. Quality, timing, how rested I felt. I can see the patterns developing before they become episodes. Mood alone isn’t enough to catch this. The sleep data has to be there, tracked consistently, not reconstructed from memory.

I have a “no major decisions” rule after short sleep. This is the 48-hour rule in practice. If I slept poorly, I don’t make financial decisions, relationship decisions, or career decisions. Nothing that can’t be reversed. The urgency I feel is the symptom, not the situation.

I protect the second night. The first bad night is often out of my control. Travel, work, stress, a baby crying. The second night is where I intervene aggressively. Cancel plans. Take the prescribed sleep aid. No screens. No stimulants after noon. Whatever it takes to get 7 hours. Because that second night is the difference between a bad day and a bad month.

I tell someone. My dad, usually. “I didn’t sleep well last night, and if I don’t sleep tonight, I need you to watch for signs.” This isn’t dramatic. It’s practical. The people around you see it before you do. Giving them a heads-up when you know you’re vulnerable is one of the smartest things you can do.

The data makes it obvious

Looking back through months of tracking data, the pattern is so clear it’s almost boring. Every significant mood episode I’ve logged has a cluster of short sleep nights somewhere in the preceding week. Not every cluster of short sleep nights leads to an episode. But every episode has one.

That asymmetry is important. It means sleep tracking doesn’t give you certainty. But it gives you probability. And probability is enough to act on.

If you’re not tracking sleep alongside mood and energy, you’re missing the single most predictive dimension of bipolar stability. A complete tracking approach includes all of them. Because the cascade doesn’t show up in any single number. It shows up in the relationship between numbers over time.

I built Steadyline to catch this cascade before it plays out. The AI looks at exactly these patterns: sleep dropping, energy rising, mood and irritability diverging. It flags the pattern early, when there’s still time to do something about it. Not after the episode has started. Before.

Because sleep deprivation in bipolar isn’t just about being tired. It’s the first domino. And the only reliable way to keep the rest from falling is to see it tipping before you feel it.

Related reading:

I’m Sam, a software engineer living with bipolar disorder. I built Steadyline because the cascade from sleep loss to episode is predictable and preventable, if you have the data. More at steadyline.app.

Como eu penso e escrevo meus testes

Testes são uma parte fundamental do desenvolvimento de software. Eles nos ajudam a garantir que nosso código funcione como esperado e que possamos fazer alterações sem quebrar funcionalidades existentes.

Antes de criar uma aplicação, eu gosto de pensar em como vou testá-la. Isso me ajuda a projetar meu código de forma que ele seja mais fácil de testar.

Piramide de testes é uma forma de organizar os testes em uma aplicação. Ela é dividida em três camadas.
Na base temos os testes unitários, no meio os testes de integração e no topo os testes de ponta a ponta.

Piramide de testes

O próximo conceito de como escrever testes é o TDD (Test-Driven Development).
No TDD, escrevemos o teste antes de escrever o código. O fluxo é o seguinte:

  1. Escreva um teste que falhe.
  2. Escreva o código mínimo necessário para que o teste passe.
  3. Refatore o código para que ele fique mais limpo e organizado.
  4. Repita o processo.

Anatomia de um teste. Eu divido em 3 partes, setup, ação e verificação.

# setup
let(:user) { User.new(name: "Ewerton") }

# ação
user.save!

# verificação
expect(user).to be_persisted
``

Pensando em casos de uso, eu gosto de pensar em como o usuário vai interagir com o sistema. 
O BDD (Behavior-Driven Development) é uma forma de escrever testes que foca no comportamento do sistema.
Eu usei bastante na minha carreira uma gem para testes chamada rspec. O rspec é uma biblioteca de testes para Ruby que implementa o BDD. Aqui eu penso em 3 palavras, Given, When e Then.

1. Given (Dado que): Eu tenho uma calculadora e os números 5 e 10.
2. When (Quando): Eu executo a operação de soma.
3. Then (Então): O resultado deve ser igual a 15.

ruby
RSpec.describe “Calculator” do
describe “addition” do
let(:calculator) { Calculator.new }

context "when adding two positive numbers" do
  it "returns the sum of the two numbers" do
    result = calculator.add(5, 10)
    expect(result).to eq(15)
  end
end

end
end

class Calculator
def add(a, b)
a + b
end
end




Esse exemplo é bem simples, mas ele ilustra bem o conceito de BDD. Isso é um pouco de como penso e como escrevo testes. Mas esses conceitos são um mundo a ser explorado. Não cabe em um único artigo e pretendo escrever mais sobre testes em breve. 

4 Useful Terminal Commands for Awkward Finder Tasks (Bite-size Article)

Introduction

Today’s article is a short one, but I’d like to share four useful Terminal commands I recently learned for Finder-related tasks (Mac).

Until not long ago, I used to check these things manually, but doing them in Terminal is much faster, so I’ve been using these commands a lot lately. I decided to write this article partly as a memo for myself, and also to share them with people who may not know them yet. Much of this is fairly basic, so some of you may already know these, but if you’re interested, I hope you’ll give it a quick read.

1. Show Only Recently Updated Files

find . -type f -mtime -7

This command lists files that have been updated recently under the current directory and its subdirectories.

Finder does have a “Recent” section in the sidebar, but in my case—perhaps because of my own PC—it can sometimes take time to load, or miss files I expect to see. So this feels like a more reliable method.

The -7 part represents the number of days in the past, so it’s convenient that you can change it to any value you want.

2. Count Files

First, here is the simplest version as a basic way to count files.

find . -type f | wc -l

This command counts the number of files under the current directory (excluding folders).

If you want to include folders as well, you can use:

find . | wc -l

Note: In this form, the starting point . itself is also counted as one item. If you do not want to include it, use find . -mindepth 1 | wc -l instead.

Next, based on these commands, here is an example of excluding a specific folder.

find . -type f ! -path '*/node_modules/*' | wc -l

This counts files while excluding node_modules. In this case, the added condition is “exclude this folder.”

If you want to count only files that contain a specific string in their filename, you can change it like this:

find . -type f -name '*test*' | wc -l

This counts only files whose names contain test.

Here, the added condition is “filter by name.”

Finder already shows the number of items in a directory at the bottom by default, but by combining commands like these, you can investigate with much more specific conditions, which makes them very useful.

3. Find Empty Folders

find . -type d -empty

This command lists only empty directories.

Sometimes you create a folder during work and forget to put anything in it, or empty folders remain after reorganizing files. It is possible to look for them in Finder, but it becomes quite troublesome once the folder hierarchy gets deep.

In those situations, this command is convenient because it lets you check unnecessary empty folders all at once. It is a small thing, but quite useful when cleaning up a project.

4. Quickly See Which Folders Are Heavy

du -sh ./* | sort -h

This command displays the sizes of files and folders directly under the current directory in an easy-to-read order.

du -sh ./* shows the size of each item, and sort -h arranges them in order by size. Because of that, it is useful when you want to get a rough idea of which folders are taking up the most space.

In Finder, you can check information for individual items with Command + I, but comparing multiple folders in a list is a bit inconvenient. When you want to identify unused large data and delete it, it can be very productive to go to a suspicious directory first and run this command.

Note: In this example, hidden files and hidden folders whose names begin with a dot are not included.

Conclusion

Today I introduced four Finder-related Terminal commands that I have been using a lot recently.

Personally, many of these tasks could already be checked manually, so for a long time I never really had the idea of looking into whether there was a faster way to do them. However, once I started using them in real work, I found them extremely convenient, and I feel that they noticeably improve the speed of small verification tasks, even if only in subtle ways.

Finder can already do many things quite well, but when you want to check things with more specific conditions, Terminal still feels more useful. If I come across more commands like these, I’d like to keep sharing them here as notes for myself as well.

Thank you very much for reading!

Anchoring the Vibe: The AEGIS model

Mexian Standoff (Photo by Emilio del Prado, Wikimedia Commons)

Where different perspectives matter more than the prompt

We live in interesting and challenging times. With the introduction of AI and Large Language Models into everyone’s lives, we are experiencing a great shift in how work gets done across all industries.

As a software developer with over 20 years experience, this is by far the most significant disruption I’ve experienced. Many of the skills I’ve practiced daily — things that makes me feel proud about my work and that makes me feel professional — suddenly appears to have less value. Skills like clean code and TDD are still valuable, but they no longer have the same leverage.

I realize that I need to adapt to this reality. We all do. But my purpose of this article is not just to express personal reflections; it’s also about a concern I have related to what’s happening to the way we work and collaborate. I see patterns emerging that makes me worried.

The Mexican Standoff

Mexian Standoff (Photo by Emilio del Prado, Wikimedia Commons)

I’m a firm believer of the Agile principles — not as a set of rituals and ceremonies, but as a commitment to human relations and communication. For me the magic happens when everyone; product owners, developers , designers and other stakeholders; come together and collaborate. We all bring different backgrounds and experiences to the table and together I believe we bring the best value to our customers. And this is not easy.

I also know that far from everyone works like this today and I think that is sad. I’ve had my fair share of experiences in “the desert” but I’ve always strived to work in “a forest”, and even trying to plant one if it does not exist.

As Marc Andreessen has suggested, it looks like we are currently seeing a “Mexican standoff” happening between three roles in the software development: product owner, UX:er and developer. And this is happening because of AI.

  • The Product Owners believe they can “vibe code” a fully working, and decent looking application with a few prompts and without the involvement of developers and UX:ers.

  • The UX:ers can talk directly to the end-user to understand requirements, and develop a similar application without the need of developers or product owners.

  • The Developers can bypass the product owners and UX designers (and even other developers), figure out requirements, generating design and create the application on their own.

The scariest part? It works. It’s fast. You can go from idea to something that appears “customer-ready” in a fraction of time.

Does that mean that these three roles are now the same and capable of producing the same value? No, I don’t think so.

Does it mean that we should stick to the old barriers and silos between roles? Absolutely not.

Does this mean that the roles will change? Yes, most certainly.

Instead of waiting for the answers on how it will change, I think that all of us should embrace the tools, to learn and play with them as much as possible. But even more important, I also think we should challenge and experiment with the way we work together. Instead of continuing in the same rituals and processes as before, try to collaborate in different ways. And in doing this, recognize that our unique professional perspectives may now matter more than ever.

Cutting corners because our processes are broken is like painting over rust. Bypassing existing quality gates and challenges and leaning into “Vibe coding” might remove the immediate frustration of a handover, a conflict and disappointment, but the underlying structural issues remain. If we ignore human interaction to gain speed, we aren’t actually solving the problem of slow, incorrect deliveries — we’re just accelerating the path to a failed product.

Instead of using new technology to route around each other, let’s use it to tear down the walls. Gather around the tools and try to solve actual problems. I strongly believe that this is a recipe for success.

The Danger of “Vibe Coding”

Speed Warning Sign (Photo Adrian Newell, Pexels)

Are we underestimating the value of the team? If a product owner vibe-codes a solution without anchoring ideas and requirements with a developer, or validating the behavior and design with a UX expert, are we not missing the very essence and value of collaboration with other humans?

Speed is attractive, but we should’ve learned the hard way by now that Agile is not about speed. Vibe coding can make you addicted to the features. It is so easy and cheap to generate one new feature after another that we don’t stop to seek feedback from the users of the features. How do you ensure that you are providing value or learn from your mistakes when you can ship 30 features in a couple of vibe prompts?

I argue we should embrace a feature-by-feature approach. Keeping changes small and controlled allows us to test our assumptions with real users and learn, even if the code itself is written by AI.

Introducing the AEGIS model

A Forest (Photo Anton Atanasov, Pexels)

I’ve been experimenting with a mental model. I call it the AEGIS model. It’s an idea to ensure that we don’t drown in a flood of AI output and presumptions. The aim is instead to produce only what provides true value while encouraging close collaboration, ensuring that everyone has a place at the table.

**AEGIS **is a commitment to resolving our different perspectives into a high-density specification before an agent writes a single line of code. Whether applied to a solo project — where you rotate different “professional hats” — or within a full team, the principle remains the same: we should not underestimate the unique value that different domains and people bring to the table. By forcing this alignment up front it naturally encourages smaller, controlled features and a a continuous loop of testing and feedback.

The model is derived from the idea of Spec-Driven Development. By capturing multiple professional perspectives and storing them as part of the repository, we create a “Living Specification” that lives close to the code. This serves two critical purposes:

  1. For the Agent: It provides high-resolution requirements for the current task as well as access to a historical record of decisions taken. The AI can see the evolution and reasoning of the product, not just its current state and prompt.

  2. For the Team: It acts as professional guardrails. It reminds us to not ignore the architectural, design, or business implications that we take for granted today, and that has been built up by individuals years of experience building software. By centering our work around this shared specification, my hope is that we can break down barriers between the professions and work closer than before.

If we want to break the silos we need a new way to shake hands. AEGIS provides that structure by making sure that all features are filtered through multiple professional lenses before the actual code is generated:

  • A — Architecture: We utilize Architectural Decision Records (ADRs) to anchor the feature. This is not about what we are building, but why we have chosen a specific path, what alternatives we have discussed and what the consequences of the implementation will be.

  • E — Engineering: This is where the developer’s current craft remains essential. We provide the rules for the agent: how we want the code to be written and structured, the expected interface between modules, and strict separation of concerns. I’ve experimented with **EARS (Easy Approach to Requirements Syntax) **and have found it invaluable as a lightweight syntax for defining system logic and library preferences.

  • G — Goal: All changes needs to be justified. This is where we capture the business problem and the Value Hypothesis — what we think we’ll achieve and how we will measure success.

  • I — Interaction: This is the domain of the designer/UX:er. It can be anything from high-fidelity prototypes and wireframes to the specific color schemes, fonts, and interaction flows to ensure the product feels like ours and not something generic that the AI infers from prediction.

  • S — Specification: These are the behavioral requirements. We can use structured formats like Gherkin (Given/When/Then) or User Stories to define the functional requirements. The idea is that this becomes the source of truth that the agent must fulfill and we need a testing harness to be able to verify the behavior.

The power of traceability

I have experimented with a tagging system where every feature is assigned a unique, permanent ID. This is not just for organization; it is about creating a Traceable Thread through the life of the product.

By indexing specifications this way, we transform the collection of documents into a Relational Knowledge Base. This allows both humans and agents to navigate the “Why” behind the “What”. The idea is to get the agent to stop guessing and start operating with good context of the product’s evolution.

The Verification Loop

I think that one of the greatest risk with “Vibe Coding” is a false sense of completion. An agent might generate a beautiful UI or a complex function in seconds, but without a testing harness, that speed can be an illusion.

I believe that each feature requires a non-negotiable layer of verification. Whether the test itself is created by an agent or a human is secondary to the integrity of the proof. We should ensure that the result produced by the agent isn’t just “working code”, but a perfect match for the behavior we have specified.

This is where the human remains the ultimate judge. We can’t just “accept” the output; we need to verify it. By combining unit tests with automated test suites and acceptance testing, the hope is to turn the probabilistic nature of AI into a more deterministic professional outcome.

From Hype to Handshake

I am still experimenting with this model. Testing how AEGIS performs in the wild — not just on new “greenfield” projects, but also in the complex reality of “brownfield” legacy systems where the historical context is often missing or broken and refactoring is required.

Right now, the industry is strapped onto the hype train. I feel that we are so fascinated by the magic of the tools that we’ve stopped reflecting on how to use them together. We are treating AI as a replacement for labor, when I think we should be using it as a catalyst for closer human collaboration.

The promise of AI isn’t that it replaces the human handshake; it’s that it enables us to reach the user sooner and learn from our mistakes faster. It gives us bandwidth to stop worrying about the “how” and start obsessing over the “why”.

Why Cutting People Now is a Structural Risk

Another troubling trend that starts to appear are corporations demanding justification for their AI investments. It fells like a growing expectation that “less people can do more” now, leading to layoffs. And this before we have managed to identify and incorporate new ways of working using these tools.

Doing this while we are still in the experimentation phase is harmful. We are currently finding new ways to utilize this technology and this is changing by the minute; we must let that process take its time. If we are so stressed by the need for immediate ROI that we cut the very people who provide the “Anchor”, I think we have to question if that investment made sense in the first place.

Leaning into “Vibe Coding” as a way to justify a smaller headcount is a trap. It prioritizes the appearance of productivity over the reality of a sustainable product. If we lose our diverse professional perspectives where we seek to deliver value by balancing needs and costs, we aren’t becoming more efficient — we are just becoming more fragile.

I really want to stress that this is my opinion today. In an era where things change by the hour, my views might shift. But I strongly believe that the value right now is not in the “vibe” — it’s in the anchoring.

I would love to hear your reflections on this. Don’t hesitate to reach out to me — let’s make sure that all of us cultivate this forest together!

Simulating P2P Attacks and Teaching AI to Resume Sessions

TL;DR

It’s been one of those weeks where the terminal never really closed. I clocked 78 commits and pushed 4 PRs across 9 different repositories, maintaining a perfect 7-day streak. The bulk of the heavy lifting happened in the P2P space—specifically simulating network attacks—and adding some much-needed session persistence to my AI coding tool, nanocoder. With over 21,000 lines added and about 6,500 deleted, it was a high-output week focused on building out complex systems and then immediately refining them.

WHAT I BUILT

The star of the show this week was definitely P2P-Attack-Simulation. I’ve been obsessed with how decentralized networks handle adversarial conditions, and I finally merged a massive PR for Topology ts simulation. We’re talking 18,756 additions and 6,436 deletions. I essentially rewrote how the network topology is represented in TypeScript to make it more modular. Before, the simulation was a bit too rigid; now, I can spin up different node behaviors and see how the gossip protocols hold up when half the network starts acting malicious. It’s one thing to read about Sybil attacks in a paper, but seeing the message propagation latency spike in your own simulation is a different kind of satisfying.

Over in nanocoder, I spent a good chunk of time on the developer experience. If you’ve used AI coding assistants, you know the pain of losing context or having to restart a session from scratch. I implemented a new Feature /resume command to continue session. This involved a fair bit of state management (about 2,300 lines of code) to ensure that when you come back to a project, the AI knows exactly where you left off. It’s all about reducing friction. I want to spend my time coding, not re-explaining my file structure to a LLM.

I also dipped back into the low-level world with the networking repo. This is where I get my Rust fix. I merged two PRs here: one to Implement proper RST generation and another to refactor: remove timer module and associated functionality. The RST (Reset) generation is crucial for proper TCP handling—basically telling the other side “I have no idea what you’re talking about, let’s start over.” The refactor felt great, too. I realized the custom timer module was overkill for what we needed, so I ripped out 137 lines of brittle code. Less code, fewer bugs.

Beyond the big features, I kept the wheels turning on several other projects. I pushed 10 commits to py-libp2p and 1 to dotnet-libp2p, mostly keeping the Python and C# implementations of the p2p stack in sync. I also spent some time tweaking my environment, with 8 commits to my nvim config (because a dev’s work is never done when it comes to Lua plugins) and 7 commits to my notes repo, which is where I dump my raw thoughts on C and systems programming.

PULL REQUESTS

I managed to get four significant PRs merged this week, and they really tell the story of my transition from “building the foundation” to “polishing the experience.”

The Topology ts simulation PR was the most exhausting but rewarding. It wasn’t just about adding lines; it was about restructuring the entire simulation engine. I had to ensure that the TypeScript types correctly reflected the state of a peer at any given millisecond during an attack.

On the AI side, the Feature /resume command in nanocoder was a fun challenge in serialization. Making sure the session state could be saved and reloaded without losing the “vibe” of the conversation took a few iterations, but it’s finally in a place where it feels seamless.

The Rust work in the networking repo was much more surgical. Implementing RST generation required me to get back into the weeds of packet headers, while the timer module refactor was a classic “addition by subtraction” move.

CODE REVIEWS

I only did one formal review this week, but it was a deep one. I looked at a PR for py-ipld-dag titled Add Block API, IPLD data model, codecs, registry, and tests.

I actually ended up requesting changes on this one. IPLD (InterPlanetary Linked Data) is the backbone of how we structure data in the p2p world, and the proposed implementation of the Block API felt a bit too coupled to specific codecs. I suggested a more registry-based approach to make it easier for others to plug in new data formats later. It’s important to get these base-level APIs right early on, or you end up paying for it in technical debt for years.

TECH STACK

This week was a true polyglot marathon.

TypeScript was my primary driver for the simulation work and the AI tooling. With over 15 million bytes of TS in my environment, it’s definitely my “get things done” language. But Python isn’t far behind—especially with the work on py-libp2p and the IPLD reviews.

The Rust work in the networking repo reminded me why I love the language: the compiler is a jerk until you get it right, and then your code just works. I also touched some Lua for my Neovim setup and a bit of C for my personal notes and experiments.

My add/delete ratio was pretty high (21k vs 6k), which usually means I’m in a heavy feature-building phase. However, that 6k deletions in the P2P repo shows I’m not just piling code on top of code—I’m actively replacing the old stuff as I go.

And yeah, that 7-day streak feels good. It wasn’t a forced grind; I just genuinely had something I wanted to solve every single morning when I woke up.

WHAT’S NEXT

Next week, I’m planning to take the P2P simulation even further. Now that the topology is flexible, I want to start implementing specific Eclipse attack scenarios to see how quickly a node can be isolated from the rest of the network.

I’m also keeping an eye on the feedback for the /resume command in nanocoder. There are probably some edge cases with large file buffers that I’ll need to iron out.

On the Rust side, now that RST generation is handled, I might start looking into better congestion control mechanisms for the networking library. There’s always more to optimize. See you in the commit logs!

Cómo enviar Emails desde Oracle APEX usando OCI Email Delivery

Menu Email Domain

📑 Tabla de contenidos

  • 📦Introducción
    • Dos opciones para enviar Emails desde OCI
    • OCI Email Delivery
  • ⚙️Configuración paso a paso
    • 1. Crear Email Domain
    • 2. Crear claves DKIM
    • 3. Configurar el registro DKIM en tu proveedor de DNS.
    • 4. Verificar el estado de claves DKIM
    • 5. Configurar SPF
    • 6. Generar credenciales SMTP en OCI
    • 7. Crear remitente aprobado (Approved Sender) en OCI
    • 8. Configurar Servicio SMTP en Autonomous Database
    • 9. Validar configuración de Email Delivery
    • 10. Prueba de envío de Email desde Apex
    • 11. Gestión de Emails
  • 🚀Próximo paso

¿Cómo se pueden enviar Emails desde OCI?

En Oracle Cloud Infrastructure (OCI) existen dos formas principales de configurar SMTP para enviar emails desde tus aplicaciones:

  1. Usar el servicio nativo de OCI: Email Delivery (SMTP)
  2. Usar un proveedor SMTP de terceros

En este tutorial vamos a configurar OCI Email Delivery para enviar emails desde Oracle APEX ejecutándose en Autonomous Database.

Sin embargo, es importante saber que no estás obligado a usar SMTP de OCI. También puedes usar proveedores externos como: SendGrid, Mailgun, Amazon SES, Brevo, etc. En este caso, simplemente configuras en tu aplicación: SMTP Host del proveedor, Puerto SMTP y Usuario/contraseña o API Key.
OCI en este escenario solo actúa como la infraestructura donde corre tu aplicación, mientras que el envío de emails lo gestiona el proveedor externo.

OCI Email Delivery: SMTP nativo de Oracle Cloud

Oracle Cloud Infrastructure (OCI) tiene su propio servicio para envío de correos llamado Email Delivery.

Este servicio forma parte de OCI y permite enviar emails desde tus aplicaciones usando SMTP, sin depender de proveedores externos.

OCI Email Delivery proporciona:

  • Un servidor SMTP administrado por Oracle
  • Credenciales SMTP generadas en IAM (Identity and Access Management)
  • Integración directa con recursos dentro de tu VCN (Virtual Cloud Network)

Esto permite que aplicaciones que corren dentro de OCI — como Oracle APEX en Autonomous Database — puedan enviar emails de forma segura y confiable.

⚙️ Manos a la obra: Configuración paso a paso

1. Crear Email Domain

Después de iniciar sesión en OCI con una cuenta de administrador,
ir a 👉 Menu → Developer Services → Email Delivery → Email Domains
Seleccionar Create Email Domain
Menu Email Domain

Asegúrese de que el compartment seleccionado sea el correcto antes de continuar.
Create Email Domain
⚠️ Importante! Debes tener acceso para modificar los registros DNS del dominio que utilices.

Ingresa el nombre de Email Domain que utilizarás para enviar correos. En este caso es ‘midominio.com’.

Este debe ser un dominio propio o un dominio cuyo DNS puedas administrar, ya que durante la configuración deberás agregar registros DNS para verificar y autenticar el dominio (por ejemplo, registros SPF y DKIM).

El dominio que ingreses será el que utilizarás en la dirección “From” de los correos enviados desde tu aplicación. Por ejemplo: From: [email protected]

Dominio Active
Unos segundos después de crear el dominio, su estado cambiará a ‘Active’. Y DKIM Signing lo verás ‘Inactive’.

Cuando el dominio esté activo, podrás continuar con el siguiente paso: crear las claves DKIM. Estas claves permiten autenticar los correos enviados desde tu dominio. Para ello selecciona la solapa DKIM.

2. Crear claves DKIM

DKIM (DomainKeys Identified Mail) es un mecanismo de autenticación de correo que permite verificar la legitimidad de los emails enviados desde un dominio.

Con DKIM:

  • ☑️ Los correos se firman digitalmente al momento de enviarse.
  • ☑️ El servidor receptor puede verificar que el mensaje no fue modificado durante la transmisión.
  • ☑️ Se confirma que el dominio del remitente autorizó el envío del correo.

Esto ayuda a mejorar la confianza del correo y reduce la probabilidad de que los mensajes sean marcados como spam.

Primero haz clic en Add DKIM para crear una nueva clave DKIM para tu dominio.
Add DKIM

Luego selecciona Add New DKIM

Add New DKIM

¿Por qué elegir Add New DKIM?
Porque:

  • ☑️ OCI genera automáticamente la clave DKIM por ti.
  • ☑️ Te proporciona un registro CNAME que deberás agregar en el DNS de tu dominio.
  • ☑️ OCI firmará automáticamente los correos enviados desde ese dominio.

La opción Import Existing DKIM, se utiliza sólo si ya generaste previamente un par de claves DKIM en otro sistema. Es poco común en la mayoría de los casos.

Selecciona Add New DKIM y presiona Next

DKIM Selector

En esta pantalla debes ingresar un DKIM Selector.

El selector es simplemente un identificador que se utiliza para generar y organizar las claves DKIM de tu dominio.

OCI sugiere usar un patrón como:

<prefix>-<shortregioncode>-<yyyymm>

Este formato es solo una convención que ayuda a organizar futuras rotaciones de claves DKIM.
Por ejemplo, podrías usar algo como: oci-us-202603
En mi caso utilicé oci1 como identificador del selector.
Luego presiona Next

DKIM Record

Haz clic en Generate DKIM Record.

OCI generará automáticamente el registro CNAME, mostrando el Record y el Value correspondientes.

El CNAME generado es el que deberás copiar y agregar en el DNS de tu dominio para completar la verificación DKIM.

Presiona Add DKIM para terminar de crear la clave. Verás que Email Domain está activo, pero aún no DKIM Signing.

DKIM Inactive

Para activar las claves deberás ir al panel de control de tu DNS.
En mi caso, administro el DNS con DonWeb, por lo que a continuación te mostraré cómo agregar este registro desde su panel de control.

3. Configurar el registro DKIM en tu proveedor de DNS.

⚠️ Si tu dominio está en otro proveedor (Cloudflare, GoDaddy, Namecheap, etc.), el proceso es el mismo: solo debes crear un registro CNAME con los valores que te proporcionó OCI.

Ahora debes agregar el registro CNAME generado por OCI en el DNS de tu dominio.

🔹 Ejemplo con DonWeb

En mi caso, administro el dominio con DonWeb. Los pasos son:

  1. Iniciar sesión en el panel de DonWeb.
  2. Seleccionar el dominio que deseas configurar.
  3. Hacer clic en Gestionar.
  4. Ir a la opción Nameservers y Zona DNS.

Una vez allí, verás una pantalla como esta donde podrás administrar los registros DNS de tu dominio.

Panel DNS DonWeb

Luego, haz clic en Agregar Registro, e ingresa los valores proporcionados por OCI. En mi caso:

[Tipo] = CNAME
CNAME Record [Nombre] = oci1_domainKey.midominio.com
CNAME Value [Contenido]= oci1_midominio.com.dkim.gur1.oraclemaildelivery.com

Add Record CNAME

Presiona Guardar
En la siguiente pantalla podrás ver el registro agregado en tu zona DNS.

Registros DNS

⚠️ Importante: Puedes tener varios registros DKIM configurados en tu dominio al mismo tiempo. Esto es completamente válido, ya que cada proveedor SMTP utiliza un selector distinto para firmar los correos, permitiendo que múltiples servicios envíen emails desde el mismo dominio sin conflictos.

En este ejemplo se observa el registro mail._domainkey.midominio.com, que corresponde a otro DKIM configurado para un servicio distinto.

Por este motivo, cada servicio de correo utiliza: su propia clave DKIM, su propio selector y su propio registro DNS.

⏳Después de agregar el registro, debes esperar a que la configuración DNS se propague. Este proceso no es inmediato y puede demorar algunos minutos (o más, dependiendo del proveedor de DNS). Y una vez propagado, OCI podrá verificar automáticamente el registro DKIM.

4. Verificar el estado DKIM

Una vez que el registro DNS se haya propagado, vuelve a la consola de OCI para verificar el estado de DKIM.

Ir a 👉 Menu → Developer Services → Email Delivery → Email Domains y selecciona tu dominio.

En la solapa DKIM, haz clic en Refresh DKIM Status.

Refresh Status DKIM

Si todo está configurado correctamente, el campo DKIM Signing Status debería aparecer como Active.

Estados Active

Si todos los estados aparecen como Active, significa que la configuración se realizó correctamente.

  • ☑️ OCI puede firmar los correos desde tu dominio (en mi caso midominio.com)
  • ☑️ DKIM está activo y validado
  • ☑️ La configuración DNS es correcta

5. Configurar SPF

SPF (Sender Policy Framework) es un mecanismo de autenticación de correo que permite definir qué servidores están autorizados a enviar emails desde tu dominio.
Esto ayuda a evitar suplantación de identidad (spoofing) y mejora la entregabilidad de tus emails.

Para configurar SPF en OCI Email Delivery debes agregar un registro TXT en el DNS de tu dominio.

En mi caso vuelvo al panel de administracion de DNS de mi dominio en DonWeb, si no existe un registro creas uno nuevo con los siguientes valores:

[Tipo] = TXT
TXT Name [Nombre] = midominio.com
TXT Value [Contenido]= v=spf1 include:spf.oraclecloud.com -all

¿Qué significa este registro?

  • v=spf1 → versión de SPF
  • include:spf.oraclecloud.com → autoriza a OCI a enviar correos desde tu dominio
  • -all → indica que otros servidores no autorizados serán marcados como sospechosos

⚠️ Importante: Si ya tienes un registro SPF configurado, no debes crear otro. En su lugar, debes incluir tu valor de OCI SPF al registro existente.

En mi caso ya tengo un SPF configurado por lo que modifico el registro agregando a continuacion a spf.oraclecloud.com

Registro SPF

6. Generar credenciales SMTP en OCI

Las credenciales SMTP son las que permitirán a tu aplicación (en nuestro caso una app en Oracle APEX) autenticarse contra el servidor de OCI Email Delivery para enviar correos.
No es necesario crear un usuario completamente nuevo, pero es recomendable por buenas prácticas. Ya que usarlo exclusivamente para el envío de correos facilita la administración y auditoría y mejora la seguridad.

Regresa a la consola de OCI y, desde el menú principal, ve a 👉 Identity & Security → Domains.

Selecciona el dominio actual (Current Domain), verifica que estás en el compartment correcto.

Luego, dirígete a User Management, selecciona el usuario con el que enviarás los correos y accede a la solapa SMTP Credentials.

Desde allí, podrás generar las credenciales haciendo clic en Generate Credentials.

Generate Credentials

Luego de ingresar un nombre para las credenciales, en mi caso APEX OCI EMAIL, podrás ver el Username y Password generados.

⚠️ Importante! Solo verás el password al generar las credenciales por lo que debes guardarlo. No podrás recuperarlo posteriormente.

Credentials data

Luego haz clic en Close, verás un listado de todas las credenciales generadas para tu usuario.

Credential list

7. Crear un remitente aprobado (Approved Sender) en OCI

⚠️ Solo podrás enviar correos desde direcciones que estén registradas como Approved Senders.

Se debe configurar un remitente aprobado para todas las direcciones “From:” que envíen correos electrónicos a través de OCI; de lo contrario, el correo será rechazado.

Ir a 👉 Menu → Developer Services → Email Delivery → Approved Senders

Menu Approved Senders

Haz click en Create Approved Sender

Create Approved Sender

Ingresa la dirección de correo que utilizarás como remitente (From:). En mi caso [email protected].
Esta dirección debe pertenecer al mismo dominio que configuraste y verificaste con DKIM.

⚠️ Importante: El dominio del remitente debe tener el mismo dominio que configuraste y verificaste con DKIM. De lo contrario, los correos pueden ser marcados como spam o incluso rechazados por los servidores de destino.

Listado de Approved Senders

8. Configurar Servicio SMTP en Autonomous Database

Antes de comenzar la configuración en Oracle APEX, debemos obtener los datos SMTP necesarios desde OCI Email Delivery que utilizaremos en los siguientes pasos.

Para obtener estos datos, navega a:
👉 Menu → Developer Services → Email Delivery → Configuration

SMTP DATA

En esta pantalla encontrarás la información necesaria para la conexión SMTP. Copia los siguientes datos:

  • SMTP Public Endpoint
  • SMTP Port (generalmente 587)

Con estos datos y las credenciales generadas en el paso 6, podrás configurar los parámetros SMTP en tu instancia de Oracle APEX.

Accede a tu Autonomous Database con un usuario que tenga permisos de administrador.

Puedes hacerlo utilizando cualquiera de las siguientes herramientas: Oracle APEX, SQL Workshop, SQL Developer, etc.

En mi caso, me conecto directamente desde OCI a través de:
👉Database Actions → SQL

Open SQL User Admin

Ejecuta el siguiente bloque PL/SQL para configurar los parámetros SMTP en tu instancia de Oracle APEX:

BEGIN
    -- Paso 8: Datos SMTP Host (public endpoint de OCI)
    APEX_INSTANCE_ADMIN.SET_PARAMETER('SMTP_HOST_ADDRESS', 'smtp.public_endpoint');

    -- Paso 8: Datos SMTP Puerto 
    APEX_INSTANCE_ADMIN.SET_PARAMETER('SMTP_HOST_PORT', '587');

    -- Paso 6: Credenciales SMTP Usuario
    APEX_INSTANCE_ADMIN.SET_PARAMETER('SMTP_USERNAME', 'SMTPcredentials.username');

    -- Paso 6: Credenciales SMTP Password
    APEX_INSTANCE_ADMIN.SET_PARAMETER('SMTP_PASSWORD', 'SMTPcredentials.password');

    -- Paso 7: Direccion de envio (From) configurado en Approved Sender
    APEX_INSTANCE_ADMIN.SET_PARAMETER('SMTP_FROM', '[email protected]');

   COMMIT;
END;
/

SQL SET_PARAMETER

9. Validar configuración de Email Delivery

Antes de enviar correos desde Oracle APEX, debemos asegurarnos de que el proveedor SMTP esté correctamente configurado y disponible para su uso con APEX_MAIL.

Ejecuta el siguiente código desde tu cliente SQL, asegurándote de utilizar un usuario con permisos de administrador:

BEGIN
  APEX_INSTANCE_ADMIN.VALIDATE_EMAIL_CONFIG;
END;
/

VALIDATE_EMAIL_CONFIG

Si se informa algún error (por ejemplo, "ORA-29279: SMTP permanent error: 535 Authentication credentials invalid"), ajuste los parámetros SMTP o revise la configuración y repita la validación.

10. Prueba de envío de Email desde APEX

Para enviar un email de prueba, inicia sesión en Oracle APEX y desde 👉 SQL Workshop → SQL Commands, ejecuta lo siguiente:

BEGIN
  APEX_MAIL.SEND(
    p_from => '[email protected]', -- Ex: [email protected]
    p_to   => '[email protected]',
    p_subj => 'Email from Oracle Autonomous Database',
    p_body => '<table width="100%" cellpadding="0" cellspacing="0" bgcolor="#ff0000"> <tr> <td align="center" style="padding:40px; font-family:''Trebuchet MS'', Arial, sans-serif; color:#000000; font-size:20px;"> <b>MAIL ENVIADO DESDE ORACLE APEX</b> <br><small>Configurado con OCI Email Delivery</small> </td> </tr> </table>');
  APEX_MAIL.PUSH_QUEUE;
END;
/

APEX_MAIL.PUSH_QUEUE se utiliza para enviar inmediatamente los correos que están en cola en Oracle APEX.

APEX_MAIL.SEND

Cuando usas APEX_MAIL.SEND, el correo no se envía al instante, sino que queda en una cola interna.

Al ejecutar APEX_MAIL.PUSH_QUEUE, APEX procesa esa cola y realiza el envío.

💡 Nota:

En entornos productivos, el envío suele ejecutarse automáticamente mediante un job programado, en lugar de forzarlo manualmente con APEX_MAIL.PUSH_QUEUE.
Forzar el envío inmediato de todos los correos puede impactar la performance del sistema.
Por este motivo, es recomendable definir una estrategia de envío adecuada según el entorno: Desarrollo, Testing, Producción.

Email recibido usando APEX_MAIL.SEND:

MAIL FROM APEX

11. Gestión de emails enviados y con errores

Dentro de la consola de administración de Oracle APEX, navega a 👉 Manage Instance → Mail Queue

Acá podrás ver los errores de los mails que no pudieron ser enviados. Ejemplo: ORA-29279: SMTP permanent error: 535 Authorization failed: Envelope From address <info@dominio_sin_autorizacion.com> not authorized, lo cual ocurre cuando se envía un mail con una dirección de email no autorizada en el From: (una de las causas puede ser que ese correo no haya sido agregado en Approved Senders ).

Gestion Mail Queue

También se puede supervisar la entrega de correos, consultando las tablas APEX_MAIL_QUEUE y APEX_MAIL_LOG desde SQL con usuario administrador.

select * from APEX_MAIL_QUEUE

Table APEX_MAIL_QUEUE

select * from APEX_MAIL_LOG

Table APEX_MAIL_LOG

🚀Próximo paso

Ahora ya puedes enviar correos desde APEX. En el siguiente post veremos cómo utilizar Email Templates en Oracle APEX, para crear mensajes más profesionales y reutilizables.

⬆️ Ir a Tabla de Contenidos

How to compare team capacity before committing to a software deadline

Calculate team options - Motionode

What most people expect

Change the team → change the timeline.

More devs → faster delivery.

What actually happens

In many cases, the timeline barely changes.

But:
Same duration. Very different execution risk.

Why?

Because:

  • The total work stays the same
  • The steps are still sequential (design → build → test → deploy)
  • Someone still has to do each step

So instead of reducing time, you just redistribute it.

What really changes

Different team setups change how your project behaves:

  • idle time between steps
  • how busy each person is
  • how much coordination is needed
  • where bottlenecks appear

Not necessarily the final date.

What this looks like in practice

Calculate team options - Motionode

You can run 3 different team setups and still get ~4 weeks.

But:

Simulation results - Motionode

  • one has overloaded devs
  • one has idle time
  • one has smoother flow

This is why many projects don’t fail because of bad estimates. They fail because the execution model was never stress-tested.

If your planning process only outputs a date,
you’re missing the part that actually determines whether you hit it.

15 Java String Method Questions I Practiced — Here’s What I Actually Learned

15 Java String Method Questions I Practiced — Here’s What I Actually Learned

I’ve been learning Java for a while now, and strings are everywhere. But knowing that String exists and actually knowing how to use it in real situations are two very different things.

Here’s everything I picked up, question by question.

Q1 — Why Does Login Fail Even When the Password is Correct?

Scenario: User types Admin123 (with a trailing space). Login fails even though the password matches.

This one is so common in real applications and nobody talks about it enough. The fix is .trim() — it removes all leading and trailing whitespace from a string before you compare it.

if (password.equals(CheckPassword.trim())) {
    System.out.println("Login Success");
}

What I learned: Never compare user input directly. Always .trim() first. Users accidentally add spaces all the time — especially on mobile keyboards.

Q2 — UI Sends “Vijay” but Database Has “vijay” — How to Compare Safely?

Scenario: The UI sends the name with a capital V but your database stores it lowercase.

.equals() is case-sensitive — “Vijay” and “vijay” would fail. The fix is .equalsIgnoreCase() which ignores the case completely.

if (DataBaseName.equalsIgnoreCase(UIName.trim()))
    System.out.println("login success");

What I learned: Always use .equalsIgnoreCase() for usernames and names. Use .equals() only when case matters — like passwords.

Q3 — What’s the Difference Between == and .equals()?

This question genuinely confused me before I practiced it.

String s1 = "JAVA";
String s2 = new String("JAVA");

System.out.println(s1 == s2);      // false
System.out.println(s1.equals(s2)); // true
  • == compares memory addresses (reference). s1 is in the String Pool, s2 is a new object in the heap — different address, so false.
  • .equals() compares the actual value — both contain “JAVA”, so true.

What I learned: Never use == to compare strings in Java. Always use .equals(). The == trap has broken countless real applications.

Q4 — Validate an Email Address

Scenario: Keep asking the user for an email until it contains @ and .

if (CheckEmail.contains("@") && CheckEmail.contains(".")) {
    System.out.println("Your Email : " + CheckEmail);
    condition = false;
}

.contains() checks if a string has a specific sequence of characters inside it. I put it inside a while loop to keep asking until the input is valid.

What I learned: .contains() is great for quick format checks. For production-level validation you’d use regex, but .contains() is perfect for learning the logic first.

Q5 — Convert a String to an Integer

Scenario: User types “1234” as text. You need it as a number to do math.

int number = Integer.parseInt(scan.nextLine());

Integer.parseInt() converts a numeric string into an actual int. If the user types letters instead of numbers, it throws a NumberFormatException — which is why real apps wrap this in try-catch.

What I learned: Keyboard input is always a String. Whenever you need a number from the user, Integer.parseInt() is your go-to.

Q6 — Remove Extra Spaces from Start and End

String extraSpace = " welcome to java ";
System.out.println(extraSpace.trim()); // "welcome to java"

.trim() again — but this time I saw it as a standalone method, not just inside a comparison. It’s great for cleaning up any user-facing text before displaying or storing it.

Q7 — Count How Many Times a Word Appears

Scenario: "Java is easy, Java is powerful" — how many times does “Java” appear?

This one I still had a doubt on during practice. The clean way to count occurrences:

String count = "java is easy , java is powerful";
int occurrences = count.split("java", -1).length - 1;
System.out.println(occurrences); // 2

Split the string by the word you’re looking for. The number of pieces minus 1 = the number of occurrences. The -1 flag makes sure empty trailing strings are counted too.

What I learned: There’s no .countOccurrences() method in Java — you build it using .split() or a loop.

Q8 — Reverse a String Without StringBuilder

Scenario: Reverse "abishek" without using StringBuilder.reverse().

String word = "abishek";
String reverse = "";

for (int i = 0; i < word.length(); i++) {
    reverse = word.charAt(i) + reverse;
}
System.out.println(reverse); // kehsiba

.charAt(i) picks out one character at a time by its index. Each character is added to the front of the result string, which builds the reverse naturally.

What I learned: .charAt() is how you access individual characters in a string. This loop pattern is a classic interview question — worth memorizing.

Q9 — Check if a String is a Palindrome

A palindrome reads the same forwards and backwards — like “madam” or “racecar”.

String reverse = "";
for (int i = 0; i < word.length(); i++) {
    reverse = word.charAt(i) + reverse;
}
System.out.println(reverse.equals(word) ? "it is palindrome" : "not palindrome");

Reverse the string using the same loop as Q8, then compare it to the original with .equals(). I also used the ternary operator here which keeps the code clean.

What I learned: Palindrome check = reverse + compare. That’s it.

Q10 — Remove Duplicate Characters

Scenario: Input "aabbccddee" → Output "abcde"

String duplicate = "aabbccddee";
String result = "";

for (int i = 0; i < duplicate.length(); i++) {
    if (result.indexOf(duplicate.charAt(i)) == -1) {
        result += duplicate.charAt(i);
    }
}
System.out.println(result); // abcde

.indexOf() returns the position of a character in a string — or -1 if it’s not there. I use that -1 check to decide if the character has already been added to the result.

What I learned: .indexOf() returning -1 means “not found” — this is a very useful pattern for checking membership in a string.

Q11 — Extract Part of a String (Log Parser)

Scenario: Log entry is "ERROR: File not found". You only want "File not found".

String logFormat = "ERROR: File not found";
System.out.println(logFormat.substring(7)); // File not found

.substring(7) starts from index 7 and returns everything after it. You can also do .substring(start, end) to get a specific range.

What I learned: .substring() is essential for parsing structured text like logs, file paths, or formatted data where you know the position of what you need.

Q12 — Parse a CSV String

Scenario: "Abishek,Chennai,23" — extract name, city, age separately.

String CSV = "Abishek,Chennai,23";
String[] result = CSV.split(",");

System.out.println("Name: " + result[0]);
System.out.println("City: " + result[1]);
System.out.println("Age:  " + result[2]);

.split(",") breaks the string wherever it finds a comma and returns an array of pieces. I then access each piece by its array index.

What I learned: .split() is how you parse any delimiter-separated data — CSV files, API responses, config strings, all of it.

Q13 — Replace Words in a String

Scenario: Replace “Java” with “Spring Boot” and “Love” with “Hate” in one sentence.

String given = "I Love Java Programming";
String result = given.replace("Java", "C++").replace("Love", "Hate");
System.out.println(result); // I Hate C++ Programming

.replace() swaps every occurrence of a word with something new. You can chain multiple .replace() calls together since each one returns a new String.

What I learned: String methods in Java return new strings — they don’t modify the original. This is because strings in Java are immutable. You have to store the result.

Q14 — Validate a Mobile Number

Scenario: Validate that a number is 10 digits and starts with 6, 7, 8, or 9.

if (number.matches("[6-9][0-9]{9}")) {
    System.out.println("Valid number");
}

.matches() checks if the entire string matches a regular expression (regex) pattern. [6-9] means any digit from 6 to 9. [0-9]{9} means exactly 9 more digits after that. Together = a valid Indian mobile number format.

What I learned: .matches() with regex is the professional way to validate formats — phone numbers, emails, passwords, PINs. This was my first time really using regex and it clicked here.

Q15 — Split a String by a Custom Delimiter

Scenario: "apple#banana#mango" — split by # and store in an array.

String GivenString = "apple#banana#mango";
String[] arr = GivenString.split("#");
System.out.println(Arrays.toString(arr)); // [apple, banana, mango]

Same concept as Q12 but with a custom delimiter. Arrays.toString() prints the array in a readable format instead of the raw memory address.

What I learned: .split() works with any delimiter — comma, hash, pipe, space. And always use Arrays.toString() when printing arrays or you’ll get gibberish output.

The Full Method Cheat Sheet

Here’s every method I used across all 15 questions:

Method What It Does
.trim() Removes whitespace from start and end
.equals() Compares values — case sensitive
.equalsIgnoreCase() Compares values — ignores case
.contains() Checks if a substring exists inside
.charAt(i) Gets the character at a specific index
.length() Returns total number of characters
.indexOf() Finds position of a character (-1 if not found)
.substring(n) Returns string from index n onwards
.substring(a, b) Returns string from index a to b
.split(delimiter) Breaks string into array by a separator
.replace(old, new) Swaps all occurrences of a word
.matches(regex) Checks if string matches a pattern
Integer.parseInt() Converts numeric string to int

What I Actually Took Away From This

The biggest thing these 15 questions taught me is that String methods are not just syntax — they’re tools for solving real problems.

Before this, I knew .trim() existed. Now I know when I need it — every time I take user input. I knew .split() was a thing. Now I know it’s how you parse CSV data. I knew == and .equals() were different. Now I know exactly why — and why getting it wrong breaks login systems.

If you’re learning Java, I’d genuinely recommend doing scenario-based practice over reading method documentation. The methods stick when you see the problem they solve.

GCC vs Clang: Same Instructions, Different Performance (AGU Insight)

GCC VS CLANG

*I noticed something interesting while running a GCC vs Clang benchmark.
*

Same code. Same machine.
Both loops are scalar (no vectorization).

Yet… GCC consistently used fewer CPU cycles.

At first, this doesn’t make sense.

If both:

execute roughly the same instructions
are not vectorised

Why is there a performance gap?

🔍 The Missing Piece: It’s Not Just Instructions
Most people focus on:
instruction count
vectorization

But in this case, that’s not the full story.

What actually matters more is:

  • how address computations are structured
  • how instructions are scheduled
  • how well latency is hidden

Here is the data

GCC VS CLANG

⚙️ AGU Pressure (Address Generation Units)

On x86 CPUs, memory instructions rely on AGUs (Address Generation Units).

Complex addressing patterns like:

base + index * scale + offset

👉 increase AGU pressure

Whereas simpler patterns like:
pointer++
👉 are cheaper and easier for the CPU to execute efficiently

🧪 What I Observed
GCC:
Generates simpler addressing patterns
Reduces AGU contention
Keeps execution more consistent
Clang:
Shows higher AGU pressure
More stalls
Less efficient scheduling (in this case)

⚡ Key Takeaway
It’s not just about what instructions exist.

It’s about:
How efficiently the compiler feeds the CPU pipeline

Same instruction count ≠ same performance.

📊 Why This Matters

In tight loops:

AGU pressure
addressing patterns
instruction scheduling

👉 can matter as much as (or more than) vectorization

🔗 Want to Dive Deeper?

👉 Full benchmark + assembly breakdown:

👉 Complete analysis article:

💬 Discussion

Have you seen cases where:

similar assembly
same instruction count

👉 still results in very different performance?

Would love to hear your observations.