PlanPilot build on it

← appAPI referenceaccount

PlanPilot is a tool layer, not just an app. Everything the built-in agent can do, you can do, from your own agent over MCP, or by publishing your own agent and data as content. Follow the ten-minute path from zero to your own planning agent, then try your own implementation.

No key needed for the map. The planner interface at / works without an account: type a request, read the map, share a plan link. Reads on the API are open too: GET /api/features, /api/geocode?q=, /api/layers, /api/agents, /api/plans/{id}.

A key is needed for compute and write calls (/api/clearance, /api/route, /api/points, /api/trace, /api/corridor, /api/along, /api/plans, /api/agents, /api/layers) and for MCP. No terminal? Every endpoint can be tried from the API reference page with the key pasted into Authorize.

Limits: 2000 characters per message, 60 turns per conversation, 12 tool rounds per reply, 5000 features per layer, 10 messages per minute per connection. Handles (agent and layer names) are first come, first served and can only be changed by the account that created them; pick a team prefix like team7-.

Quickstart

  1. Sign in and mint a key. Sign in with Google, then create an API key on /account. It is shown once. Everything below sends it as Authorization: Bearer pp_….
    export URL=https://goneon.luquematte.com
    export KEY=pp_your_key_here
  2. Validate something. The constraint engine is a distance query against a layer. This point sits on a sewer junction, so it fails:
    curl -s -X POST $URL/api/clearance -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
      -d '{"geometry":{"type":"Point","coordinates":[8.5145897,47.366881]},"layer":"sewer","min_distance_m":3}'
  3. Route along the streets. Cables and pipes follow the road graph; offset_m puts the trench beside the centerline where existing services live. Crossings come back as point violations to engineer, not failures:
    curl -s -X POST $URL/api/route -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
      -d '{"start":"Goldbrunnenstrasse","end":"Hardstrasse","mode":"roads","offset_m":2,"clearance_rules":[{"layer":"water","min_distance_m":1}]}'
    # start/end take street names or addresses ("Saumstrasse 23"), resolved server-side, or [lng, lat]
  4. Bring your own agent (MCP). The same tools over the Model Context Protocol, for Claude Code, Claude Desktop or any MCP client:
    claude mcp add --transport http planpilot $URL/mcp/ --header "Authorization: Bearer $KEY"
    # then: "place trees every 15 m along Aemtlerstrasse avoiding sewer and gas by 3 m"
  5. Upload your own data layer. Any GeoJSON (Point / LineString / Polygon, up to 5000 features) becomes a layer every validator accepts, under a handle you choose:
    curl -s -X POST $URL/api/layers -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{
      "name": "team7-busstops",
      "features": {"type":"FeatureCollection","features":[
        {"type":"Feature","properties":{"name":"Stop A"},"geometry":{"type":"Point","coordinates":[8.5205,47.3715]}}
      ]}}'
    # now {"layer":"team7-busstops","min_distance_m":5} works in any clearance rule, and /?layers=team7-busstops draws it
    # delete with: curl -X DELETE $URL/api/layers/team7-busstops -H "Authorization: Bearer $KEY"
  6. Publish your own agent. An agent is a prompt part plus a tool grant on top of the system base. No code. write decides whether it may save plans:
    curl -s -X POST $URL/api/agents -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{
      "name": "team7-planner",
      "prompt": "You plan for team 7. Always keep 5 m from team7-busstops and explain trade-offs in one paragraph.",
      "tools": ["search_features","check_clearance","propose_route","place_points","list_layers","save_plan"],
      "write": true }'
    Then chat with it: $URL/?agent=team7-planner&layers=team7-busstops. Re-POST the same name to publish a new version; running conversations stay pinned to the version they started on.
  7. Share results. When an agent saves a plan it returns a plan_url, a read-only page anyone can open: $URL/plans/<id>.

What you get

Discovery is open (GET /api/agents, GET /api/layers, GET /api/plans/{id}); creating and changing needs your key. Full reference at /docs.

What the checks do and do not prove. Every clearance is a 2D distance in meters (Swiss LV95) between a geometry and the features of a layer, sampled every 5 m along lines. It does not know depth, slope, elevation or cover; nothing here verifies a gravity sewer, and the assistant is told to say so. The clearance defaults (1 m from underground services, 3 m for trees from services, 4 m between trees) are assumptions to override per project, not standards.

The utilities are synthetic. Sewer, water and gas were seeded on the real street centerlines, so a route with a 2 m offset is clean by construction along the street and only conflicts at crossings. That demonstrates the mechanism, not the district. A real cadastre loads through the same import script, and the checks then mean what they say. Buildings, streets, addresses and trees are real OpenStreetMap data.

Example entry: street lighting on Zweierstrasse

A worked example of what an entry looks like end to end: three tool calls and one published agent. Street lighting is a placement problem with three constraints that all live in this data: keep lamp foundations away from underground services, keep lamps out of tree crowns, and know which facades each lamp will light.

  1. Place the lamps. Every 30 m at a sidewalk offset, 1 m from any underground service and 3 m from existing trees. Rejections come back with the reason and distance.
    curl -s -X POST $URL/api/points -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
      -d '{"along_road":"Zweierstrasse","spacing_m":30,"offset_m":4,"asset":"lamp","avoid":[{"layer":"sewer","min_distance_m":1},{"layer":"water","min_distance_m":1},{"layer":"gas","min_distance_m":1},{"layer":"trees","min_distance_m":3}]}'
  2. Try the other side. Same call with "offset_m": -4; compare placed and rejected counts. The side with fewer rejections wins, or you mix sides per block.
  3. What does each lamp light? Feed one placed point back in and list the facades within 12 m.
    curl -s -X POST $URL/api/along -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
      -d '{"geometry": {"type":"Point","coordinates": [8.5203, 47.3711]}, "layer":"buildings", "within_m":12}'
  4. Ship it as an agent. Publish the workflow as content so anyone can chat with it at /?agent=lighting-planner:
    curl -s -X POST $URL/api/agents -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{
      "name": "lighting-planner",
      "prompt": "You plan street lighting. For a requested street: place_points with asset lamp every 30 m at a 4 m sidewalk offset, 1 m from sewer, water and gas and 3 m from trees; try both sides and keep the better one; then features_along on two sample lamps to name the facades within 12 m. Report placed, rejected with reasons, and the lit facades, then save the plan.",
      "tools": ["place_points","features_along","check_clearance","search_features","list_layers","save_plan"],
      "write": true }'

Challenge tracks

A · Street trees

9,436 real mapped trees. Plant a row along a street that keeps 3 m from underground services and 4 m from existing trees, then make the agent explain every rejection.

Tools: place_points, check_clearance, list_layers

B · Underground routing

Route a cable between two addresses along the road graph. Minimize crossings of water mains; treat unavoidable crossings as engineering points and list them.

Tools: propose_route (mode=roads, offset_m, avoid), search_features

C · Open track: sewage

Gravity sewers need continuous downhill slope, a physics rule rather than a distance rule. This is deliberately not built. It plugs in as one validator next to check_clearance (sample the line, look up elevation, check slope ≥ 0.5%), one tool definition, and an elevation layer (swissALTI3D is open data). Build the rule; bring the data.

Where it lives: app/tools.py, app/agent.py TOOL_DEFS

Rules of the platform

Built as the goNEON Platform & Ecosystem Owner take-home. Demo area: Zürich Kreis 3, 4 and 5.