Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7e71851
[a2a] Add JSON-RPC protocol foundation and module structure
EugeneTheDev Sep 29, 2025
ab98cf2
[a2a] Implement AgentCard and core A2A data models
EugeneTheDev Aug 27, 2025
d208bfb
[a2a] Add client and server transport abstractions
EugeneTheDev Aug 30, 2025
2ae67f0
[a2a] Implement HTTP JSON-RPC client transport with tests
EugeneTheDev Aug 31, 2025
becbf37
[a2a] Implement HTTP JSON-RPC server transport with full protocol sup…
EugeneTheDev Sep 4, 2025
b536450
Add integration tests for HttpJSONRPCClientTransport and update depen…
kpavlov Sep 9, 2025
757964f
[a2a] Implement A2AClient foundation with AgentCardResolver
EugeneTheDev Sep 29, 2025
4dc50ae
[a2a] Update A2A models for spec compliance and add client methods
EugeneTheDev Sep 10, 2025
a998dea
[a2a] Add comprehensive A2A client test suite with Python test server
EugeneTheDev Sep 10, 2025
2ac63dd
[a2a] Improve agent card loading and resolution
EugeneTheDev Sep 12, 2025
0ac934c
[a2a] Implement A2AServer core with session management and storage
EugeneTheDev Sep 13, 2025
ad7c61a
[a2a] Implement session management with push notifications
EugeneTheDev Sep 19, 2025
f63824a
[a2a] Add comprehensive documentation for server components
EugeneTheDev Sep 20, 2025
e934cf3
[a2a] Introduce a2a-test module, create BaseA2AProtocolTest
EugeneTheDev Sep 29, 2025
da87a8f
[a2a] Fix SessionEventProcessor concurrency and event management
EugeneTheDev Sep 21, 2025
13dd663
[a2a] Refine AgentExecutor interface and add test utilities
EugeneTheDev Sep 24, 2025
64bfafd
[a2a] Add task locking, add A2A server integration tests
EugeneTheDev Sep 24, 2025
8e98faa
[a2a] Implement full TCK compliance with all tests passing
EugeneTheDev Sep 29, 2025
43ab8fa
[a2a] Add CORS configuration
EugeneTheDev Sep 29, 2025
9d5927c
[a2a] Add A2A server and A2A client agent features
EugeneTheDev Sep 29, 2025
1fd35d1
[a2a] Fix compilation issues and build configuration
EugeneTheDev Sep 30, 2025
0c9fc72
[a2a] Add module documentation and metadata field support
EugeneTheDev Sep 30, 2025
6768b42
[a2a] Add interactive joke agent example
EugeneTheDev Sep 30, 2025
3b2c9e2
[agents] Fix EventHandlerTest
EugeneTheDev Oct 1, 2025
80abf56
[agents] Update simple joke a2a example
EugeneTheDev Sep 30, 2025
1a80d6c
[a2a] Add stress tests, fix race conditions, and refine testing approach
EugeneTheDev Oct 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions .github/workflows/a2a-tck-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
name: A2A TCK Test

on:
push:
paths:
- 'a2a/**'
pull_request:
paths:
- 'a2a/**'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }}

env:
JAVA_VERSION: 17
JAVA_DISTRIBUTION: 'corretto'

jobs:
a2a-tck-test:
runs-on: ubuntu-latest
timeout-minutes: 10

permissions:
contents: read

steps:
- name: Configure Git
run: |
git config --global core.autocrlf input

- uses: actions/checkout@v5

- name: Set up JDK ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v5
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: ${{ env.JAVA_DISTRIBUTION }}

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Set up Python 3.12
uses: actions/setup-python@v6
with:
python-version: '3.12'

- name: Install uv
uses: astral-sh/setup-uv@v6
with:
version: "0.8.15"
enable-cache: true

- name: Setup A2A TCK
working-directory: ./a2a/test-tck
run: ./setup_tck.sh

- name: Build test server
run: ./gradlew :a2a:test-tck:a2a-test-server-tck:assemble

- name: Start test server in background
working-directory: ./a2a/test-tck
run: |
./run_sut.sh > server.log 2>&1 &
SERVER_PID=$!
echo "SERVER_PID=$SERVER_PID" >> $GITHUB_ENV

# Wait for server to start (max 60 seconds)
timeout 60 bash -c '
while ! grep -q "Responding at http://0.0.0.0:9999" server.log 2>/dev/null; do
sleep 1
done
'

echo "Server started successfully"

- name: Run TCK tests - Mandatory
working-directory: ./a2a/test-tck
run: ./run_tck.sh --sut-url http://localhost:9999/a2a --category mandatory --report

- name: Run TCK tests - Capabilities
working-directory: ./a2a/test-tck
run: ./run_tck.sh --sut-url http://localhost:9999/a2a --category capabilities --report

- name: Run TCK tests - Transport Equivalence
working-directory: ./a2a/test-tck
run: ./run_tck.sh --sut-url http://localhost:9999/a2a --category transport-equivalence --report

- name: Run TCK tests - Quality
working-directory: ./a2a/test-tck
run: ./run_tck.sh --sut-url http://localhost:9999/a2a --category quality --report

- name: Run TCK tests - Features
working-directory: ./a2a/test-tck
run: ./run_tck.sh --sut-url http://localhost:9999/a2a --category features --report

- name: Stop test server
if: always()
run: |
if [ ! -z "$SERVER_PID" ]; then
kill $SERVER_PID || true
wait $SERVER_PID 2>/dev/null || true
fi

- name: Upload TCK reports
if: always()
uses: actions/upload-artifact@v4
with:
name: a2a-tck-reports
path: a2a/test-tck/a2a-tck/reports/*.html
if-no-files-found: warn
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ local.properties
docs/src/main/kotlin/*.kt
**/.env
.venv
.DS_Store
**/kotlin-js-store
263 changes: 263 additions & 0 deletions a2a/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
# A2A Module Development Guidelines

## Module Overview

The A2A (Agent-to-Agent) module is a **meta-module** within the larger Koog project that implements a comprehensive client and server library for the A2A protocol, a standardized communication protocol for AI agents based on the specification at https://a2a-protocol.org/latest/specification/.

**Important**: Since this is a meta-module inside a bigger root project, the Gradle wrapper executable is located one directory up. All Gradle commands must use `../gradlew` when running from the a2a directory.

### Purpose
- **Agent Discovery**: Through AgentCard manifests describing agent capabilities and interfaces
- **Message Exchange**: Standardized message format with support for different content types
- **Task Management**: Long-running operations with state tracking and lifecycle management
- **Push Notifications**: Asynchronous notifications for task updates
- **Multiple Transport Protocols**: JSON-RPC, HTTP+JSON/REST with extensibility for gRPC
- **Authentication & Security**: OpenAPI 3.0 compatible security schemes

### Submodules Architecture

1. **a2a-core**: Core abstractions, data models, and transport interfaces
- AgentCard, Message, Task, Event hierarchy
- Transport interfaces: ClientTransport, ServerTransport, RequestHandler
- No external dependencies beyond Kotlin stdlib and serialization

2. **a2a-client**: High-level client library for communicating with A2A servers
- A2AClient wrapper, AgentCardResolver
- Capability validation, request/response handling
- Depends on: a2a-core, Ktor HTTP client

3. **a2a-server**: Server-side implementation for hosting A2A agents
- A2AServer, AgentExecutor interface, session management
- Storage abstractions with in-memory implementations
- Depends on: a2a-core, coroutines, logging

4. **a2a-transport**: Multiple transport protocol implementations
- a2a-transport-core-jsonrpc: JSON-RPC protocol base classes
- a2a-transport-client-jsonrpc-http: HTTP JSON-RPC client transport
- a2a-transport-server-jsonrpc-http: HTTP JSON-RPC server transport
- a2a-transport-*-rest: HTTP+JSON/REST protocol implementations

## Technologies & Libraries

### Core Dependencies (from gradle/libs.versions.toml)
- **Kotlin Multiplatform**
- **kotlinx-serialization**: JSON serialization for protocol messages
- **kotlinx-coroutines**: Async/concurrent programming, Flow APIs
- **kotlinx-datetime**: Timestamp handling in protocol messages
- **ktor3**: HTTP client/server for transport implementations
- **oshai-kotlin-logging**: Structured logging

### Testing Libraries
- **kotlin-test**: Multiplatform test framework
- **kotest-assertions**: Rich assertion library for complex objects
- **kotlinx-coroutines-test**: Testing coroutines with runTest
- **testcontainers**: Docker-based integration testing
- **logback-classic**: Runtime logging for tests

### Platform Support
- **JVM**: Full server and client support
- **JS (Browser)**: Client-only support
- **Future platforms**: Architecture ready for native support

## Development Guidelines

### Architecture Decisions
⚠️ **CRITICAL**: Never make design and architecture decisions independently. Always ask the user for confirmation before:
- Adding new transport protocols
- Changing storage interfaces
- Modifying core protocol message formats
- Adding new authentication schemes
- Changing session management behavior
- Doing other sorts of major architectural changes

### Code Style Conventions

#### API Visibility
- Use `explicitApi()` - all public APIs must have explicit visibility modifiers
- Prefer `public` declarations for APIs, `internal` for implementation details
- Use `@InternalA2AApi` annotation for APIs that are public but internal to the module

#### Naming Conventions
- Classes: PascalCase (`A2AServer`, `SessionManager`, `AgentExecutor`)
- Interfaces: Same as classes, often ending in -or/-er for behaviors (`AgentExecutor`)
- Functions: camelCase with descriptive names (`onSendMessage`, `getByContext`)
- Properties: camelCase (`agentCard`, `sessionMutex`)

#### Async Patterns
- Use `suspend` functions for all async operations
- Prefer `Flow` for streaming APIs (task events, message streams)
- Use `RWLock` pattern: `rwLock.withReadLock/withWriteLock` for concurrent access
- Use `Mutex` for single-threaded critical sections: `mutex.withLock`

#### Error Handling
- Use domain-specific exceptions (`A2AInternalErrorException`, `TaskOperationException`)
- Propagate errors properly in async contexts
- Include contextual information in exception messages

#### KDoc Documentation
- **Placement**: KDoc directly above declarations (classes, functions, properties)
- **Constructor properties**: Document using `@param` tags in class KDoc
- **Public class properties**: Document using `@property` tags in class KDoc
- **Cross-references**: Use `[ClassName]`, `[ClassName.propertyName]` syntax for linking components
- **Examples**: Include practical code examples for complex APIs
- **Validation rules**: Document constraints with bullet points and clear explanations
- **Exception documentation**: Use `@throws` with specific conditions
- **Required**: All public APIs must have KDoc (enforced by `explicitApi()`)

## Testing Requirements

### Mandatory Test Execution
⚠️ **ALWAYS** run the specific test suite using Gradle's jvmTest task to ensure changes work correctly:

```bash
# Run all tests in a specific module
../gradlew :a2a:a2a-server:jvmTest

# Run a specific test class
../gradlew :a2a:a2a-server:jvmTest --tests "ai.koog.a2a.server.tasks.InMemoryTaskStorageTest"

# Run a specific test method
../gradlew :a2a:a2a-client:jvmTest --tests "ai.koog.a2a.client.A2AClientIntegrationTest.test get agent card"
```

### Testing Approach: Crucial Minimum
- **Focus on core functionality** - test essential behavior, not implementation details
- **Separate dedicated tests** - one test method per core scenario/edge case
- **Avoid verbose, unnecessary tests** - don't test trivial getters/setters
- **Test error conditions** - verify proper exception handling and error states

#### Preferred Assertion Pattern
**✅ DO**: Assert on whole objects when possible (especially data classes):
```kotlin
assertEquals(expectedTask, actualTask) // Compares all properties
assertEquals(listOf(expectedArtifact), retrieved?.artifacts)
```

**❌ AVOID**: Bunch of individual field assertions unless necessary:
```kotlin
// Avoid this pattern:
assertEquals(expected.id, actual.id)
assertEquals(expected.contextId, actual.contextId)
assertEquals(expected.status, actual.status)
// ... many more individual assertions
```

### Integration Tests
- **Docker-based testing**: a2a-client uses testcontainers with Python A2A server
- **Docker build dependency**: Integration tests automatically build required containers
- **Test isolation**: Each test should be independent and clean up after itself

### Test Structure Examples

**Unit Tests** (InMemoryTaskStorageTest pattern):
```kotlin
class InMemoryTaskStorageTest {
private lateinit var storage: InMemoryTaskStorage

@BeforeTest
fun setUp() {
storage = InMemoryTaskStorage()
}

@Test
fun testStoreAndRetrieveTask() = runTest {
val task = createTask(id = "task-1", contextId = "context-1")
storage.update(task)
val retrieved = storage.get("task-1")

assertNotNull(retrieved)
assertEquals(task, retrieved) // Whole object assertion
}
}
```

**Integration Tests** (A2AClientIntegrationTest pattern):
```kotlin
@Testcontainers
class A2AClientIntegrationTest {
@Container
val testA2AServer = GenericContainer("test-python-a2a-server")
.withExposedPorts(9999)
.waitingFor(Wait.forListeningPort())

@Test
fun `test get agent card`() = runTest {
val agentCard = client.getAgentCard()
val expectedAgentCard = AgentCard(...)
assertEquals(expectedAgentCard, agentCard) // Full object comparison
}
}
```

## Implementation Patterns

### Transport Layer Abstraction
- Implement `ClientTransport` interface for new client protocols
- Implement `ServerTransport` and `RequestHandler` for new server protocols
- Keep protocol logic separate from transport mechanism
- Use suspend functions for all transport operations

### Storage Interface Pattern
```kotlin
public interface SomeStorage {
public suspend fun save(item: Item)
public suspend fun get(id: String): Item?
public suspend fun delete(id: String)
}

// In-memory implementation for testing/development
@OptIn(InternalA2AApi::class)
public class InMemorySomeStorage : SomeStorage {
private val rwLock = RWLock()
private val items = mutableMapOf<String, Item>()

override suspend fun save(item: Item) = rwLock.withWriteLock {
items[item.id] = item
}
}
```

### Session Management Pattern
- Use `SessionEventProcessor` for event-driven session handling
- Implement proper cleanup with `Closeable` interface
- Use structured concurrency with `CoroutineScope`
- Handle session lifecycle properly (start → active → closed)

### Agent Executor Implementation
```kotlin
public class MyAgentExecutor : AgentExecutor {
override suspend fun execute(
context: RequestContext<MessageSendParams>,
eventProcessor: SessionEventProcessor
) {
// Process request and send events through eventProcessor
eventProcessor.sendTaskEvent(/* task event */)
}
}
```

## Some Common Commands

```bash
# Build specific modules (a2a is a meta-module, build individual modules)
../gradlew :a2a:a2a-core:assemble
../gradlew :a2a:a2a-client:assemble
../gradlew :a2a:a2a-server:assemble

# Run JVM tests for specific modules
../gradlew :a2a:a2a-core:jvmTest
../gradlew :a2a:a2a-client:jvmTest
../gradlew :a2a:a2a-server:jvmTest
../gradlew :a2a:a2a-transport:a2a-transport-core-jsonrpc:jvmTest

# Run JS tests for specific modules
../gradlew :a2a:a2a-core:jsTest
../gradlew :a2a:a2a-client:jsTest
../gradlew :a2a:a2a-server:jsTest

# Build all non-transport a2a modules at once
../gradlew :a2a:a2a-core:assemble :a2a:a2a-client:assemble :a2a:a2a-server:assemble

# Run all JVM tests across a2a modules
../gradlew :a2a:a2a-core:jvmTest :a2a:a2a-client:jvmTest :a2a:a2a-server:jvmTest
```
4 changes: 4 additions & 0 deletions a2a/a2a-client/Module.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Module a2a-client

High-level client library for communicating with A2A protocol servers. Provides the A2AClient wrapper with capability validation,
request/response handling, and AgentCard resolution. Built on top of a2a-core abstractions with Ktor HTTP client support.
Loading
Loading