This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle
What I Built
Woolly is a board for a client who pays a few freelancers against a project budget.
The client sets the budget, then puts an allocation on each cycle. Tasks on that project split the allocated money, and the shares always add up to 100%. A worker can edit a task and submit proof. Marking it complete requires a written summary of the proof, and that moves the task to Under Review. The client is the only person who can approve it (Complete) or send it back (Revision Needed). A worker who tries to approve their own work is refused by the server.
That is the thing someone would pay for. A client who already hires people by the piece wants the money to stay a percentage of a number they typed, with one approver. Woolly is not a marketplace and it does not send invoices. It is the agreement: budget, cycles, shares, proof, and a client who decides.
In the walkthrough, Field notes has an $800 allocation and four tasks. Each one sits at 25%, $200. Move one slider and the others give up the remainder so the total stays 100%.
Gemma does not touch that number. A client can paste a summary ("add the landing page to Cycle 1") and a Mastra agent asks Gemma to propose the tasks, on the cycle named in the summary. If that cycle is missing, the task comes back unplaced. The agent does not quietly pick a different cycle. A second agent can recommend credit or dispute on a proof. The recommendation is stored. The payment decision is a separate action, and only the client can take it.
The people in the demo are fictional: Cool Client, Worker One, and Worker Dos. There is no password.
Demo
The video is the client board, a task and its allocation, Manage (budgets and cycles), a new project, then the same app as Worker One. The worker has no project button and no Settings. The account menu is Sign out. Nothing in the recording is saved.
To run it locally, from a clone of the repo:
cd server && npm install && npm run migrate && npm run dev
cd app && flutter run -d chrome
Copy .env.example to .env first. The API listens on http://127.0.0.1:8787, and the app is the Flutter web client. npm run migrate rebuilds the tables and the fictional rows. Sign in as Cool Client or Worker One.
The board loads without a model call. Gemma runs when someone asks for tasks from a summary, or for a recommendation on a proof.
Code
Woolly
Pay for an iteration, measured by the subtasks a client accepts.
A project is the epic. An iteration is one sprint inside that project: it has an amount and an optional deadline. Tasks are the stories. Subtasks are the tickets. Pay for an iteration is its amount times the share of subtask effort the client has credited.
A client has many workers. A worker has many projects. The client adds work and reviews it, filtered by worker or by project. The worker adds and edits tasks, and submits subtasks for review. The worker cannot credit their own work.
The people in the demo are fictional: Cool Client (client), Worker One (worker), and Worker Dos (worker).
Demo
Walkthrough of the client board, task allocation, Manage, and the worker account: https://youtu.be/IX1FJzK4vcU
Look
Dark brown, flat, closer to a Darcula editor than a paper form. Near-black background, warm brown panels, amber marks…
How I Built It
The open pieces are Gemma and Mastra. Gemma is gemma-4-26b-a4b-it, called through the Gemini API (server/src/env.ts). It is an open-weight model on a hosted endpoint. This build does not run the weights on the laptop, and I am not going to pretend it does. Proof text and summaries go to that API when an agent runs.
Mastra is the harness (server/src/woolly.ts):
| Agent | Job |
|---|---|
| Place | Turn a summary into one task per piece of work, on the cycle the summary names |
| Split | Break a brief into tasks and subtasks. Effort is the size, value is how much it matters |
| Review | Read a proof against the description and recommend credit or dispute |
Place and split return JSON the server checks before anything is written. Review writes model_outcome on the proof. The client's decision is a different request. Shares are basis points on the task row, and the server normalizes them so a project sums to 10000. Cycle allocations that would pass the project budget are rejected. None of that is a sentence in the prompt.
search-past-work is a Mastra tool the split and review agents call. It looks up this project's own accepted subtasks, with Postgres full text and a pgvector embedding, and hands a few titles back. The embedding call is Gemini's embedding model, not Gemma. The memory is the client's past work, not a general chat log.
flowchart LR
summary[Client summary] --> place[Mastra place agent]
place --> gemma[Gemma via the Gemini API]
gemma --> tasks[Tasks on the named cycle]
tasks --> board[Board and allocation]
proof[Worker proof] --> review[Mastra review agent]
review --> note[Recommendation only]
note --> client[Client approves or sends it back]
client --> paid[Share of the allocation]
Stack around that: a Flutter web client, a TypeScript API, Postgres. The theme is a dark brown board. Projects, workers, cycle, and status are filters. The client and the worker are two workplaces in one app.
Why Does Open Innovation Matter?
A client will pay for this only if the money rule is boring and visible. An allocation is a number they set. A share is a percentage of that number. A worker cannot approve the proof. If those rules lived inside a closed chat product, the same vendor would propose the work and hold the ledger, and a prompt would be the only thing standing between a suggestion and a payment.
Other people building agents next to money are drawing the same line. A spending limit written in a prompt is not a control (Prompts don't enforce budgets. Signatures do.), and the approval has to sit outside the model (Should this AI agent be allowed to pay?). Woolly keeps Gemma on the proposal. The budget check, the shares, and the approver check are ordinary server code.
Open is what makes the split possible. Mastra is the harness, so the three agents are a file I can read, and the model is a setting (GEMINI_MODEL, default gemma-4-26b-a4b-it) rather than the product. I can point Place at another Gemma endpoint without rewriting who is allowed to mark a task paid. The board also has a price of zero when nobody is asking the model anything. The calls a client would pay for are the ones that propose work or read a proof.
What open does not do here: it does not keep the proof on the machine. Hosted Gemma means a summary leaves the laptop when an agent runs. Local weights would close that gap. They are not what this submission runs.
Prize Categories
Featured
-
Best Use of Gemma. Place, split, and review are Gemma (
gemma-4-26b-a4b-itthrough the Gemini API). Place turns a summary into tasks. Review recommends credit or dispute and does not get to apply it.
Partner
-
Best Use of Mastra. Those three agents are Mastra
Agents, and past work is acreateToolthe split and review agents call before they answer.
Solo entry.
Top comments (0)