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.
What Is a link?
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:
- An
ArangoRoutethat gives your connector a user-friendly URL - An
ArangoPlatformLinkthat 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.
Step 2: Implement the link
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
timeoutfield 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.