Building Orden, an autonomous document-filing agent, alone, for the Google All Things Agentic Hackathon 2026


The idea started with Hazel, which I have used happily for years. It is excellent software: you write a rule, and it runs quietly forever.

Rules have one specific failure, though, and it is the failure that made this project worth building. That is where Orden came from.

Most hackathon write-ups are reverse-engineered into a straight line after the fact. This one was not straight. I deleted my cleverest feature four days before the deadline. I shipped a security hole to a public URL and did not notice for a week. I lost an entire evening to a dependency I had never heard of, percent-encoding a pair of parentheses.

I finished with a deployed agent at ordenagent.com, a four-minute video, and sixty-five commits over ten evenings. Here is what actually happened.

(This article was written for the Google All Things Agentic Hackathon, 2026.)

The Orden landing page: an autonomous agent that reads and files documents from a Google Drive folder
Orden, live at ordenagent.com. The hosted demo runs the real agent over invented paperwork, so it needs no sign-in and touches nobody's Drive.

The word

Orden is Spanish. I am Spanish, and it is one of those words that does not quite survive translation.

English splits the concept. Order is either a command, a sequence, or a purchase, and only distantly the state of things being tidy. Spanish keeps them fused: orden is the arrangement, the calm of a thing being where it belongs, and poner orden (to put order) is what you do to a mess. There is no English verb for it that does not sound clinical. “Organise” is a filing cabinet. Poner orden is a feeling.

Naming it in my own language did something I did not anticipate. Every time I wrote orden.core in an import statement, I was reminded that the product is not “a document classifier”. It is the absence of that low background hum of paperwork you have not dealt with yet. That is a different thing to build, and it made me throw away features that classified documents beautifully but did not make the hum stop.

It is also, pleasingly, four letters, unclaimed as a .com, and unambiguous to type.


The inspiration was Hazel, and the argument against it took one paragraph

A Hazel rule is a string match: if the filename contains this, if the text contains that, move it there. It works until it does not, and the way it fails is specific.

You need one rule per vendor, so setup cost grows without limit. And when a supplier changes their invoice template, the rule silently stops firing. Nothing tells you. The file just sits in the inbox, indistinguishable from a file that has not arrived yet, and you find out in March.

That failure mode is the entire justification for putting a model in the loop. Reading the document instead of matching its filename changes the shape of the problem: an issuer the system has never seen needs no configuration at all, because the question being asked is what is this rather than does this string appear.

Diagram of how Orden works: it reads each document, decides where it belongs, files it, and learns from corrections
The argument in one picture: read the document, rather than match its filename.

I made myself write that as one paragraph before writing any code, because if I could not state why the AI was not decoration, a judge was going to ask the same question in the first ten seconds and I would have no answer. It went near the top of the README. It is also the docstring of core/classify.py, which is deliberate. I wanted the argument sitting in the file that makes the claim true:

"""Document understanding: turn a file's bytes into a filing decision.

Rule engines match strings and need one rule per vendor. This asks for meaning,
so an unseen issuer needs no configuration, and every answer carries an explicit
confidence and a reason the user can read.
"""

Reading the weights before having the idea

The hackathon published its judging criteria, which is common and almost universally ignored:

WeightCriterion
40%Innovation and operational usefulness
30%Architectural discipline and correct use of the stack
30%Demo quality and production readiness

Read that as a solo entrant on a sofa. Sixty per cent of the score has nothing to do with how clever the idea is. It is a diagram, a README that works first try, and a video with a script.

That 60% is not won by inspiration. It is won by doing the boring parts properly, and the boring parts are exactly what a team under pressure sacrifices first, because four people generate features faster than one evening can package them. A team beats you on volume. You beat them on finish. That asymmetry is the only structural advantage a solo entrant has, and it is substantial.

The prize table said the same thing. The Grand Prize had one winner and the entire field, including funded startups. But there was an Individual / Hobbyist category with two winners, restricted to people in exactly my situation, and Best Architectural Design with two more, a prize you win with a diagram and a README rather than with more agent code.

So the target was set before a line of code, and it was not the Grand Prize. That is not lowering ambition. That is aiming it where it converts.

The stack was mandatory and non-negotiable: Gemini 3.5 or newer, at least one Google agent framework, at least one Google Cloud service. I chose Google ADK, Cloud Run and Firestore.


The filter I put ideas through

An inbox of 33 real documents with meaningless filenames like doc(1).pdf and Scan_20260131_0002.pdf
Real documents, exactly as they arrive. Thirty-three of them named doc(1).pdf, Scan_20260131_0002.pdf, download (9026).pdf, none of those names chosen by me. You cannot fake a demo of a problem you do not have.

I had a list of candidates. The instinct is to pick the most impressive one. That instinct is wrong. Four questions killed everything else:

Does it survive the four-hour test? “Operational usefulness” is 40% of the score, and you satisfy it by being able to say a sentence with two numbers in it. Ninety seconds per document (open it, work out what it is, choose a folder, rename, move) times thirty-three documents is forty-nine minutes of my life, and that is one inbox, once.

Do I personally have this problem? Not “is it real”. Do I have it, today, in a folder I could open right now. This matters for a reason that only becomes obvious at recording time: you cannot fake a demo of a problem you do not have. Judges watch dozens of videos. They can tell the difference between someone showing real documents and someone showing test1.pdf, test2.pdf, test3.pdf.

Is it autonomous, or is it a chat window? The brief asked for agents operating beyond standard chat loops. Every hackathon in 2026 is drowning in chat interfaces. An idea where a human types a prompt and reads a reply was dead on arrival.

Can it be finished? Alone. In evenings. Including the video, which needs half a day of its own.

What survived was almost boring, and that was the point: an agent that watches one folder, reads whatever lands in it, and files it. Not a chatbot about documents. A thing that does the work while you are not looking.


The one decision that paid for itself

Orden's architecture: a vendor-neutral core surrounded by thin adapters for Google Drive, Gemini, ADK, Firestore and Cloud Scheduler
The seam, drawn. A core that knows the domain, a thin ring of adapters that know the vendors. A diagram can only reveal a structure that is already there.

I was also planning to enter two related hackathons with different mandatory SDKs, and I did not want to write the same agent three times. So the rule for the codebase, written down on day one:

The core imports no vendor SDK.

Everything Orden knows how to do lives in orden/core/: classify, decide, file, learn, the ledger, the memory, the insights. It imports Drive and Gemini and nothing else. The agent framework lives in exactly one file, adapters/adk.py, which exposes that core as eight ADK tools: list_inbox, list_folders, inspect_file, file_document, undo_last, remember_correction, review_archive, spending_summary.

The web panel calls the core directly, so the adapter is the same agent driven a second way rather than a layer the panel hides behind. You can run adk run orden/adapters and talk to it, or open the panel and watch it work unattended, and they are the same code.

I did this for portability. What it actually bought me was a diagram worth showing.

When an architecture has a real seam in it, the drawing has a centre: a core that knows the domain, a thin ring of adapters that know the vendors. That reads instantly, and architectural discipline is 30% of the score.

The lesson generalises past hackathons. An architecture diagram is not a documentation chore you do at the end. It is a scored deliverable, and a diagram can only reveal a structure that is already there. If your code has no shape, no amount of drawing will give it one. That decision is made in week one or never.


Deleting the best idea in the project

Here is the part I am least comfortable writing, and the part I think is worth the most.

Not everything in an inbox is a document. There are wallpapers, memes, screenshots of chats, duplicate exports, empty scans. Filing has nothing to say about any of them, and each one still costs a full multimodal read.

So I built a gate. Every file was screened first by Gemma 4 (a fraction of the price, same API endpoint) asked exactly one question: is this worth filing at all? One word back. The filing prompt is around 2,100 tokens before it even looks at the document. The triage prompt was 185.

It was, on paper, the most elegant thing in the codebase. I wrote it up. I put it in the architecture diagram. I was proud of it.

Then I measured it on the real archive.

Thirty-three documents in. Thirty-three through. It vetoed nothing at all, while costing one extra model call per file.

That is not a tuning problem, and I want to be precise about why, because the temptation to tune it was enormous. The gate was designed around an asymmetry: a wasted read costs a fraction of a cent, and a wrongly discarded invoice costs the user’s trust in the entire agent, permanently. So it was built to veto only when confident and to default to letting everything through, including on any internal failure, quota exhaustion, network loss, anything.

But a gate that must not veto, and in practice never does, is a call per file buying nothing. The junk it was built to catch is rarer in a real inbox than the design assumed.

So four days before the deadline I deleted it: core/triage.py, the screen() call, the Verdict field on Decision, the second model from the runtime line, and the Gemma box from the architecture diagram, the README and the home page. 139 lines out.

Removing it also settled what the product is. Point Orden at a folder and everything in it gets read. That is easier to explain than a two-model pipeline whose first stage is invisible, and it is what somebody choosing an inbox actually wants.

Deleting it with the deadline in sight was not comfortable. But a box on a diagram that exists for the diagram’s sake is exactly the thing an architecture score is meant to catch, and I would rather cut it myself than have a judge do it for me.

The bug inside the thing I deleted

The gate is gone, but the bug it taught me is the most transferable thing in this article.

Gemma 4 reasons before it answers, and offers no way to turn that off: passing a thinking budget is rejected outright. A one-word answer measured between 359 and 446 thinking tokens.

Which means if you set the output token ceiling to something sized for the answer, the response comes back empty with finish_reason: MAX_TOKENS.

An empty response from a gate that answers in one word is indistinguishable from a model that declined to answer. My code read that as “no verdict”, and “no verdict” meant “let it through”, which was the correct default. So every single file silently passed the gate, the expensive model read all of them, and nothing anywhere reported a problem. The system worked perfectly and did none of what it was built to do.

The general lesson outlives the specific model. When you build a component whose failure mode is fall back to the safe path, you have also built a component that can fail totally and invisibly. Safe defaults hide outages. If you write one, you must also write the thing that tells you it is happening.

Which, in hindsight, is also how I failed to notice the gate was useless for several days.


The security hole I put on a public URL

The mistake I would most like to have back.

I deployed the panel to Cloud Run so judges could try it. It worked. It looked good. It had my Google Drive behind it, no authentication in front of it, and thirty-one API endpoints that would answer a stranger as readily as they answered me.

Anyone with the link could read, and move, my documents.

That is not a subtle bug. It is the most ordinary security failure there is, and I shipped it because I had been developing locally where I was the only person who could reach it, and deployment did not feel like a change in kind. It felt like the same app, somewhere else.

The fix I did not choose was bolting authentication onto thirty-one endpoints two days before a deadline. What I did instead: the hosted copy serves the demo and nothing else. Every non-demo endpoint refuses. Filing a real Drive happens on the owner’s own machine, where the only person who can reach it is them.

That turned a security patch into a product decision, and I think a better one. A public URL with one person’s Drive behind it is not a demo, it is a liability, and it also cannot show a judge anything, because they would be looking at my electricity bills.

So the demo runs the actual agent (same prompts, same models, same ledger, same review queue) over invented paperwork held in memory. Drive, the proposals, the inbox snapshot and the document store all resolve per request through context variables, so two visitors cannot see each other’s filing, and neither can see mine.

The uncomfortable part is how long it took me to see it. The commit that fixed it also carried seven other bugs I only found because I finally sat down and used the thing as a stranger would.


The dependency that only broke in production

Every Firestore read failed in the deployed container with:

400 Invalid database id %28default%29

The panel reported nobody was signed in. The token was sitting in Firestore the whole time and could not be read. /api/status returned 500. And it worked perfectly on my laptop.

The cause was a transitive dependency nobody had pinned. Firestore builds its database path with google.api_core.path_template.expand, and a version after 2.34.0 started percent-encoding the segment values. The default Firestore database is literally named (default), parentheses and all, so they became %28 and %29, and the API rejected its own default database’s name.

No code of mine could reach it. Passing the name explicitly got encoded just the same.

The reason it only ever broke deployed: my laptop had 2.34.0 cached and the container installed the newer one on every build. The two environments were never running the same code, and nothing said so.

I chased this in the wrong place first. My first fix decoded the environment variable Cloud Run injects, which made the symptom go away in one code path and left it in others, because I had correctly identified where the bad string appeared and wrongly identified who wrote it. Pinning google-api-core was the real fix.

If you deploy anything: pin your transitive dependencies, or accept that your local environment is a different program from the one your users run.


State, and the mistake I made first

I stored everything on the instance filesystem. Ledger, queue, learned corrections. It worked beautifully in testing.

Cloud Run discards an instance’s filesystem between revisions. So every deploy wiped everything the agent had learned, which meant the exact feature I was proudest of, that it improves as you correct it, was being quietly reset every time I shipped.

Everything moved to Firestore, which was also one of the required Cloud services, so the fix and the requirement turned out to be the same piece of work.

The broader point is about demo-day risk. Anything that lives only on the machine you are developing on can vanish between your last test and your recording. Assume all local state is temporary and you will lose fewer evenings.


Push, with a schedule as the safety net

The naive version polls the folder every few minutes. It works, and it is the wrong product, because “the agent notices immediately” is a completely different thing from “the agent notices eventually”.

So Drive pushes. changes.watch registers a channel, Drive POSTs to /api/drive-changed, and a file is read seconds after it lands.

I measured before building it, which I mention because I nearly did not: reading a file costs about 0.17 cents, and asking Drive whether anything changed is free within quota. So polling saved nothing: the cost was always in the reading, and that happens either way. The argument for push was never cost. It was the experience.

But Drive expires those channels within a week and renews nothing itself. A deployment that registers one and forgets quietly stops noticing files. Push alone fails silently, which is the one failure mode worth engineering against. So there is an hourly Cloud Scheduler job whose entire purpose is to be the safety net: renew the subscription, and re-read the inbox in case a notification was ever missed.

Push for the experience, poll for the guarantee.

There is a coda to this that I like. The scheduled jobs kept recording failed runs every hour: the demo-only guard refused them with a 404, and past that they returned 503 for want of a Drive token. My first instinct was to delete the jobs. That was the wrong repair: they are part of the architecture, and they are worth showing in the console, which the video does.

What was wrong was the answer, not the schedule. With no Drive connected they now return 200 {"status": "idle"}, because that is the truth. The job asked, there was nothing to do, and the run succeeded. Reporting it as a failure put a permanent red mark on a console describing a system working exactly as designed.


Making the agent answerable

Orden's reasoning panel showing what decided a filing, what it read from the document, the issuer, date, amount and the dates that matter
Every decision on the record: the sentence that decided it, the facts read off the page, and the dates it found. Built entirely from what the sweep already stored, so opening it makes no second call to the model.

An agent that moves your files without asking has a trust problem, and trust problems are not solved by accuracy. They are solved by legibility.

So every decision is on the record. Each proposal opens a panel showing the sentence that decided it, the facts read off the page (issuer, date, amount), the dates and actions it found, and which of your own past corrections applied. It is built entirely from what the sweep already stored, so opening it costs nothing and makes no second call to the model.

And when you overrule it, that is the input:

"""The gap every rules engine leaves open: when you move a file it misplaced, it
learns nothing, and you are left editing rules by hand. Here a correction is
the input. The user moves a file, or overrules a suggestion, and that becomes
...twelve corrections about one issuer become one durable rule instead of
twelve examples.
"""

Correct the same sender and the correction is promoted to a stated rule, shown in plain words on screen rather than buried in a prompt you never see. The History tab tracks the agreement rate, the share of Orden’s decisions left standing, because a store of past conversations is memory, but a memory that changes the next decision, plus a number saying whether it changed it for the better, is the difference between an agent and a chatbot that remembers.

I did not build this to score points. I built it because I would not use the thing otherwise. It turned out to be the strongest thirty seconds of the video, which is a pattern I now trust: the feature that makes you personally willing to use the tool is usually the feature that sells it.

Orden's history tab showing past filings, the reason for each, and the user corrections that were promoted into stated rules
Correct the same sender twice and the correction is promoted to a rule, shown in plain words rather than buried in a prompt you never see.

What filing alone never does

Orden's insights tab listing upcoming dates read off filed documents: a fee note due, an appointment, a lease renewal
None of this is visible in any individual file. It is arithmetic over what filing already recorded, so opening the tab costs nothing.

One more thing, because it is the part I would keep if I could keep only one.

Filing a single document is clerical. The things actually worth knowing only exist once a year of documents sits in one place, and none of them are visible in any individual file:

  • This arrives every month. Three documents from one sender at a steady interval are a series. Orden works out the rhythm, what it typically costs, and when the next is due.
  • This one costs more than usual. Measured against the median of the earlier ones, not the last one, so a single estimated meter reading does not quietly redefine normal. Under 15% it stays quiet: utility bills move a few percent constantly, and an alert you learn to ignore is worse than none.
  • This one never came. The only thing in Orden concerning a file it has never been given. A bill that stops arriving is invisible to every filing tool there is, and it is the one that costs money, because the unpaid invoice is the one nobody put in the inbox.
  • What it all came to. Totals per currency, month, sender and category. Currencies are never converted into one another: Orden has no rate, and an invented one is a number you cannot check.

All of it is arithmetic over what filing already recorded, no model call and no Drive call, so opening that tab costs nothing and can change nothing.

Dates it reads off a document go on a Google Calendar named Orden, separate from your own, so everything it created can be seen in one place and deleted in one gesture. Events carry a deterministic id, so re-filing the same document updates the reminder instead of adding a second one. And if the calendar cannot be reached, the file is still filed: filing is the promise, the reminder is the bonus, and a bonus may never break the promise.


Choosing a model on the column the spend actually lands in

Orden runs Gemini 3.6 Flash, pinned rather than tracking a -latest alias, so an upstream release cannot silently change how documents are read.

3.6 rather than 3.5 purely on cost: input is half the price, and output is $3.75 per million against $9.00, for the same decisions on the same documents. Output is where the spend lands here, because a structured extraction returns far more than it is given, so that is the column worth choosing on.

Two measurements worth the twenty minutes they cost:

Thinking is off. Filing is extraction, not deliberation. The answer is on the page. On a rental letter, thinking spent 1,509 tokens and 5.5 of the call’s 7.5 seconds to arrive at byte-identical values for category, destination, confidence and filename. Four times faster per file, for nothing lost.

The budget is 1, not 0, and that difference is not cosmetic: 3.5 accepted a budget of 0, and 3.6 rejects it with a 400 on every call. Pinning the model forward without changing that number takes the whole agent down. A budget of 1 is accepted and still returns no thinking tokens at all.

There is also a cache keyed on the document’s SHA-256 rather than its Drive id, because the same bytes uploaded again, a sweep re-run after a correction, or a demo rehearsed against one corpus all arrive with a fresh id, and each one used to pay for a full multimodal read. The prompt is part of the cache key, which is what makes it safe: correcting Orden invalidates exactly the entries whose reasoning just changed, and nothing else.


The video is 30%. Act like it.

I wrote a script before recording anything. Not notes. A script, with timestamps, shot by shot. This is where solo entrants lose to teams for no reason at all, and where the effort-to-points ratio is the best in the whole competition.

Record the screen silently, then record audio over it. Narrating live produces the ums and the dead air. Two passes and the whole thing sounds deliberate.

Show the product working in the first fifteen seconds. My first draft opened with 35 seconds establishing the problem. It was good writing and it was a mistake. Earn the context by showing the thing first.

Never type live. Paste, or cut to the finished result. Nothing is more expensive per second than watching someone type.

Empty your history before recording. My agreement-rate scorecard was sitting at a suspiciously perfect number from testing. A believable number is more persuasive than a perfect one.

Prove the cloud part on screen. The rules required demonstrating the backend runs on Google Cloud, so the Cloud Run console appears explicitly, showing the service, the region and the revision serving 100%. This is a checkbox. Checkboxes are free points and people still miss them.

Burn in the subtitles. Half of judging happens with the sound off.


Read the emails. Actually read them.

Two things nearly slipped past me, both sitting in routine hackathon emails I had skimmed.

The track descriptions in the welcome email were more specific than the ones on the rules page. I had been planning to enter as a Collaborative Partner, because Orden remembers corrections and improves with use. But the fuller description was “an interactive expert that guides users through complex challenges, asks the right questions”, and my agent guides nobody. It acts, alone, triggered by a webhook, and speaks only when unsure.

It was a Taskmaster: “takes over a complete multi-step workflow and handles it from start to finish without hand-holding.” Word for word.

Fitting half of one category is worse than fitting all of another. I had been about to file myself into a track whose first line argued against me.

The second: bonus points for publishing a write-up require it to be public, not unlisted, and to state explicitly that it was created for the hackathon. One sentence. Miss it and the points do not count. (Mine is near the top of this article, and this is the second time I have said it.)


What I would tell someone entering their first one alone

Read the judging weights before you have the idea. They are the spec. If 60% of the score is architecture and demo, a modest idea finished cleanly beats an ambitious one that is 80% done. Every time.

Find the category where you are not competing with everyone. Two winners in a restricted category is a different game from one winner against the world. Same submission, radically different odds.

Pick a problem you personally have. You cannot fake the demo otherwise, and the demo is 30%.

Decide your architecture’s seam in week one. The diagram is scored, and a diagram can only reveal a structure that already exists.

Instrument your safe defaults. Anything designed to fail gracefully is something that can fail invisibly.

Deploy early enough to be embarrassed. My worst bug, a public URL over my own Drive, existed only because deploying felt like the same app somewhere else. It is not. It is a different program with different callers.

Be willing to delete the clever part. The Gemma gate was the best-argued component in the project. It also did nothing, and keeping it would have meant defending a box on the diagram that existed for the diagram’s sake.


I do not know yet whether Orden won anything. By the time you read this the deadline will have passed and judging will be underway, and the result is honestly the least interesting part.

What I know is that there is an agent watching the folder those documents land in, and that filing them is no longer work anybody has to do. It files a rental contract correctly because it read the contract. When it gets one wrong and I move the file, it does not make the same mistake twice. And when a bill that arrives every month stops arriving, it is the only piece of software I own that will notice.

Poner orden. That was the whole brief. It took ten evenings, sixty-five commits, one deleted model, one security hole, and a pair of percent-encoded parentheses.

Deadlines are a technology. Use them.


Orden is a document-filing agent built on Google ADK and Gemini 3.6 Flash, running on Cloud Run with Firestore, with Gemini TTS reading a daily brief. Live at ordenagent.com, and the hosted demo needs no sign-in and touches nobody’s Drive. Source is MIT-licensed. Written for the Google All Things Agentic Hackathon, August 2026.