Add External Session API

What does this MR do and why?

Implements the external agent session API.

When a developer runs Claude Code or OpenCode against a GitLab project, glab calls this API to create and complete session records in GitLab. Each session captures the agent type, identity, and sync method, providing the foundation for per-turn audit event attribution.

Sessions are stored in duo_workflows_workflows using environment: :external, giving sessions immediate access to the existing Sessions UI, the AiAuditEvent audit report, and the ClickHouse write path.

How to set up and validate locally

Prerequisites

  1. GDK running with an Ultimate license
  2. Feature flag enabled: Feature.enable(:ai_agent_session_tracking)
  3. duo_external_agents_enabled => true
  4. An ai_agent_identities record exists (create via the registration API or console)

Create Session

curl -X POST "http://gitlab.localdev:3000/api/v4/projects/1/ai_agent/sessions" \
  -H "PRIVATE-TOKEN: <your-pat>" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_type": "claude-code",
    "agent_identity_id": 1,
    "sync_type": "hook"
  }'
# Expected: 201 with session JSON, status: "running"

Idempotency

curl -X POST "http://gitlab.localdev:3000/api/v4/projects/1/ai_agent/sessions" \
  -H "PRIVATE-TOKEN: <your-pat>" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_type": "claude-code",
    "agent_identity_id": 1,
    "sync_type": "hook",
    "idempotency_key": "test-uuid-123"
  }'
# Expected: 201 with same id on repeat calls

Complete Session

curl -X PATCH "http://gitlab.localdev:3000/api/v4/projects/1/ai_agent/sessions/<session_id>" \
  -H "PRIVATE-TOKEN: <your-pat>" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "completed",
    "jsonl_sha256": "a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3"
  }'
# Expected: 200 with status: "finished"

List Sessions

curl "http://gitlab.localdev:3000/api/v4/projects/1/ai_agent/sessions" \
  -H "PRIVATE-TOKEN: <your-pat>"
# Expected: 200 with array of sessions

Database

scope :external query plan

Query:

SELECT "duo_workflows_workflows".*
FROM "duo_workflows_workflows"
WHERE "duo_workflows_workflows"."environment" = 6
AND "duo_workflows_workflows"."project_id" = $1
ORDER BY "duo_workflows_workflows"."id" DESC
LIMIT 20

Execution plan:

Limit (cost=2.18..2.18 rows=1 width=520) (actual time=0.041..0.042 rows=5 loops=1)
-> Sort (cost=2.18..2.18 rows=1 width=520) (actual time=0.040..0.041 rows=5 loops=1)
Sort Key: id DESC
Sort Method: quicksort Memory: 26kB
-> Index Scan using index_duo_workflows_workflows_on_project_id on duo_workflows_workflows (cost=0.14..2.17 rows=1 width=520) (actual time=0.018..0.027 rows=5 loops=1)
Index Cond: (project_id = 1)
Filter: (environment = 6)
Planning Time: 4.204 ms
Execution Time: 0.060 ms

Uses existing index_duo_workflows_workflows_on_project_id. No new index required.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Related to #603150

Edited by Jean van der Walt

Merge request reports

Loading
Loading