Swarms Logo

Swarms for Real Estate

Lease abstraction, acquisition due diligence, listing generation, rent roll review and portfolio reporting, run as agent teams on the Swarms API.

  • Lease reader, term extractor, critical-dates and abstract-writer agents in one pipeline
  • Structured abstracts in the shape you define, ready for the asset management system
  • Token usage and cost per lease, per property and per deal in Swarms Cloud
Riverside Plaza · lease expiries2 expiring within 12 monthsPaused
now+1y+2y+3yNorthwind Dentalsuite 410Carraway Bookssuite 120Pell & Grange LLPsuite 600Vela Fitnesssuite 100Orchard Pediatricssuite 520Tessera Studiosuite 210Marlow Coffeesuite 105Halden Insurancesuite 300
expires within 12 monthsrenewal optionrent stepnotice date

Lease Reader: Northwind Dental, suite 410, base lease and amendments ordered

Use cases

Where real estate teams put agents to work

Each card names the Swarms API swarm type it maps to and the agents you would send. Abstracts, memos and listings come back as drafts; a lease administrator, analyst or counsel reviews them.

SequentialWorkflow

Lease abstraction

A reader orders the lease and its amendments, an extractor pulls the terms with page citations, a dates agent computes notice windows, and a writer assembles the abstract in your schema. Counsel reviews the flagged clauses.

  • Lease Reader
  • Term Extractor
  • Critical Dates
  • Abstract Writer
HierarchicalSwarm

Acquisition due diligence

A director splits the data room into workstreams, title, environmental, financials and physical, and worker agents read their folders and draft findings. The director integrates them into a diligence memo for the deal team.

  • Deal Director
  • Title Agent
  • Environmental Agent
  • Financials Agent
  • Physical Agent
ConcurrentWorkflow

Listing generation

From one fact sheet, agents write the long description, the highlights list and the portal snippets at the same time, each within the fair housing language rules you send as rules.

  • Description Writer
  • Highlights Writer
  • Portal Snippets Writer
MixtureOfAgents

Comparable sales and rent analysis

Sales comps, rent comps and market context are each prepared by a specialist on the same subject property, and an aggregator writes the valuation support note with the adjustments it made.

  • Sales Comps Agent
  • Rent Comps Agent
  • Market Agent
  • Aggregator
MultiAgentRouter

Tenant request routing

Tenant emails go to one specialist each: maintenance, leasing, billing or legal notices. The router returns the draft reply or the work order summary and the reason for the route.

  • Router
  • Maintenance Agent
  • Leasing Agent
  • Billing Agent
  • Notices Agent
DebateWithJudge

Investment committee pre-read

One agent argues the case for the acquisition, another argues against it on the same underwriting, and a judge writes the pre-read with the points the committee should press on.

  • Bull Case
  • Bear Case
  • Judge

Code

Run it from Python, cURL or TypeScript

The Swarms API examples share one request shape. The TypeScript example runs due diligence as a HierarchicalSwarm, the Swarms Cloud example reconciles a portfolio run against the logs, and the framework example writes listings in-process. Set SWARMS_API_KEY from your Swarms Cloud account before running.

lease_abstraction.py
import os
from swarms_client import SwarmsClient

client = SwarmsClient(api_key=os.environ["SWARMS_API_KEY"])

# The lease and its amendments as text, already OCR'd by your document system.
lease_text = open("leases/riverside-plaza-suite-410.txt").read()

# The abstract schema the Abstract Writer must fill. This is your own shape,
# sent in the prompt, so the output drops straight into the asset system.
ABSTRACT_FIELDS = (
    "tenant, premises, rentable_sf, commencement, expiration, base_rent (list of "
    "{from, to, annual_psf}), renewal_options (list of {term_months, notice_by, "
    "rent_basis}), termination_option, security_deposit, cam_structure, "
    "exclusive_use, assignment_rights, critical_dates (list of {date, event}), "
    "flags_for_counsel (list of {clause, page, reason})"
)

response = client.swarms.run(
    name="Lease Abstraction",
    description="Lease to a structured abstract with page citations and counsel flags.",
    swarm_type="SequentialWorkflow",
    task=f"Abstract this lease into the fields {ABSTRACT_FIELDS}.\n\n{lease_text}",
    rules=(
        "Quote the page for every extracted term. Never infer a date that is not "
        "stated or computable from stated terms. Where the lease and an amendment "
        "conflict, the later document wins and the conflict is flagged."
    ),
    agents=[
        {
            "agent_name": "Lease Reader",
            "system_prompt": (
                "You read commercial leases. Identify the base lease and every amendment, "
                "order them by date, and produce a clause map: section, title, page range."
            ),
            "model_name": "gemini-2.5-flash",  # long context, cheap for a first pass
            "max_loops": 1,
        },
        {
            "agent_name": "Term Extractor",
            "system_prompt": (
                "Using the clause map, extract premises, term, base rent schedule, options, "
                "deposit, CAM, exclusive use and assignment terms. Cite a page for each."
            ),
            "model_name": "gpt-4.1-mini",
            "max_loops": 1,
        },
        {
            "agent_name": "Critical Dates",
            "system_prompt": (
                "Compute every critical date from the extracted terms: rent steps, option "
                "notice windows, expiration, termination windows. Show the arithmetic."
            ),
            "model_name": "claude-haiku-5-5",
            "max_loops": 1,
        },
        {
            "agent_name": "Abstract Writer",
            "system_prompt": (
                "Assemble the final abstract as JSON with exactly the requested fields. Put "
                "any ambiguous or unusual clause in flags_for_counsel with its page."
            ),
            "model_name": "claude-sonnet-5-5",  # the document a person will read
            "max_loops": 1,
        },
    ],
    max_loops=1,
)

print(response)
lease_abstraction.sh
curl -X POST https://api.swarms.world/v1/swarm/completions \
  -H "x-api-key: $SWARMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Lease Abstraction",
    "description": "Lease to a structured abstract with page citations and counsel flags.",
    "swarm_type": "SequentialWorkflow",
    "task": "Abstract the attached lease into: tenant, premises, rentable_sf, commencement, expiration, base_rent schedule, renewal_options, termination_option, security_deposit, cam_structure, exclusive_use, assignment_rights, critical_dates, flags_for_counsel. LEASE TEXT: ...",
    "rules": "Quote the page for every extracted term. Never infer a date that is not stated or computable. Where the lease and an amendment conflict, the later document wins and the conflict is flagged.",
    "agents": [
      {
        "agent_name": "Lease Reader",
        "system_prompt": "Identify the base lease and every amendment, order them by date, and produce a clause map with page ranges.",
        "model_name": "gemini-2.5-flash",
        "max_loops": 1
      },
      {
        "agent_name": "Term Extractor",
        "system_prompt": "Extract premises, term, base rent schedule, options, deposit, CAM, exclusive use and assignment terms. Cite a page for each.",
        "model_name": "gpt-4.1-mini",
        "max_loops": 1
      },
      {
        "agent_name": "Critical Dates",
        "system_prompt": "Compute rent steps, option notice windows, expiration and termination windows from the extracted terms. Show the arithmetic.",
        "model_name": "claude-haiku-5-5",
        "max_loops": 1
      },
      {
        "agent_name": "Abstract Writer",
        "system_prompt": "Assemble the abstract as JSON with exactly the requested fields. Put ambiguous or unusual clauses in flags_for_counsel with their page.",
        "model_name": "claude-sonnet-5-5",
        "max_loops": 1
      }
    ],
    "max_loops": 1
  }'

# The response carries "output" (the abstract) and "usage" with
# input_tokens, output_tokens and total_tokens for the run.
runDiligence.ts
// Acquisition due diligence as a hierarchical swarm: the director splits the
// data room into workstreams, the workers draft findings, the director
// integrates them. The memo is a draft for the deal team and counsel.
const SWARMS_API = "https://api.swarms.world/v1/swarm/completions"

type DataRoom = { property: string; folders: Record<string, string[]> }

type SwarmResponse = {
  output: unknown
  usage: { input_tokens: number; output_tokens: number; total_tokens: number }
}

const worker = (name: string, folder: string, focus: string, model: string) => ({
  agent_name: name,
  description: "Reads the " + folder + " folder of the data room.",
  system_prompt:
    "You are a due diligence analyst. Read the documents listed for the " + folder + " folder " +
    "and draft findings on: " + focus + " For each finding give the document, the page and " +
    "whether it is a closing condition, a price adjustment or a note. Mark anything that " +
    "needs outside counsel or a consultant.",
  model_name: model,
  max_loops: 1,
})

export async function runDiligence(room: DataRoom): Promise<SwarmResponse> {
  const index = Object.entries(room.folders)
    .map(([folder, docs]) => folder + ":\n  " + docs.join("\n  "))
    .join("\n")

  const res = await fetch(SWARMS_API, {
    method: "POST",
    headers: {
      "x-api-key": process.env.SWARMS_API_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "Acquisition Diligence " + room.property,
      description: "Director-led diligence across four workstreams.",
      swarm_type: "HierarchicalSwarm",
      task:
        "Run due diligence on " + room.property + ". Data room index:\n" + index +
        "\nReturn a diligence memo with findings by workstream, open items and a closing checklist.",
      rules: "Cite the document and page for every finding. Do not estimate values that are not in the documents.",
      agents: [
        worker("Title Agent", "title", "vesting, easements, encroachments, exceptions on the commitment, survey conflicts.", "gpt-4.1-mini"),
        worker("Environmental Agent", "environmental", "Phase I findings, RECs, tank records, remediation history, next steps.", "claude-haiku-5-5"),
        worker("Financials Agent", "financials", "rent roll vs leases, trailing twelve, recoveries, arrears, capital spend.", "gpt-5.4"),
        worker("Physical Agent", "physical", "roof and structure, mechanical age, deferred maintenance, ADA, code items.", "gemini-2.5-flash"),
      ],
      max_loops: 1,
    }),
  })

  if (!res.ok) throw new Error("Swarms API " + res.status + ": " + (await res.text()))
  return (await res.json()) as SwarmResponse
}

runDiligence({
  property: "Riverside Plaza, 212 Market St",
  folders: {
    title: ["commitment-2026-02.pdf", "alta-survey-2025.pdf", "recorded-easements.pdf"],
    environmental: ["phase-i-2026-01.pdf", "ust-closure-1998.pdf"],
    financials: ["rent-roll-2026-03.xlsx", "t12-2026-02.xlsx", "cam-recon-2025.pdf"],
    physical: ["pca-2026-01.pdf", "roof-warranty.pdf", "elevator-inspection-2025.pdf"],
  },
}).then((r) => {
  console.log(r.output)
  console.log("tokens:", r.usage.total_tokens)
})
reconcile_runs.py
import os
import requests

BASE = "https://api.swarms.world"
HEADERS = {"x-api-key": os.environ["SWARMS_API_KEY"]}

# After a portfolio abstraction run, pull the request history from Swarms
# Cloud and reconcile it: every lease that was submitted should have a
# completed run, and the token usage per property goes to the cost report.
# Request names follow "Lease Abstraction <property> <suite>".
logs = requests.get(f"{BASE}/v1/swarm/logs", headers=HEADERS, timeout=60).json()
metrics = requests.get(f"{BASE}/v1/metrics/summary", headers=HEADERS, timeout=30).json()


def walk(node, key):
    """Yield every value stored under key anywhere in a nested JSON document."""
    if isinstance(node, dict):
        for k, v in node.items():
            if k == key:
                yield v
            yield from walk(v, key)
    elif isinstance(node, list):
        for item in node:
            yield from walk(item, key)


submitted = {
    ("Riverside Plaza", "410"), ("Riverside Plaza", "520"), ("Riverside Plaza", "600"),
    ("Harbor Point", "100"), ("Harbor Point", "210"), ("Harbor Point", "300"),
}

seen: set[tuple[str, str]] = set()
tokens_by_property: dict[str, int] = {}

for entry in walk(logs, "data"):
    if not isinstance(entry, dict):
        continue
    name = str(entry.get("name", ""))
    if not name.startswith("Lease Abstraction "):
        continue
    rest = name.removeprefix("Lease Abstraction ")
    prop, _, suite = rest.rpartition(" ")
    seen.add((prop, suite))
    total = sum(int(t) for t in walk(entry, "total_tokens") if isinstance(t, (int, float)))
    tokens_by_property[prop] = tokens_by_property.get(prop, 0) + total

missing = submitted - seen
for prop, suite in sorted(missing):
    print(f"no run found for {prop} suite {suite}: resubmit")

for prop, total in sorted(tokens_by_property.items()):
    print(f"{prop:24s} {total:>10,} tokens")

print("account metrics:", metrics)
listing_writers.py
from swarms import Agent, ConcurrentWorkflow

# The open-source swarms package runs the listing writers in-process. Three
# writers work from one fact sheet at the same time; the output is a draft
# the leasing team reviews before it goes to the portals.
FACT_SHEET = """
Unit 7B, 1180 Elm Street. 2 bed, 2 bath, 1,140 sf, 7th floor, south-facing.
Renovated 2024: quartz counters, induction range, in-unit washer and dryer.
Building: 2008, elevator, bike room, roof deck, 24-hour entry system.
Rent 3,250 USD per month, 12-month minimum, available 1 June. One cat permitted.
Transit: 6-minute walk to the Elm Street station. Parking space 175 USD per month.
"""

RULES = (
    "Fair housing: describe the property, never the ideal tenant. No references to "
    "families, religion, national origin, disability or any protected class. State "
    "only facts in the sheet. No superlatives."
)

description = Agent(
    agent_name="Description Writer",
    system_prompt="Write a 120 to 160 word listing description from the facts. " + RULES,
    model_name="claude-sonnet-5-5",
    max_loops=1,
)

highlights = Agent(
    agent_name="Highlights Writer",
    system_prompt="Write eight bullet highlights, each under 60 characters, from the facts. " + RULES,
    model_name="gpt-4.1-mini",
    max_loops=1,
)

snippets = Agent(
    agent_name="Portal Snippets Writer",
    system_prompt=(
        "Write a 70-character headline and a 150-character summary for listing portals, "
        "plus a two-line social post. " + RULES
    ),
    model_name="gpt-5.4-mini",
    max_loops=1,
)

workflow = ConcurrentWorkflow(
    name="Listing Writers",
    description="Description, highlights and portal snippets from one fact sheet.",
    agents=[description, highlights, snippets],
    max_loops=1,
)

draft = workflow.run(FACT_SHEET)
print(draft)  # reviewed by the leasing team before publishing

The Python SDK (pip install swarms-client) sends one request that abstracts a lease as a SequentialWorkflow. The reader maps the documents on a long-context model, the extractor and dates agents run on small models, and the writer assembles the abstract in the fields you named in the task. Flagged clauses go to counsel.

Architecture

Lease abstraction as a sequential workflow

Read, extract, compute dates, write. Each agent receives the previous agent's output, and the model changes with the job: a long-context model for the first read, small models for extraction and arithmetic, a strong writer for the abstract a person will read. Watch a lease move through the pipeline.

swarm_typeSequentialWorkflowSimulation paused
READEXTRACTDATESWRITETaskPOST /v1/swarmLease Readergemini-2.5-flashTerm Extractorgpt-4.1-miniCritical Datesclaude-haiku-5-5Abstract Writerclaude-sonnet-5-5Abstract200 OK · JSON
  • 01run #1041 started

The reader orders the documents

A lease rarely arrives alone. The first agent finds the base lease and every amendment, orders them by date and maps the clauses to pages, so the extractor works from a table of contents instead of 80 loose pages.

Every term carries a page

The rules string asks for a page citation on every extracted term and forbids inferring dates that are not stated or computable. A reviewer can open the lease to the page instead of re-reading it.

Ambiguity goes to counsel

The writer puts anything it could not resolve into flags_for_counsel with the clause, the page and the reason. The agents draft the abstract; a lease administrator or counsel decides what it means.

Output

What the abstraction returns

The abstract's shape is yours. You name the fields in the task and ask the Abstract Writer to return exactly those as JSON; the API hands the writer's output back in the output field. Below is an example of the response shape we asked the agents to produce for the Suite 410 lease. The field names are ours, not the API's.

example abstract · output field of the responseexample
{
  "tenant": "Northwind Dental PLLC",
  "premises": "Suite 410, Riverside Plaza, 212 Market St",
  "rentable_sf": 3240,
  "commencement": "2021-09-01",
  "expiration": "2027-08-31",
  "base_rent": [
    { "from": "2021-09-01", "to": "2024-08-31", "annual_psf": 27.00, "page": 14 },
    { "from": "2024-09-01", "to": "2027-08-31", "annual_psf": 28.50, "page": 14 }
  ],
  "renewal_options": [
    { "term_months": 60, "notice_by": "2026-11-30", "rent_basis": "95% of fair market", "page": 31 }
  ],
  "termination_option": null,
  "security_deposit": { "amount": 14580, "form": "cash", "page": 9 },
  "cam_structure": { "type": "pro rata", "base_year": 2021, "cap_pct": 5, "page": 18 },
  "exclusive_use": "general dentistry within the building",
  "assignment_rights": "landlord consent not unreasonably withheld; affiliates permitted",
  "critical_dates": [
    { "date": "2026-11-30", "event": "renewal notice deadline" },
    { "date": "2027-02-28", "event": "landlord relocation notice window closes" },
    { "date": "2027-08-31", "event": "expiration" }
  ],
  "flags_for_counsel": [
    { "clause": "Relocation", "page": 22, "reason": "amendment 2 changes the notice period; base lease still says 120 days" },
    { "clause": "Exclusive use", "page": 27, "reason": "carve-out for 'medical office' is broader than typical" }
  ]
}

Where each field comes from

  • base_rent[]

    Term Extractor, with the page for each step

  • renewal_options[]

    Term Extractor, notice_by computed by Critical Dates

  • critical_dates[]

    Critical Dates, with the arithmetic in the run log

  • flags_for_counsel[]

    Abstract Writer, every ambiguity it could not resolve

Make it your schema

Rename fields, add your asset system's identifiers, or drop what you do not track. The task string carries the field list, so the same pipeline can produce a retail abstract for one portfolio and an office abstract for another. Keep flags_for_counsel: it is where the agents say what they could not decide.

Swarms Cloud

Every lease and every deal with its run, its findings and its cost

The board shows a due diligence run as the director's workers draft findings and the analyst reviews them. Each run is logged in Swarms Cloud with every agent's output, token usage, latency and cost, so a deal team can trace a finding back to the document and a portfolio lead can see what the quarter's abstraction cost.

Riverside Plaza · diligence boardHierarchicalSwarm · 4 workstreamsPaused
pending 17drafted 0reviewed 0reviewed by the deal analyst

Title

0/4

Title Agent · gpt-4.1-mini

  • Commitment exceptions
  • ALTA survey vs legal
  • Recorded easements
  • Zoning letter

Environmental

0/4

Environmental Agent · claude-haiku-5-5

  • Phase I RECs
  • UST closure records
  • Asbestos survey
  • Wetlands check

Financials

0/5

Financials Agent · gpt-5.4

  • Rent roll vs leases
  • T12 normalisation
  • CAM reconciliation
  • Arrears and credits
  • Capex history

Physical

0/4

Physical Agent · gemini-2.5-flash

  • PCA immediate repairs
  • Roof warranty
  • Elevator inspection
  • ADA items

Analyst notes

Phase I lists one REC: former dry cleaner, 1998 UST closure letter on file

Swarms Cloudlease-abstraction · SequentialWorkflowSimulation paused

Runs / min

14

▲ last 30s

p50 latency

2.3s

▼ last 30s

Tokens / min

89.5k

▲ last 30s

Spend / hour

$3.11

▲ last 30s

Every run on the Swarms API appears in Swarms Cloud with its logs, token usage, cost and context use.

Open Swarms Cloud

Questions real estate teams ask

What asset managers, lease administrators, acquisitions analysts and proptech engineers ask before they put agents on a portfolio.

How accurate is lease abstraction, and who checks it?

The pipeline is built so a person can check it quickly: every extracted term carries a page citation, dates show their arithmetic, and anything ambiguous lands in flags_for_counsel. The abstract is a draft for a lease administrator or counsel to review, and the agents never decide a legal question. Teams usually start by running the swarm on leases they have already abstracted by hand and comparing the two.

Which swarm type fits which real estate workflow?

SequentialWorkflow when each step depends on the last (read, extract, compute dates, write). HierarchicalSwarm when a director should split a job into workstreams and integrate them, as in due diligence. ConcurrentWorkflow when several writers should work from the same facts at once. DebateWithJudge when the committee wants both sides of a deal argued on the same underwriting.

Swarm types on the Swarms API

Can we define our own abstract schema?

Yes. The fields are yours: name them in the task and tell the Abstract Writer to return exactly those as JSON. The API returns the writer's output as the output field, so the shape of the abstract is whatever you asked for, and it can match your asset management system's import format.

What does a portfolio of leases cost to abstract?

It depends on lease length and the models you pick. The reader on a cheap long-context model such as gemini-2.5-flash carries most of the input tokens; the extractor and dates agents on small models are cheap; the writer on claude-sonnet-5-5 produces the document a person reads. Each response includes the token usage for the run, and Swarms Cloud totals it per request name, so cost per property is visible after the first few leases.

Model pricing

Where do the documents go?

The lease text goes in the request to the Swarms API, and the run is logged in Swarms Cloud with its inputs and outputs for your account. Teams that cannot send documents to a hosted API can run the same agents in-process with the open-source framework and keep the API for workflows that use public or already-shared documents.

Swarms framework

Abstract your next lease with a swarm

Create a Swarms Cloud account, paste one lease into the example above, and name the fields you track. The abstract comes back with page citations and counsel flags, and the run shows up in your logs with its cost.

New Swarms Cloud accounts start with $5 in API credits. Enterprise teams can talk to us about volume pricing, data handling and deployment options.