Appearance
Connections
A connection is a registry entry for an external service your workspace talks to — a Jira site, a GitHub app, a Teams webhook. Projects don't hold tokens or service settings themselves — they bind connections, and each connection points at a credential from Workspace → Settings (the shared service-credential pool). That indirection is the point: rotate a token or rename a channel in one place and every project using it picks up the change.
Connections are shared across the whole workspace, and you mostly meet them inside project Settings: a New connection shortcut sits beside every picker there, and the connection it creates lands in the shared registry so your next project finds it already there. The full registry lives under Workspace → Connections — visible to everyone (what you see follows your team), with kind tabs across the top.
There are four connection kinds:
| Kind | What it does | Bound where |
|---|---|---|
| Ticketing | An external ticket source (GitHub Issues, Jira Cloud, or Shortcut). Projects on Tenbi Tickets need none — the built-in tracker is not an external service | The project's ticketing destination |
| Version Control | Push branches and open PRs (GitHub, Bitbucket Cloud, or Azure Repos) | Each repo row in the project's registry |
| Messaging | Discord / Slack / Microsoft Teams pings on PR opened, run failed, or overlap | Project Settings → Operations |
Coding Standards is deliberately not here: your house style is a document, not an external service — it lives beside Skills under Workspace → Skills · Coding Standards, is tenant-wide (one house style for everyone), and attaches per lane under Agent Context.
Every connection shares the same anatomy:
- A Name — yours to choose, shown everywhere the connection is referenced.
- An Enabled toggle — on by default; see disable vs delete below.
- An Adapter, on kinds that talk to a service — chosen at creation and fixed afterwards (when editing, it shows as a read-only value). To switch providers, create a new module.
- A Credential picker, on kinds that need a secret — select an existing credential of the matching type, or create one inline without leaving the form.
Ticketing
Defines an external ticket source. Which tickets actually get picked up is decided by each lane — its discovery labels (label trackers) or its Ready status (status trackers), not the connection. The connection is just "where to look and with what access."
| Adapter | Tracker style | Credential |
|---|---|---|
| GitHub | Label-based | GitHub PAT |
| Jira Cloud | Status-based (via transitions) | Jira Cloud |
| Shortcut | Status-based (direct set) | Shortcut |
GitHub Issues
A label tracker: tickets are one repository's issues, and the run lifecycle is driven by labels.
- Owner and Repo — the single repository this module watches. One module = one repo; watching a second repo means a second module.
- API base URL (optional, GitHub Enterprise) — leave blank for github.com.
- Credential: a GitHub PAT that can read, label, and comment on the repo's issues.
As a run moves through its stages, the Lane's label transitions add and remove labels on the issue, and progress comments are posted back to it. Which labels mark a ticket as AI-eligible — and which get applied or stripped at each stage — is all configured on the Lane, not here.
Jira Cloud
A status tracker: Lanes drive it by your native workflow statuses (Ready → In Progress → Done, etc.), so your Jira board reflects run progress without any Tenbi-specific labels.
- Project key (optional) — scopes discovery to one project (JQL
project = KEY). Blank = every project the credential can see. - Credential: a Jira Cloud credential — the site URL, account email, and API token all live there, not on the module.
Status moves go through Jira transitions. Tenbi resolves the transition whose destination matches the target status by name — which means the target must be reachable from the ticket's current status in your Jira workflow. If it isn't, the move is skipped and logged (the log names the transitions that were available); the run itself continues, but the ticket stays where it was.
The practical rule: the statuses you map on a Lane have to be connected in the workflow. If your workflow only allows Ready → In Progress → In Review → Done, a Lane stage that tries to jump a ticket straight from Ready to Done will silently leave it in Ready — fix the workflow (or the Lane's status mapping), not the module.
Shortcut
A status tracker too: Lanes drive it by workflow states.
- No config fields beyond the basics — the module is credential-only.
- Credential: a Shortcut API token.
- Unlike Jira there are no transition constraints — a state is set directly by name, no reachability to worry about. Comments are posted, and labels can still be applied where a stage maps to one.
- Discovery always skips archived stories. Completed stories are skipped only for label-based discovery — a status condition is taken at face value, since it may legitimately target a done-type state.
Version Control
Supplies the access a Lane uses to clone repos, push branches, and open PRs.
| Adapter | Repo naming | Credential |
|---|---|---|
| GitHub | owner/repo | GitHub PAT |
| Bitbucket Cloud | owner/repo | Bitbucket Cloud |
| Azure Repos | project/repo | Azure DevOps PAT |
All three adapters share one field:
- Default base branch (overridable per Lane) — what PRs target when nothing more specific is set. The full precedence is per-repo → Lane → this module →
main.
GitHub
- Credential: a GitHub PAT with push + PR rights on every repo the Lane will touch.
- API base URL (optional, GitHub Enterprise) — leave blank for github.com.
Bitbucket Cloud
- Credential: a Bitbucket Cloud credential — account email plus an Atlassian API token.
- No extra module fields beyond the base branch.
Azure Repos
- Credential: an Azure DevOps PAT with Code read & write scope.
- Azure DevOps organization — required. Repos are then entered per-Lane as
project/repo(notowner/repo); the organization is set here, once.
One Version Control module is shared across all of a Lane's workspace repos, so the credential must be able to reach every repo on the Lane — the Verify button on each Lane repo row checks exactly that. Run it whenever you swap the credential or add a repo; it's much cheaper than finding out at clone time.
Messaging
Sends a ping to a channel at key moments. Adapters: Discord, Slack, and Microsoft Teams, each backed by a webhook credential — the webhook URL is the secret, so it lives on the credential (see webhooks in Credentials), not the module.
The module carries the channel identity; every field is optional:
- Discord — Bot username (optional) and Avatar URL (optional) override how the webhook's posts appear.
- Slack — Channel override (optional) posts somewhere other than the webhook's default channel, plus Bot username (optional), Icon emoji (optional), and Icon URL (optional, overridden by emoji).
- Microsoft Teams — no module fields. The target channel and sender identity are fixed by the Teams workflow behind the webhook, so the credential is the whole configuration.
Hooks are chosen on the Lane
Which events a module fires on is decided per Lane: when you attach it, you pick its hooks — PR opened, Run failed, or both (a newly attached module starts with both checked). The same module can be attached to many Lanes with different hooks on each, and a Lane can attach several Messaging modules — say, a team channel that hears about everything and an alerts channel that only hears about failures.
What the messages look like
- PR opened — "AI opened PR(s) for ticket title", with the Lane name, the attempt number, links to each PR, and a link back to the run's record in Tenbi.
- Run failed — "AI run failed: ticket title", with the Lane name, the failure reason, the state the run failed in, and the record link.
When telemetry was captured for the run, both messages append a compact cost line — dollar cost, tokens, turns, duration.
Delivery is best-effort by design: a rejected or unreachable webhook is logged and never fails the run. If pings stop arriving, check the webhook credential first — a deleted webhook fails silently from the run's point of view.
Coding Standards
A markdown document of your team's standards. When attached to a Lane, it's written into every repo of the workspace as CODING-STANDARD.md at provision time, and the agents treat it as a normative file — naming, patterns, things to avoid.
- Markdown content — the document itself. This is the one required field; there's no adapter and no credential, because the module is the content.
- A Lane attaches at most one Coding Standards module. The list page shows each module's content size, so you can tell a stub from the real thing at a glance.
Keep it focused: rules that change what the agent writes, not general philosophy. Concrete conventions — naming schemes, error-handling patterns, "never edit generated files" — earn their tokens; a page of engineering values does not.
How projects bind connections
The connection defines the service; the project decides where it's used:
- Ticketing — the project's destination (Settings → Ticketing): one connection plus its per-tracker destination (a Jira project key, a Shortcut workflow). The Composer files into it and every lane polls it — one fact, consumed from both ends.
- Version Control — named per repo row in the project's registry. The repos a lane runs on must share one connection.
- Messaging — any number under Settings → Operations, applying to every lane in the project.
- Skills & Coding Standards — not connections: guidance documents living beside each other under Workspace → Skills; skills attach per lane (runs) and per project (Composer), coding standards per lane. See Customizing.
- Optimize — not a connection: a project toggle that builds curated codebase maps and serves them to the agents. See Optimize.
Creating and editing
Most connections are born from the New connection shortcuts beside the pickers in project Settings. The registry view, Workspace → Connections, holds one list page per kind — Ticketing, Version Control, Messaging — each with a New connection button, a Used by column showing which projects and lanes bind it, and each row linking through to the connection's form.
A few behaviors to know:
- Adapter first — on kinds with a provider choice, the adapter radios sit at the top of the form and decide which fields appear below. The choice is locked in at creation for every kind, Messaging included — a webhook URL is adapter-specific, so switching providers means a new module.
- Inline credential creation — the credential picker only offers credentials of the type the kind + adapter needs, and lets you create a new one without leaving the form. Changing the adapter clears the selection, since a different adapter needs a different credential type.
- Disable instead of delete — the Enabled chip on the list page toggles a module in place (the form has a matching checkbox). A disabled module is hidden from Lane pickers, so nothing new attaches to it — but Lanes already referencing it keep resolving it at run time; they just flag the reference so you know something's off. Disabling is how you retire a module gracefully, and how you stage a replacement without a hard cutover.
- Deleting is protected — deleting asks for confirmation and cannot be undone, and a module still referenced by a Lane can't be deleted at all ("Cannot delete — referenced by one or more Lanes"). Detach it from the Lane first, or disable it and leave it be.