# Integrating Your Agent with Synqed

## For External Agent Developers

Have an AI agent built with **LangChain**, **CrewAI**, **AutoGPT**, or your own custom framework?

Want it to collaborate with other agents in Synqed workspaces?

**You don't need to rewrite anything.** Just expose an A2A-compliant endpoint.

## Quick Start

### 1. Expose AgentCard

Create a file at `/.well-known/agent-card.json` that describes your agent:

```json
{
  "protocol_version": "1.0",
  "name": "My Agent",
  "description": "What this agent does",
  "url": "https://my-agent.example.com",
  "preferred_transport": "JSONRPC",
  "skills": [
    {
      "id": "my-skill",
      "name": "My Skill",
      "description": "What my agent can do"
    }
  ],
  "security_schemes": {
    "bearer": {
      "http_auth_security_scheme": {
        "description": "Bearer token authentication",
        "scheme": "bearer"
      }
    }
  },
  "security": [
    {
      "schemes": {
        "bearer": {
          "list": []
        }
      }
    }
  ]
}
```

### 2. Implement Message Endpoint

Implement a JSON-RPC endpoint for receiving messages:

**Python (FastAPI) Example:**

```python
from fastapi import FastAPI, Header
from pydantic import BaseModel
from typing import Optional, List, Dict, Any

app = FastAPI()

class MessagePart(BaseModel):
    text: Optional[str] = None

class Message(BaseModel):
    message_id: str
    role: str
    parts: List[MessagePart]

class SendMessageParams(BaseModel):
    message: Message

class JSONRPCRequest(BaseModel):
    jsonrpc: str
    method: str
    params: SendMessageParams
    id: int

@app.post("/")
async def handle_jsonrpc(
    request: JSONRPCRequest,
    authorization: Optional[str] = Header(None)
):
    """Handle A2A JSON-RPC requests."""
    
    # Validate auth if needed
    if authorization:
        token = authorization.replace("Bearer ", "")
        # Validate token...
    
    if request.method == "message/send":
        # Extract user message
        user_message = ""
        for part in request.params.message.parts:
            if part.text:
                user_message += part.text
        
        # Process with your agent
        # (This is where YOUR agent logic goes)
        response_text = await process_with_your_agent(user_message)
        
        # Return A2A response
        return {
            "jsonrpc": "2.0",
            "result": {
                "task": {
                    "id": f"task-{request.params.message.message_id}",
                    "status": {
                        "state": "TASK_STATE_COMPLETED"
                    },
                    "artifacts": [
                        {
                            "artifact_id": "result-1",
                            "parts": [{"text": response_text}]
                        }
                    ]
                }
            },
            "id": request.id
        }

async def process_with_your_agent(message: str) -> str:
    """
    YOUR AGENT LOGIC HERE.
    
    This is where you:
    - Call your LangChain chain
    - Run your CrewAI crew
    - Execute your custom logic
    - Call any LLM or tool you want
    
    Just return the response text.
    """
    # Example with LangChain:
    # from langchain import LLMChain
    # result = await chain.arun(message)
    # return result
    
    # Example with CrewAI:
    # crew = Crew(agents=[...], tasks=[...])
    # result = crew.kickoff(inputs={"task": message})
    # return result
    
    # For now, echo:
    return f"Processed: {message}"

@app.get("/.well-known/agent-card.json")
async def get_agent_card():
    """Serve AgentCard for discovery."""
    return {
        "protocol_version": "1.0",
        "name": "My Agent",
        "description": "What this agent does",
        "url": "https://my-agent.example.com",
        "preferred_transport": "JSONRPC",
        "skills": [...]
    }
```

### 3. Register with Synqed

Users can now register your agent in their Synqed workspaces:

```python
import synqed

synqed.AgentRuntimeRegistry.register_remote(
    role="YourAgent",
    url="https://your-agent.example.com",
    auth_token="your-api-key"  # If auth is required
)

# Your agent can now collaborate in Synqed workspaces!
```

## Framework-Specific Examples

### LangChain

```python
from langchain.chat_models import ChatOpenAI
from langchain.chains import LLMChain
from langchain.prompts import ChatPromptTemplate

# Your existing LangChain setup
llm = ChatOpenAI(model="gpt-4")
prompt = ChatPromptTemplate.from_template("Process this task: {input}")
chain = LLMChain(llm=llm, prompt=prompt)

# Wrap it in A2A endpoint
@app.post("/")
async def handle_jsonrpc(request: JSONRPCRequest):
    if request.method == "message/send":
        user_message = extract_message_text(request.params.message)
        
        # Use your LangChain chain
        result = await chain.arun(input=user_message)
        
        return build_a2a_response(result, request.id)
```

### CrewAI

```python
from crewai import Agent, Task, Crew

# Your existing CrewAI setup
researcher = Agent(
    role="Researcher",
    goal="Research topics",
    backstory="Expert researcher"
)

task = Task(description="Research task", agent=researcher)
crew = Crew(agents=[researcher], tasks=[task])

# Wrap it in A2A endpoint
@app.post("/")
async def handle_jsonrpc(request: JSONRPCRequest):
    if request.method == "message/send":
        user_message = extract_message_text(request.params.message)
        
        # Use your CrewAI crew
        result = crew.kickoff(inputs={"task": user_message})
        
        return build_a2a_response(str(result), request.id)
```

### AutoGPT

```python
from autogpt import AutoGPT

# Your existing AutoGPT setup
agent = AutoGPT(
    ai_name="MyAgent",
    ai_role="Task executor",
    # ... other config
)

# Wrap it in A2A endpoint
@app.post("/")
async def handle_jsonrpc(request: JSONRPCRequest):
    if request.method == "message/send":
        user_message = extract_message_text(request.params.message)
        
        # Use your AutoGPT agent
        result = await agent.run(user_message)
        
        return build_a2a_response(result, request.id)
```

## Helper Functions

Here are some helper functions to make implementation easier:

```python
def extract_message_text(message: Message) -> str:
    """Extract text content from A2A message."""
    texts = []
    for part in message.parts:
        if part.text:
            texts.append(part.text)
    return "\n".join(texts)

def build_a2a_response(content: str, request_id: int) -> dict:
    """Build A2A JSON-RPC response."""
    return {
        "jsonrpc": "2.0",
        "result": {
            "task": {
                "id": f"task-{request_id}",
                "status": {
                    "state": "TASK_STATE_COMPLETED"
                },
                "artifacts": [
                    {
                        "artifact_id": "result-1",
                        "parts": [{"text": content}]
                    }
                ]
            }
        },
        "id": request_id
    }
```

## Deployment

Once your agent exposes an A2A endpoint, deploy it:

- **Local**: `uvicorn main:app --host 0.0.0.0 --port 8000`
- **Docker**: Package as container
- **Cloud**: Deploy to AWS Lambda, GCP Cloud Run, Azure Functions, etc.
- **Kubernetes**: Deploy as K8s service

As long as it's reachable via HTTP/HTTPS, Synqed can route to it.

## Authentication

If your agent requires auth, implement it in your endpoint:

```python
@app.post("/")
async def handle_jsonrpc(
    request: JSONRPCRequest,
    authorization: str = Header(...)
):
    # Validate token
    token = authorization.replace("Bearer ", "")
    if not validate_token(token):
        raise HTTPException(status_code=401, detail="Invalid token")
    
    # Process request...
```

Declare it in your AgentCard:

```json
{
  "security_schemes": {
    "bearer": {
      "http_auth_security_scheme": {
        "description": "Bearer token",
        "scheme": "bearer"
      }
    }
  },
  "security": [
    {
      "schemes": {
        "bearer": {"list": []}
      }
    }
  ]
}
```

Users will provide the token when registering:

```python
synqed.AgentRuntimeRegistry.register_remote(
    role="YourAgent",
    url="https://your-agent.example.com",
    auth_token="secret-key"
)
```

## Testing

Test your A2A endpoint:

```bash
curl -X POST https://your-agent.example.com \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-token" \
  -d '{
    "jsonrpc": "2.0",
    "method": "message/send",
    "params": {
      "message": {
        "message_id": "test-123",
        "role": "ROLE_USER",
        "parts": [{"text": "Hello agent!"}]
      }
    },
    "id": 1
  }'
```

Expected response:

```json
{
  "jsonrpc": "2.0",
  "result": {
    "task": {
      "id": "task-test-123",
      "status": {
        "state": "TASK_STATE_COMPLETED"
      },
      "artifacts": [
        {
          "artifact_id": "result-1",
          "parts": [
            {"text": "Agent's response here"}
          ]
        }
      ]
    }
  },
  "id": 1
}
```

## Full A2A Specification

For complete A2A protocol details, see:
- **A2A Website**: https://a2a-protocol.org
- **Specification**: https://github.com/a2aproject/A2A
- **Python SDK**: https://github.com/a2aproject/a2a-python

## Support

Need help integrating your agent?

- **GitHub Issues**: [Link to Synqed repo]
- **Discord**: [Link to community]
- **Email**: support@synq.dev

## Benefits of Integration

Once your agent implements A2A:

- ✅ **Works with Synqed** - Can join multi-agent workspaces
- ✅ **Works with other A2A agents** - Collaborate with agents from any framework
- ✅ **Future-proof** - A2A is an open standard
- ✅ **Marketplace ready** - Can be listed in agent marketplaces
- ✅ **No vendor lock-in** - You control your agent

## Examples

See `examples/intro/universal_substrate_demo.py` for a full example of how local and remote agents collaborate in Synqed workspaces.

