Iurii RoguliaIurii Rogulia
AboutAI EngineeringServicesProjectsAI SkillsStackTestimonialsPhrasesBlog
Contact
Iurii ships.

Iurii Rogulia, senior full-stack software engineer. Professionally building software since 2001.

Think of a number
Hourly ratesChecklistsPrivacy PolicyCookie Policy

Business

TMI Iurii Rogulia
VAT ID: FI29845875
DUNS: 368664211
Lappeenranta, Finland 🇫🇮

Agentic AI Engineer

I design and build agentic AI systems that automate real business workflows, from multilingual customer support to automated lead research and qualification. Production-focused, with an emphasis on reliability, evaluation and integration, on top of 25+ years of software engineering.

4 AI projects · Multi-agent orchestration · RAG · MCP · Evaluation · Google Cloud

First page of Iurii Rogulia's Agentic AI Engineer CVDownload CV (PDF)

Who I am as an AI engineer

I came to AI as an experienced software engineer, not as a prompt writer. For 25+ years I have built and run production systems, and I apply the same discipline to LLM-based ones: queues that do not lose messages, idempotent writes, audit trails, and logs you can search when something goes wrong.

I built a multilingual RAG support assistant with AI ticket triage that answers customers in 30 languages and triages the support inbox, a multi-model localization pipeline, an open-source MCP server, and an agentic CRM on Google Cloud that turns inbound email into a researched and qualified lead.

What I do

Multi-agent orchestration

Pipelines of specialised LLM agents, each its own service with its own permissions, coordinated by a workflow engine that owns the control flow: parallel fan-out, fan-in, retries with backoff, and a partial result instead of a lost one when an agent fails.

Retrieval and RAG

Multilingual retrieval-augmented assistants in production: embeddings, vector search, re-ranking, query enrichment from conversation context, and incremental indexing that re-embeds only what changed.

Evaluation and guardrails

Labelled eval sets with asymmetric metrics, structured output validated against a schema, evidence quotes checked against the source, confidence thresholds that route uncertain cases to a person.

Integrations and data

Webhooks, queues and CRM-style data flows around the models: idempotent ingestion, a single trusted writer into BigQuery with an audit log, and MCP servers that expose tools to assistants such as Claude and Cursor.

Selected AI engineering work

1. Agentic CRM for GTM Teams

Inbound B2B email goes in; a researched, scored and qualified lead record comes out. Three specialised LLM agents run as separate Cloud Run services on Google Cloud, and a single trusted service owns every write to the BigQuery Golden Record. The agents only propose. Code decides.

specialised agents on Gemini 2.5 Flash
3 specialised agents on Gemini 2.5 Flash
intent accuracy on a 24-case eval set
95.8% intent accuracy on a 24-case eval set
priority-cap violations in 24 cases
0 priority-cap violations in 24 cases
Gemini 2.5 FlashCloud RunPub/SubCloud WorkflowsBigQuery

An orchestrated pipeline of specialised agents, not an autonomous one: Cloud Workflows, not the model, decides the order of steps, the retries and what happens when an agent fails. Each agent has a fixed role, a fixed output schema and no tool that can change anything outside its own answer.

Design decisions

Why three agents and not one prompt

Intent, Research and Qualification have different inputs, failure modes and trust levels. Research reads a web page the sender controls; Intent reads only the email; Qualification consumes both. As separate services with their own service accounts, the agent that fetches sender-controlled pages holds no write access to the CRM.

Why Intent and Research run in parallel

They do not depend on each other, so a run takes as long as the slower of the two rather than their sum, and a slow or failed site fetch does not block intent classification. Qualification waits for both because it needs both results.

Why a workflow engine and not an agent loop

The order of steps is known in advance, so it is declared, not left to a model. Retries, fan-out, fan-in and partial failure are explicit in Cloud Workflows and can be read and audited, instead of being emergent behaviour of a prompt.

Where deterministic code replaces the LLM

Spam is forced to a low score with no reply. A low-confidence lead cannot be high priority. Counts and dates are recomputed from all interactions. Quotes are checked against the source. Writes go through one validating service. None of these is left to a model’s judgement.

What happens when an agent fails

Its result is marked failed and the others continue. Qualification is told to rely on the email alone and lower its confidence. A nightly workflow republishes the last seven days of emails where an agent failed, and the idempotent writer fills in only what is missing.

What it automates: Sender research, intent classification, lead scoring, next-step choice and a reply draft for every inbound email.

Agentic CRM architectureAn inbound email passes through a signed webhook, a Pub/Sub queue and a worker, then a Cloud Workflows execution runs Company Research and Intent Analysis in parallel, Lead Qualification with both results, and a single trusted writer that stores the result in the BigQuery Golden Record shown in the CRM.1 · INGEST AND QUEUEInbound emailResend receives itwebhook: ID + metadataingestCloud Run · publicverify signature · claimPub/Subinbound-email topicpush with OIDC tokenworkerCloud Run · privatefetch full email · storeEventarcemail-stored eventstarts one workflowFirestore atomic claim:a duplicate is answered200 and droppedRetries 10–600 s, then adead-letter topic after5 attemptsBigQuery raw_emails:MERGE inserts only ifthe email ID is new2 · AGENTS AND GOLDEN RECORDone Cloud Workflows execution per emailCloud Workflowsorchestrator · retriespartial result on failureCompany ResearchGemini · DNS · safe fetchIntent AnalysisGemini · intent · urgencyfan-out, in parallelLead QualificationGemini · score · prioritynext step · draft replyfan-ingolden-record-writeronly service that writesvalidates every proposalBigQueryGolden Recordcontacts · companies · auditCRM web appCloud Run · read-onlyinbox · evidence · draftsCloud Scheduler: nightlyreprocess of emails wherean agent failedStructured output (Pydantic),untrusted input in delimiters,quotes checked against sourceRules in code decide last:spam gets no draft, lowconfidence is never highOne transaction: MERGE,recompute counts, audit_logwith before and afterOur code on Cloud RunLLM agent (Gemini 2.5 Flash on Vertex AI)Single trusted writerManaged or external service
Every service has its own service account. Agents can read the email table but cannot write to it; only the writer changes CRM tables.
Full walkthrough

How it works

  1. 1
    Email arrives through Resend

    The MX record points to Resend. A webhook carries only the email ID and metadata, so the body is fetched separately.

  2. 2
    ingest verifies and hands over

    The only public endpoint. It verifies the webhook signature, claims the email ID in Firestore as received, publishes the ID to Pub/Sub, marks it published and answers 200. Only a published ID counts as a duplicate, so a delivery whose publish failed is retried on the next webhook; if the hand-over fails it answers 503 and Resend retries.

  3. 3
    Pub/Sub queues the event

    A push subscription delivers each message with an OIDC identity token. Failed deliveries retry with backoff from 10 to 600 seconds; after 5 attempts a message moves to a dead-letter topic, so nothing is dropped silently.

  4. 4
    worker stores the full email

    Private service. It fetches the full message from the Resend API and writes it to BigQuery with a MERGE that inserts only if the email ID is new, then publishes an email-stored event. Any failure returns 500 so Pub/Sub redelivers; every step is safe to repeat.

  5. 5
    Eventarc starts a workflow

    One Cloud Workflows execution per event. The worker knows nothing about orchestration, and more subscribers can be added without touching it.

  6. 6
    Intent and Research run in parallel

    Fan-out in Cloud Workflows. Calls are authenticated HTTP; transient failures (429, 502, 503, 504, timeouts) retry up to three times with exponential backoff. If an agent still fails, its result is marked failed and the others continue.

  7. 7
    Qualification waits for both

    Fan-in. The agent receives the email and both results. A failed or missing result is left out and the model is told to rely on the email alone and lower its confidence.

  8. 8
    golden-record-writer validates and writes

    The only service with write access to the CRM tables. It validates every agent result against its schema, then one BigQuery transaction upserts the interaction, rebuilds the contact and company rows and appends to an audit log with the state before and after.

The three agents

Intent Analysis

Gemini 2.5 Flash returns a verdict that must match a fixed Pydantic schema: intent (demo request, pricing, partnership, support, recruiting, vendor solicitation, spam, other), urgency, summary, requested action, language, confidence and verbatim supporting quotes. Quotes that do not appear in the email are dropped.

Company Research

Works from the sender’s domain. Public mailbox providers get no company at all. For a business domain it looks up MX, SPF and DMARC, fetches the home page and asks Gemini for a profile based only on that page text. If the site cannot be read, the result is unknown and no profile is invented.

Lead Qualification

Score 0 to 100, priority, rationale, one next step and a reply draft in the sender’s language. Rules in code have the last word: spam is forced to a low score with no reply, a low-confidence lead cannot be high priority, and the service has no tool for sending mail, so a draft stays a draft.

Guardrails and safety

Limited permissions

Agents can read the email table but cannot write to it. Only golden-record-writer changes CRM tables. Each service runs under its own service account with only the roles it needs, and private services accept calls only from the identity meant to call them. A manipulated model has nothing dangerous to do.

Validated output

Every agent answers in a fixed schema, and the writer validates it again before storing anything. A schema guarantees the shape of an answer, not that it is right, so business rules in code apply on top.

Evidence quotes

Supporting quotes are checked to exist verbatim in the email or page and dropped otherwise. That proves where a quote came from, not that the conclusion follows from it, so quotes help a person review a decision rather than replace the review.

Untrusted input

Email text, website text and earlier agent results are passed to the model as delimited data with an instruction not to follow them. This is a secondary measure; the real protection is the permissions above. The eval set includes prompt-injection attempts.

SSRF-safe fetching

The sender controls the domain, so the page fetch connects only to public IP addresses, checks every redirect hop, pins the connection to the checked address, accepts only http(s) on default ports and limits time and size.

Reliability and data quality

At-least-once, idempotent

Every queue may deliver twice. Duplicates are caught by the Firestore claim in ingest, by the insert-if-new MERGE in the worker and by keyed upserts in the writer.

Recomputed, not incremented

Counts and dates are rebuilt from all interactions, so a repeated run cannot inflate them. A company profile is replaced only by one with equal or higher confidence, and a failed agent never erases what an earlier run stored.

Scheduled recovery

Cloud Scheduler runs a reprocess workflow nightly. It finds emails from the last seven days where an agent failed and republishes them; the idempotent writer fills in only what is missing.

Audit and identifiers

Audit rows hold the fields that changed, before and after, and are written only when something changed. Rows get UUIDv7 identifiers, which sort by creation time and keep inserts and clustered scans local.

Evaluation and observability

Eval harness

24 hand-written emails (demo requests, pricing, partnership, support, recruiting, vendor pitches, spam, prompt-injection attempts, short and non-English messages) run through Intent and Qualification: 95.8% intent accuracy and 83.3% next-step accuracy on this set, and no case exceeded the priority cap. Spam, vendor pitches and injection attempts never exceed low priority. The set is small, so one case moves a figure by about 4 points: it checks for the expensive mistakes and is not a statistical estimate.

Cost and latency

Every model call logs model name, prompt tokens, output tokens and latency; the CRM shows them per email. Log-based metrics feed a Cloud Monitoring dashboard.

Alerts

Cloud Monitoring emails on a message reaching the dead-letter queue, a failed workflow execution, a service logging ERROR or answering 5xx. A monthly budget alert watches cost.

CRM interface

FastAPI and Jinja2 views over the Golden Record: inbox, email page with each agent’s conclusion, draft reply, model, tokens and latency, audit entries, and merged contact and company records. Read-only account, Basic Auth, escaped output, UUIDv7 in URLs.

Stack

Python 3.13FastAPIPydanticgoogle-genai SDKGemini 2.5 FlashVertex AICloud RunPub/SubEventarcCloud WorkflowsCloud SchedulerBigQueryFirestoreSecret ManagerCloud MonitoringResenduvpytest

2. Multilingual RAG Support Assistant with AI Ticket Triage

A production retrieval-augmented assistant for an international e-commerce company: a streaming chat widget on the site and a support-desk pipeline that classifies tickets and drafts replies, both on one retrieval function over 1,614 documents in 30 languages.

support queries resolved without a human (measured)
70%+ support queries resolved without a human (measured)
response time, down from 40+ s
<4 s response time, down from 40+ s
languages
30 languages
OpenAI embeddingsUpstash VectorVercel AI SDKZoho Desk API

A fixed pipeline, not an autonomous agent: the code decides the steps, and a confidence score decides what is answered automatically and what a person reviews. The ticket side is a two-stage pipeline (extract the question, then retrieve and classify).

Design decisions

One retrieval function for chat and tickets

The chat widget and the ticket classifier call the same function, so an improvement to retrieval applies to both and they cannot drift apart.

No locale filter in chat retrieval

A question in Dutch can match a document indexed in English or Finnish when the meaning is the same, so one knowledge base serves every language. Site search, by contrast, filters by the interface language.

A deterministic product signal on top of vector search

The product is detected from a multilingual keyword dictionary, and results for that product get a boost while mismatching ones get a penalty, so the answer comes from the right product’s documents.

Do not guess when unsure

A low-confidence ticket gets an empty reply and goes to a person with the extracted question and context prepared. The threshold is the line between automatic handling and human review.

Index only what changed

A SHA-256 cache decides which documents are new, changed or deleted, so the cost of keeping the knowledge base current tracks the change, not its size.

What it automates: Answering repetitive customer questions in the customer’s language, and extracting the question, classifying and drafting a reply for incoming support tickets.

Full walkthrough

How it works

  1. 1
    Ingestion extracts and chunks

    A TypeScript ingestion script pulls FAQs, product copy, delivery times, shipping costs and Markdown docs from the locale files and config, one document set per locale.

  2. 2
    SHA-256 cache decides what changed

    Each document is hashed and compared with the cache. Only added and changed documents are re-embedded and deleted ones are removed, so the knowledge base stays current at the cost of the change.

  3. 3
    Embeddings go to Upstash Vector

    OpenAI text-embedding-3-large, 3,072 dimensions, batched embedding and batched upserts.

  4. 4
    Query enrichment

    A follow-up such as ‘And to Germany?’ is enriched with the earlier conversation before retrieval, so the customer does not have to repeat the question.

  5. 5
    One retrieval function

    Embed the query, detect the product from a multilingual keyword dictionary, run vector search with no locale filter, then re-rank with a product boost and mismatch penalty. The chatbot and the ticket classifier share this single function.

  6. 6
    Streaming answer

    Vercel AI SDK streams the answer into an assistant-ui widget. The model answers in the customer’s language with the retrieved context injected into the prompt.

Retrieval design

Cross-lingual search

Chat retrieval does not filter by locale. A question in Dutch can match a document indexed in English or Finnish when the meaning is the same. Site search, by contrast, filters by the interface language.

Product-aware re-ranking

Candidates below a similarity threshold are dropped. Results matching the detected product get a boost, mismatching ones a penalty, and the top five go to the model.

Hybrid search

The search endpoint adds fuzzy keyword matching to vector similarity, with normalisation for Cyrillic and Nordic characters and a stricter threshold when no keyword matches.

Support-desk classifier

Two-stage pipeline

A Zoho Desk webhook first extracts the core question from the email, stripping greetings and order numbers, then runs retrieval on it.

Classification with confidence

The model assigns one of 18 categories, generates a draft reply and returns a confidence score. Low-confidence tickets get an empty reply and go to a person with the extracted question and context prepared.

Human in the loop

The assistant does not guess when unsure. The threshold decides what is handled automatically and what a human reviews.

Stack

TypeScriptNext.jsVercel AI SDKassistant-uiOpenAI APItext-embedding-3-largeUpstash VectorRedisZoho Desk APInext-intlZod

3. Multi-Model Localization Pipeline

A Python pipeline that keeps 30 locales in sync from a single English source using three AI stages from two providers, with structural correctness enforced by tests instead of trusted to a model.

locales
30 locales
AI stages
3 AI stages
model providers
2 model providers
PythonOpenAI APIAnthropic APIpytest

A fixed pipeline of stages, not agents: each stage has one job, and the order is set by code. The models handle language quality; deterministic code and tests handle structure.

Design decisions

Why more than one model

A second model from a different provider reviews translation quality, which reduces reliance on self-evaluation. It does not guarantee that errors are independent, so the checks that must always hold are kept out of the models entirely.

Where code replaces the model

Placeholder preservation, HTML tag preservation, key parity with the source and SEO length limits are enforced by a pytest suite. No model is trusted to keep them.

What happens when a stage fails

A structural violation (a lost placeholder, a broken tag, a missing key, a text over its length limit) fails the build, so it cannot ship. Each run fills only missing keys, so an interrupted run is finished by running it again.

Why a diff and not a full re-translation

Re-running touches only genuinely missing keys, so cost follows new content and not catalogue size, and existing translations are never overwritten.

What it automates: Finding missing translation keys, translating them to market-specific rules, reviewing the result and checking structure for every locale.

Full walkthrough

How it works

  1. 1
    Deterministic diff

    Python walks the English source, finds keys missing from each locale, including inside nested objects and lists, and removes keys that disappeared upstream. Existing translations are untouched.

  2. 2
    Translate

    An OpenAI call receives the text plus the market’s keyword spec and hard formatting rules for that key, such as title length budgets.

  3. 3
    Verify

    A model from a different provider, Anthropic, grades the translation against the source and flags dropped placeholders, calques, forbidden terms and length violations.

  4. 4
    Proofread

    A separate AI stage does a naturalness and grammar pass in the target language, catching stilted but technically correct phrasing.

  5. 5
    Invariants gate the build

    A pytest suite enforces what no model is trusted to hold: placeholder preservation, HTML tag preservation, key parity with the source and SEO length limits. A violation fails the build.

Design principles

Code owns structure, AI owns language

Deterministic code and tests guarantee that the output is well-formed. The models are trusted only for language quality.

Cross-provider verification

The reviewing model comes from a different provider than the translating one, so the two are less likely to agree because they share the same blind spots.

Idempotent by construction

Re-running fills only genuinely missing keys, so cost tracks new content and not catalogue size.

Readable run state

A status panel shows per locale and per stage what is synced, verified and proofread, so a content owner can see what is safe to ship without reading thirty languages.

Stack

PythonOpenAI APIAnthropic APIpytestpycountrypython-dotenv

4. MCP Server for EU VAT Validation

An open-source Model Context Protocol server that lets Claude Desktop, Cursor and other MCP clients validate EU VAT numbers and look up rates directly in chat. Published to npm and listed in the official MCP Registry.

typed tools
5 typed tools
work offline, no API key
4 work offline, no API key
stdio smoke tests
13 stdio smoke tests
TypeScriptMCP SDKZodnpmGitHubnpm

A tool provider, not an agent: the server contains no LLM. It exposes typed tools, and the client’s model decides when to call them. All five tools are annotated read-only and non-destructive.

Design decisions

stdio, not HTTP

The transport every desktop MCP client supports, so there is nothing to host. The server runs locally from a single npx command.

Offline first

Four tools answer from a bundled VAT-rates dataset with no network call, no API key and no quota, so the server is useful before any account exists. Only live VIES validation calls the vatnode API.

Errors the model can act on

A failed call returns an error result with a message and a hint, for example pointing to the API key when the key is rejected, so the assistant can tell the user what to fix instead of retrying blindly.

Auditable by design

An AI client hands the server an API key. Trust comes from being able to read the code, so the package is MIT-licensed and the repository is public.

What it automates: Looking up VAT rates and number formats, and verifying a VAT number against the EU VIES service, from inside an AI assistant.

Tools

list_eu_vat_ratesoffline

Input: none

Returns: Standard, reduced, super-reduced and parking rates for the EU member states and Northern Ireland.

get_country_vat_ratesoffline

Input: countryCode (ISO 3166-1 alpha-2)

Returns: Rates, VAT number format and regex for one country, including some non-EU European jurisdictions.

On error: Unknown country code returns an error result.

check_vat_formatoffline

Input: vatId

Returns: Normalised number, country, and whether it matches the country’s format. Does not contact VIES.

list_supported_countriesoffline

Input: none

Returns: Every covered country, with a flag for which ones support live VIES validation.

validate_vat_numberlive, API key

Input: vatId

Returns: Live VIES verification through the vatnode API: validity, company name, address and registration date.

On error: Error result with the failure code, a message and a hint; needs VATNODE_API_KEY and uses one request from the monthly quota.

Connect it to Claude Desktop

{
  "mcpServers": {
    "vatnode": {
      "command": "npx",
      "args": ["-y", "vatnode-mcp"],
      "env": { "VATNODE_API_KEY": "optional, only for validate_vat_number" }
    }
  }
}
Full walkthrough

How it works

  1. 1
    stdio transport

    The format every desktop MCP client supports, so there is no HTTP or SSE server to host. Installed with a single npx command.

  2. 2
    Five typed tools

    Zod schemas describe each tool’s input and output. Four tools answer from a bundled VAT-rates dataset with no network call or API key; live VIES validation goes through the vatnode API.

  3. 3
    Attribution

    Every outgoing API request carries a versioned User-Agent so MCP traffic is measurable in backend logs.

  4. 4
    Signed releases

    Published to npm with OIDC Trusted Publishing and signed provenance, so every release is tied to a commit of the public source and the pipeline holds no tokens.

Why it is built this way

Try before committing

The free tools work without an account, which lowers the cost of trying the server inside an assistant.

Discoverability

Listed in the official MCP Registry with a server manifest, which is how assistants and catalogues find tool servers.

Stack

TypeScriptNode.jsMCP SDKZodtsupnpmGitHub Actions

How I work

The model proposes, code decides

An LLM returns a suggestion. Plain code validates it against a schema and business rules, and only one trusted service is allowed to write. Replies are drafts, and low-confidence cases go to a person with the context prepared.

Limit what a wrong answer can do

Emails, web pages and earlier agent output are data, not instructions. Delimiters help, but the real protection is permissions: a manipulated model has no tool to send mail, no write access and only a fixed output schema.

Measure before trusting

I build the eval set early and look at the expensive mistakes first, such as spam getting a reply or a support request taken for a sale. I state the size of the set next to the result. Where quality matters, a model from a second provider reviews the first, and tests guard what no model should be trusted to keep.

Stack

Agentic AI
Multi-agent orchestration, RAG, evaluation, guardrails, human-in-the-loop, MCP servers
Models and APIs
OpenAI API, Anthropic API, Gemini API, Vertex AI (Gemini 2.5 Flash), Vercel AI SDK
Google Cloud
Cloud Run, Pub/Sub, Eventarc, Cloud Workflows, Cloud Scheduler, BigQuery, Firestore, Secret Manager, Cloud Monitoring
Languages
Python, TypeScript, SQL
Back end
FastAPI, Pydantic, Node.js, Zod, REST APIs, webhooks
Data
BigQuery, Firestore, Upstash Vector, Redis, PostgreSQL
Quality
pytest, eval harnesses, structured logging, Cloud Monitoring alerts
Iurii RoguliaAvailable

Hiring for an agentic AI role?

Send me the role and what the team is building – I respond within 24 hours.

Download CV