lucidAGENTS
Examples

Lucid multi-service task example

Compose Agent Card-shaped discovery with Lucid-owned HTTP task operations.

This example demonstrates how Lucid services discover cards and compose Lucid HTTP task operations.

The example does not use an official A2A v1 JSON-RPC, gRPC, or HTTP+JSON binding. /entrypoints, /tasks, the task states, message.content, access token, and SSE events are Lucid contracts. A2A-Version and the A2A TCK are not implemented.

Full integration (three Lucid services)

File: packages/examples/src/a2a/full-integration.ts

This comprehensive example demonstrates the facilitator pattern where an agent acts as both server and client:

Agent 3 (Client) → Agent 2 (Facilitator) → Agent 1 (Worker)

                 Results flow back

Architecture

AgentRoleDescription
Agent 1WorkerDoes the actual work (echo, process, stream)
Agent 2FacilitatorReceives calls, delegates to Agent 1, returns results
Agent 3ClientInitiates requests to Agent 2

The code

full-integration.ts
import {
  a2a,
  fetchAgentCard,
  findSkill,
  hasCapability,
  supportsPayments,
  waitForTask,
} from '@lucid-agents/a2a';
import { createAgent } from '@lucid-agents/core';
import { createAgentApp } from '@lucid-agents/hono';
import { http } from '@lucid-agents/http';
import { z } from 'zod';

// Agent 1: Worker
const agent1 = await createAgent({
  name: 'worker-agent',
  version: '1.0.0',
  description: 'Worker agent that processes tasks',
})
  .use(http())
  .use(a2a())
  .build();

const { app: app1, addEntrypoint: addEntrypoint1 } =
  await createAgentApp(agent1);

addEntrypoint1({
  key: 'echo',
  description: 'Echoes back the input text',
  input: z.object({ text: z.string() }),
  output: z.object({ text: z.string() }),
  handler: async ctx => {
    console.log(`[Agent 1] echo called with: "${ctx.input.text}"`);
    return {
      output: { text: `Echo: ${ctx.input.text}` },
    };
  },
});

// Agent 2: Facilitator (both server AND client)
const agent2 = await createAgent({
  name: 'facilitator-agent',
  version: '1.0.0',
  description: 'Facilitator that proxies to worker',
})
  .use(http())
  .use(a2a())
  .build();

const {
  app: app2,
  addEntrypoint: addEntrypoint2,
  runtime: runtime2,
} = await createAgentApp(agent2);

// Get the Lucid Agent Card/task client from the facilitator runtime
const a2aClient = runtime2.a2a;

addEntrypoint2({
  key: 'echo',
  description: 'Proxies echo requests to worker agent',
  input: z.object({ text: z.string() }),
  output: z.object({ text: z.string() }),
  handler: async ctx => {
    console.log(`[Agent 2] Received request, forwarding to Agent 1`);

    // Fetch Agent 1's card
    const agent1Card = await a2aClient.fetchCard('http://localhost:8787');

    // Create task on Agent 1
    const taskAccess = await a2aClient.client.sendMessage(agent1Card, 'echo', {
      text: ctx.input.text,
    });

    // Wait for result
    const task = await waitForTask(a2aClient.client, agent1Card, taskAccess);

    if (task.status === 'failed') {
      throw new Error(task.error?.message || 'Task failed');
    }

    // Validate output with Zod (runtime safety)
    const outputSchema = z.object({ text: z.string() });
    const validatedOutput = outputSchema.parse(task.result?.output);

    return { output: validatedOutput };
  },
});

// Agent 3: Client (no HTTP server needed)
const agent3 = await createAgent({
  name: 'client-agent',
  version: '1.0.0',
})
  .use(a2a())
  .build();

const a2a3 = agent3.a2a;

// Call Agent 2, which calls Agent 1
const card2 = await a2a3.fetchCard('http://localhost:8788');
const taskAccess = await a2a3.client.sendMessage(card2, 'echo', {
  text: 'Hello from Agent 3!',
});

const result = await waitForTask(a2a3.client, card2, taskAccess);
console.log('Final result:', result.result?.output);
// Output: { text: "Echo: Hello from Agent 3!" }

Lucid profile patterns

Agent Card-shaped discovery

Fetch another agent's capabilities at runtime:

const card = await fetchAgentCard('https://other-agent.com');

console.log(card.name); // Agent name
console.log(card.skills); // Available entrypoints
console.log(hasCapability(card, 'streaming')); // Check capabilities
console.log(supportsPayments(card)); // Check payment support

Task-based operations

Create a task and poll for results:

// Create task (returns immediately)
const taskAccess = await client.sendMessage(card, 'skillId', input);
// taskAccess = { taskId, accessToken, status: 'running' }

// Wait for completion
const task = await waitForTask(client, card, taskAccess);

if (task.status === 'completed') {
  console.log(task.result?.output);
} else if (task.status === 'failed') {
  console.error(task.error?.message);
}

Multi-turn conversations

Group related tasks with contextId:

const contextId = `conversation-${Date.now()}`;

// First message establishes the owner capability
const first = await client.sendMessage(
  card,
  'chat',
  { text: 'Hello' },
  undefined,
  { contextId }
);

// Second message reuses the same owner and conversation
await client.sendMessage(card, 'chat', { text: 'Tell me more' }, undefined, {
  contextId,
  accessToken: first.accessToken,
});

// List all messages in conversation
const { tasks } = await client.listTasks(card, first.accessToken, {
  contextId,
});

Task cancellation

Cancel a running task:

try {
  const cancelled = await client.cancelTask(card, taskAccess);
  console.log('Cancelled:', cancelled.status); // 'cancelled'
} catch (error) {
  // Task may have already completed
  const task = await client.getTask(card, taskAccess);
  console.log('Task status:', task.status);
}

Validating remote output

Always validate output from remote agents:

const task = await waitForTask(client, card, taskAccess);

// Don't trust remote data - validate with Zod
const outputSchema = z.object({ result: z.number() });
const validated = outputSchema.parse(task.result?.output);

Running the example

bun run packages/examples/src/a2a/full-integration.ts

Output

STEP 1: Creating Agent 1 (Worker Agent)
Agent 1 running at: http://localhost:8787

STEP 2: Creating Agent 2 (Facilitator Agent)
Agent 2 running at: http://localhost:8788

STEP 3: Creating Agent 3 (Client Agent)
Agent 3 ready

STEP 6: A2A Composition (Agent 3 -> Agent 2 -> Agent 1)
[Agent 2] Received request, forwarding to Agent 1
[Agent 1] echo called with: "Hello from Agent 3!"
Final result at Agent 3: {"text":"Echo: Hello from Agent 3!"}

What this proves—and does not

The example proves card lookup, Lucid task ownership, polling, composition, and remote-output validation across three local services. It does not prove A2A v1 interoperability, marketplace publication, production load balancing, durable task recovery, payment settlement, or trust in a discovered origin.

For production, add an origin/SSRF allowlist, durable TaskStore, task-token secret handling, target idempotency, payment policy, timeouts, and trace correlation. See A2A compatibility and the @lucid-agents/a2a reference.

On this page