Build Aldena

KINDAreplaces $99/mosaves $1,188/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A local multi-agent room whose hard problems are containing work that was written by a model from text you did not write, making an approval gate that actually blocks, and producing a run record somebody who is not an engineer can read.

estimated effort multi-daythe files for this build are in the project pack

RuntimeNode 24, a CLI plus a small server-rendered page on loopback that streams the runAgentsRows in SQLite seeded from a roles file, each one a model call with its own instructionsSandboxOne container per run, one checkout mounted, no socket, no host networkTool gateA policy file of allow, ask and deny, where deny means the tool is not in the schemaDeliveryA branch, a commit and a pull request created by the CLI, never by an agent

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why The container is the only real boundary in this design, and the daemon's own privileges are part of that calculation.

    Get it Install Docker Engine and read its security documentation on rootless mode and user namespace remapping before your first run. open ↗

    Verify docker run --rm alpine id prints a non-root mapping, or rootless mode is confirmed active

  2. installfree

    Why Runs the CLI, the orchestrator and the local page.

    Get it nodejs.org, or your version manager. open ↗

    Verify node --version prints v24 or higher

  3. API keypay per call; measure one real run before planning a day of them

    Why Every agent turn is a call, and a multi-agent run makes many of them, which is why the cost ceiling is in phase five rather than in a wish list.

    Get it Create a key and set a provider-side spend limit.

    Verify One manager turn returns a tool call against your schema

  4. API keyfree

    Why It lives in the CLI process and never enters the container, and the scopes are what make that separation meaningful rather than decorative.

    Get it Create a fine-grained token limited to the single repository, with contents and pull request write and nothing else.

    Verify The token can open a pull request and cannot merge one, tested by trying

  5. have readyfree

    Why The first few runs will do something you did not intend, and the right time to find that out is on a checkout you can throw away.

    Get it A scratch clone, or a branch you do not mind losing.

    Verify A run produces a branch and a pull request you can close without regret

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- agents(id, name, role, instructions_ref, model_id, created_at) · seeded from roles.json and editable there
- runs(id, request, started_at, finished_at, state, container_id, branch, pr_url, cost_minor)
- messages(id, run_id, from_agent, to_agent, kind, body, depth, at) · every hand-off and every reply, so a run reads back a fortnight later
- tool_calls(id, run_id, agent_id, tool, arguments_json, policy, state, decided_by, decided_at, exit_code, output_ref)
- approvals(tool_call_id, prompt_shown, decision, at) · what the human was actually shown, not what the agent claimed it wanted
- memory(id, scope, agent_id, line, added_at, run_id) · scope is project or agent, and the table is capped by row count rather than by hope
- The approvals table stores the rendered command as the human saw it, because an approval that recorded only an intention proves nothing about what ran.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
MODEL_API_KEYsecretrequiredsk-...redactedThe model. Never passed into the container.
REPO_TOKENsecretrequiredgithub_pat_...redactedHeld by the CLI process only. If this appears inside a container, phase five has failed.
DATABASE_PATHrequired./data/runs.dbRuns, messages, tool calls and approvals. This is the record you would show somebody.
SANDBOX_IMAGErequirednode:24-bookwormPinned by digest in production use, because an image that moves is a sandbox that changed.
PROJECT_PATHrequired/home/you/code/projectThe single directory mounted into the container. Exactly one.
MAX_DEPTHrequired5Chain depth cap. A run that hits it stops in a named state.
MAX_FANOUTrequired3Agents the manager may delegate to per turn.
RUN_COST_CEILINGrequired4.00A run stops here and tears down its container.

The build, in order

  1. A room with one door

    Only the manager takes your request, hand-offs are bounded, and every message is a row.

    1. Roles get tuned constantly and a database row is a bad editor.

      Files roles.jsonsrc/agents/seed.ts

    2. A delegation loop is a paid loop.

      Files src/run/limits.tstests/limits.test.ts

    3. The run record is the product for anyone who is not watching live.

      Files src/store/messages.ts

    done when · tick each as it passes
    watch out
    • Agents that can call each other freely produce a loop where two of them agree to keep refining, and because each turn is a paid model call the first sign is a bill rather than a hang. Cap fan-out per turn and depth per chain in code, count them in the database, and stop the run with a clear state rather than a crash when either cap is reached.
  2. One container, one mount, no socket

    Every file edit and shell command happens inside a container that can reach the checkout and nothing else.

    1. Every extra mount is a directory an agent can rewrite.

      Files src/sandbox/container.ts

    2. The socket inside a container is the host.

      Files tests/no-socket.test.ts

    3. Root-owned files on your host are the symptom, not the problem.

      Files docs/rootless.mdsrc/sandbox/user.ts

    done when · tick each as it passes
    watch out
    • Running the container as root with the checkout bind-mounted leaves every file the agents touched owned by root on your host, so your next ordinary git operation fails with permission errors on files you appear to own, and the usual fix people reach for is running the whole thing with elevated privileges, which removes the last of the boundary. Run as a matching non-root user inside, or turn on user namespace remapping, and check ownership on the host after a run.
  3. A denied tool is an absent tool

    The policy file decides what each agent can call, and denial means the tool never appears in the schema.

    1. Absence is the only restriction that cannot be argued with.

      Files tools.jsonsrc/tools/schema.tstests/denied-absent.test.ts

    2. An approval of a command nobody rendered proves nothing.

      Files src/tools/approve.ts

    3. Rejecting one command should not waste the other four agents' work.

      Files src/run/refusal.ts

    done when · tick each as it passes
    watch out
    • Putting the restrictions in the system prompt and filtering the results afterwards means the model still sees the tool, still tries it, and any text it read along the way can argue it into trying harder, while your filter only catches the shapes you anticipated. Build the schema from the policy so the tool does not exist for that agent, and treat a filter as a second line rather than the first.
  4. Memory that does not grow forever

    Two small files carry context between runs and neither can quietly become a novel.

    1. The prompt budget is real and this is where it leaks.

      Files src/memory/cap.tstests/memory.test.ts

    2. Forgetting is a decision worth recording.

      Files src/memory/write.ts

    3. A note file is the easiest place for a key to end up.

      Files src/memory/scan.ts

    done when · tick each as it passes
    watch out
    • An unbounded memory file grows until it is the largest thing in every prompt, at which point you are paying to resend a thousand lines of last month's opinions on every single turn and the model is weighting them as heavily as your actual request. Cap the line count, make the agent choose what to delete before it can add, and keep the removals in the database so you can see what it decided to forget.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more automation.