Building a link

A link is a program that lets AI tools execute operations on external systems (databases, APIs, services) through a standardized job-based interface.

A link runs as a container inside a Kubernetes pod. The platform injects an integration sidecar into the pod that provides:

  • A job queue (MetaStore) — where submitted jobs wait to be processed
  • A file store (StorageV2) — where the link uploads results
  • An internal gRPC API at INTEGRATION_SERVICE_ADDRESS — how the link communicates with the platform (HTTP gateway also available)

Your link binary registers its info at startup, then enters a simple loop: pick up a job, execute it, upload results, report completion.

How It Works

  AI Tool                    Platform Sidecar                Your Connector
  -------                    ----------------                --------------

  POST /link/<name>/job
    ──────────────────────►  Stores job (Pending)
                                    │
                                    │  ◄──── POST /job/pickup ──────  polls every N seconds
                                    │        (Scheduled)
                                    │
                                    │  ◄──── POST /job/{id}/status ── Running
                                    │
                                    │        ... executes work ...
                                    │
                                    │  ◄──── BatchUploadFiles ─────── result.0000000.jsonl
                                    │                                  result.0000001.jsonl ...
                                    │
                                    │  ◄──── UpdateJobStatus ──────── Completed
                                    │
  GET /link/<name>/job/{id}
    ──────────────────────►  Returns job (Completed)
  StorageV2.List / Receive
    ──────────────────────►  Lists and reads result files

What Is a Job?

A job is a single unit of work — for example, “run this AQL query” or “search this vector index”. Each job has:

  • An input — JSON matching the link’s input schema
  • A status history — tracks Pending → Scheduled → Running → Completed/Failed
  • A result path — where output files are stored in FileStore

See Jobs for the full state machine and details.

Authentication and RBAC

You do not need to implement authentication or RBAC in your connector. The platform handles this:

  • The external API (AI tool facing) goes through the gateway with authentication
  • The internal API (your connector) runs on 127.0.0.1 — only accessible inside the pod, no auth needed
  • The connector uses service credentials to access external systems

Step 1: Create the CRD and Route

Create two Kubernetes resources in your Helm chart’s templates/ directory:

  1. An ArangoRoute that gives your connector a user-friendly URL
  2. An ArangoPlatformLink that registers it with the platform
# templates/route.yaml
apiVersion: networking.arangodb.com/v1beta1
kind: ArangoRoute
metadata:
  name: -route
spec:
  deployment: 
  route:
    path: /link//
  destination:
    path: /_integration/connector/v1/
    service:
      name: 
      port: 9193
---
# templates/connector.yaml
apiVersion: platform.arangodb.com/v1beta1
kind: ArangoPlatformLink
metadata:
  name: 
spec:
  type: Active
  deployment:
    name: 
  route:
    name: -route
  description: "What this link does"
  tags:
    - my-tag
  schema:
    type: object
    properties:
      myParam:
        type: string
    required:
      - myParam
  version: "1.0.0"

The schema field defines what input your connector accepts. The platform validates submitted jobs against this schema — your link does not need to validate the schema itself, but may optionally do so for defense in depth.

Startup: Register Info

Before entering the job loop, your link must call UpdateInfo to register its tool definition with the sidecar. This makes the link healthy and discoverable by AI agents:

client := pbLinkV1.NewLinkV1InternalClient(conn)

info := &pbLinkV1.LinkInfo{
    Description: "Execute AQL queries on ArangoDB",
    Tags:        []string{"database", "aql", "query"},
    InputSchema: `{ "type": "object", "required": ["query"], ... }`,
    OutputSchema: `{ "contentMediaType": "application/jsonl", ... }`,
    Examples: []*pbLinkV1.LinkExample{
        {Name: "Simple query", Input: `{"query": "RETURN 1"}`, Output: "1\n"},
    },
    ResultFiles: []string{"result.0000000.jsonl"},
}

client.UpdateInfo(ctx, info) // retry until sidecar is ready

The request body is LinkInfo directly — no wrapper. Include input_schema and examples so AI agents can construct valid inputs without prior knowledge. See API Reference for all fields.

The link should retry this call until the sidecar is available (it starts concurrently). Once UpdateInfo succeeds, the sidecar marks link.v1 as healthy, the readiness probe passes, and the pod becomes ready.

Job Loop

Your link binary polls via gRPC for jobs and processes them:

for {
    // 1. Pick up a job
    pickup, _ := client.PickUpJob(ctx, &pbSharedV1.Empty{})
    if pickup.GetId() == "" {
        time.Sleep(5 * time.Second)
        continue
    }

    // 2. Get job details
    job, _ := client.GetJob(ctx, &pbLinkV1.GetJobRequest{Id: pickup.GetId()})

    // 3. Mark as running
    client.UpdateJobStatus(ctx, &pbLinkV1.UpdateJobStatusRequest{
        Id:     job.Id,
        Status: &pbLinkV1.JobStatus{State: pbLinkV1.JobState_JOB_STATE_RUNNING},
    })

    // 4. Execute work + upload results via BatchUploadFiles
    stream, _ := client.BatchUploadFiles(ctx)
    // ... write results as JSONL files via io.Writer wrapper ...
    stream.CloseAndRecv()

    // 5. Mark as completed
    client.UpdateJobStatus(ctx, &pbLinkV1.UpdateJobStatusRequest{
        Id:     job.Id,
        Status: &pbLinkV1.JobStatus{State: pbLinkV1.JobState_JOB_STATE_COMPLETED},
    })
}

For streaming uploads, wrap BatchUploadFiles in an io.Writer — each new file starts with a message containing job_id and name, subsequent writes send data chunks. See the sample AQL connector for the full batchFileWriter implementation.

Handling Cancellation

After picking up a job, your connector should periodically check whether the job has been cancelled (by polling GET /job/{id} and checking the state). If cancelled, stop processing and move on to the next job. If your link does not check for cancellation, a cancelled job will simply be ignored when it tries to update status — the update will fail because the state transition from Cancelled is not allowed.

Step 3: Create a Helm Chart

Your Helm chart is the link’s own packaging — it deploys the link binary alongside the CRD and route. It is not a chart for consumers of the link.

The chart should include:

my-connector/
├── Chart.yaml              # apiVersion: v1, name must match directory
├── values.yaml             # accepts arangodb_platform.deployment.name
├── platform.yaml           # connector metadata (name, tags)
├── templates/
│   ├── connector.yaml      # ArangoPlatformLink CRD
│   ├── route.yaml          # ArangoRoute for user-friendly URL
│   └── deployment.yaml     # Deployment with sidecar labels

Deployment with Sidecar Labels

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: 
  labels:
    profiles.arangodb.com/deployment: 
spec:
  template:
    metadata:
      labels:
        profiles.arangodb.com/deployment: 
        integration.profiles.arangodb.com/link: v1
        integration.profiles.arangodb.com/meta: v1
        integration.profiles.arangodb.com/authn: v1
        integration.profiles.arangodb.com/storage: v2
    spec:
      containers:
        - name: connector
          image: ":"
          command: ["/bin/my-connector"]

The profiles.arangodb.com/deployment label triggers sidecar injection. The integration.profiles.arangodb.com labels enable specific integrations:

Label Integration Purpose
link: v1 Link V1 Job queue, external API, health reporting
meta: v1 Meta V1 Key-value store for job persistence and handler heartbeats
authn: v1 Authentication V1 JWT validation (required by Meta V1)
storage: v2 Storage V2 File store for result upload (UploadFile, BatchUploadFiles)

Connector ID Profile

The chart must also create an ArangoProfile to pass the connector ID to the integration sidecar:

# templates/profile.yaml
apiVersion: scheduler.arangodb.com/v1beta1
kind: ArangoProfile
metadata:
  name: -link
spec:
  selectors:
    label:
      matchLabels:
        app: 
        profiles.arangodb.com/deployment: 
  template:
    priority: 127
    container:
      containers:
        integration:
          env:
            - name: INTEGRATION_LINK_V1_CONNECTOR_ID
              value: 

Environment Variables

The platform injects these environment variables into all containers:

Variable Example Description
INTEGRATION_HTTP_ADDRESS_FULL http://127.0.0.1:9203 Internal HTTP API address
INTEGRATION_SERVICE_ADDRESS 127.0.0.1:9201 Internal gRPC address
ARANGODB_ENDPOINT https://cluster.ns.svc:8529 ArangoDB endpoint

Your link binary should read these instead of hardcoding addresses.

Standard Values

# values.yaml
arangodb_platform:
  deployment:
    name: ""   # populated by ArangoPlatformService or helm --set

Step 4: Deploy

There are two ways to deploy:

Option A: Direct Helm Install

helm install my-connector ./my-connector \
  --namespace <namespace> \
  --set arangodb_platform.deployment.name=<deployment-name>

You manage the lifecycle (upgrades, rollbacks) yourself.

Option B: ArangoPlatformService (Managed)

Upload the chart as an ArangoPlatformChart, then create an ArangoPlatformService. The operator manages the deployment lifecycle, including upgrades and health monitoring.

Operational Notes

  • Crash handling: If your connector crashes, the pod restarts automatically (Kubernetes restart policy). Jobs that were in Scheduled or Running state from the crashed handler will remain stuck until the handler heartbeat TTL (1 minute) expires. A future cleanup mechanism can return these jobs to Pending.
  • Multiple instances: You can run multiple replicas. Job pickup is atomic (revision-based) — only one instance claims each job.
  • Local state: Do not store state between jobs locally. Each job should be self-contained. Use the FileStore for persistent output.
  • Resource limits: Standard Kubernetes resource limits apply to your container. Set them in the Deployment spec as needed.
  • Job runs forever?: No. Each job should complete in bounded time. Use the timeout field to enforce a maximum duration. If your job exceeds the timeout, your connector should report it as Failed.

Example

See the sample AQL connector for a complete working example including the Go binary, Helm chart, and integration test.