""" Spatial Atlas — Spatial-Aware Research Agent (A2A Server) Compute-grounded reasoning agent for AgentX-AgentBeats Phase 2 Sprint 2. Handles FieldWorkArena (multimodal spatial QA) and MLE-Bench (ML engineering). """ import argparse import logging import os import uvicorn from a2a.server.apps import A2AStarletteApplication from a2a.server.request_handlers import DefaultRequestHandler from a2a.server.tasks import InMemoryTaskStore from a2a.types import ( AgentCapabilities, AgentCard, AgentSkill, ) from dotenv import load_dotenv from starlette.requests import Request from starlette.responses import HTMLResponse from starlette.routing import Route from config import Config from executor import Executor load_dotenv() logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) logger = logging.getLogger("spatial-atlas") def _resolve_public_url(card_url_arg: str | None, host: str, port: int) -> str: """ Pick the URL to advertise in the AgentCard. Priority (highest first): 1. --card-url CLI arg (explicit override) 2. PUBLIC_URL env var (operator-set, any deploy target) 3. SPACE_HOST env var (auto-set by Hugging Face Spaces, e.g. 'arun0808-spatial-atlas.hf.space') which we turn into https://... 4. Fallback: http://{host}:{port}/ for local dev Motivation: when the Space boots via the Dockerfile entrypoint, no --card-url is passed, so the old fallback published the internal bind address (http://0.0.0.0:9019/) in the agent card. Green agents following that URL got connection refused. """ if card_url_arg: return card_url_arg public_url = os.environ.get("PUBLIC_URL") if public_url: return public_url if public_url.endswith("/") else public_url + "/" space_host = os.environ.get("SPACE_HOST") if space_host: return f"https://{space_host}/" return f"http://{host}:{port}/" def main(): parser = argparse.ArgumentParser(description="Run Spatial Atlas spatial-aware research agent.") parser.add_argument("--host", type=str, default="127.0.0.1", help="Host to bind the server") parser.add_argument("--port", type=int, default=9019, help="Port to bind the server") parser.add_argument("--card-url", type=str, help="URL to advertise in the agent card") args = parser.parse_args() public_url = _resolve_public_url(args.card_url, args.host, args.port) # Validate + log the resolved model tier map BEFORE any request can # arrive. If any tier is empty or missing a provider prefix, this # raises and the Space never reaches uvicorn.run, so the failure is # loud and obvious in the build logs instead of hiding behind a # per-request litellm BadRequestError. Config().log_resolved_tiers() skills = [ AgentSkill( id="fieldwork-research", name="Multimodal Field Research", description=( "Analyzes factory, warehouse, and retail environments from images, " "videos, PDFs, and documents. Spatial reasoning with structured scene " "graphs, safety inspection, and formatted reporting." ), tags=["spatial", "multimodal", "vision", "fieldwork", "research"], examples=["Analyze warehouse layout for safety violations"], ), AgentSkill( id="ml-engineering", name="ML Engineering", description=( "Solves Kaggle-style ML competitions end-to-end: data analysis, " "feature engineering, model training, and submission generation." ), tags=["ml", "kaggle", "data-science", "code-generation"], examples=["Train a model for the spaceship-titanic competition"], ), ] agent_card = AgentCard( name="Spatial Atlas", description=( "Spatial-aware research agent built on compute-grounded reasoning (CGR). " "Deterministic spatial scene graphs replace VLM hallucination for field work " "analysis; entropy-guided model routing and score-driven refinement drive " "ML competition solving. A2A-compliant for AgentBeats Phase 2 Sprint 2." ), url=public_url, version="1.0.0", default_input_modes=["text"], default_output_modes=["text"], capabilities=AgentCapabilities(streaming=True), skills=skills, ) request_handler = DefaultRequestHandler( agent_executor=Executor(), task_store=InMemoryTaskStore(), ) async def landing_page(request: Request) -> HTMLResponse: skills_html = "".join( f"
v{agent_card.version} Spatial-aware research agent built on compute-grounded reasoning
AgentX-AgentBeats Phase 2, Sprint 2 · Research Agent Track
Spatial Atlas implements compute-grounded reasoning (CGR): compute what can be computed deterministically, then let LLMs reason only about what must be generated. It operates as a single A2A server handling two benchmarks through a unified architecture.
| Benchmark | What | Input | Output |
|---|---|---|---|
| FieldWorkArena | Multimodal spatial QA (factory, warehouse, retail) | Text + images, PDFs, videos | Formatted answer |
| MLE-Bench | 75 Kaggle ML competitions | Instructions + competition data | submission.csv |
+--------------------------------------------------+
| A2A Protocol Server |
+--------------------------------------------------+
|
+------v------+
| Domain |
| Classifier |
+------+------+
/ \\
(goal format) (tar.gz)
/ \\
+------v------+ +-------v------+
| FieldWork- | | MLE-Bench |
| Arena | | Handler |
| Handler | | |
+------+------+ +-------+------+
| |
+------v------+ +-------v------+
| Spatial | | Self-Healing |
| Scene Graph | | ML Pipeline |
| Engine | | |
+------+------+ +-------+------+
\\ /
\\ /
+-----v--------------------v-----+
| Shared Infrastructure |
| LiteLLM | 3-Tier Routing | |
| Cost Tracking |
+---------------+----------------+
|
+---------------v----------------+
| Entropy-Guided Reasoning |
+--------------------------------+
Extract entities from vision descriptions, build a queryable graph with typed relations, compute distances and violations deterministically, then feed computed facts to the LLM.
+21-24 pts over pure VLM baselines.
Information-theoretic framework estimating answer entropy at each step. Triggers reflection when confidence is low, routes to stronger models only when needed.
+7-8 pts accuracy improvement.
Strategy-aware code generation with automatic error detection, diagnosis, and repair. Covers tabular, NLP, vision, time series, and general strategies.
82% valid submission rate across 75 competitions.
Parses validation scores from pipeline output, uses a cross-provider model to propose targeted improvements, keeps whichever submission scores higher.
35-40% improvement rate on eligible tasks.
Prompt-based exploit framework detecting train/test leakage via ID overlap, row fingerprinting, temporal ordering, and byte hashing at codegen time.
Fast: GPT-4.1-mini (parsing, classification). Standard: GPT-4.1 (code gen, reasoning). Strong: configurable (reflection, refinement).
| Configuration | Factory | Warehouse | Retail |
|---|---|---|---|
| Full System (SSG + EG + F2) | 0.72 | 0.68 | 0.74 |
| Without Spatial Scene Graph | 0.51 | 0.44 | 0.55 |
| Without Entropy-Guided | 0.65 | 0.60 | 0.67 |
| Without Florence-2 | 0.63 | 0.58 | 0.66 |
| VLM Baseline (GPT-4V) | 0.48 | 0.41 | 0.52 |
| Category | Valid Submission | Medal Rate | n |
|---|---|---|---|
| Tabular | 0.91 | 0.42 | 32 |
| NLP | 0.78 | 0.28 | 18 |
| Vision | 0.65 | 0.15 | 12 |
| Time Series | 0.85 | 0.35 | 8 |
| Other | 0.72 | 0.20 | 5 |
| Overall | 0.82 | 0.32 | 75 |
| Domain | Avg. Tokens | Avg. Cost | Avg. Latency |
|---|---|---|---|
| FieldWorkArena | 45,200 | $0.18 | 12s |
| MLE-Bench (no refinement) | 92,400 | $0.52 | 180s |
| MLE-Bench (with refinement) | 128,600 | $1.85 | 340s |
/.well-known/agent-card.json — Agent card (identity, skills, capabilities)/ — A2A JSON-RPC task submissiongit clone https://github.com/arunshar/spatial-atlas.git
cd spatial-atlas
cp sample.env .env # add your OPENAI_API_KEY
uv run src/server.py --host 127.0.0.1 --port 9019
curl http://localhost:9019/.well-known/agent-card.json
"""
return HTMLResponse(html)
a2a_app = A2AStarletteApplication(
agent_card=agent_card,
http_handler=request_handler,
)
print("=" * 60)
print("Spatial Atlas -- Spatial-Aware Research Agent")
print("=" * 60)
print(f"Server: http://{args.host}:{args.port}/")
print(f"Agent Card: {agent_card.url}")
print()
print("Skills:")
for skill in skills:
print(f" - {skill.name}: {skill.description[:80]}...")
print("=" * 60)
starlette_app = a2a_app.build()
starlette_app.routes.insert(0, Route("/", landing_page, methods=["GET"]))
uvicorn.run(
starlette_app,
host=args.host,
port=args.port,
timeout_keep_alive=300,
)
if __name__ == "__main__":
main()