Imported from harness/ti-client (
AGENTS.md). Install upstream withnpx skills add harness/ti-client. Copyright stays with the author.
AGENTS.md - Context for AI Coding Assistants
This document provides essential context about the ti-client codebase to help AI coding assistants (like Cursor, Windsurf, etc.) understand the project structure, patterns, and conventions.
Project Purpose
ti-client is a Go client library for the Harness Test Intelligence (TI) service. It provides a clean, type-safe interface for Go applications to communicate with the TI service backend.
Key Context:
- This is a library/package, not a standalone application
- It's imported by other Harness repositories:
harness-coreandlite-engine - The TI service backend is in a separate repository:
harness-ti - The client enables CI/CD pipelines to use test intelligence features
Core Concepts
Test Intelligence (TI)
Test Intelligence automatically selects which tests to run based on:
- Code changes: Tests that depend on modified source files
- New tests: Tests introduced in the current branch/PR
- Updated tests: Existing tests that were modified
- Previous failures: Tests that failed in previous builds
- Flaky tests: Tests with inconsistent results
Callgraph
A callgraph represents code dependencies - which source files are called by which tests. This is used to determine which tests need to run when source code changes.
- V1 API: Uses Avro-encoded binary format (
UploadCg) - V2 API (Chrysalis): Uses JSON format (
UploadCgV2) - newer, preferred approach
Test Selection Flow
- Client sends changed files and branch info to TI service
- TI service analyzes callgraph and returns list of tests to run
- Client receives
SelectTestsRespwithRunnableTestlist - Tests are executed
- Results are written back via
Write() - Callgraph is uploaded via
UploadCg()orUploadCgV2()
Architecture Patterns
Interface-Based Design
The client uses Go interfaces for abstraction:
client.Clientinterface defines all operationsclient.HTTPClientimplements the interface- This allows for testing and potential alternative implementations
Error Handling
- Custom
client.Errortype with Code and Message - Errors are returned, not panicked
- HTTP errors are wrapped in
client.Errorwith status codes
Retry Logic
All HTTP operations use exponential backoff retry:
- Network errors: Retry with backoff
- 5xx server errors: Retry (configurable per operation)
- 4xx client errors: Not retried
- Different operations have different max retry times
Validation Pattern
Each operation has a corresponding validate*Args() function:
validateWriteArgs()forWrite()validateSelectTestsArgs()forSelectTests()validateUploadCgArgs()forUploadCg()- etc.
Package Structure
ti-client/
├── client/ # Core client implementation
│ ├── client.go # Client interface (all methods)
│ └── http.go # HTTPClient implementation
│
├── types/ # Core type definitions
│ ├── types.go # Main types (TestCase, SelectTestsReq/Resp, etc.)
│ ├── savings.go # Savings tracking types
│ └── cache/ # Cache-related types
│ ├── buildcache/
│ ├── dlc/
│ ├── gradle/
│ └── maven/
│
├── chrysalis/ # V2 API types (newer API)
│ └── types/
│ ├── types.go # UploadCgRequest, SkipTestsRequest
│ ├── chain.go # Chain type (code dependency)
│ ├── test.go # Test type
│ └── identifier.go # Identifier type
│
└── clientUtils/ # Utility functions
└── telemetryUtils/ # Telemetry helpers
Key Files and Their Roles
client/client.go
- Defines the
Clientinterface - All public methods that consumers use
- Custom
Errortype
client/http.go
HTTPClientstruct: ImplementsClientinterfaceNewHTTPClient(): Factory function to create client- HTTP request/response handling
- Retry logic with exponential backoff
- TLS/mTLS configuration
- Validation functions
types/types.go
- Core data structures:
TestCase: Test execution resultRunnableTest: Test to be executedSelectTestsReq/Resp: Test selection request/responseFile: Changed file informationStatus,FileStatus,Selection: Enums/constants
- Environment variable constants
- Telemetry data structures
types/savings.go
- Savings tracking for intelligence features
SavingsFeature: BUILD_CACHE, TI, DLCIntelligenceExecutionState: FULL_RUN, OPTIMIZED, DISABLED
chrysalis/types/types.go
- V2 API request types
UploadCgRequest: JSON callgraph uploadSkipTestsRequest: File checksum-based skip logic
Code Conventions
Naming
- Interfaces: Single word (e.g.,
Client) - Implementations: Descriptive (e.g.,
HTTPClient) - Methods: PascalCase, descriptive verbs (e.g.,
SelectTests,UploadCg) - Constants: SCREAMING_SNAKE_CASE (e.g.,
StatusPassed,FileModified)
Function Signatures
- Always takes
context.Contextas first parameter - Returns
erroras last return value - Request/response types are in
typespackage - V2 types are in
chrysalis/typespackage
HTTP Patterns
- Endpoints are defined as
conststrings with format specifiers - Path construction uses
fmt.Sprintf()with endpoint template - Headers:
X-Harness-Token: AuthenticationX-Request-ID: Request tracking (usually SHA)
- Request body: JSON-encoded
- Response body: JSON-decoded into response struct
Error Patterns
// Custom error with code
return &Error{Code: res.StatusCode, Message: out.Message}
// Validation errors
return fmt.Errorf("stepID is not set")
// Context errors are not retried
if err := ctx.Err(); err != nil {
return res, err
}
Common Tasks
Adding a New API Endpoint
-
Add method to interface (
client/client.go):// NewMethod does something NewMethod(ctx context.Context, param string) (Response, error) -
Add endpoint constant (
client/http.go):newEndpoint = "/api/new?accountId=%s¶m=%s" -
Implement method (
client/http.go):func (c *HTTPClient) NewMethod(ctx context.Context, param string) (Response, error) { if err := c.validateNewMethodArgs(param); err != nil { return Response{}, err } path := fmt.Sprintf(newEndpoint, c.AccountID, param) backoff := createBackoff(5 * 60 * time.Second) var resp Response _, err := c.retry(ctx, c.Endpoint+path, "GET", "", nil, &resp, false, true, backoff) return resp, err } -
Add validation function:
func (c *HTTPClient) validateNewMethodArgs(param string) error { if err := c.validateTiArgs(); err != nil { return err } if param == "" { return fmt.Errorf("param is not set") } return nil } -
Add types (
types/types.goor new file):type Response struct { Field string `json:"field"` }
Modifying Existing Endpoints
- Endpoint URL changes: Update the endpoint constant
- Request/response changes: Update types in
types/package - New parameters: Add to function signature, update validation
- Retry behavior: Adjust
createBackoff()timeout
Adding New Types
- Core types: Add to
types/types.goor create new file intypes/ - V2 API types: Add to
chrysalis/types/ - Cache types: Add to
types/cache/{category}/ - Use JSON tags for serialization
- Use BSON tags for MongoDB (in chrysalis types)
Security Considerations
mTLS Support
- Certificates can be provided as:
- Base64-encoded strings (preferred for containers)
- File paths (default:
/etc/mtls/client.crt,/etc/mtls/client.key)
- mTLS is optional - only enabled if certificates are provided
TLS Configuration
SkipVerify: For development/testing only- Custom root CAs: Loaded from directory
- System cert pool: Used as base, additional certs appended
Authentication
- Token-based:
X-Harness-Tokenheader - Token is required for all operations
- No token refresh logic (assumed to be valid for request lifetime)
Testing Patterns
When adding tests (if test files exist):
- Use table-driven tests for multiple scenarios
- Mock HTTP client for unit tests
- Test validation functions separately
- Test retry logic with mock servers
Race detection (required)
Always run tests with the race detector. Use the Makefile targets from the repo root:
make test # go test -race ./...
make test-short # go test -race -short ./...
PR / CI checks should invoke make test (or equivalent go test -race ./...) so data races do not regress. Do not treat a green suite without -race as sufficient.
Dependencies
External
github.com/cenkalti/backoff: Exponential backoff for retriesgithub.com/cespare/xxhash/v2: Hashing (used internally)go.mongodb.org/mongo-driver: BSON types for chrysalis (MongoDB ObjectID)
Internal
- All types are in
types/orchrysalis/types/ - No circular dependencies
clientdepends ontypes, not vice versa
Common Workflows
Test Selection Workflow
- Get changed files from git/VCS
- Create
SelectTestsReqwith files and branch info - Call
SelectTests()orMLSelectTests() - Receive
SelectTestsRespwith tests to run - Execute tests
- Write results via
Write() - Upload callgraph via
UploadCg()orUploadCgV2()
Callgraph Upload Workflow
- Generate callgraph (done by TI agent, not this client)
- Encode as Avro (V1) or JSON (V2)
- Call
UploadCg()orUploadCgV2() - Service stores callgraph for future test selection
Savings Tracking Workflow
- Track time taken and time saved for intelligence features
- Collect metrics (Gradle, Maven, DLC)
- Call
WriteSavings()with metrics - Service aggregates and reports savings
Important Notes
- No State Management: Client is stateless - each request is independent
- Context Usage: Always respect context cancellation/timeout
- Retry Behavior: Different operations have different retry timeouts based on expected duration
- V2 API: Chrysalis (V2) is the newer API - prefer when possible
- Backward Compatibility: V1 API still supported for existing integrations
- Error Handling: Always check errors, don't ignore them
- Validation: All public methods validate inputs before making requests
Extension Points
Adding New Intelligence Features
- Add feature constant to
types.SavingsFeature - Add metrics type if needed (e.g., in
types/cache/) - Update
SavingsRequestif new metrics needed - Add endpoint/method if new API needed
Supporting New Test Frameworks
- No client changes needed - framework-agnostic
- TI service handles framework-specific logic
- Client just passes language/framework info
Adding New Telemetry
- Add fields to
TelemetryDataintypes/types.go - Use
telemetryUtilsfor common calculations - Write telemetry via existing
Write()method
Related Codebases
- harness-ti: TI service backend (API server)
- harness-core: Uses this client in pipeline execution
- lite-engine: Uses this client to communicate with TI service
When making changes, consider:
- Backward compatibility with existing consumers
- API contract with
harness-tiservice - Impact on
lite-engineandharness-coreintegrations