HN user

0x63_Problems

298 karma

evan@gauge.sh

Posts13
Comments34
View on HN

Gauge | Founding Engineer | San Francisco, CA | ONSITE | Full-time

Gauge helps companies show up in more AI answers. Search traffic is shifting away from Google into AI chat, and companies are figuring out how to adapt right now. Our system delivers real-time analytics derived from millions of LLM answers, while providing an intuitive, powerful UX. We also generate optimized articles to improve our customers’ visibility, and offer an agentic chat interface over their data (think ‘Why was there a spike on 12/10?’ or ‘Which of my pages should I refresh?’).

I'm Evan, the CTO, and we're looking for engineers with strong programming fundamentals, creative problem solving skills, and a high-agency working style. You might be a good fit if you are an ex-founder, ex-founding engineer, or have worked at a Seed/Series A startup.

We primarily use Django, React, Postgres, Celery, and Clickhouse.

Contact me with your resume (and mention HN) if you're interested: evan[at]withgauge.com

More details/JD also here: https://www.ycombinator.com/companies/gauge/jobs/qemTjv7-fou...

Very cool!

Yup I definitely see confusion in our responses around the product and brand names. We do another pass through an LLM specifically aimed at ‘canonicalizing’ the names, but we’ll need to get more sophisticated to catch most issues.

In that case you mentioned, the brand confusion is what accounts for the top three omission for QBO. Both OpenAI and Perplexity rank it #1, but Anthropic ranks the slightly different “Quickbooks” product as #1. Our overall ranking prioritizes products that appear in all three responses, so both are dropped down.

These are structured results from explicitly asking the LLM for a ranking in the given category, and we provide guidance in the prompt telling the LLM to 'use best judgment' when the topic doesn't clearly include products.

Also we include the 'key features' from each answer - you can see this by clicking the cell containing the rank (e.g. '1st' in the Anthropic column)

In this case, Anthropic said of 'Death during sleep':

Anthropic Analysis for Death During Sleep

    Painless and unaware experience
    No anticipatory anxiety
    Common with certain cardiac conditions
    Often described as 'peaceful'
    No suffering

Yes!

Here is an example project that is configured as if it were a uv workspace: https://github.com/gauge-sh/tach/tree/main/python/tests/exam...

In that project, `tach check-external` would handle between-workspace dependencies, while the core `modules` and `interfaces` config would handle within-workspace dependencies.

Soon these will be better unified, we kept the 1st-party/3rd-party distinction separate while we learned what the UX should be.

This should definitely be supported out-of-the-box, we can take a closer look in Discord or through GH Issues! But generally I would expect:

```

[[modules]]

path = "foo"

depends_on = []

[[modules]]

path = "bar"

depends_on = ["foo"]

```

would do the trick, assuming both are within a configured source root, and their children are _not_ also marked as modules. If the children are marked as modules, their dependency rules are treated separately and wouldn't automatically inherit from a parent.

I can understand the frustration, but I think there are legitimate reasons to run this remotely.

Tach is an installable Python package, shipping a full web app would have to come in a separate form factor and has significant maintenance implications. Given we are explicit about the remote app before anything is sent, require explicit opt-in, and we provide usable alternatives locally, we prioritize shipping a useful graph experience that is immediately usable.

If you are at an enterprise that cannot tolerate this, then you can use a local viewer with either GraphViz DOT format or Mermaid which is generated by using `tach show` or `tach show --mermaid` respectively.

Co-author here, I can understand where you're coming from!

Part of the philosophy here is that the tools and techniques you're describing can (and should) be used diligently to solve this problem, and Tach is often a complement to this approach.

The benefit of centralizing the concern into a single tool, and often a single config file, is that teams get better documentation, earlier feedback (in-editor vs. code review), and more visibility when planning new development. Teams also get to choose _how_ they would like to satisfy Tach's config, and other teams can still rely on the same guarantees due to Tach's static checks.

Co-author here, fair question!

In short, we want to make the visualization UX as smooth as possible, and this is best done with a web app. The URLs use UUIDs, and the contents being sent don't include literal source code, only module names and Tach configuration. We will also delete graphs by UUID on request, and have done so in the past.

That said, we do try to be up-front about this, which is why that disclaimer exists, and when running this command on the CLI, you must supply an explicit `--web` argument to `tach show`. Otherwise, the default behavior is to generate a GraphViz DOT file locally.

I'm a big fan of the modular monolith pattern, but haven't used feature flags for the purpose you're describing. Do you use any specific tools or frameworks for that? I'd also imagine there would be calls between features from within the same codebase, do those become network calls? And how does this interact with your Docker Compose/single server recommendation?

That's a good question. I don't have an easy way to rerun the comparison since this happened actually a while ago, but I do remember some relevant numbers.

In the first iteration of the Rust extension, I actually used the parser from RustPython. Although I can't find it at the moment, I think the RustPython parser was actually benchmarked as worse than the builtin ast parse (when both returned Python objects).

Even with this parser, IIRC the relevant code was around 8-11x faster when it avoided the Python objects. Apart from just the 35% spent in GC itself, the memory pressure appeared to be causing CPU cache thrashing (`perf` showed much poorer cache hit rates). I'll admit though that I am far from a Valgrind expert, and there may have been another consequence of the allocations that I missed!

Very fair points. For general purpose ASTs from Python your design should be more efficient while essentially keeping the existing interface.

When I referenced numpy, I was thinking of a query layer which could push traversal into the extension as well. Something that could have given me “.select(ast.Import).all()”, which in my head is kind of like doing a filtered sum in numpy.

Very cool to get your thoughts on this, thanks for making an account :)

This seems like it could be implemented without much trouble for consumers, but I actually think for the common case of full AST traversal you'd still want to avoid building objects for the nodes while traversing.

That is to say, ast.NodeVisitor living in Python is part of the problem for use cases like mine. I need the extension to own the traversal as well so that I can avoid building objects except for the result set (which is typically a very small subset). That was what led me to imagine a query-like interface instead, so that Python can give concise traversal instructions.

First off, thank you for all your contributions to Python!

I completely take your point that there are many places where this approach won't fit. It was a surprise for me to trace the performance issue to allocations and GC, specifically because it is rare.

WRT boxing and unboxing, I'd imagine it depends on access patterns primarily - given I was extracting a small portion of data from the AST only once each, it was a good fit. But I can imagine that the boxing and unboxing could be a net loss for more read-heavy use cases.

Totally agree, keeping the interface with the extension as thin as possible makes sense.

I hadn't considered object pooling in this context, it might be more involved since each node has distinct data but for my use case it might still be a performance win.

Have you ever used pyo3 for rust bindings? I haven't measured the overhead but I have been assuming that it's worth the tradeoff vs. rolling my own.

(I'm the author)

In my experience, Python devs rarely use the underscore.

Package authors are pretty good about it. But when the only people using your code are your coworkers, people don't seem to put as much thought into having a clean interface.

I quit a job with nothing lined up, but it was due to a liquidity event for a former startup. Not enough to permanently stop working, but also not quite as counter-culture as this describes.

In retrospect, the experience was primarily about changing my relationship to work. I can still feel the mindset shift. Being able to test my internal narrative about the urgency and primacy of work (by forcefully deprioritizing it) gave me a stronger sense of agency and a wider view on what I might do each day.

I hope that the people referenced in the article have the economic leniency or support systems to experience something similar.

The article mentions that the business still had almost zero revenue in 2022, which would seem to mean that it went for 5 years and through Series A without making any money?

Congrats to the team, but what really strikes me about this article is how unfamiliar that story feels in today's startup environment.

It's kind of interesting to think about how tighter funding environments lead to teams breaking up and reorganizing more often (likely within the 5 year period for Clay) whereas the looser, ZIRP-like environment allowed the same team to keep learning and iterating in roughly the same space until it eventually worked.

I'm definitely a fan of making bad data 'unrepresentable' through the type system. The bit flag enum pattern is not so familiar to me in Python, but I recognize it from the CPython internals so maybe it's more common in systems languages. Not sure how I feel about explicitly listing every valid combination of flags, but I'm sure in some cases that's the right move.

Is the kernel of this advice to start with a known problem which is already solved with a paid service, and then just sell that service more efficiently? That's how I'm interpreting the "skip the R&D phase" idea. In that case I think the question becomes "why is this a better strategy for a startup", since this framing implies going after well-established markets with (at first) an undifferentiated product. Both 'productize' and 'automate' sound like things that incumbents would be able to do more effectively than startups, and likely has already happened in most cases silently in the background.

Probably the most formative experience in my early career was seeing a massive (attempted) rewrite from a monolith to microservices. Multi-unicorn company spent a year and $Ms in developer hours just to realize that the network separation did more harm than good. There are use cases for network separation (fault tolerance, independent scaling, polyglot teams etc.) but for essentially every startup I've been at, the better bet is to enforce logical boundaries in the codebase itself.