Skip to content

Connectors: let people link their own accounts for agents to use #698

Description

Problem

People who use assistants like Meta AI or OpenClaw can link their own accounts once (Gmail, Google Docs, Spotify, GitHub) and then any agent working for them can use those accounts: read by default, ask before doing something consequential, and stop the moment the person disconnects.

Orka cannot do this today. Every credential is set up by an operator, belongs to a namespace or a single Task, and never expires or refreshes. There is no way for a person to say "connect my GitHub" and have their agents act as them. Concretely:

  • A Tool reaches an outside service with a fixed Secret (spec.http.authSecretRef) or an OutboundAccessPolicy that swaps a service identity for a token. Nothing is tied to the person who asked for the work.
  • Orka only knows who a person is when they signed in through OIDC or a transaction token. That identity lands on Task.spec.requestedBy. Tokens minted for a ServiceAccount carry no human identity.
  • There is no OAuth "sign in with X and grant access" flow, no refresh-token handling, no settings page in the dashboard, and no per-person storage.

What we want

A connector system where:

  1. An operator registers a service once (a provider: its OAuth settings and the tools it offers).
  2. A signed-in person links their account to that provider (a connection) by clicking through the service's consent screen.
  3. Any Task that person starts, including Tasks created from chat or delegated to child agents, can use those tools as that person.
  4. Read actions just work. Write actions (send an email, open a pull request) pause for the person's approval, using the approval prompt Orka already has.
  5. Disconnecting deletes the stored tokens and makes in-flight calls fail.
  6. Agent processes, worker Pods, and namespace admins never see the person's tokens.

The full design is in docs/adr/0033-user-connectors.md (on the connectors branch until merged). The decisions that matter most are repeated below so this issue stands alone.

Decisions already made

Question Decision Why
Who can link an account? Only people signed in through OIDC or a transaction token. ServiceAccount callers are refused. Those are the only identities Orka verifies. A shared ServiceAccount would let anything using it act as your Gmail.
Where do tokens live? A Secret in a namespace only the controller can read, encrypted with the controller's existing snapshot key. Keeps tokens away from kubectl get secret in the Task namespace and away from worker Pods.
How does chat use them? Chat creates a Task; the Task uses the connector. Chat never calls connector tools directly. One execution path means approvals and audit always apply. Slower reads are accepted.
First provider? GitHub The GitHub tools already exist. Only the consent and token flow is new, so it proves the whole path with the least code. Gmail, Google Docs, Spotify come next.

How it fits together

Two new custom resources:

  • ConnectorProvider (operator-owned catalog entry). Holds the OAuth client settings for one service: authorize URL, token URL, PKCE on, scopes grouped into read and write, revocation URL, and a reference to the client-secret Secret. It also declares the tools the service offers. For GitHub these are the existing built-in GitHub tools; for later providers they are a curated list of HTTP tool definitions.
  • Connection (one person, one provider). Spec: subject (immutable, copied from the signed-in identity, never from the request body), providerRef, mode (readOnly or readWrite), granted scopes. Status: state, expiry, last refresh. No token material anywhere in spec or status.

The flow a person experiences:

  1. They open the dashboard's new Settings > Connectors page (or run orka connect github) and click Connect.
  2. The API creates a Connection and returns the provider's authorize URL. The state value is signed, short-lived, and tied to the person's subject and the Connection UID.
  3. The person approves on the provider's consent screen. The provider redirects to Orka's callback URL (an operator flag; it must match what the provider has on file exactly).
  4. The callback exchanges the code (with PKCE), encrypts the tokens, stores them in the controller-only namespace, and marks the Connection ready.
  5. Later, a Task runs a tool whose OutboundAccessPolicy is in the new connection mode. At call time the resolver finds the Connection for the Task's requestedBy subject, refreshes the token if needed (one refresh at a time per Connection, writing rotated tokens back), and adds the bearer header. Missing, expired, or revoked Connection means the call fails; it never falls back to another credential.
  6. Write tools are in the Agent's approvalRequiredTools by default, so the existing approval prompt appears. A readOnly Connection hides write tools from the agent entirely.
  7. Deleting the Connection removes the Secret and tries to revoke the token at the provider.

Where connector tools run: only in the controller. ACP runtimes already go through the controller-hosted MCP broker. Native type: ai workers currently run tools inside their own Pod, so they need a small internal controller endpoint for connector calls. The in-Pod executor must refuse connector-backed tools.

Where to start (code map)

Read these before writing code; they are the pieces being reused.

  • api/v1alpha1/outboundaccesspolicy_types.go and internal/outboundaccess/resolver.go — the credential resolver that gets the new connection mode. ResolveRequest is where the Task's requester subject needs to be threaded in.
  • internal/worker/tool_executor.go (around applyOutboundAccessPolicy) — where a resolved credential is applied to an outgoing tool call.
  • internal/controller/acp_mcp_broker.go and acp_mcp_approval.go — controller-side tool execution for ACP runtimes and the approval gate (ApprovalPolicy.Requires).
  • internal/api/auth.go, internal/api/transaction_metadata.go (stampTaskRequesterFromUserInfo) — how a signed-in identity becomes Task.spec.requestedBy. Connections use the same subject.
  • internal/store/sqlite/agent_execution_snapshot_store.go — the AES-256 sealing helper to reuse for token Secrets; key comes from --agent-execution-snapshot-key-file / --agent-execution-snapshot-secret in cmd/main.go.
  • internal/tokenexchange/exchange.go — an existing OAuth token-endpoint client with caching and single-flight; reuse its request shape for the code and refresh exchanges.
  • internal/tools/github_helpers.go — GitHub token resolution order today. The connector path becomes the first choice when the Task has a requester with a GitHub Connection.
  • internal/api/server.go (setupRoutes) — where the connection and callback routes go.
  • cmd/cli/login.go — the browser-handoff pattern to copy for orka connect.
  • ui/src/routes/ and ui/src/components/layout/sidebar.tsx — there is no settings page yet; add one.

Suggested PR breakdown

  • PR 1: CRDs. ConnectorProvider and Connection types, CEL immutability on subject, make manifests generate, controller that validates a provider's URLs with the same public-address checks as internal/outboundaccess/validation.go.
  • PR 2: Consent flow. API routes to create a Connection, return the authorize URL, and handle the callback (PKCE, signed state, code exchange). Encrypted Secret written to the controller-only namespace, garbage-collected with the Connection. API lists only the caller's own Connections. Flag for the callback base URL.
  • PR 3: Credential injection. OutboundAccessPolicy.spec.connection, resolver lookup by requester subject, lazy single-flight refresh with write-back, fail-closed on missing or revoked Connections, Connection UID and generation frozen into the execution snapshot.
  • PR 4: Controller-only execution. Internal endpoint for native type: ai workers, in-Pod executor refuses connector tools, readOnly filtering in internal/aitools/resolver.go, write tools default into approvalRequiredTools, ExternalEffect records carry a Connection digest.
  • PR 5: GitHub provider. ConnectorProvider example for GitHub OAuth, GitHub tools resolve the requester's Connection first, docs page under website/docs/guides/.
  • PR 6: User surfaces. Settings > Connectors page in the dashboard, orka connect <provider> and orka connection list|delete in the CLI, list_connections tool for agents and chat.
  • PR 7: E2E. Kind-based test with an OIDC stub and a fake OAuth provider proving link, use, approval on write, refresh, and disconnect.

Rules to keep true

  • Never put tokens in Task specs, status, events, logs, or the SQLite store. Use the redaction helpers for any header logging.
  • subject comes from the authenticated request only. Reject it in request bodies the same way requestedBy is rejected today.
  • Connector tools fail closed. No fallback to Task Secrets, environment variables, or other users' Connections.
  • Worker Pods never receive connector tokens. Only the controller talks to the provider.
  • Existing per-Task GitHub token Secrets keep working. This is additive.

Out of scope for this issue

  • Proactive connectors (a calendar change waking an agent). That needs provider webhooks landing as Gateway events and its own design.
  • A generic hosted-MCP-server provider with tool discovery. Later ADR.
  • Gateway-originated Tasks using Connections. They carry a gateway sender subject, not a person, and stay fail-closed until an explicit link exists.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    apiAPI surfacedesignArchitecture or behavior-contract decisions needed before implementationenhancementNew feature or requestepicUmbrella/tracking issuesecuritySecurity-related

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions