Deep Dive - Agent Backend & SCM Integrations
Powering agent workflows and taming source-control APIs
Agent workflow backend
After this you can design backend systems for long-running, stateful AI agent work.
The agent backend is the surface Cursor lives or dies on. An Engineering Manager, Core Services who treats it as plumbing will lose the room - treat it as the product, because it is.
A single agent run is not a request. It is a long-running, stateful session that may take seconds or minutes, calls an LLM many times, touches the user's files and SCM and streams partial results the whole way. Modeling that as synchronous request/response is the first mistake an interviewer will probe for.
The shape you want is a durable workflow: accept the run, persist its intent, drive it forward through a queue and stream progress out of band. If the box handling step 7 dies, step 8 should resume from durable state somewhere else.
Why request/response breaksthe wrong default
A run can outlive any single HTTP timeout or deploy.
Holding a connection open for the whole run wastes a server slot and dies on the first restart.
Context, tool results and partial edits accumulate across many model calls.
That state must survive a crash, not live only in process memory.
Load is spiky: a model release or a workday peak floods the queue.
Provision for the burst with backpressure, not for the average with hope.
Every LLM call has real per-token cost and latency.
A retry storm or a runaway loop is a bill, not just an error rate.
The orchestration spinewhat you'd whiteboard
Decompose the run into steps, persist each transition and let workers pull work. This is the table to draw when the interviewer says "design the agent backend."
Interactive diagram. Tab through its regions; each focused region shows its detail in the panel below.
Read bottom-up: durable state and the queue carry recovery; workers stay stateless.
- Intake API
- Validates the request, writes a run record, returns a run id fast. Cheap and synchronous.
- Durable state store
- Run status, step cursor, accumulated context, tool outputs. The single source of truth a worker can recover from.
- Work queue
- Steps to execute, with visibility timeouts and retry policy. Decouples intake rate from execution rate.
- Worker pool
- Stateless executors that pull a step, call the model or a tool, write results back and enqueue the next step.
- Streaming channel
- Pushes tokens and step events to the editor (SSE or websocket), independent of where the worker runs.
Workers carry no durable state; the store and queue do. That is what makes resumption possible.
Partial failure and resumptionthe senior-signal point
A transient model timeout on step 6 of a 10-step run must not restart from step 1. Checkpoint after each step, make each step idempotent so a retry can't double-apply an edit and key external side effects so re-running is safe.
- Checkpoint progress after every step so recovery resumes mid-run, not from zero.
- Make steps idempotent - a retried tool call or file write must converge to the same result, never duplicate it.
- Distinguish retryable (timeout, 429) from terminal (bad request, auth failure) errors and route them differently.
- Cap total cost and step count per run so a confused agent loops into a guardrail, not a budget.
Streaming and backpressurethe editor is watching
Users expect tokens to appear as the model produces them. That means a long-lived connection per active run and a plan for when the consumer is slower than the producer. If the client can't keep up, buffer with a bound and shed or pause rather than blowing memory on the server.
Under a burst, something has to give. Decide explicitly: queue depth limits that reject new runs early with a clear signal, bounded streaming buffers that pause the producer and admission control at intake. Saying "we'd add more workers" without a saturation plan is the answer that fails the screen.
Open by naming the three constraints out loud - long-running, stateful, bursty - then say cost is a first-class SLO alongside latency and reliability. Most candidates optimize for speed and correctness and forget that every step is metered. Tying the design back to "this is Cursor's defining feature surface, so its reliability is strategic" shows you grasp why this team exists.
Takeaway. Model an agent run as a durable, resumable workflow - queue plus checkpointed state plus out-of-band streaming - and treat cost, latency and reliability as co-equal SLOs.
Self-check
QAn agent run fails on step 6 of 10 because the model provider returns a 429. Your current design restarts the whole run. What is the core fix and why does it matter for Cursor specifically?
Resilient SCM integrations
After this you can build abstractions over GitHub/GitLab that survive their failures.
The job description names it directly: build resilient abstractions over source-control providers. The skill being tested is whether you can hide GitHub's, GitLab's and Bitbucket's quirks behind one interface the rest of Cursor can trust.
Product surfaces should ask for "the diff for this PR" or "open a pull request," not learn that GitHub paginates with a Link header while GitLab uses X-Next-Page or that one returns 403 for rate limits and the other 429. The abstraction is where that knowledge lives and dies.
The clean interfaceone contract, many backends
Define a provider-neutral interface - repos, refs, diffs, PRs, comments, webhooks - and implement an adapter per provider behind it. Normalize the data model so product code never branches on which provider it's talking to.
interface ScmProvider {
getPullRequest(repo: RepoRef, number: number): Promise<PullRequest>;
listChangedFiles(pr: PullRequestRef): AsyncIterable<ChangedFile>; // hides pagination
createReviewComment(pr: PullRequestRef, c: NewComment): Promise<Comment>;
verifyWebhook(headers: Headers, body: Buffer): WebhookEvent; // hides per-provider signing
}
// GithubAdapter, GitlabAdapter, BitbucketAdapter implement this.
// Rate-limit, pagination and retry logic live inside each adapter, never above it.Rate limits and paginationthe two things candidates get wrong
GitHub gives you a primary hourly budget plus an opaque secondary rate limit that punishes bursts and concurrency. Respect both. Read the limit headers, throttle proactively with a token bucket and back off when told to.
- Failure
- Primary rate limit (403/429 + reset header)
- Naive handling
- Retry immediately in a loop
- Resilient handling
- Honor the reset time, throttle via token bucket, queue non-urgent work
- Failure
- Secondary rate limit (abuse detection)
- Naive handling
- Hammer harder with parallel calls
- Resilient handling
- Add
Retry-Afterbackoff with jitter, drop concurrency, serialize the offender
- Failure
- Pagination
- Naive handling
- Fetch page 1 and assume it's complete
- Resilient handling
- Iterate cursor/Link headers to exhaustion behind an async iterator
- Failure
- Partial outage (provider 5xx)
- Naive handling
- Surface the 5xx to the user
- Resilient handling
- Serve cached data if fresh enough, queue the write, retry with backoff
| Failure | Naive handling | Resilient handling |
|---|---|---|
| Primary rate limit (403/429 + reset header) | Retry immediately in a loop | Honor the reset time, throttle via token bucket, queue non-urgent work |
| Secondary rate limit (abuse detection) | Hammer harder with parallel calls | Add Retry-After backoff with jitter, drop concurrency, serialize the offender |
| Pagination | Fetch page 1 and assume it's complete | Iterate cursor/Link headers to exhaustion behind an async iterator |
| Partial outage (provider 5xx) | Surface the 5xx to the user | Serve cached data if fresh enough, queue the write, retry with backoff |
The resilient column is what "resilient abstraction" means in the JD.
Graceful degradationtheir outage is not your outage
GitHub has bad days. When a provider degrades, the rest of Cursor should keep working. Reads fall back to cache; writes get queued and replayed; only the directly affected feature shows a soft failure.
- Cache reads (repo metadata, recent diffs) with a TTL so a provider blip serves slightly stale instead of nothing.
- Queue writes (comments, PR creation) and replay them when the provider recovers, with idempotency so replays don't duplicate.
- Wrap each provider in a circuit breaker so a hung GitHub doesn't exhaust your connection pool and starve unrelated features.
- Show a scoped, honest degraded state in the UI rather than a generic crash.
Inbound webhooks and API driftthe integration is two-directional
Providers also push events to you - PR opened, push, comment. Treat that ingress like any untrusted, at-least-once stream: verify the signature, dedupe on the delivery id and tolerate replays and out-of-order arrival. Separately, providers change their APIs without asking. The adapter layer is the firewall that absorbs that churn so no product team has to.
Don't promise "exactly-once" webhook processing - providers deliver at-least-once and will redeliver. The honest answer is idempotent handlers keyed on the delivery id, so a duplicate is a no-op. Claiming exactly-once at the network boundary is a tell that you haven't run one of these in production.
"I'd put one provider-neutral interface in front of GitHub, GitLab and Bitbucket and push every quirk - pagination, the secondary rate limit, signature verification, API drift - down into per-provider adapters. Product surfaces get a stable contract; when GitHub changes an endpoint or has an outage, I change one adapter and degrade gracefully and nobody else's code moves."
Takeaway. Hide every provider quirk behind one neutral interface, respect both rate limits and degrade with cache-read, queue-write, circuit-break so a provider outage never becomes Cursor's outage.
Self-check
Service contracts & abstractions
After this you can draw the boundaries that keep shared infra clean.
Core Services sits between every product surface and the infrastructure underneath. The contracts you draw there are impact: a good one unblocks every product team at once, a leaky one creates coordination tax forever.
The JD calls for "clean abstractions, well-defined service contracts." In the interview that means you can name what each service promises, version those promises without breaking callers and defend where you drew each line.
What a contract actually specifiesbeyond the function signature
A contract is more than an API shape. It states the guarantees a caller can build on, so product teams can reason about your service without reading its code.
- Semantics
- At-least-once vs at-most-once, ordering guarantees, consistency model, idempotency expectations.
- Latency
- Target percentiles (p50/p99) per operation and what happens on timeout.
- Availability
- The SLO callers can design around, plus the degraded behavior when you miss it.
- Error model
- A stable taxonomy: which errors are retryable, which are terminal, how they're typed.
- Versioning
- How callers opt into changes and how long old behavior is supported.
Versioning and backward compatibilityyou cannot break product teams
Shared infra changes constantly while its callers can't all redeploy in lockstep. The discipline is additive change: add fields and endpoints, never repurpose or remove them under a live contract. When a breaking change is unavoidable, run both versions and migrate callers off the old one before you retire it.
- Make changes additive by default - new optional fields, new endpoints - so old callers keep working untouched.
- Run contract tests in CI on both sides so a breaking change to the auth or webhook service fails the build, not production.
- For unavoidable breaks, version explicitly, dual-run, migrate callers, then deprecate on a published timeline.
- Treat the contract as the artifact you review most carefully, because it has the widest blast radiusHow much breaks if a change goes wrong; the scope of potential damage..
Build vs buya judgment call you must defend
Auth, queues and SCM access each have credible buy options. The interviewer wants your reasoning, not a dogma. Decide on reliability, cost and control and be honest about the cost of owning a thing forever.
Interactive diagram. Tab through its regions; each focused region shows its detail in the panel below.
Buy the commodity, build the differentiator and own the abstraction in between.
- Domain
- Auth (OAuth/OIDCOpenID Connect. A modern standard that powers single sign-on, built on OAuth.)
- Lean buy
- Proven providers handle SSOSingle Sign-On. One company login (usually via SAML or OIDC) instead of a separate password per tool./SAMLAn enterprise standard that powers single sign-on., rotation, compliance
- Lean build
- Only when you need control buy can't give
- Deciding factor
- Security surface area; rarely worth building from scratch
- Domain
- Queues
- Lean buy
- Managed queue for standard delivery semantics
- Lean build
- Custom only for unusual ordering/throughput needs
- Deciding factor
- Operational burden vs the exact semantics you need
- Domain
- SCM access
- Lean buy
- Use provider SDKs as a base
- Lean build
- Build the resilient abstraction layer yourself
- Deciding factor
- The value is in the abstraction, not re-implementing Git APIs
- Domain
- Agent orchestration
- Lean buy
- Little to buy at Cursor's specifics
- Lean build
- Build - it is the differentiator
- Deciding factor
- Core IP; control and cost levers matter most
| Domain | Lean buy | Lean build | Deciding factor |
|---|---|---|---|
| Auth (OAuth/OIDCOpenID Connect. A modern standard that powers single sign-on, built on OAuth.) | Proven providers handle SSOSingle Sign-On. One company login (usually via SAML or OIDC) instead of a separate password per tool./SAMLAn enterprise standard that powers single sign-on., rotation, compliance | Only when you need control buy can't give | Security surface area; rarely worth building from scratch |
| Queues | Managed queue for standard delivery semantics | Custom only for unusual ordering/throughput needs | Operational burden vs the exact semantics you need |
| SCM access | Use provider SDKs as a base | Build the resilient abstraction layer yourself | The value is in the abstraction, not re-implementing Git APIs |
| Agent orchestration | Little to buy at Cursor's specifics | Build - it is the differentiator | Core IP; control and cost levers matter most |
Buy the commodity, build the differentiator and own the abstraction in between.
When you draw a boundary, justify it by what changes independently. Auth changes for security and compliance reasons; SCM changes when providers drift; the agent backend changes with the product. Different rates of change and different failure domains are why they're separate services - say that and your boundaries stop looking arbitrary.
Takeaway. A contract specifies semantics, latency, availability and an error model - keep it additive, guard it with contract tests and buy the commodity while you build the differentiator.
Self-check
QYou need to change the response shape of the webhook service that a dozen product teams depend on. How do you ship it without breaking anyone?
Reliability engineering at scale
After this you can operate all four domains to a high reliability bar.
Auth, webhooks, the agent backend and SCM all run for a tool with over a million daily developers. Reliability here is not a quality of the system - it is the personal responsibility the JD assigns to this role.
The mature way to talk about reliability is in budgets, not absolutes. You don't promise five nines on everything; you set a target per service and spend the remaining error budget on velocity. That framing alone reads as senior.
SLOs, SLIs and error budgetsthe vocabulary that signals seniority
- SLI
- The measured signal: success rate, p99 latency, freshness of a streamed token.
- SLO
- The target on that signal, e.g. 99.95% of auth checks under 100ms.
- Error budget
- The allowed failure (1 − SLO). Spend it on shipping; freeze features when it's exhausted.
- Tiering
- Auth gets a stricter SLO than a best-effort feature - set the bar by blast radiusHow much breaks if a change goes wrong; the scope of potential damage..
Different services earn different bars. Auth being down is existential; a delayed webhook is recoverable.
Containing failureblast-radius discipline
One failing dependency must not take the product with it. Circuit breakers, timeouts and bulkheads keep a slow SCM provider or a degraded model from cascading into auth or the editor.
- Circuit breakers around every external dependency, so a hung provider fails fast instead of consuming the thread pool.
- Aggressive timeouts plus bounded retries with backoff and jitter - retries without jitter synchronize into a thundering herd.
- Bulkheads: isolate resource pools per dependency so one saturated integration can't starve the others.
- Graceful degradation paths defined per feature, so the answer to "GitHub is down" is a known reduced mode, not a guess.
Always-on and burstythe two scale realities here
Auth and the agent backend can't take maintenance windows - a developer in another timezone is always working. That pushes toward multi-region and tested failover. The agent load is also spiky, so capacity planning has to target the burst, not the daily average.
- Concern
- Always-on auth
- What to plan for
- Multi-region with automated failover; no single-region dependency
- How you'd validate it
- Game-day a region outage and measure failover time
- Concern
- Bursty agent load
- What to plan for
- Headroom for peak (release day, work-hours spike), backpressure when exceeded
- How you'd validate it
- Load test to 2–3x projected peak; watch queue depth and shed behavior
- Concern
- Capacity vs growth
- What to plan for
- Plan against the 1M+ DAU curve, not last quarter's average
- How you'd validate it
- Track utilization trend; alert before saturation, not after
- Concern
- Unknown failure modes
- What to plan for
- Failures you haven't imagined yet
- How you'd validate it
- Chaos testing - kill workers, inject latency, drop a dependency
| Concern | What to plan for | How you'd validate it |
|---|---|---|
| Always-on auth | Multi-region with automated failover; no single-region dependency | Game-day a region outage and measure failover time |
| Bursty agent load | Headroom for peak (release day, work-hours spike), backpressure when exceeded | Load test to 2–3x projected peak; watch queue depth and shed behavior |
| Capacity vs growth | Plan against the 1M+ DAU curve, not last quarter's average | Track utilization trend; alert before saturation, not after |
| Unknown failure modes | Failures you haven't imagined yet | Chaos testing - kill workers, inject latency, drop a dependency |
Validate before incidents do. A failover you've never tested is a hope, not a plan.
Load tests find where the system saturates; chaos tests find what happens when a dependency dies mid-flight. Both run before the incident. The interview-grade version of "reliable" is being able to say "we game-day our auth failover quarterly and load-test the agent queue to 3x peak" - concrete drills beat adjectives every time.
Don't quote five nines for everything - it signals you've never owned an error budget. Real reliability work is choosing where the bar is lower so velocity can be higher and saying out loud which service gets the strict SLO and why. Uniform perfection is a budget you can't afford and a tell you haven't operated at scale.
Takeaway. Set tiered SLOs with error budgets, contain failure with breakers, timeouts and bulkheads and validate failover and burst capacity with game-days and chaos before an incident does it for you.
Self-check
Cross-domain design exercise
After this you can synthesize the domains into one defensible architecture.
Here is the prompt to rehearse cold: design the pipeline that authenticates a user, calls SCM, runs the agent and emits a webhook on completion. It forces all four domains into one diagram with clean contracts between them.
This is close to what the paid onsite working session actually feels like - a real system, built with the team, no AI assistance beyond autocomplete. The artifact you want in your head is the data flow plus the failure mode at every hop.
The end-to-end flownarrate it as steps
- 1Authenticate. The intake API validates the user's token (OAuth/OIDCOpenID Connect. A modern standard that powers single sign-on, built on OAuth.), checks RBACRole-Based Access Control. Granting permissions by role rather than configuring each person individually. for the repo and action and rejects fast on failure. Auth is on the hot path, so it must be cheap and cached.
- 2Accept and persist. Write a run record to durable state, return a run id immediately and enqueue the first step. The user is now unblocked while work proceeds asynchronously.
- 3Call SCM. Through the provider-neutral abstraction, fetch the repo context - branch, diff, files - with caching, pagination and rate-limit handling hidden inside the adapter.
- 4Run the agent. Workers drive the run step by step, calling the model, checkpointing after each step, streaming tokens to the editor and enforcing cost and step caps.
- 5Emit completion. On finish, publish to the webhook service with at-least-once delivery, retries with backoff and jitter, signing and a dead-letter queue for the deliveries that never succeed.
Interactive diagram. Tab through its regions; each focused region shows its detail in the panel below.
Two gates - auth and the run's cost/step caps - are where the pipeline says no.
How the contracts composefour services, clean seams
- Auth → Agent backend
- A validated identity + scopes token. Agent backend trusts it, never re-implements auth.
- Agent backend → SCM
- Provider-neutral calls; the agent never knows whether it's GitHub or GitLab.
- Agent backend → Webhooks
- A typed completion event handed to a delivery service with its own retry/DLQ semantics.
- Each → Observability
- Every hop emits a trace span on one run id, so a failure is traceable end to end.
Failure modes and blast radiusname them before you're asked
- Hop
- Auth
- Failure mode
- Auth provider slow or down
- Containment
- Cache valid sessions; fail closed on the action, not the whole app
- Hop
- SCM
- Failure mode
- Provider rate limit or 5xx
- Containment
- Serve cached reads, queue writes, circuit-break the adapter
- Hop
- Agent run
- Failure mode
- Model timeout or runaway loop
- Containment
- Checkpoint + idempotent retry; cost and step caps kill runaways
- Hop
- Webhook
- Failure mode
- Receiver down or slow
- Containment
- Retry with backoff + jitter; dead-letter after N attempts; never block the run
| Hop | Failure mode | Containment |
|---|---|---|
| Auth | Auth provider slow or down | Cache valid sessions; fail closed on the action, not the whole app |
| SCM | Provider rate limit or 5xx | Serve cached reads, queue writes, circuit-break the adapter |
| Agent run | Model timeout or runaway loop | Checkpoint + idempotent retry; cost and step caps kill runaways |
| Webhook | Receiver down or slow | Retry with backoff + jitter; dead-letter after N attempts; never block the run |
Each hop fails into a contained mode, so no single dependency takes the pipeline down.
Quantify itnumbers make the design real
Reach for order-of-magnitude figures and the levers behind them, framed as your targets rather than Cursor's published numbers.
"Four services, four contracts. Auth hands the agent backend a validated identity; the agent backend calls SCM through a provider-neutral seam; on completion it hands a typed event to the webhook service, which owns at-least-once delivery and a DLQ. Every hop has a contained failure mode and a trace span on one run id. If GitHub is down, only the SCM step degrades; if the webhook receiver is down, the run still completes and delivery retries."
The management anglewhere most engineers stop and you shouldn't
This is an EM loop, so end on people, not boxes. How would you staff, sequence and de-risk building this with a small, high-talent-density team in a flat org?
- Sequence by risk: harden auth and the durable state spine first, since everything else depends on them.
- Ship a thin vertical slice end-to-end early - one provider, one agent step, one webhook - then deepen, so integration risk surfaces in week two not month three.
- Staff to ownership, not layers: a small team where engineers own a domain end to end fits Cursor's "no narrow job descriptions" culture better than a handoff chain.
- De-risk with contract tests and a staging game-day before the first real load and stay hands-on in the reviews yourself - this is a player-coach role.
Close the design by switching hats out loud: "As the EM, I'd sequence auth and durable state first, ship a thin end-to-end slice to flush integration risk and keep myself in code review on the contract boundaries." That one move shows you're the player-coach the JD describes, not a manager who only draws boxes - which is exactly the bar the founder round is checking.
Takeaway. Compose four services behind four contracts with a contained failure mode at every hop, quantify your targets, then close on the player-coach sequencing - auth and state first, a thin end-to-end slice early.
Self-check
QIn the agent-run pipeline, the webhook receiver on the customer side is down when a run completes. What should happen and what should NOT happen?