HN user

luodaint

27 karma

Founder building Feedjolt, customer feedback + public roadmaps for B2B SaaS. Also CTO at Medullar. Trail runner. Techno-optimist.

Posts4
Comments32
View on HN

Agentic development transforms the scope of work. Once a session is committed to having one topic from the get-go — one move, one service, one abstraction — the diff generated from such a session is atomic by construction. Committing happens at the session level, and the commitment discipline problem solved by Jujutsu does not come into play.

It is also true in reverse. Scopes set too broadly ("dark mode implementation," "auth flow fixes") lead to un-readable diffs no matter what tool you use for version control. Un-readable diff does not stem from commitment discipline; it is a scope problem.

That said, this fact does not diminish the usefulness of Jujutsu. There are valid use cases for the rebase and stacking operations. However, the discussion about commit granularity takes on a whole new context once the constraint of having readable commits is established at the scope setting stage.

For organizations that make this an operational reality rather than a slogan, there is likely to be at least one individual for each product development cycle that has had recent contact with a customer. Not from reading a support transcript. From contact. That direct, uncensored communication is what the principle truly represents.

The recovery from being an orphaned customer account serves as the litmus test. In this case, it took someone who was unique and non-interchangeable and poked "the right bear" -- and it succeeded. But that's precisely the way that enshittification of the principle occurs.

Data from six months of production from one SaaS codebase provides a more limited response. Maintainability doesn't depend on the level of AI usage. Maintainability depends on the discipline during diff reviews. Good sessions: One topic per session; scope defined prior to the agent starting; all diffs read prior to committing. Poor sessions: Broad scope; undefined constraints; rubber-stamped results.

The quality of the codebase decays precisely at the rate you stop reading the results. This is not an issue of AI writing the code. This is an issue of unreviewed code. geohot's issue is entirely valid. This problem does exist. But this isn't dependent on the generation phase.

Not those carefully designed constraints that I set up from the beginning, but short-term ones that I came up with after an agent failed in some way: "Validate JWT at the route level, not the component." "Call workspace provisioning on each user creation." Both because of things the agent had done incorrectly.

Aspiration vs. consequence, in other words. An aspiration constraint describes a desired outcome for the system; a consequence constraint maps to a problem already encountered. And the agent ignores the former when faced with the path of least resistance while obeying the latter because it is brief, unambiguous, and precise about preventing that particular failure mode. Which is key rather than the harness in determining survival through session rotation.

Your extensions in VSCode have ambient access to your filesystem, your tokens, and your environment. The servers of tools like Claude Code or Cursor have that ambient access too. This was justified for Nx Console's purposes. This is justified in a coding agent's filesystem MCP. The exact same trust model: install it, it runs, you trust its scope implicitly.

What I ended up changing after contemplating this matter: all my MCP servers are scripts from my repository and not npm packages. All the information about the scopes these servers can use is contained explicitly in my context file (certain directories, certain tools). There's nothing untrusted reaching my filesystem/tokens.

There's the same supply chain problem in VSCode extensions as there is with the MCP servers. Very few companies that audit their extensions started auditing their MCP servers.

In any case, once the agent session finishes, the constraint file for the next session is written by the same individual. In this case, if the individual didn't read every output line by the agent, he will not know what to include in the new constraint file, leading the agent to make some wrong assumptions about the model, leading to some issues at a certain point in time, which, again, he cannot diagnose because his knowledge of the model is incorrect.

Diff reading is not a practice forced on developers from above. On the contrary, it is the only way for a developer to stay competent enough to lead the next session properly.

Instead of discussing how to ensure that developers will understand the importance of diff reading, the question here is whether the developers understand they cannot shift the responsibility of creating a mental model of the system away from themselves and still maintain effective control over the agent's behavior.

When it comes to the business logic of production use, this particular failure type is less obvious compared to benchmarking tasks. Benchmarking involves having the answer already known — it helps detect mismatches easily. Business logic pipeline does not. If LLM gives out a valid output that happens to be semantically incorrect, the pipeline goes through. There is no mistake to catch.

Created a dedupe pipeline where an LLM decides whether two feature requests are similar enough to merge. Occasional mistakes in terms of false positives — valid JSON structure, but incorrectly assessed similarity. In this case, it didn’t help to implement the retry technique. The solution was implementing a deterministic gate validating the output of the model based on its semantic similarity score calculated separately.

The reason why recovery works only with the help of additional tools when the error rate is at zero percent becomes clear: the LLM does not recognize the fact that it made a mistake. The guardrail becomes necessary for that — the retry is just one way of implementing the guardrail concept.

Benchmarks measure turn-level capabilities: you feed a task into the system and then grade the result. Capability for production-level usage concerns session-level decision making: does the agent know when to stop editing, retain the right amount of context, or go back and reread the file if the state has changed?

This is not a property of the model, but a property of the discipline; it can be operationalized by what you have documented before the session begins. Without "stop editing where you can no longer follow your changes to the spec" and "go back and read the migration file before changing the schema," there is nothing to halt the process until it fails integration.

Those teams who get consistent results independent of the model being used typically do so because they have operationalized their discipline first. Those switching out models monthly tend to expect the model to supply them.

If you don't know what exactly the user needs, the AI feature is the pitch itself. "Powered by AI" is something to say when you do not know how to sell the outcome. It's also something to develop when you have not set up the feedback loop to know which outcomes to optimize for.

If the signal is clear – if you have observed the same person facing the same problem in the same workflow – then the AI feature deserves its place in the product by automating one step that they hate. The outcome does not necessarily need to be AI-powered. The user simply stops facing that problem anymore.

The Gruber's logic works on the level of the whole product. But there is also a diagnostic implication here – the louder the product sells its AI capabilities, the less the team understands what exactly the product does.

Metric that measures the quality beyond simple tokens count: correction loop frequency.

When grep does not find a file of interest, the agent does not fail; it will continue working on an incomplete context. For a monolingual code base, the miss rate is okay. In case of polylingual code (Python backend code and TypeScript frontend code), the problems emerge when it comes to querying for cross-file dependencies. Grep will return a route from the backend API. However, there is an interface in TypeScript that needs to be matched. Agent generates a response that does not fit the type. Correction cycle is one; two if the type conflict is ambiguous.

Combining grep with the understanding of semantic relations between files is a solution. Number of tokens saved is real but underestimates the actual benefit since fewer correction cycles are more valuable than tokens themselves.

"The problem is that there are several bottlenecks internally," which include the requirements, specs, and testing. Another one not mentioned in the article.

Before you had faster implementation times, something would take six weeks to implement. Feedback from the client about how far off target you were came through in the same amount of time: a help desk ticket, a post-call check-in, a quarter end review. The price you paid for being off target was proportional to how long it took to figure out.

Now, when you can ship features in an afternoon, the customer feedback loop remains the same speed. Surveys, help desk tickets, and churn analysis come back days, even weeks later, by which point you've shipped five new features going the same way.

You can fix the internal bottlenecks easily enough: write better specs, have faster test cycles, deploy continuously. The customer feedback loop bottleneck is built into the system. It won't get any faster just because implementation did.

Today most organizations are busy fixing the internal bottleneck, but not the external one.

MCP Hello Page 2 months ago

Bearer tokens using mcp-remote are the pragmatic way forward out of the authentication mess.

Spec-based implementations of OAuth 2.0/2.1, including dynamic client registration and token exchanges, are absolutely necessary on a large scale but are an enormous barrier for adoption during early deployments. Cookie tricks ignore the spec and produce special cases when client credentials need to be rotated.

Solution in the real world: configure the server to provide the WWW-Authenticate header with value "Bearer" on unauthorized requests, issue limited-scoped tokens, leave the rest up to mcp-remote. The client authenticates once, uses bearer token in its requests, server verifies against scopes. No dynamic client registration required.

The authentication solution becomes clearer if you distinguish between "what the spec allows" and "what you need in your deployment." Not many servers really require OAuth on day 1.

The difference that affected my approach: CLAUDE.md includes three types of content, which function differently.

Facts (directory organization, commands, references in docs), always work. There’s no discussion here.

Constraints related to regression prevention (specifying certain restrictions based on a particular failure) – work consistently if each individual item doesn’t exceed one sentence and describes a failure I’ve already observed. Example: “Validate JWT at the route level, not the component.” It works since the agent was caught doing it incorrectly. “Always call workspace provisioning when creating a user” – ditto.

Behavior rules (rules regarding comments, naming, dos and don'ts) – this is where the OP fits in. My observation: such rules work if they are concise, specific, and based on an already existing failure pattern. They don’t work if created in anticipation of some failure that didn’t happen yet. Rules made from scratch are usually not followed. The 50 line limitation is justified for this category.

When an agent writes a component, the styles come along with the JSX. "flex items-center gap-4 bg-purple-600 rounded-lg" doesn’t require any mental context switch to a separate stylesheet file. Custom classes force you into a separate file for each component; utility classes keep the style information close at hand.

The other thing Tailwind stops from happening: class name bloat. In its absence, agents invent classes such as "card-header-inner", "feature-block-content", "sidebar-item-wrapper" – all separate naming choices. After a few months of development, you accumulate hundreds of classes that are not owned by anyone. The limit placed by Tailwind is in its vocabulary; there are no names to invent. This trade-off described by Julia exists. It's just articulated a bit differently.

Those sessions which stayed together had something in common: singularity of concern. 1 session = 1 feature, 1 bug, 1 migration. As soon as you have a session covering several subsystems, the diff becomes too large to read properly, and proper reading is the only thing keeping the mental model up-to-date.

Also useful: writing the constraint before the session, not after the failure. "The auth state should be checked on the route level rather than the component" becomes quite clear once you see an agent applying the same rule in three slightly different ways in two files. Writing down the constraint beforehand allows you to detect the violation; rubber-stamping achieves nothing.

What really multiplies in value is not prompting but understanding your own system enough to prompt the agent properly.

As a result of shipping a working product for six months with Claude Code integrated into SaaS production, the only surviving constraints had all been less than 50 lines: migrations' generation vs execution conventions, authentication flow invariants, internationalization setup rules. Any longer constraints would get either selectively ignored or cause confusion when starting sessions.

The important distinction: CLAUDE.md will not explain how the model understands your architecture. Rather, it will prevent certain kinds of regression from happening. "Never create a user without calling the workspace provision step" is the right constraint. "This is how our entire system works" is not – the model learns it from the codebase.

The mistake is writing constraints based on an architecture constructed with slop. The sequence is important here.

The idea of the Pydantic-as-code-smell hinges on the objective being type-safety throughout the codebase. It isn't the aim when an agent creates the majority of the internal logic.

The winning architectural approach: enforcement at the borders, but flexibility within. The agent uses Pydantic for validating FastAPI schemas and models for the database—those are the contracts that need validation. The internal logic the agent produces is subject to line-by-line analysis, rather than being inferred from type propagation.

That's the right way to do things. It isn't some sort of a compromise. There is a clear boundary between validated "external input" and internal logic. And you aren't counting on type inference to propagate across the codebase. You catch errors at the border, where they come into or out of your codebase.

Your criticism of the type system in Python is spot on. The problem is that it is an add-on. It isn't consistent. And a language developed from the ground up for type annotations will do a far better job. However, this isn't the general case for agent-generated codebases.

The problem is that you create an HTTP client for each incoming request. In other words, you recreate SSL context and cause reference cycles with each request. From the memory point of view, the program seems to have a memory leak. However, the solution to the issue lies at the application level: just create your client once and reuse it for each subsequent request.

One of heuristics to find memory leaks can be stated as follows: if you instantiate any HTTP or connection objects inside the request handler, it's likely that you've made a mistake.

There is an iterative kind which applies specifically to the code-writing agents. Accepting the output of your coding assistant without checking whether it is correct will cause the loss of knowledge about your codebase. Context files, such as CLAUDE.md, migration protocols, and authentication protocols, function correctly if you possess sufficient knowledge to be able to update them properly.

Sometimes I have had sessions in which I blindly accepted the code produced by the agent for two hours, but afterwards was not able to create a new context file, having forgotten how my codebase worked. Such skill debt does not appear in the diff – it becomes apparent in situations when you must guide the agent, but cannot do it. Such is the nature of the practice proposed by this skill.

Drop-the-database events occur whenever the review process is bypassed. It’s “Just run it; the agent wrote it” that undermines trust. The migration may be semantically correct yet practically incorrect, such as renaming a column to break a service that is in use or creating an index that locks the table during heavy use.

What Ardent does makes sense for a team setting where several agents/developers require their own environment before deploying code. But from a one-man-show founder’s perspective, the constraint is not about isolation but rather self-discipline.

The framing of the "career-ending" doesn't get what's really changing.

As a solo founder, I've shipped a complete multi-tenant SaaS product: auth, multi-tenancy, webhooks, multiple programming languages, all done via an agent. Did the job go away? Nope. It got reduced to its non-delegable core: choosing what to build, designing the software architecture, reading through all diffs generated by the agent.

These are the kinds of skills that compound; engineers who have developed a holistic and deep understanding of how systems work (instead of just pattern-matching solutions) are increasingly in demand, not the other way around. An agent increases the surface area much quicker, thus rewarding depth.

What's threatened here is the career that wasn't really "engineering" in the first place but was "producing output under supervision." That job used to exist everywhere: engineers shipping code they didn't understand, reviewing PRs without thinking about them. AI can do that job cheaper.

Engineers who ask "will AI replace my job?" are often asking the wrong question. The question should be: "Do I have enough understanding of my systems to direct something smarter than me?"

The article is all about technical communication — diagrams, architectural discussions, code snippets. The more difficult piece to communicate is product sense: which user feedback indicates a genuine trend, when a feature request is a workaround for an underlying issue vs. the issue itself.

It’s not difficult for seasoned engineers with deep technical backgrounds to whiteboard a distributed system in twenty minutes. It takes hundreds of customer discussions, invalid hypotheses, and years of experience building judgment about whether this is the right solution at the right time.

The engineers who compound quickly have usually built their skills in both areas concurrently. Communication of the latter is more challenging due to the judgment-based foundation beneath it.

Good point about subjectivity; it depends entirely on what you are reviewing for.

Static typing holds more ground while making assumptions about contracts between components in huge codebases written by many developers. But in the realm of agents, it all boils down to a simpler question, will this particular function generated by an agent do the job that was requested? Line-by-line readability of Python suffices in this case, regardless of whether type annotations are used throughout.

The pragmatic approach would be to enforce type rules where needed (i.e., when working with Pydantic schemas or in FastAPI routes), while not applying any constraints within the code itself.

But under this frame, it appears that the developer's task involves prompt engineering. This is not the case.

Even if an agent generates 90% of the code, each and every diff is going to be in my review queue. Code readability of Python isn't an advantage during write; it's an advantage while reviewing. As an agent generates a piece of code, I will have to read the code, comprehend the code, and determine whether it does what I want. This is the other 10% of the task, and it's the crucial one.

Python is, thus, clearly superior to other languages in terms of ease of review.

The architecture assignment that I found most useful did not come from a book. It involved the attempt to articulate my system to an agent session starting with no background information at all.

If the agent cannot deduce the constraint, then you have to write it out. Conventions for migration. Invariant properties of the authentication protocol. Multi-tenant behavior. This act of articulation – making your implicit reasoning explicit – led to a kind of clarity that several months of construction alone did not bring. Three architectural errors emerged instantly.

It’s true that learning by doing is the way to go. I would add: learn by explaining it. The agent is an unusually picky audience.

Notable that in each step, there’s an added abstraction; specifically, an authentication abstraction is the hardest one to reverse.

Using a passwordless login from scratch (magic link + Google OAuth2, sessions stored in Postgres without an external auth vendor) gets us around that altogether. The fears about why one would avoid it are generally not justified. Deliverability is the only true problem. Address that, with a proper provider for transactions, and we’re in boring territory – which is the most delightful kind.

To move from Clerk to Better Auth is logical if the choice is between sovereignty and convenience. It’s the core problem that any group doesn’t want to confront right away: “How much of this am I truly willing to own?”

The paper hits the nail right on the head, but it misses the mark on the next constraint: how to decide what to build.

In the old days when writing code took up a lot of resources, the constraint was self-correcting since being off in your implementation was obvious enough that the error could be easily seen after three months of work on the wrong feature. Today, you could spend five wrong efforts in the same amount of time that it used to take you to implement one wrong effort.

That's right, and even more alarming: no one is pushing back, not only the engineers. The leadership team has AI estimates. The product team has AI estimates. Even the engineering team has AI estimates. Everybody says "Ship." So we ship.

What the company lacked was never engineering deliverables; what it really lacked was a prioritization owner who could draw the line. Bad code certainly wasn't the cause of this problem.

#10 needs more emphasis than it receives. Cheaper code doesn't automatically lead to good product decisions.

Instead of focusing on whether you can build it, the scarcer resource becomes whether you should build it. And most teams lack a clear process for addressing this latter question. Requirements are collected in all sorts of places without ever being prioritized in an organized fashion. This is exacerbated by cheaper code. With cheaper code, you can release five times what you used to be able to release in a given period of time, but only if you knew which five products you needed.