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
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.
It's from previous searches actually, we have an 'enrichment' step after the initial rankings come back which helps with semantic deduplication and tries to give us a canonical website domain. We store the Product and tag all matching rankings: https://productrank.ai/product/microsoft and use a 3rd party to map website <-> brand logo.
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
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 - We do provide a web platform (https://www.gauge.sh/platform) which we have been developing with design partners. The fundamental difference between using Tach alone vs. the platform is that the platform provides incremental enforcement at the pull request level.
We're always happy to chat about adding more design partners!
email: [email protected]
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.
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.
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...