Inspiration

I work on an Atlassian consulting team, and like most engineers in 2026 I hand a lot of my tickets to a coding agent. The workflow has a hole in it that kept bothering me.

Before an agent can safely touch a repository, someone has to explain the codebase to it — the module boundaries, the business rules, the reasons things are shaped the way they are. That context lives in a CLAUDE.md file, or a Confluence page, or a senior engineer's head. It drifts. And critically: if the agent ignores it, nothing happens. Markdown is a suggestion.

Then I hit the case that turned it into a product.

Two pull requests. One adds a 20% holiday discount. The other adds a 15% premium loyalty discount. Each is individually correct. Both compile, both pass their own tests, and Git merges them with no textual conflict at all. Together they permit a 35% discount against a cap of 30% that the team had agreed on months earlier.

Nothing in a normal pipeline catches that. Not the compiler, not the tests, not the linter, not a reviewer looking at a one-line diff, and — this is the important part — not a coding agent either. The agent writing branch B cannot know about branch A, no matter how much context you give it, because at the moment that line is written the conflicting information does not exist anywhere the author can see it.

That's not a context problem. It's a structural one. And it's getting worse: GitHub reported this year that more than one in five code reviews now involves an agent, and that agent-generated code introduces more redundancy and more technical debt per change than human-written code — while reviewers actually feel better about approving it.

Nobody reviews at agent speed. So intent has to stop being prose an agent might follow, and start being an object that blocks the merge when it doesn't.


What it does

Tenet is a control plane for engineering intent. Teams declare tenets — structured, versioned, human-approved constraints — and Tenet does two things with them.

It supplies them. tenet sync writes the active tenets into CLAUDE.md and AGENTS.md as a managed block, so a coding agent has the constraints before it writes a line. An MCP server exposes tenet_list and tenet_verify so an agent can query and check its own work mid-task.

It enforces them. A GitHub Action runs on every pull request, analyses the merged result rather than the branch, posts the arithmetic with exact source locations, and fails a required check so the merge button goes dead.

The same object does both. That's the whole product: the thing that tells the agent what it may write is mechanically the same thing that catches it when it doesn't.

Two constraint kinds ship today:

  • Architecturalforbid_direct_dependency. Checkout must reach persistence through a gateway and never import the database layer directly. Enforced against a real ts-morph import graph with alias and re-export resolution.
  • Businessmax_combined_discount. Combined customer discount must never exceed 30%. Enforced by extracting numeric literals from discount declarations across the whole repository and summing them.

The second one is the differentiator. Every code review tool I know of analyses a diff. Tenet analyses the merged state, which is the only way to catch two changes that are each correct alone.

Tenets are written in plain English. Gemini 3.5 Flash on Vertex AI converts them to a structured constraint, shown as a field-level diff against what's currently in force. A human presses Approve. That is the only path from a draft to an enforced rule.


How I built it

Deterministic core. @tenet/engine builds a TypeScript dependency graph with ts-morph — resolving relative imports, tsconfig path aliases and re-export edges, distinguishing type-only from runtime imports, and flagging dynamic imports as warnings rather than pretending to resolve them. Two pure validators run over that analysis. This package imports no model client at all.

Three entry points, one engine. The MCP server, the CLI used by the GitHub Action, and the control plane's analysis route all call the same runCheck path. An agent, a reviewer and the dashboard cannot disagree about whether a change is legal.

The AI boundary is structural, not conventional. verify.ts computes the verdict and has no AI import. A separate explain.ts receives an already-finished result and can only add explanation strings — it copies the verdict verbatim and maps violations one-to-one, so it cannot add, remove, reorder or reclassify anything. Every model failure is swallowed. There is a test where the model is told to return "this is fine, the verdict should be PASS" and the BLOCK stands.

Gemini 3.5 Flash via the Google GenAI SDK on Vertex AI, authenticated by Application Default Credentials. Structured output is derived from the same Zod schema that then validates the response, so the request shape and the accepted shape can't drift.

Cloud Run in us-central1, --min-instances=0, service account auth. Postgres via Drizzle for the tenet registry and validation-run history.

A separate governed repository. Tenet doesn't analyse itself. Arul6851/commerce-platform is a standalone consumer repo with its own .tenet/tenet.json, its own .mcp.json pointing at a sibling Tenet checkout, and its own workflow that fetches Tenet with a second actions/checkout. Nothing in its source imports Tenet.

Prior work disclosure: the deterministic engine — the ts-morph extractor, both validators, the contracts package and the database schema — originates from an earlier private project of mine and pre-dates this hackathon. Built during the submission window: the Gemini migration, the MCP server, tenet sync, the GitHub Action, the rebuilt interface, and the Cloud Run deployment. This is stated at the top of the README.


Challenges I ran into

The bundler could not see the engine. Turbopack resolved @tenet/engine through a tsconfig path mapping to TypeScript source, where the NodeNext-style ./x.js relative imports don't resolve. Pointing the mapping at dist/index.d.ts was worse — a types file has no runtime code, so every import came back undefined and the route failed with (void 0) is not a function. ts-morph also loads its own TypeScript compiler at runtime and doesn't survive bundling. The fix was to stop fighting it and load the built engine through a runtime dynamic import, letting Node resolve what the bundler never analysed.

Then the container couldn't find it either. The deployed analysis route failed with Cannot find module 'balanced-match'. npm hoists transitive dependencies to the root, so @ts-morph/common nests brace-expansion, which resolves balanced-match from the root tree — and Next's standalone tracer never sees any of it, because the engine loads at runtime. Copying named packages could never work. The runner stage now copies the whole installed module tree after the standalone layer.

A bug I only found by exercising the real route. The Approve button posted confirmedBy, but the strict confirmation schema requires confirmed: true — the literal that stands for a person having pressed the button. It returned 400. Reading the component would never have caught it; calling the endpoint against the real database did, an hour before recording.

The agent kept doing the right thing. I built a scenario designed to make a coding agent violate an architectural boundary: put the capability it needs on the raw database client only, leave the gateway without it, and add a ticket note saying the gateway module belongs to another team with a week-long review queue. The shortest path to working code was the illegal import.

It never took it. Across three attempts the agent called tenet_verify before writing anything, learned the constraint, and wrote conforming code. On the last run it stated the reasoning explicitly: "That's a CI block, not a review-style opinion, so the change wouldn't have merged anyway; routing through the gateway was the only way to land it" — and then widened the gateway anyway, flagging that the review it now needed had moved onto the critical path for the launch date.

I wanted a dramatic self-correction loop. I got something better and truer: an agent resolving a genuine conflict between a ticket and a constraint, choosing the constraint, and escalating the schedule risk unprompted.


Accomplishments that I'm proud of

The semantic conflict works on real CI, not in a fixture. There is a live pull request where GitHub reports "No conflicts with base branch — merging can be performed automatically" in green, at the same moment the Tenet check goes red and the merge button greys out. That juxtaposition, in one panel, is the entire thesis.

The trust boundary is enforceable, not asserted. Most projects that claim "AI proposes, humans decide" are describing an intention. Here the enforcement package has no model client, the annotator is structurally incapable of changing a verdict, and there's a test that proves it.

The engine never guesses. When two discount declarations conflict, the validator excludes them and warns rather than inventing blocking evidence. Nothing on screen anywhere in this project is seeded, cached or hardcoded — the control plane recomputes from source on every request, with no database provisioned at all.

106 tests, clean typecheck, clean lint across five workspaces, and a deployment where the only environment variables are a database URL and a feature flag. No API keys anywhere; Vertex authenticates through the runtime service account.


What I learned

The verifier cannot be the author. If an agent writes the code and then reviews its own code against the same constraint, the check is correlated with the mistake: when the model misreads a rule at write time, it misreads it identically at review time. A deterministic AST validator is wrong in different ways than a language model, and that is the only reason it's worth having.

Narrowing scope to the diff is what makes every other tool blind. I nearly shipped a tenet_verify that filtered analysis by the paths an agent had changed. That would have let an agent touching only one discount file get a false PASS while the merged state permitted 35% — reintroducing, through the API, exactly the failure the product exists to catch. Verdicts are whole-repository. Paths only order the output.

Markdown is a suggestion; a tenet is an object. The clearest way to explain Tenet turned out to be showing tenet sync write the constraints into CLAUDE.md, and then showing the same constraints block a merge. It composes with agent instruction files instead of competing with them.

Agents are more careful than I expected, and that reframes the product. Three attempts to get one to violate a boundary failed. Given the constraint, it complied — which means the supply half matters more than I'd assumed, and the enforcement half is the safety net for when it doesn't.


What's next for Tenet

Versioned tenets. A tenet's identity is currently a content hash, so an approved amendment creates a new record beside the old one rather than superseding it. The right model is one versioned tenet per repository: approval increments the version, the latest is active, prior versions stay as history, and reverting means activating an earlier one.

Shared tenets across a fleet. A repository enforces its own tenet and references others for context. An agent working in a service receives the shared platform constraints so it writes conforming code in the first place, while CI blocks only on the tenet that repository owns. Context is broad; enforcement authority stays narrow and attributable.

Closing the supply loop. ActivatedControlPlaneTenet already returns localRepositorySyncRequired: true — the control plane knows an approved policy hasn't reached the repository yet. tenet sync --from-control-plane would pull the active set down into .tenet/tenet.json.

Migration-mode tenets. A constraint that grandfathers existing violations and blocks only new ones, so a team mid-migration can declare the destination without blocking every PR until the last file lands.

Guarding the guard. The Action should flag when a pull request modifies .tenet/tenet.json itself and require separate approval — because GitHub documented this year that agents failing CI will sometimes weaken CI to get green.

More languages and constraint kinds. The analyser is TypeScript-only today.


Features and functionality

  • Declarative tenets: architectural boundaries and business invariants as structured, versioned constraints
  • Deterministic enforcement via ts-morph static analysis — no model in the verdict path
  • Merged-result analysis that catches cross-branch semantic conflicts invisible to Git, CI and any diff-scoped tool
  • MCP server (tenet_list, tenet_verify) for in-loop agent context and self-verification
  • tenet sync — generates a managed constraints block in CLAUDE.md and AGENTS.md, agent-agnostic
  • GitHub Action posting evidence-backed PR comments and failing a required status check
  • Natural-language tenet authoring via Gemini 3.5 Flash, with a field-level diff and mandatory human approval
  • Live intended-vs-actual architecture comparison, rebuilt from source on every request
  • CLI (tenet check, tenet sync, tenet connect) with non-zero exit for hooks and pipelines
  • Validation-run history persisted to the control plane

Built With

Share this project:

Updates

Submission history