# Connectors

Course: [Loop Engineering](https://www.devtutorials.ai/learn/loop-engineering) · Section: Loops at Scale
Source: https://www.devtutorials.ai/learn/loop-engineering/loops-at-scale/connectors

> The loop acts on real tools. A loop that only says "here is the fix" changes nothing. Attach a tracker, CI and chat, and make it open a PR, link the ticket, and post only after CI is green.

## The Loop Acts on Real Tools

A loop that fixes code inside its own folder is only half done. The team works in other places: the ticket tracker, CI and the team chat. This lesson connects the loop to them.

## Watch a Loop Do Nothing First

This loop fixes three tickets. Its checker passes every fix. But it has no **connectors**.

Press **Run it in the Loop Lab**. Then look at the **event log** under the table:

1. No pull request was opened.
2. No ticket was updated.
3. Nobody was told.

The fixes exist only in the loop's own log. To the team, nothing happened.

**In simple words:** the loop did the homework, then left it in its bag.

## TL;DR

- A **connector** lets the loop use a real tool, such as an issue tracker, CI or a chat app. Most tools use **MCP** for this.
- After a fix, a connected loop should: **open a pull request**, **link the ticket**, **wait for CI**, then **post to chat**.
- The order matters. Never post "fixed!" before CI is green.
- All three tools share one **event log**, so you can follow a ticket from start to finish.
- In this lab, the connectors are **mock** tools in your browser. No real messages are sent.

## A Real-World Example

Think about a parcel delivery.

The driver could leave your parcel in the van and text you "delivered!". The parcel is real, but it is not at your door. That is a loop with no connectors.

A good delivery has steps, in order:

1. The parcel is scanned at the depot.
2. It is put on the van.
3. It is handed to you, and a photo is taken.
4. **Then** you get the "delivered" text.

If the text came before step 3, you would run to the door and find nothing.

Now map this to a loop:

1. The scan is **linking the ticket**.
2. Putting it on the van is **opening the pull request**.
3. The hand-over and photo is **CI going green**.
4. The text is the **chat message**. It comes last.

## What You Will Learn

In this lesson, you will learn:

1. What a connector is, and what MCP is.
2. The four actions a loop takes after a fix.
3. Why "post to chat" must come after green CI.
4. How to follow a ticket through the event log.
5. Where each tool sets up connectors.

## What a Connector Is

A **connector** is a bridge between the agent and another tool. The agent asks the connector to do something, such as "open a pull request". The connector does it in the real tool.

Most coding tools use **MCP** (the Model Context Protocol) for connectors. You met MCP in its own course on this site.

| Tool | Connectors |
|---|---|
| Claude Code | MCP servers and plugins |
| Codex | Connectors (MCP) and plugins |
| Cursor | MCP, plus Automation integrations |

In the lab's loop file, you list the connectors, then the actions to take after a fix:

```yaml
connectors:
  tracker: mock-tracker
  ci: mock-ci
  chat: mock-chat
on_fix:
  - open_pr
  - link_ticket
  - wait_for_ci
  - post_to_chat
```

### Each Action Needs Its Connector

| Action | Needs | What it does |
|---|---|---|
| `open_pr` | `ci` | Opens a pull request with the fix |
| `link_ticket` | `tracker` | Links the pull request to the ticket |
| `wait_for_ci` | `ci` | Waits for CI. Red stops the rest. |
| `post_to_chat` | `chat` | Tells the team, only after green CI |

The next box runs the four actions. One action has no connector.

```js runnable title="An action with no connector fails"
const connectors = { tracker: "mock-tracker", ci: "mock-ci" }; // no chat
const needs = { open_pr: "ci", link_ticket: "tracker", wait_for_ci: "ci", post_to_chat: "chat" };
for (const action of ["open_pr", "link_ticket", "wait_for_ci", "post_to_chat"]) {
  const c = needs[action];
  console.log(action.padEnd(13) + (connectors[c] ? "ok" : "failed: no " + c + " connector"));
}
```

Expected output:

```text
open_pr      ok
link_ticket  ok
wait_for_ci  ok
post_to_chat failed: no chat connector
```

The fix landed, but the team was never told. A missing connector fails quietly unless you check the log.

## Order Matters: Post Only After Green CI

Ticket #2 has a broken first fix. CI turns red on that first try. If the loop posts to chat before it waits for CI, the team hears "fixed!" about broken code.

```js runnable title="Post before CI vs post after CI"
function run(order, ciGreen) {
  const log = [];
  let ci = "not run";
  for (const action of order) {
    if (action === "wait_for_ci") {
      ci = ciGreen ? "green" : "red";
      log.push("CI " + ci);
      if (!ciGreen) break; // red stops the rest
    }
    if (action === "post_to_chat") log.push(ci === "green" ? "posted: fixed" : "posted: fixed (but CI is " + ci + "!)");
  }
  return log.join(" -> ");
}
console.log("post first: " + run(["post_to_chat", "wait_for_ci"], false));
console.log("CI first:   " + run(["wait_for_ci", "post_to_chat"], false));
```

Expected output:

```text
post first: posted: fixed (but CI is not run!) -> CI red
CI first:   CI red
```

With CI first, the red result stops the trail. Nobody is told something false. The loop's checker sends the ticket back, and the next try goes green.

A chat message is a promise to people. Once someone reads "fixed!", they act on it: they close the ticket, or tell a customer. Only post after a check that a program decided.

```js runnable title="The trail stops at red CI, then the retry finishes it"
const tries = [
  { ticket: "#2", ci: "red" },
  { ticket: "#2", ci: "green" },
];
const log = [];
for (const t of tries) {
  log.push(t.ticket + ": PR opened, ticket linked, CI " + t.ci);
  if (t.ci === "green") log.push(t.ticket + ": posted 'fixed, CI is green'");
}
console.log(log.join("\n"));
```

Expected output:

```text
#2: PR opened, ticket linked, CI red
#2: PR opened, ticket linked, CI green
#2: posted 'fixed, CI is green'
```

The first try stopped at red CI. The checker sent #2 back, the second try went green, and only then did the team hear about it.

## The Event Log

All three mock tools write to **one shared event log**. Each line says which tool did what, in order. That lets you follow one ticket from start to finish.

```js runnable title="Follow one ticket through the event log"
const events = [
  { n: 1, source: "ci", text: "#1: opened a pull request" },
  { n: 2, source: "tracker", text: "#1: linked the pull request to ticket #1" },
  { n: 3, source: "ci", text: "#2: opened a pull request" },
  { n: 4, source: "ci", text: "#1: CI is green" },
  { n: 5, source: "chat", text: "#1: posted 'fixed, CI is green'" },
];
const trail = events.filter((e) => e.text.startsWith("#1:")).map((e) => e.source);
console.log("ticket #1 trail: " + trail.join(" -> "));
```

Expected output:

```text
ticket #1 trail: ci -> tracker -> ci -> chat
```

When something goes wrong, the log tells you where the trail stopped.

The lab's connectors are **mocks**: small pretend tools inside your browser. They make no network calls and send no real messages. They behave the same way every time, so your loop can be graded.

## Check the Whole Trail

A fixed ticket is only really finished when all three things happened: a pull request, a linked ticket, and a message after green CI. The next box checks that for three tickets.

```js runnable title="Is every fixed ticket fully delivered?"
const tickets = [
  { id: "#1", pr: true, linked: true, postedAfterGreen: true },
  { id: "#2", pr: true, linked: true, postedAfterGreen: false },
  { id: "#3", pr: true, linked: false, postedAfterGreen: true },
];
for (const t of tickets) {
  const gaps = [];
  if (!t.pr) gaps.push("no PR");
  if (!t.linked) gaps.push("ticket not linked");
  if (!t.postedAfterGreen) gaps.push("no message after green CI");
  console.log(t.id + ": " + (gaps.length ? gaps.join(", ") : "delivered"));
}
```

Expected output:

```text
#1: delivered
#2: no message after green CI
#3: ticket not linked
```

## Lab Exercise: Act on Real Tools, in the Right Order

The connectors are attached, but the actions under `on_fix:` are in the wrong order. Put them in this order:

1. `open_pr`
2. `link_ticket`
3. `wait_for_ci`
4. `post_to_chat`

Press **Check my loop**. For every fixed ticket, on all 3 versions, there must be a pull request, a linked ticket, and a chat message only after CI is green.

Stuck? Open the **Timeline** after Check runs, and read the event log. A red line names the ticket and the step that went wrong.

## Common mistakes

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| No connectors | "The fix is in the repo, that is enough." | Open a PR, link the ticket, tell the team. | Nobody knows the work exists. |
| Posting before CI | "Tell them fast." | Wait for green CI, then post. | The team hears "fixed" about broken code. |
| A missing connector | "I listed the action, so it runs." | Every action needs its connector. | The action fails quietly. |
| Skipping the ticket link | "The PR title says the ticket number." | Link the PR to the ticket in the tracker. | The ticket stays open, and someone fixes it twice. |

## Exercises

Each exercise ends with a value on the last line. Press **Check** to compare it with the expected answer.

## What to Remember

- A connector lets the loop act in a real tool. Most tools use MCP.
- After a fix: open a PR, link the ticket, wait for CI, then post to chat.
- A red CI stops the trail. Nobody is told something false.
- One shared event log lets you follow any ticket from start to finish.
- The lab's connectors are mocks: no network, same result every time.

## Further Reading

- [Loop Engineering, by Addy Osmani](https://addyosmani.com/blog/loop-engineering): connectors and plugins as a building block.
- [Cursor Cloud Agents and Automations, on developertoolkit.ai](https://developertoolkit.ai/en/cursor-ide/advanced-techniques/cloud-agents-automations/): GitHub, Slack and Linear integrations.
- The **Model Context Protocol** course on this site explains how MCP connectors work.

Next: **Cost, Rate and Blast-Radius Budgets**, where a loop spends a whole day's tokens on one flaky test, and you set limits on what it may spend and touch.
