DEV Community

Cover image for How I Used Aura to Build a Lead Discovery Tool Without Burning Tokens
CarpseDeam
CarpseDeam

Posted on • Originally published at aura-ide.hashnode.dev

How I Used Aura to Build a Lead Discovery Tool Without Burning Tokens

Signal Desk finds relevant conversations, ranks them as leads, writes reviewable inbox cards, and helps draft outreach replies. I built it with Aura’s phased workflow, visible diffs, validation, and cheap models for about ten cents.


While building Aura, I try to greenfield real projects with it as often as possible.

Not toy prompts. Not “make a todo app.” I mean projects that are actually useful, a little messy, and close to the kind of work I do in real life.

Most of the time, I run the same project twice: once before a major Aura change, and once after. It becomes a practical A/B test:

  • Did Aura get faster?
  • Did the output get cleaner?
  • Did the project shape improve?
  • Did I have to fight it less?

That is the test I care about, because real codebases do not behave like polished demos. They evolve in phases. Requirements shift. Some parts are obvious, some are fuzzy, and the tool has to stay useful through all of it.

For this release, the dogfood project was Signal Desk, a local lead discovery and outreach tool.

What Signal Desk Does

Signal Desk scans sources like RSS and Hacker News, turns relevant posts into candidates, scores them as potential leads, writes reviewable inbox cards, and helps draft replies.

The product loop is:

sources → candidates → dedupe → scoring → findings → inbox cards → show / ignore / reply
Enter fullscreen mode Exit fullscreen mode

That is the kind of workflow I like testing Aura against. It is small enough to build quickly, but real enough to expose whether the generated code has an actual product shape.

The Setup

For this run, I used cheap models for both planning and implementation.

Aura running Planner and Worker on DeepSeek V4 Flash. The full dogfood build cost about ten cents in model usage._

That cost matters, but the point is not just “cheap tokens.”

The point is that cheap models are getting good enough that the harness around them matters more and more. If Aura can keep the work focused, visible, and validated, low-cost models can go much further than people expect.

The model is the fuel.

Aura is the harness that turns it into controlled work.

My Aura Workflow for Larger Projects

For larger projects, I usually do not ask Aura to build the whole thing in one giant prompt.

My workflow is:

  1. Write a real spec.
  2. Ask Aura to split it into phases.
  3. Dispatch one phase at a time.
  4. Let Aura execute the slice.
  5. Inspect the output, validation, files, and commits.
  6. Move to the next phase.

This keeps the model focused, and it keeps me in control.

Instead of reviewing a huge mystery blob at the end, I can inspect each slice as it lands.

The Phase Plan

Signal Desk was split into phases:

  • Phase 1: Project skeleton and init command
  • Phase 2: Data models and storage
  • Phase 3: Source adapters
  • Phase 4: Scoring and ranking
  • Phase 5: Reply drafting
  • Phase 6: Inbox and remaining CLI commands
  • Phase 7: Tests and polish

Aura starts from a real spec, then turns it into a focused phase with concrete files and validation targets._

That phase structure matters. Aura is not just being asked to “make an app.” Each dispatch has a focused job.

The Flow Is the Feature

My favorite part of using Aura is the flow.

When Aura is working well, it feels smooth and quick. Planner shapes the slice, Worker moves through the files, validation runs, the project tree changes, and the output starts becoming real.

That is the feeling I am chasing: AI coding that does not feel like wrestling. It feels like watching the work click into place.

A good run looks like this:

spec → phase → files → validation → next phase
Enter fullscreen mode Exit fullscreen mode

Not chaos. Not blind trust. Not “hope the agent did the right thing.”

Just a controlled build loop.


Caption: Phase 1 completed with acceptance criteria checked, validation run, and a clean handoff to the next phase.

What Aura Built

By the end, Signal Desk had:

  • an installable Python package
  • signal-desk init
  • local config and workspace setup
  • SQLite storage
  • RSS and Hacker News sources
  • candidate deduplication
  • deterministic scoring
  • markdown inbox cards
  • reply drafting
  • scan, watch, inbox, show, ignore, and reply commands
  • 109 tests passing

Caption: By the end, Signal Desk had a full project structure and 109 tests passing.

The final user loop looked like this:

scan sources → rank leads → review inbox → inspect details → ignore or draft a reply
Enter fullscreen mode Exit fullscreen mode

That is the difference between generated files and a usable product slice.

Example: The Main Product Loop

This is the kind of output I am looking for from Aura.

The scan command is not just a placeholder. It follows the actual product flow.

def _run_scan(config_dict: dict) -> dict:
    started_at = datetime.utcnow()

    conn = db.init_db(paths.get_db_path())
    logging_setup.setup_logging(paths.get_logs_dir())

    sources_cfg = config_dict.get("sources", {})
    scoring_cfg = config_dict.get("scoring", {})
    dedupe_days = scoring_cfg.get("dedupe_days", 14)

    source_instances = []

    if sources_cfg.get("rss", {}).get("enabled", True):
        source_instances.append(RSSSource(sources_cfg["rss"], config_dict))

    if sources_cfg.get("hackernews", {}).get("enabled", True):
        source_instances.append(HNSource(sources_cfg["hackernews"], config_dict))

    all_candidates = []

    for source in source_instances:
        candidates = source.fetch()
        all_candidates.extend(candidates)

    new_candidates = []

    for candidate in all_candidates:
        existing = find_candidate_by_url_or_normalized_title(
            conn,
            candidate.url,
            candidate.title,
            dedupe_days,
        )

        if existing is None:
            new_candidates.append(candidate)

    findings = rank_candidates(conn, new_candidates, config_dict)

    for finding in findings:
        if finding.priority in ("high", "medium"):
            write_inbox_card(finding, paths.get_inbox_dir())

    insert_scan_run(conn, scan_run)
Enter fullscreen mode Exit fullscreen mode

What I like here is the shape:

config → sources → candidates → dedupe → scoring → inbox → scan record
Enter fullscreen mode Exit fullscreen mode

That is a real product loop. The code is not trying to be impressive. It is trying to be useful.

Example: Domain-Shaped State

Signal Desk has a simple domain:

  • sources produce candidates
  • candidates become findings
  • findings go into the inbox
  • scan runs record what happened
@dataclass
class Candidate:
    id: str
    source: str
    source_item_id: str | None
    title: str
    body: str
    url: str
    author: str | None
    created_at: datetime | None
    fetched_at: datetime
    raw: dict


@dataclass
class Finding:
    id: int | None = None
    candidate_id: str = ""
    source: str = ""
    title: str = ""
    url: str = ""
    score: int = 0
    priority: str = "low"
    reason: str = ""
    best_angle: str = ""
    avoid_note: str = ""
    draft_reply: str = ""
    status: str = "new"


@dataclass
class ScanRun:
    id: int | None = None
    started_at: datetime | None = None
    finished_at: datetime | None = None
    candidates_found: int = 0
    new_candidates: int = 0
    findings_created: int = 0
    errors: str | None = None
Enter fullscreen mode Exit fullscreen mode

This is boring in the right way.

The names match the product. The state is explicit. The rest of the project has something clean to build around.

That is the kind of code I want from AI: not generic DataManager soup, but simple concepts that belong to the thing being built.

Example: Scoring That Explains Itself

For a lead discovery tool, scoring needs to be inspectable. I do not just want a number. I want to know why something landed in the inbox.

def score_candidate(candidate: Candidate, config: dict) -> dict:
    aura_cfg = config.get("aura", {})

    include_kw = aura_cfg.get("include_keywords") or []
    pain_kw = aura_cfg.get("pain_keywords") or []
    avoid_kw = aura_cfg.get("avoid_keywords") or []

    title = candidate.title or ""
    body = candidate.body or ""
    combined = title + " " + body

    score = 0
    reason_parts = []
    avoid_note_parts = []

    for keyword in _check_exact_matches(title, include_kw):
        score += 35
        reason_parts.append(f"Matched '{keyword}' in title (+35)")

    for keyword in _check_exact_matches(body, include_kw):
        score += 20
        reason_parts.append(f"Matched '{keyword}' in body (+20)")

    if _has_alternative_intent(combined):
        score += 10
        reason_parts.append("Alternative intent detected (+10)")

    for keyword in _check_exact_matches(combined, avoid_kw):
        score -= 40
        avoid_note_parts.append(f"Avoid keyword '{keyword}' matched (-40)")

    score = max(0, min(100, score))

    return {
        "score": score,
        "reason": "; ".join(reason_parts) or "No strong signals detected",
        "best_angle": best_angle,
        "avoid_note": "; ".join(avoid_note_parts),
    }
Enter fullscreen mode Exit fullscreen mode

This is exactly what I want for this kind of tool.

The scoring is deterministic. The reasons are visible. The user can inspect why something matched before deciding whether to respond.

Example: Reviewable Inbox Cards

Signal Desk does not just dump rows into a database. It writes markdown cards that can be reviewed directly.

def format_finding_card(finding: Finding, include_draft: bool = True) -> str:
    priority_emoji = {
        "high": "🟢",
        "medium": "🟡",
        "low": "",
    }.get(finding.priority, "")

    lines = [
        f"# {priority_emoji} {finding.priority.upper()} · score {finding.score}",
        "",
        f"**Source:** {finding.source}",
        f"**Title:** {finding.title}",
        f"**URL:** {finding.url}",
        f"**Status:** {finding.status}",
        "",
        "## Why this matters",
        finding.reason or "No reason recorded.",
        "",
        "## Best angle",
        finding.best_angle or "No angle recorded.",
        "",
        "## Avoid",
        finding.avoid_note or "None",
    ]

    if include_draft and finding.draft_reply:
        lines += ["", "## Draft reply", finding.draft_reply]

    return "\n".join(lines)
Enter fullscreen mode Exit fullscreen mode

That is product thinking.

The user’s job is not “own a database.” The user’s job is to review useful conversations and decide whether to respond.

The output supports that job.


Caption: Signal Desk generated ranked lead cards from scanned conversations.

Example: Reply Drafting

Signal Desk also helps draft replies from a finding.

It supports different styles and a no-Aura mode for cases where I want to respond without mentioning the project directly.

def generate_reply(
    finding: Finding,
    style: str = "helpful",
    no_aura: bool = False,
) -> str:
    ctx = _build_context(finding)

    if no_aura:
        reply = no_aura_template(ctx)
    elif style == "technical":
        reply = technical_template(ctx)
    elif style == "short":
        reply = short_template(ctx)
    else:
        reply = helpful_template(ctx)

    if no_aura and "aura" in reply.lower():
        reply = no_aura_template(ctx)

    return reply
Enter fullscreen mode Exit fullscreen mode

Again, this connects back to the workflow.

Signal Desk is not just finding leads. It helps move from discovery to outreach.

Why This Matters

What I liked about this run was not that Aura generated a lot of code.

It was that the code had a shape I could keep working with.

The CLI followed the user flow. The models matched the domain. The storage layer served the product. The scoring explained itself. The inbox output was reviewable. The reply drafting connected back to the findings.

That is what I want from AI coding:

  • not a pile of files
  • not blind trust
  • not an expensive black box
  • not code I have to babysit forever

I want a useful slice I still control.

That is what Aura is built around.

Where Aura Is Headed

Aura is an open-source desktop AI coding harness.

The goal is simple:

Ask for something real.
Break it into phases.
Keep control.
Review the work.
Validate the result.
Spend almost nothing.
Get code that feels like it belongs in the repo.
Enter fullscreen mode Exit fullscreen mode

That is the workflow I want to build with every day.

Aura is open source on GitHub:

[(https://github.com/CarpseDeam/Aura-IDE)]

Top comments (0)