ACP Gateway Integration Architecture

Explanation: Deep dive into how the ACP Gateway integrates SEA-Forge™ with Zed IDE and external services.

Overview

The ACP (Agent Communication Protocol) Gateway is SEA-Forge™’s bridge between Zed IDE and the semantic execution stack. It implements a JSON-RPC 2.0 server that exposes SEA™ capabilities as IDE-native tools.

Architecture Location: Integration Adapter Layer (per AGENTS.md §Integration Adapter Exception)

Specification References:


Component Architecture

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌─────────────────────────────────────────────────────────────────┐
│                         Zed IDE                                  │
│  (sends JSON-RPC requests via stdio)                            │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             │ JSON-RPC 2.0 (stdio)
                             │
┌────────────────────────────▼────────────────────────────────────┐
│                      ACP Gateway                                 │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          │
│  │   Message    │  │  Tool        │  │  Capability  │          │
│  │   Handler    │  │  Executor    │  │  Registry    │          │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘          │
│         │                 │                 │                   │
│         └─────────────────┴─────────────────┘                   │
│                           │                                      │
└───────────────────────────┼──────────────────────────────────────┘
                            │
        ┌───────────────────┼───────────────────┐
        │                   │                   │
        ▼                   ▼                   ▼
┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│ LLM Provider │  │  Knowledge   │  │  Validation  │
│   Service    │  │    Graph     │  │  + Pipeline  │
│  (HTTP API)  │  │ (Oxigraph/   │  │  (subprocess)│
│              │  │   rdflib)    │  │              │
└──────────────┘  └──────────────┘  └──────────────┘
  SDS-049           SDS-003           SDS-002/021

Key Design Decisions

1. Graceful Degradation

Problem: External services may not be running (LLM, Oxigraph).

Solution: Multi-tier fallback strategy for each capability.

Example - Chat Completion:

1
2
3
4
5
1. Try: HTTP call to LLM service (localhost:8000)
   ↓ fails
2. Try: Return informative echo message
   ↓ always succeeds
3. Result: User gets response + guidance to start service

Example - Knowledge Graph:

1
2
3
4
5
6
1. Try: HTTP call to Oxigraph (localhost:7878)
   ↓ fails
2. Try: In-memory RDF via rdflib + .snapshot.json files
   ↓ fails
3. Try: Return empty results + error message
   ↓ always succeeds

2. Optional Dependencies

Problem: Not all users need all integrations.

Solution: Conditional imports with runtime checks.

1
2
3
4
5
6
7
8
try:
    import httpx
except ImportError:
    httpx = None  # type: ignore

# Later in code
if httpx is None:
    return {"error": "Install httpx: pip install httpx"}

Benefits:

3. Interface Preservation

Problem: Zed IDE expects consistent JSON-RPC responses.

Solution: Wrapper pattern maintains contract even when services fail.

1
2
3
4
5
6
# Always returns valid JSON-RPC response
{
    "success": bool,
    "result": {...} | None,
    "error": str | None
}

Invariant: No exceptions escape to Zed IDE (caught and converted to error responses).


Integration Points

1. LLM Provider Service (SDS-049)

Endpoint: POST http://localhost:8000/v1/chat/completions

Request:

1
2
3
4
5
6
7
8
{
  "messages": [
    {"role": "user", "content": "Validate specs in docs/specs/orders/"}
  ],
  "model": "ollama/llama3.2",
  "temperature": 0.7,
  "max_tokens": 500
}

Response:

1
2
3
4
5
6
7
8
9
10
11
12
{
  "id": "chatcmpl-...",
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "I'll validate the specifications..."
      },
      "finish_reason": "stop"
    }
  ]
}

Implementation:

1
2
3
4
5
async with httpx.AsyncClient(timeout=30.0) as client:
    response = await client.post(
        f"{llm_service_url}/v1/chat/completions",
        json={"messages": messages, "model": model}
    )

Fallback: Echo response with service start instructions.

2. Knowledge Graph Service (SDS-003)

Oxigraph Endpoint: POST http://localhost:7878/query

Request:

1
2
3
4
5
POST /query HTTP/1.1
Accept: application/sparql-results+json
Content-Type: application/x-www-form-urlencoded

query=SELECT ?entity WHERE { ?entity rdf:type sea:Entity }

Response:

1
2
3
4
5
6
7
{
  "results": {
    "bindings": [
      {"entity": {"type": "uri", "value": "http://sea-forge.com/concept/orders/Entity/Order"}}
    ]
  }
}

Fallback - In-Memory RDF:

1
2
3
4
5
6
7
graph = rdflib.Graph()
for snapshot_file in glob.glob(f"docs/specs/{context}/*.snapshot.json"):
    snapshot = json.load(open(snapshot_file))
    turtle_rdf = snapshot["rdf_turtle"]
    graph.parse(data=turtle_rdf, format="turtle")

results = graph.query(sparql)  # Execute locally

Fallback Chain:

  1. Oxigraph HTTP (distributed, persistent)
  2. rdflib in-memory (local, ephemeral)
  3. Error message (no SPARQL capability)

3. Validation Service (SDS-002)

Subprocess Call: python tools/validate_sds.py <file>

Implementation:

1
2
3
4
5
6
7
8
9
result = subprocess.run(
    [sys.executable, "tools/validate_sds.py", path],
    capture_output=True,
    text=True,
    timeout=30
)

if result.returncode != 0:
    errors.append(result.stderr.strip())

Why Subprocess?

4. Pipeline Service (SDS-021)

Just Command: just pipeline <context>

Implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
result = subprocess.run(
    ["just", "pipeline", spec_id],
    cwd=repo_root,
    capture_output=True,
    timeout=120
)

# Parse generated files from output
output_lines = result.stdout.strip().split('\n')
generated_files = [
    line for line in output_lines
    if 'generated' in line.lower()
]

Why Just Command?


Error Handling Strategy

Principle: Fail Gracefully, Guide Users

Every integration point follows this pattern:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
try:
    # Primary: Attempt service call
    result = await call_service(...)
    if result.status_code == 200:
        return success_response(result)
    else:
        # Service returned error
        return error_response_with_guidance(result)
except ConnectionError:
    # Service not running
    return {"error": "Service unavailable. Start with: uvicorn ..."}
except TimeoutError:
    # Service too slow
    return {"error": "Service timeout. Check service health."}
except ImportError:
    # Dependency missing
    return {"error": "Install dependency: pip install httpx"}
except Exception as e:
    # Unexpected error
    return {"error": f"Unexpected error: {str(e)}"}

Never:

Always:


Performance Considerations

Timeouts

Service Timeout Rationale
LLM 30s Chat completion typically < 10s
Validation 30s File I/O + schema validation
Pipeline 120s Large contexts may generate 50+ files
Oxigraph 10s SPARQL queries are fast or hanging

Caching Strategy

Current: No caching (stateless gateway)

Future Optimization:

Concurrency

Current: Serial execution per request (simple, correct)

Future Optimization:


Testing Strategy

Unit Tests

Located in tests/acp/test_acp_gateway.py:

1
2
3
4
5
6
7
async def test_handle_send_message():
    gateway = ACPGateway()
    result = await gateway._handle_send_message({
        "message": {"role": "user", "content": "hello"}
    })
    assert "response" in result
    assert result["response"]["role"] == "agent"

Coverage:

Not Covered (integration tests):

Integration Tests

Recommended: Add tests/acp/test_integration.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@pytest.mark.integration
async def test_llm_service_integration():
    """Test with real LLM service running."""
    gateway = ACPGateway()

    # Start LLM service in background
    proc = subprocess.Popen(["uvicorn", "services.llm-provider.main:app"])
    await asyncio.sleep(2)  # Wait for startup

    try:
        result = await gateway._handle_send_message({
            "message": {"role": "user", "content": "hello"}
        })
        assert result["response"]["content"] != "Service unavailable"
    finally:
        proc.kill()

Deployment

Local Development

1
2
3
4
5
6
7
8
9
# Terminal 1: Start LLM service
cd services/llm-provider
uvicorn main:app --reload

# Terminal 2: Start Oxigraph
docker run -p 7878:7878 oxigraph/oxigraph serve

# Terminal 3: Run ACP Gateway
python -m libs.sea.adapters.acp.src.main

Production (Kubernetes)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# deployment.yaml
apiVersion: v1
kind: Service
metadata:
  name: acp-gateway
spec:
  selector:
    app: acp-gateway
  ports:
    - port: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: acp-gateway
spec:
  replicas: 2
  template:
    spec:
      containers:
      - name: acp-gateway
        image: sea-forge/acp-gateway:latest
        env:
        - name: LLM_PROVIDER_URL
          value: "http://llm-provider:8000"
        - name: OXIGRAPH_URL
          value: "http://oxigraph:7878"

Monitoring

OpenTelemetry Spans

Future: Add OTel instrumentation per SDS-030

1
2
3
4
5
6
7
8
from opentelemetry import trace

tracer = trace.get_tracer("acp-gateway")

async def _handle_send_message(self, params):
    with tracer.start_as_current_span("acp.send_message") as span:
        span.set_attribute("message.role", params["message"]["role"])
        # ... implementation

Metrics

Recommended:


Security Considerations

1. No Authentication (Current)

Status: ACP Gateway runs on stdio (local process)

Threat Model: Zed IDE is trusted (user’s local machine)

Future: If exposing via HTTP, add JWT authentication

2. No Authorization

Status: All capabilities exposed to Zed IDE

Mitigation: Capabilities are read-only or sandbox-safe

3. Command Injection

Risk: User-controlled strings in subprocess calls

Mitigation:

1
2
3
4
5
# BAD: Shell injection risk
subprocess.run(f"just pipeline {user_input}", shell=True)

# GOOD: Array args (no shell)
subprocess.run(["just", "pipeline", user_input], shell=False)

4. Dependency Security

httpx: Pinned to 0.28.1, audit for CVEs rdflib: Pinned to 7.5.0, audit for CVEs

Policy: Update dependencies monthly via Dependabot


Future Enhancements

Phase 1: Performance

Phase 2: Observability

Phase 3: Resilience

Phase 4: Features



Summary

The ACP Gateway demonstrates SEA-Forge™’s graceful degradation philosophy:

  1. Primary: Use production services (LLM, Oxigraph) when available
  2. Fallback: Use local alternatives (echo, in-memory RDF) when not
  3. Guidance: Always provide actionable error messages

This design enables:

Result: Zed IDE integration works out-of-the-box, scales to production.