Route agents running in CI pipelines through Ultra so their tool calls are traced and governed
Agents that run in CI, such as PR review bots and pipeline automation, make MCP tool calls with no person at the keyboard. Running them through Ultra gives those calls the same traces, audit events, and governance rules as a developer laptop.This page uses GitHub Actions as the worked example. The same pattern applies to any CI system that can run a shell step and hold a secret.
In Ultra Hub, create a workspace that holds only CI devices, separate from the ones your laptops and cloud dev environments link to. Policy and traces are scoped per workspace, so a CI-only workspace lets you govern pipeline agents without touching anyone’s laptop. See Workspaces.
2
Provision an identity for the pipeline
Decide the address this pipeline’s activity will be attributed to, and make sure it already exists as a member of the organization the deploy key belongs to. A device whose address does not resolve to a member stays ownerless, and its events are attributed to the machine rather than to that address. See Identities.
3
Create a deploy key
Go to Settings > Security and create a key in the Deploy Keys section, scoped to that workspace. Interactive ultra login is not available in CI, so the deploy key is how the runner authenticates. Copy the dk_... value immediately, it is shown only once. See Deploy keys.
4
Store the credentials as CI secrets
Add the deploy key as ULTRA_DEPLOY_KEY. Add any connector credentials the agent needs as their own secrets. Note that GitHub rejects secret names beginning with GITHUB_.
Pipe the install script to bash, not sh. The script checks for bash and exits if it does not find it, and /bin/sh is not bash on Debian and Ubuntu, which covers most Linux CI runners and container images.
To authenticate install.sh before it runs, replace the direct curl pipeline with the verified install flow.
Remote connectors authenticate with their own credentials, not with your CI system’s built-in token. The remote GitHub MCP endpoint above needs a personal access token or a GitHub App token; a GitHub Actions GITHUB_TOKEN is not accepted. Give the pipeline a dedicated token with the narrowest scopes the agent needs, and fail the job early when it is missing rather than letting the agent fail partway through a run.
ultra add takes the command or URL as an argument. Pass environment variables with --env and headers with --header. An enabled remote connector opens a browser for sign-in unless you pass an Authorization header. In CI, pass the token with --header, or add the connector and then run ultra config set-token.The runner is a fresh machine on every run, so it installs Ultra, links a device, and registers its connectors each time. Each run appears in Hub as a device in the workspace the deploy key is scoped to.
A CI agent usually reads an MCP config from a path you control rather than from a user config directory. Commit a config that names Ultra as the only MCP server:
Then point your agent at that file. For Claude Code, pass it with --mcp-config, and add --strict-mcp-config so no other MCP configuration on the runner is picked up.For agents that read a config path Ultra already knows, ultra install --agent <name> --yes writes the entry for you. See Configure agents for the supported list and their paths.
Ultra governs MCP tool calls. An agent that reaches the same service another way, for example by calling gh or curl from a shell tool, or by reading the checkout with a built-in file tool, produces no MCP traffic, and Ultra has nothing to trace or govern.This matters more in CI than on a laptop, because CI agents are often written to shell out. If you want the traces and the governance rules to reflect what the agent actually did, the agent needs the connector and no alternate route.
Check what your agent’s flags actually do before relying on them. In Claude Code, --allowed-tools only pre-approves matching tools so they run without a prompt. It does not restrict what is available. Restricting the available tools is a different flag, and removing a tool from the agent’s context entirely is --disallowed-tools. An agent configured with only an allow-list still has its built-in shell and file tools.
Two different controls are at work here, and it is worth keeping them apart:
Which tools the agent has at all is your agent’s own configuration, not Ultra. Nothing in Ultra prevents someone adding a shell tool back later.
Which of Ultra’s tools the agent may actually call belongs in a governance rule scoped to the CI workspace. That is the half that survives a change to the workflow file, and it is the half Ultra enforces.
Relying on the agent’s flags alone gives you a configuration promise. Pairing them with a governance rule gives you an enforced one.
Set ULTRA_CI_IDENTITY to an address you reserve for that pipeline, and use a distinct address per pipeline rather than a shared one, so a noisy or misbehaving agent can be told apart from the rest of your automation.
The address only attributes activity when it resolves to an existing member of the deploy key’s organization. If it does not resolve, the device is left without an owner, its identity status is reported as unresolved, and its events are attributed to the machine rather than to the address you set. Provision the member or service identity before the first run, and check the device in Hub afterwards rather than assuming attribution worked.
CI is usually the first surface where enforcement is worth turning on. The agents are non-interactive, their tool use is narrow and repeatable, and nobody is blocked waiting on an approval.
1
Run in monitor first
Leave the workspace on its inherited posture and let real pipeline runs build the tool catalog. You need to see what the agent actually calls before you can allowlist it.
2
Write the allow rules
On the Governance page, scope rules to the CI workspace and allow the connectors and tools the pipeline needs. This is also where you narrow a broad connector down to the operations the agent should be making, rather than leaving every tool it exposes reachable.
3
Set the workspace posture to default_deny
With the allow rules in place, anything the pipeline was not approved for is denied at the device before it reaches the upstream connector, and the denial is recorded in the audit log.
A denied call surfaces to the agent as a failed tool call. Whether the job then fails, retries, or carries on depends on how your agent handles tool errors, so run at least one deliberate deny against a non-blocking pipeline before you rely on this in a required check.
# Ultra installed on the runnerultra version# Device linked, identity resolved, connectors reachableultra doctor -v# Connectors registered for this runultra connectors
In Hub, the run appears as a device in the CI workspace, and traces appear within about 60 seconds of the agent’s first MCP tool call.A green check is not the signal to trust here. The trace list is. A job can pass having done nothing, so confirm the workspace filled before concluding the pipeline is wired correctly.
Build that check into the pipeline rather than leaving it to habit. Have the agent emit something the job can look for, such as a marker line in the comment it posts, and add a final step that fails when the marker is absent. Otherwise a denied tool call leaves the agent exiting cleanly and the job going green with no work done.
The marker has to be unique to the run. A fixed string is satisfied by anything that happens to contain it, including an earlier comment, a template, or someone describing how the check works, so the job passes without the agent having done anything. Build the run identifier into the marker and match it exactly.
# ...then, after the agent stepgh api "repos/${REPO}/issues/${PR_NUMBER}/comments" --paginate --jq '.[].body' \ | grep -qF "$REVIEW_MARKER" || { echo "::error::The agent did not post. Check the workspace traces." exit 1 }
A verification step that can be satisfied by something other than the work is decorative. It is worth testing it against a run you know did nothing, so you find that out deliberately rather than the first time it matters.
The install step exits with 'This script requires bash'
The script was piped to sh. On Debian and Ubuntu, including the standard Linux CI runners, /bin/sh is dash rather than bash. Pipe to bash instead. Every later step then fails with ultra: command not found, which is a symptom of this rather than a separate problem.
The job passes but the agent never reviewed anything
Some agent actions refuse to run when the workflow file on the branch differs from the copy on your default branch, a guard against a pull request modifying the workflow that reviews it. The agent step is skipped and the job still succeeds, so the check goes green while nothing was reviewed. Expect this on the pull request that first adds the workflow, and treat it as resolved once the file is merged to the default branch. It is also a reason not to make a run like this a required check without also checking what it produced.
No device appears in Hub
Confirm ULTRA_DEPLOY_KEY is set on the step that runs the install script, and that the key has not been revoked. ultra doctor -v reports link status and identity resolution.
The device appears but its activity is not attributed to your CI identity
The address did not resolve to a member of the deploy key’s organization, so the device has no owner and its events are machine-attributed. Provision that identity in the organization, then re-run. ultra doctor -v reports identity resolution on the runner.
The device links to the wrong workspace
A deploy key carries its own workspace, so the device links wherever the key points. Check which workspace the key was created under rather than looking for a workspace setting on the runner.
The job runs but Hub shows no traces
The agent is most likely not calling MCP at all. Confirm it is reading the config that names Ultra, and that it still has a shell or file tool it can reach the same service with. An allow-list alone does not remove those.
A remote connector returns an authentication error
Confirm the token was set for the connector name you registered, since ultra config set-token takes that name as its first argument. Check that the token itself carries the scopes the connector needs, and that it is a credential the upstream accepts. A CI system’s built-in token is often not one.
Secrets are missing on automated PRs
Some CI systems run bot-authored builds against a separate secret store. On GitHub Actions, Dependabot runs do not see Actions secrets, so guard the job rather than letting it fail at the install step.
⌘I
{"docsConfig":{"theme":"linden","$schema":"https://mintlify.com/docs.json","name":"Ultra","colors":{"primary":"#CE07A6","light":"#CE07A6","dark":"#CE07A6"},"logo":{"light":"https://mintcdn.com/ultra-52ae57d1/j2CRReV02m2L6JVA/logo/ultra-logo-black.svg?fit=max\u0026auto=format\u0026n=j2CRReV02m2L6JVA\u0026q=85\u0026s=d0bfb10ef0b4b157e8a2c1df8a018d2b","dark":"https://mintcdn.com/ultra-52ae57d1/j2CRReV02m2L6JVA/logo/ultra-logo-white.svg?fit=max\u0026auto=format\u0026n=j2CRReV02m2L6JVA\u0026q=85\u0026s=9b7c76009b13962631e3d411688897a0"},"favicon":{"light":"https://mintcdn.com/ultra-52ae57d1/j2CRReV02m2L6JVA/favicon.svg?fit=max\u0026auto=format\u0026n=j2CRReV02m2L6JVA\u0026q=85\u0026s=8c90507d793e9038865391c3b417d1ba","dark":"https://mintcdn.com/ultra-52ae57d1/j2CRReV02m2L6JVA/favicon-dark.svg?fit=max\u0026auto=format\u0026n=j2CRReV02m2L6JVA\u0026q=85\u0026s=2c1770c6b903e7f95a92b99e4fe57e97"},"background":{"color":{"light":"#FFFFFF","dark":"#181818"}},"fonts":{"heading":{"family":"Hanken Grotesk","weight":400},"body":{"family":"Hanken Grotesk","weight":400}},"styling":{"codeblocks":{"theme":{"light":"github-light","dark":"github-dark"}},"latex":false},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"ultra-52ae57d1","hasAudioNarration":false,"actualSubdomain":"ultra-52ae57d1","customDomains":["docs.ultra.security"],"gitSource":{"type":"github","owner":"ultra-security","repo":"docs","deployBranch":"main","contentDirectory":"","isPrivate":true,"mountPath":"","label":""},"chroma":{"liveDeploymentHistoryId":"6adfa28490b8f769491b0a5a","collectionId":"d882be10-be4f-498e-87ec-dfbe1485d0ac","schemaVersion":"v3","latestBuiltDeploymentHistoryId":"6adfa28490b8f769491b0a5a"},"entitlements":{},"buildId":"6adfa28490b8f769491b0a5a","clientVersion":"0.0.4044","searchFilterCounts":[{"language":"default","tag":"Documentation","count":47},{"language":"default","tag":"CLI Reference","count":34},{"language":"default","tag":"Ultra Hub","count":19},{"language":"default","tag":"Getting Started","count":14},{"language":"default","tag":"Observability","count":9},{"language":"default","tag":"Concepts","count":5}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"byok-sep-25","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-updated-9-15","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":true},"customCss":[{"filePath":"style.css","content":".tab-container ol{padding-left:2rem}html:root{--gray-50:250 250 250;--gray-100:245 245 245;--gray-200:228 228 228;--gray-300:211 211 211;--gray-400:163 163 163;--gray-500:122 122 122;--gray-600:86 86 86;--gray-700:68 68 68;--gray-800:50 50 50;--gray-900:30 30 30;--gray-950:20 20 20;--ultra-rail:#323232;--ultra-panel:#141414}@font-face{font-family:\"Hanken Grotesk\";font-style:normal;font-weight:100 900;font-display:swap;src:url(/fonts/hanken-grotesk-latin.woff2) format(\"woff2\");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}#page-title,.mdx-content h1,.mdx-content h2,.mdx-content h3,.mdx-content h4{font-weight:400}#page-title{letter-spacing:-.02em}.dark #page-title,.dark .mdx-content h1,.dark .mdx-content h2,.dark .mdx-content h3{color:#fff}.code-block,.code-block *,.code-group,.code-group *,.font-mono,code,kbd,pre{font-family:\"DM Mono\",ui-monospace,SFMono-Regular,Menlo,monospace!important}code:not(pre *){background-color:#f0f0f0!important;padding:2px 4px!important}.dark code:not(pre *){background-color:rgb(var(--gray-800))!important}[data-component-part=pagination-title]{text-transform:none}#navigation-items .sidebar-group-header,#navigation-items .sidebar-group-header .sidebar-title{font-weight:500}#navigation-items .sidebar-group a{border-radius:8px}#navigation-items .sidebar-group a:hover{background-color:#f1f1f4;color:#16161c}#navigation-items .sidebar-group a[aria-current=page]{color:#ce07a6;background-color:rgba(206,7,166,.07);font-weight:600;text-shadow:none}#navigation-items .sidebar-group a[aria-current=page]>.absolute,#table-of-contents .toc-item[data-active]>.absolute{display:none}.dark #navigation-items .sidebar-group a:hover{background-color:#222226;color:#fff}.dark #navigation-items .sidebar-group a[aria-current=page]{color:#ce07a6;background-color:rgba(206,7,166,.12)}#table-of-contents .toc-item>a{border-radius:8px;margin-left:calc(var(--toc-padding-left, 0rem) - var(--toc-focus-padding, 0rem));padding:.375rem .75rem;padding-left:calc(1rem + var(--toc-focus-padding, 0rem))}#table-of-contents .toc-item[data-depth=\"0\"]>a{margin-left:calc(var(--toc-padding-left, 0rem) - 1rem - var(--toc-focus-padding, 0rem))}#table-of-contents .toc-item+.toc-item>a,#table-of-contents .toc-item>ul>.toc-item:first-child>a{margin-top:1px}#table-of-contents .toc-item>a:hover{background-color:#f1f1f4;color:#16161c}#table-of-contents .toc-item[data-active]:not([data-active-deepest])>a{color:rgb(var(--gray-600));text-shadow:none}#table-of-contents .toc-item[data-depth=\"2\"][data-active]:not([data-active-deepest])>a{color:rgb(var(--gray-500))}#table-of-contents .toc-item[data-active-deepest]>a,#table-of-contents a[aria-current=location]{color:#ce07a6;background-color:rgba(206,7,166,.07);text-shadow:none}.dark #table-of-contents .toc-item[data-active]:not([data-active-deepest])>a{color:rgb(var(--gray-400))}.dark #table-of-contents .toc-item>a:hover{background-color:#222226;color:#fff}.dark #table-of-contents .toc-item[data-active-deepest]>a,.dark #table-of-contents a[aria-current=location]{color:#ce07a6;background-color:rgba(206,7,166,.12)}.mdx-content a.link{border-bottom-color:#7a7a7a!important}.mdx-content a.link:hover{border-bottom-color:currentColor!important}.mdx-content a.link code{-webkit-text-decoration-color:#7a7a7a!important;text-decoration-color:#7a7a7a!important}.mdx-content a.link:hover code{-webkit-text-decoration-color:currentColor!important;text-decoration-color:currentColor!important}.eyebrow{color:rgb(var(--gray-500));font-size:.75rem;letter-spacing:.2em;text-transform:uppercase}.dark .eyebrow{color:rgb(var(--gray-300))}#search-bar-entry,#topbar-cta-button,#topbar-cta-button *,.accordion,.accordion *,.accordion-group,.callout,.card,.card *,.code-block,.code-block *,.code-group,.code-group *,.frame,.frame *,.mdx-content code,.mdx-content kbd,.steps *{border-radius:0!important}.dark .accordion-group,.dark .callout,.dark .card,.dark .code-block,.dark .code-group{border-color:var(--ultra-rail)!important;box-shadow:none!important}.dark .callout,.dark .card:hover,.dark .code-block,.dark .code-group{background-color:var(--ultra-panel)!important}.callout{background-color:rgb(var(--gray-50))!important;border-color:rgb(var(--gray-200))!important}.callout [data-component-part=callout-content]{color:rgb(var(--gray-700))!important}.dark .callout [data-component-part=callout-content]{color:rgb(var(--gray-300))!important}"}],"page":{"pagePath":"installation/ci-setup","slug":"/installation/ci-setup","pageMetadata":{"title":"CI and Automation","description":"Route agents running in CI pipelines through Ultra so their tool calls are traced and governed","href":"/installation/ci-setup"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"Route agents running in CI pipelines through Ultra so their tool calls are traced and governed\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,li:`li`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,ul:`ul`,..._provideComponents(),...props.components},{Accordion,AccordionGroup,CodeBlock,Heading,Note,Step,Steps,Warning}=_components;if(!Accordion)_missingMdxReference(`Accordion`,true);if(!AccordionGroup)_missingMdxReference(`AccordionGroup`,true);if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Note)_missingMdxReference(`Note`,true);if(!Step)_missingMdxReference(`Step`,true);if(!Steps)_missingMdxReference(`Steps`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsx(_components.p,{children:`Agents that run in CI, such as PR review bots and pipeline automation, make MCP tool calls with no person at the keyboard. Running them through Ultra gives those calls the same traces, audit events, and governance rules as a developer laptop.`}),`\n`,_jsx(_components.p,{children:`This page uses GitHub Actions as the worked example. The same pattern applies to any CI system that can run a shell step and hold a secret.`}),`\n`,_jsx(Heading,{level:`2`,id:`before-you-start`,children:`Before you start`}),`\n`,_jsxs(Steps,{children:[_jsx(Step,{title:`Create a workspace for CI`,children:_jsxs(_components.p,{children:[`In Ultra Hub, create a workspace that holds only CI devices, separate from the ones your laptops and cloud dev environments link to. Policy and traces are scoped per workspace, so a CI-only workspace lets you govern pipeline agents without touching anyone’s laptop. See `,_jsx(_components.a,{href:`/hub/workspaces`,children:`Workspaces`}),`.`]})}),_jsx(Step,{title:`Provision an identity for the pipeline`,children:_jsxs(_components.p,{children:[`Decide the address this pipeline’s activity will be attributed to, and make sure it already exists as a member of the organization the deploy key belongs to. A device whose address does not resolve to a member stays ownerless, and its events are attributed to the machine rather than to that address. See `,_jsx(_components.a,{href:`/hub/identities`,children:`Identities`}),`.`]})}),_jsx(Step,{title:`Create a deploy key`,children:_jsxs(_components.p,{children:[`Go to `,_jsx(_components.strong,{children:`Settings > Security`}),` and create a key in the `,_jsx(_components.strong,{children:`Deploy Keys`}),` section, scoped to that workspace. Interactive `,_jsx(_components.code,{children:`ultra login`}),` is not available in CI, so the deploy key is how the runner authenticates. Copy the `,_jsx(_components.code,{children:`dk_...`}),` value immediately, it is shown only once. See `,_jsx(_components.a,{href:`/hub/deploy-keys`,children:`Deploy keys`}),`.`]})}),_jsx(Step,{title:`Store the credentials as CI secrets`,children:_jsxs(_components.p,{children:[`Add the deploy key as `,_jsx(_components.code,{children:`ULTRA_DEPLOY_KEY`}),`. Add any connector credentials the agent needs as their own secrets. Note that GitHub rejects secret names beginning with `,_jsx(_components.code,{children:`GITHUB_`}),`.`]})})]}),`\n`,_jsx(Heading,{level:`2`,id:`quick-start`,children:`Quick start`}),`\n`,_jsx(_components.p,{children:`Add the install and connector steps to your workflow before the step that runs your agent:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`21`,language:`yaml`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light github-dark`,style:{backgroundColor:`#fff`,\"--shiki-dark-bg\":`#24292e`,color:`#24292e`,\"--shiki-dark\":`#e1e4e8`},language:`yaml`,children:_jsxs(_components.code,{language:`yaml`,numberOfLines:`21`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`- `}),_jsx(_components.span,{style:{color:`#22863A`,\"--shiki-dark\":`#85E89D`},children:`name`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`: `}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:`Install Ultra and link to the ci workspace`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#22863A`,\"--shiki-dark\":`#85E89D`},children:` env`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`:`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#22863A`,\"--shiki-dark\":`#85E89D`},children:` ULTRA_DEPLOY_KEY`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`: `}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:\"${{ secrets.ULTRA_DEPLOY_KEY }}\"})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#22863A`,\"--shiki-dark\":`#85E89D`},children:` ULTRA_DEVICE_EMAIL`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`: `}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:\"${{ vars.ULTRA_CI_IDENTITY }}\"})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#22863A`,\"--shiki-dark\":`#85E89D`},children:` run`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`: `}),_jsx(_components.span,{style:{color:`#D73A49`,\"--shiki-dark\":`#F97583`},children:`|`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` curl -sSL https://get.ultra.security/install.sh | bash`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` ultra version`})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`- `}),_jsx(_components.span,{style:{color:`#22863A`,\"--shiki-dark\":`#85E89D`},children:`name`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`: `}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:`Register connectors`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#22863A`,\"--shiki-dark\":`#85E89D`},children:` env`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`:`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#22863A`,\"--shiki-dark\":`#85E89D`},children:` GH_MCP_TOKEN`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`: `}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:\"${{ secrets.GH_MCP_TOKEN }}\"})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#22863A`,\"--shiki-dark\":`#85E89D`},children:` run`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`: `}),_jsx(_components.span,{style:{color:`#D73A49`,\"--shiki-dark\":`#F97583`},children:`|`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` ultra connectors add filesystem \\\\`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` --command npx \\\\`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:' --args \"-y @modelcontextprotocol/server-filesystem ${{ github.workspace }}\"'})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` ultra connectors add github --url https://api.githubcopilot.com/mcp/`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` ultra config set-token github \"$GH_MCP_TOKEN\"`})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`- `}),_jsx(_components.span,{style:{color:`#22863A`,\"--shiki-dark\":`#85E89D`},children:`name`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`: `}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:`Confirm the device linked`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#22863A`,\"--shiki-dark\":`#85E89D`},children:` run`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`: `}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:`ultra doctor -v`})]}),`\n`]})})}),`\n`,_jsx(Warning,{children:_jsxs(_components.p,{children:[`Pipe the install script to `,_jsx(_components.code,{children:`bash`}),`, not `,_jsx(_components.code,{children:`sh`}),`. The script checks for bash and exits if it does not find it, and `,_jsx(_components.code,{children:`/bin/sh`}),` is not bash on Debian and Ubuntu, which covers most Linux CI runners and container images.`]})}),`\n`,_jsxs(_components.p,{children:[`To authenticate `,_jsx(_components.code,{children:`install.sh`}),` before it runs, replace the direct `,_jsx(_components.code,{children:`curl`}),` pipeline with the `,_jsx(_components.a,{href:`/installation/install-ultra#verify-the-install-script-signature`,children:`verified install flow`}),`.`]}),`\n`,_jsx(Note,{children:_jsxs(_components.p,{children:[`Remote connectors authenticate with their own credentials, not with your CI system’s built-in token. The remote GitHub MCP endpoint above needs a personal access token or a GitHub App token; a GitHub Actions `,_jsx(_components.code,{children:`GITHUB_TOKEN`}),` is not accepted. Give the pipeline a dedicated token with the narrowest scopes the agent needs, and fail the job early when it is missing rather than letting the agent fail partway through a run.`]})}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.a,{href:`/cli/add`,children:_jsx(_components.code,{children:`ultra add`})}),` takes the command or URL as an argument. Pass environment variables with `,_jsx(_components.code,{children:`--env`}),` and headers with `,_jsx(_components.code,{children:`--header`}),`. An enabled remote connector opens a browser for sign-in unless you pass an `,_jsx(_components.code,{children:`Authorization`}),` header. In CI, pass the token with `,_jsx(_components.code,{children:`--header`}),`, or add the connector and then run `,_jsx(_components.a,{href:`/cli/config`,children:_jsx(_components.code,{children:`ultra config set-token`})}),`.`]}),`\n`,_jsx(_components.p,{children:`The runner is a fresh machine on every run, so it installs Ultra, links a device, and registers its connectors each time. Each run appears in Hub as a device in the workspace the deploy key is scoped to.`}),`\n`,_jsx(Heading,{level:`2`,id:`pointing-the-agent-at-ultra`,children:`Pointing the agent at Ultra`}),`\n`,_jsx(_components.p,{children:`A CI agent usually reads an MCP config from a path you control rather than from a user config directory. Commit a config that names Ultra as the only MCP server:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`8`,language:`json`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light github-dark`,style:{backgroundColor:`#fff`,\"--shiki-dark-bg\":`#24292e`,color:`#24292e`,\"--shiki-dark\":`#e1e4e8`},language:`json`,children:_jsxs(_components.code,{language:`json`,numberOfLines:`8`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`{`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#005CC5`,\"--shiki-dark\":`#79B8FF`},children:` \"mcpServers\"`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`: {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#005CC5`,\"--shiki-dark\":`#79B8FF`},children:` \"ultra\"`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`: {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#005CC5`,\"--shiki-dark\":`#79B8FF`},children:` \"command\"`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`: `}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:`\"ultra\"`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#005CC5`,\"--shiki-dark\":`#79B8FF`},children:` \"args\"`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`: [`}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:`\"start\"`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`]`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:` }`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:` }`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`}`})}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`Then point your agent at that file. For Claude Code, pass it with `,_jsx(_components.code,{children:`--mcp-config`}),`, and add `,_jsx(_components.code,{children:`--strict-mcp-config`}),` so no other MCP configuration on the runner is picked up.`]}),`\n`,_jsxs(_components.p,{children:[`For agents that read a config path Ultra already knows, `,_jsx(_components.code,{children:`ultra install --agent \u003cname> --yes`}),` writes the entry for you. See `,_jsx(_components.a,{href:`/installation/configure-agents`,children:`Configure agents`}),` for the supported list and their paths.`]}),`\n`,_jsx(Heading,{level:`2`,id:`an-agent-with-other-tools-can-route-around-ultra`,children:`An agent with other tools can route around Ultra`}),`\n`,_jsxs(_components.p,{children:[`Ultra governs MCP tool calls. An agent that reaches the same service another way, for example by calling `,_jsx(_components.code,{children:`gh`}),` or `,_jsx(_components.code,{children:`curl`}),` from a shell tool, or by reading the checkout with a built-in file tool, produces no MCP traffic, and Ultra has nothing to trace or govern.`]}),`\n`,_jsx(_components.p,{children:`This matters more in CI than on a laptop, because CI agents are often written to shell out. If you want the traces and the governance rules to reflect what the agent actually did, the agent needs the connector and no alternate route.`}),`\n`,_jsx(Warning,{children:_jsxs(_components.p,{children:[`Check what your agent’s flags actually do before relying on them. In Claude Code, `,_jsx(_components.code,{children:`--allowed-tools`}),` only pre-approves matching tools so they run without a prompt. It does not restrict what is available. Restricting the available tools is a different flag, and removing a tool from the agent’s context entirely is `,_jsx(_components.code,{children:`--disallowed-tools`}),`. An agent configured with only an allow-list still has its built-in shell and file tools.`]})}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`6`,language:`yaml`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light github-dark`,style:{backgroundColor:`#fff`,\"--shiki-dark-bg\":`#24292e`,color:`#24292e`,\"--shiki-dark\":`#e1e4e8`},language:`yaml`,children:_jsxs(_components.code,{language:`yaml`,numberOfLines:`6`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#22863A`,\"--shiki-dark\":`#85E89D`},children:`claude_args`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`: `}),_jsx(_components.span,{style:{color:`#D73A49`,\"--shiki-dark\":`#F97583`},children:`>-`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` --mcp-config .github/ultra-mcp.json`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` --strict-mcp-config`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` --tools \"\"`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` --disallowed-tools \"Bash\"`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` --allowed-tools \"mcp__ultra\"`})}),`\n`]})})}),`\n`,_jsx(_components.p,{children:`Two different controls are at work here, and it is worth keeping them apart:`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Which tools the agent has at all`}),` is your agent’s own configuration, not Ultra. Nothing in Ultra prevents someone adding a shell tool back later.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Which of Ultra’s tools the agent may actually call`}),` belongs in a `,_jsx(_components.a,{href:`/hub/governance`,children:`governance`}),` rule scoped to the CI workspace. That is the half that survives a change to the workflow file, and it is the half Ultra enforces.`]}),`\n`]}),`\n`,_jsx(_components.p,{children:`Relying on the agent’s flags alone gives you a configuration promise. Pairing them with a governance rule gives you an enforced one.`}),`\n`,_jsx(Heading,{level:`2`,id:`identity`,children:`Identity`}),`\n`,_jsx(_components.p,{children:`There is no person behind a CI run, so set the identity explicitly. Keep the value in a CI variable rather than hardcoding it in the workflow file:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`yaml`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light github-dark`,style:{backgroundColor:`#fff`,\"--shiki-dark-bg\":`#24292e`,color:`#24292e`,\"--shiki-dark\":`#e1e4e8`},language:`yaml`,children:_jsxs(_components.code,{language:`yaml`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#22863A`,\"--shiki-dark\":`#85E89D`},children:`env`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`:`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#22863A`,\"--shiki-dark\":`#85E89D`},children:` ULTRA_DEVICE_EMAIL`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`: `}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:\"${{ vars.ULTRA_CI_IDENTITY }}\"})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`Set `,_jsx(_components.code,{children:`ULTRA_CI_IDENTITY`}),` to an address you reserve for that pipeline, and use a distinct address per pipeline rather than a shared one, so a noisy or misbehaving agent can be told apart from the rest of your automation.`]}),`\n`,_jsx(Note,{children:_jsx(_components.p,{children:`The address only attributes activity when it resolves to an existing member of the deploy key’s organization. If it does not resolve, the device is left without an owner, its identity status is reported as unresolved, and its events are attributed to the machine rather than to the address you set. Provision the member or service identity before the first run, and check the device in Hub afterwards rather than assuming attribution worked.`})}),`\n`,_jsx(Heading,{level:`2`,id:`moving-from-monitor-to-enforcement`,children:`Moving from monitor to enforcement`}),`\n`,_jsx(_components.p,{children:`CI is usually the first surface where enforcement is worth turning on. The agents are non-interactive, their tool use is narrow and repeatable, and nobody is blocked waiting on an approval.`}),`\n`,_jsxs(Steps,{children:[_jsx(Step,{title:`Run in monitor first`,children:_jsx(_components.p,{children:`Leave the workspace on its inherited posture and let real pipeline runs build the tool catalog. You need to see what the agent actually calls before you can allowlist it.`})}),_jsx(Step,{title:`Write the allow rules`,children:_jsxs(_components.p,{children:[`On the `,_jsx(_components.a,{href:`/hub/governance`,children:`Governance`}),` page, scope rules to the CI workspace and allow the connectors and tools the pipeline needs. This is also where you narrow a broad connector down to the operations the agent should be making, rather than leaving every tool it exposes reachable.`]})}),_jsx(Step,{title:`Set the workspace posture to default_deny`,children:_jsxs(_components.p,{children:[`With the allow rules in place, anything the pipeline was not approved for is denied at the device before it reaches the upstream connector, and the denial is recorded in the `,_jsx(_components.a,{href:`/observability/audit-log`,children:`audit log`}),`.`]})})]}),`\n`,_jsx(_components.p,{children:`A denied call surfaces to the agent as a failed tool call. Whether the job then fails, retries, or carries on depends on how your agent handles tool errors, so run at least one deliberate deny against a non-blocking pipeline before you rely on this in a required check.`}),`\n`,_jsx(Heading,{level:`2`,id:`verification`,children:`Verification`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`8`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light github-dark`,style:{backgroundColor:`#fff`,\"--shiki-dark-bg\":`#24292e`,color:`#24292e`,\"--shiki-dark\":`#e1e4e8`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`8`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#6A737D`,\"--shiki-dark\":`#6A737D`},children:`# Ultra installed on the runner`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#6F42C1`,\"--shiki-dark\":`#B392F0`},children:`ultra`}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` version`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#6A737D`,\"--shiki-dark\":`#6A737D`},children:`# Device linked, identity resolved, connectors reachable`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#6F42C1`,\"--shiki-dark\":`#B392F0`},children:`ultra`}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` doctor`}),_jsx(_components.span,{style:{color:`#005CC5`,\"--shiki-dark\":`#79B8FF`},children:` -v`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#6A737D`,\"--shiki-dark\":`#6A737D`},children:`# Connectors registered for this run`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#6F42C1`,\"--shiki-dark\":`#B392F0`},children:`ultra`}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` connectors`})]}),`\n`]})})}),`\n`,_jsx(_components.p,{children:`In Hub, the run appears as a device in the CI workspace, and traces appear within about 60 seconds of the agent’s first MCP tool call.`}),`\n`,_jsx(_components.p,{children:`A green check is not the signal to trust here. The trace list is. A job can pass having done nothing, so confirm the workspace filled before concluding the pipeline is wired correctly.`}),`\n`,_jsx(Heading,{level:`3`,id:`make-the-job-prove-the-agent-worked`,children:`Make the job prove the agent worked`}),`\n`,_jsx(_components.p,{children:`Build that check into the pipeline rather than leaving it to habit. Have the agent emit something the job can look for, such as a marker line in the comment it posts, and add a final step that fails when the marker is absent. Otherwise a denied tool call leaves the agent exiting cleanly and the job going green with no work done.`}),`\n`,_jsx(Warning,{children:_jsx(_components.p,{children:`The marker has to be unique to the run. A fixed string is satisfied by anything that happens to contain it, including an earlier comment, a template, or someone describing how the check works, so the job passes without the agent having done anything. Build the run identifier into the marker and match it exactly.`})}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`yaml`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light github-dark`,style:{backgroundColor:`#fff`,\"--shiki-dark-bg\":`#24292e`,color:`#24292e`,\"--shiki-dark\":`#e1e4e8`},language:`yaml`,children:_jsxs(_components.code,{language:`yaml`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#22863A`,\"--shiki-dark\":`#85E89D`},children:`env`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`:`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#22863A`,\"--shiki-dark\":`#85E89D`},children:` REVIEW_MARKER`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`: `}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:\"\u003c!-- review:${{ github.run_id }} -->\"})]}),`\n`]})})}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`6`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light github-dark`,style:{backgroundColor:`#fff`,\"--shiki-dark-bg\":`#24292e`,color:`#24292e`,\"--shiki-dark\":`#e1e4e8`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`6`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#6A737D`,\"--shiki-dark\":`#6A737D`},children:`# ...then, after the agent step`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#6F42C1`,\"--shiki-dark\":`#B392F0`},children:`gh`}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` api`}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:' \"repos/${'}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`REPO`}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:\"}/issues/${\"}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`PR_NUMBER`}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:`}/comments\"`}),_jsx(_components.span,{style:{color:`#005CC5`,\"--shiki-dark\":`#79B8FF`},children:` --paginate`}),_jsx(_components.span,{style:{color:`#005CC5`,\"--shiki-dark\":`#79B8FF`},children:` --jq`}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` '.[].body'`}),_jsx(_components.span,{style:{color:`#005CC5`,\"--shiki-dark\":`#79B8FF`},children:` \\\\`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#D73A49`,\"--shiki-dark\":`#F97583`},children:` |`}),_jsx(_components.span,{style:{color:`#6F42C1`,\"--shiki-dark\":`#B392F0`},children:` grep`}),_jsx(_components.span,{style:{color:`#005CC5`,\"--shiki-dark\":`#79B8FF`},children:` -qF`}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` \"`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:`$REVIEW_MARKER`}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:`\"`}),_jsx(_components.span,{style:{color:`#D73A49`,\"--shiki-dark\":`#F97583`},children:` ||`}),_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:` {`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#005CC5`,\"--shiki-dark\":`#79B8FF`},children:` echo`}),_jsx(_components.span,{style:{color:`#032F62`,\"--shiki-dark\":`#9ECBFF`},children:` \"::error::The agent did not post. Check the workspace traces.\"`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#005CC5`,\"--shiki-dark\":`#79B8FF`},children:` exit`}),_jsx(_components.span,{style:{color:`#005CC5`,\"--shiki-dark\":`#79B8FF`},children:` 1`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#24292E`,\"--shiki-dark\":`#E1E4E8`},children:` }`})}),`\n`]})})}),`\n`,_jsx(_components.p,{children:`A verification step that can be satisfied by something other than the work is decorative. It is worth testing it against a run you know did nothing, so you find that out deliberately rather than the first time it matters.`}),`\n`,_jsx(Heading,{level:`2`,id:`troubleshooting`,children:`Troubleshooting`}),`\n`,_jsxs(AccordionGroup,{children:[_jsx(Accordion,{title:`The install step exits with 'This script requires bash'`,id:`the-install-step-exits-with-this-script-requires-bash`,children:_jsxs(_components.p,{children:[`The script was piped to `,_jsx(_components.code,{children:`sh`}),`. On Debian and Ubuntu, including the standard Linux CI runners, `,_jsx(_components.code,{children:`/bin/sh`}),` is dash rather than bash. Pipe to `,_jsx(_components.code,{children:`bash`}),` instead. Every later step then fails with `,_jsx(_components.code,{children:`ultra: command not found`}),`, which is a symptom of this rather than a separate problem.`]})}),_jsx(Accordion,{title:`The job passes but the agent never reviewed anything`,id:`the-job-passes-but-the-agent-never-reviewed-anything`,children:_jsx(_components.p,{children:`Some agent actions refuse to run when the workflow file on the branch differs from the copy on your default branch, a guard against a pull request modifying the workflow that reviews it. The agent step is skipped and the job still succeeds, so the check goes green while nothing was reviewed. Expect this on the pull request that first adds the workflow, and treat it as resolved once the file is merged to the default branch. It is also a reason not to make a run like this a required check without also checking what it produced.`})}),_jsx(Accordion,{title:`No device appears in Hub`,id:`no-device-appears-in-hub`,children:_jsxs(_components.p,{children:[`Confirm `,_jsx(_components.code,{children:`ULTRA_DEPLOY_KEY`}),` is set on the step that runs the install script, and that the key has not been revoked. `,_jsx(_components.code,{children:`ultra doctor -v`}),` reports link status and identity resolution.`]})}),_jsx(Accordion,{title:`The device appears but its activity is not attributed to your CI identity`,id:`the-device-appears-but-its-activity-is-not-attributed-to-your-ci-identity`,children:_jsxs(_components.p,{children:[`The address did not resolve to a member of the deploy key’s organization, so the device has no owner and its events are machine-attributed. Provision that identity in the organization, then re-run. `,_jsx(_components.code,{children:`ultra doctor -v`}),` reports identity resolution on the runner.`]})}),_jsx(Accordion,{title:`The device links to the wrong workspace`,id:`the-device-links-to-the-wrong-workspace`,children:_jsx(_components.p,{children:`A deploy key carries its own workspace, so the device links wherever the key points. Check which workspace the key was created under rather than looking for a workspace setting on the runner.`})}),_jsx(Accordion,{title:`The job runs but Hub shows no traces`,id:`the-job-runs-but-hub-shows-no-traces`,children:_jsx(_components.p,{children:`The agent is most likely not calling MCP at all. Confirm it is reading the config that names Ultra, and that it still has a shell or file tool it can reach the same service with. An allow-list alone does not remove those.`})}),_jsx(Accordion,{title:`A remote connector returns an authentication error`,id:`a-remote-connector-returns-an-authentication-error`,children:_jsxs(_components.p,{children:[`Confirm the token was set for the connector name you registered, since `,_jsx(_components.code,{children:`ultra config set-token`}),` takes that name as its first argument. Check that the token itself carries the scopes the connector needs, and that it is a credential the upstream accepts. A CI system’s built-in token is often not one.`]})}),_jsx(Accordion,{title:`Secrets are missing on automated PRs`,id:`secrets-are-missing-on-automated-prs`,children:_jsx(_components.p,{children:`Some CI systems run bot-authored builds against a separate secret store. On GitHub Actions, Dependabot runs do not see Actions secrets, so guard the job rather than letting it fail at the install step.`})})]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"codeStyling":{"theme":{"light":"github-light","dark":"github-dark"}},"config":{},"pageMetadata":{"title":"CI and Automation","description":"Route agents running in CI pipelines through Ultra so their tool calls are traced and governed","href":"/installation/ci-setup"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Before you start","slug":"before-you-start","depth":2,"children":[]},{"title":"Quick start","slug":"quick-start","depth":2,"children":[]},{"title":"Pointing the agent at Ultra","slug":"pointing-the-agent-at-ultra","depth":2,"children":[]},{"title":"An agent with other tools can route around Ultra","slug":"an-agent-with-other-tools-can-route-around-ultra","depth":2,"children":[]},{"title":"Identity","slug":"identity","depth":2,"children":[]},{"title":"Moving from monitor to enforcement","slug":"moving-from-monitor-to-enforcement","depth":2,"children":[]},{"title":"Verification","slug":"verification","depth":2,"children":[{"title":"Make the job prove the agent worked","slug":"make-the-job-prove-the-agent-worked","depth":3,"children":[]}]},{"title":"Troubleshooting","slug":"troubleshooting","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"ultra-52ae57d1","siteName":"Ultra","title":"CI and Automation - Ultra","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-10-26T18:58:08.599Z","originalFileLocation":"installation/ci-setup.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"Route agents running in CI pipelines through Ultra so their tool calls are traced and governed\">\u003cmeta data-mint-head name=\"application-name\" content=\"Ultra\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/ultra-52ae57d1/-IM2rziYB89ke4HP/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/ultra-52ae57d1/-IM2rziYB89ke4HP/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/ultra-52ae57d1/-IM2rziYB89ke4HP/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/ultra-52ae57d1/-IM2rziYB89ke4HP/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/ultra-52ae57d1/-IM2rziYB89ke4HP/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/ultra-52ae57d1/-IM2rziYB89ke4HP/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/ultra-52ae57d1/-IM2rziYB89ke4HP/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/ultra-52ae57d1/-IM2rziYB89ke4HP/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/ultra-52ae57d1/-IM2rziYB89ke4HP/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"CI and Automation - Ultra\">\u003cmeta data-mint-head property=\"og:description\" content=\"Route agents running in CI pipelines through Ultra so their tool calls are traced and governed\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Ultra\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.ultra.security/installation/ci-setup\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://ultra-52ae57d1.mintlify.app/_mintlify/api/og/v2?division=Getting+Started\u0026amp;title=CI+and+Automation\u0026amp;description=Route+agents+running+in+CI+pipelines+through+Ultra+so+their+tool+calls+are+traced+and+governed\u0026amp;theme=c93fb4da46096eee06114979\u0026amp;signature=7b44dbd2eb7b33ed278417385cae0fcb2ac63ccf0542e0b00186e7e0e4571047\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"CI and Automation - Ultra\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Route agents running in CI pipelines through Ultra so their tool calls are traced and governed\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://ultra-52ae57d1.mintlify.app/_mintlify/api/og/v2?division=Getting+Started\u0026amp;title=CI+and+Automation\u0026amp;description=Route+agents+running+in+CI+pipelines+through+Ultra+so+their+tool+calls+are+traced+and+governed\u0026amp;theme=c93fb4da46096eee06114979\u0026amp;signature=7b44dbd2eb7b33ed278417385cae0fcb2ac63ccf0542e0b00186e7e0e4571047\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/ultra-52ae57d1/-IM2rziYB89ke4HP/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Ultra\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#CE07A6\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.ultra.security/installation/ci-setup\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/installation/ci-setup.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.ultra.security/#organization\",\"name\":\"Ultra\",\"url\":\"https://docs.ultra.security\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/ultra-52ae57d1/j2CRReV02m2L6JVA/logo/ultra-logo-black.svg?fit=max\u0026auto=format\u0026n=j2CRReV02m2L6JVA\u0026q=85\u0026s=d0bfb10ef0b4b157e8a2c1df8a018d2b\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.ultra.security#website\",\"name\":\"Ultra\",\"url\":\"https://docs.ultra.security\",\"publisher\":{\"@id\":\"https://docs.ultra.security/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.ultra.security/installation/ci-setup#webpage\",\"url\":\"https://docs.ultra.security/installation/ci-setup\",\"name\":\"CI and Automation\",\"description\":\"Route agents running in CI pipelines through Ultra so their tool calls are traced and governed\",\"dateModified\":\"2026-10-26T18:58:08.599Z\",\"isPartOf\":{\"@id\":\"https://docs.ultra.security#website\"},\"breadcrumb\":{\"@id\":\"https://docs.ultra.security/installation/ci-setup#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.ultra.security/installation/ci-setup#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Getting Started\",\"item\":\"https://docs.ultra.security/introduction\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"CI and Automation\",\"item\":\"https://docs.ultra.security/installation/ci-setup\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.ultra.security/installation/ci-setup#article\",\"headline\":\"CI and Automation\",\"name\":\"CI and Automation\",\"description\":\"Route agents running in CI pipelines through Ultra so their tool calls are traced and governed\",\"url\":\"https://docs.ultra.security/installation/ci-setup\",\"mainEntityOfPage\":{\"@id\":\"https://docs.ultra.security/installation/ci-setup#webpage\"},\"dateModified\":\"2026-10-26T18:58:08.599Z\",\"publisher\":{\"@id\":\"https://docs.ultra.security/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.ultra.security#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Ultra\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"Ultra","nav":{"global":null,"tabs":[{"tab":"Documentation","groups":[{"group":"Getting Started","pages":[{"title":"Introduction","href":"/introduction"},{"title":"Admin Setup","href":"/admin-setup"},{"title":"Rollout Guide","href":"/getting-started/rollout-guide"},{"title":"Quick Start for Users","href":"/getting-started/end-user-quickstart"},{"title":"Install Ultra","href":"/installation/install-ultra"},{"title":"MDM Deployment (Jamf)","href":"/installation/mdm-deployment"},{"title":"Configure Agents","href":"/installation/configure-agents"},{"title":"Agent Environments","description":"Run Ultra inside cloud agent dev environments so MCP traffic flows through Ultra automatically","href":"/installation/agent-environments"},{"title":"CI and Automation","href":"/installation/ci-setup"},{"title":"Upstream Connectors","description":"Configure and manage MCP connectors","href":"/configuration/upstream-connectors"},{"title":"Migrate Connectors","href":"/installation/migrate-connectors"},{"title":"MCP App","href":"/getting-started/mcp-app"},{"title":"Ultra MCP Tools","href":"/getting-started/ultra-mcp-tools"},{"title":"Configuration File","href":"/configuration/config-file"}]},{"group":"Ultra Hub","pages":[{"title":"Ultra Hub Overview","href":"/hub/overview"},{"title":"Onboarding","href":"/hub/onboarding"},{"title":"Authentication","href":"/hub/authentication"},{"title":"Single Sign-On (SSO)","href":"/hub/sso"},{"title":"SCIM","href":"/hub/scim"},{"title":"Deploy Keys","href":"/hub/deploy-keys"},{"title":"Devices","href":"/hub/devices"},{"title":"Identities","href":"/hub/identities"},{"title":"Teams","href":"/hub/teams"},{"title":"Workspaces","href":"/hub/workspaces"},{"title":"Connectors","href":"/hub/connectors"},{"title":"Connector Tools","href":"/hub/tools"},{"title":"Agents","href":"/hub/agents"},{"title":"Guardrails","href":"/hub/guardrails"},{"title":"Governance","href":"/hub/governance"},{"title":"RBAC","href":"/hub/rbac"},{"title":"Admin Log","href":"/hub/admin-log"},{"title":"Ultra Ledger","href":"/hub/ledger"},{"title":"Hub Settings","href":"/configuration/hub-settings"}]},{"group":"Observability","pages":[{"title":"Observability Overview","href":"/observability/overview"},{"title":"Traces","href":"/observability/traces"},{"title":"Audit Log","href":"/observability/audit-log"},{"title":"Dashboard","href":"/observability/dashboard"},{"title":"Alerts","href":"/observability/alerts"},{"title":"Slack Alerts","href":"/observability/slack-alerts"},{"title":"Anomaly Detection","href":"/observability/anomaly-detection"},{"title":"OTLP Export","href":"/observability/otlp-export"},{"title":"SIEM Export (Panther)","href":"/observability/siem-export"}]},{"group":"Concepts","pages":[{"title":"How It Works","href":"/how-it-works"},{"title":"Aggregator","href":"/concepts/aggregator"},{"title":"MCP Security","href":"/concepts/mcp-security"},{"title":"MCP Best Practices","href":"/concepts/mcp-best-practices"},{"title":"Secure Configuration Guide","href":"/concepts/secure-configuration-guide"}]}]},{"tab":"CLI Reference","pages":[{"title":"CLI Reference","href":"/cli/overview"}]}]},"basePath":"","title":"CI and Automation - Ultra","buildId":"dpl_DeHjVRGfFnhYQwHnfC2pjeXXv8h9","deploymentId":"dpl_DeHjVRGfFnhYQwHnfC2pjeXXv8h9","dynamics":[],"chunks":[]}