DEV Community

Alex Zhu
Alex Zhu

Posted on

One Free Server, Four Teammates, One Quota: A Lane Broker Playbook for Shared Agent Boxes

It is 09:40 on a Tuesday, and three people are waiting on the same small box. One is rerunning a failing agent test, one is regenerating embeddings for a demo, and one started an overnight batch at 08:55 that still holds every slot. Nobody did anything wrong, and yet the whole afternoon is gone before the interactive work gets a turn.

That opening is an illustrative composite of a pattern, not a log from one team. It has three distinct failure modes, and only the first one is obvious. Name all three before you write policy, because a playbook that fixes blocking but ignores attribution will be quietly abandoned in two weeks.

The three failure modes of a shared free box

1. Head-of-line blocking. A long batch run occupies the only concurrency you have, so short interactive requests queue behind work that nobody is watching. On paid infrastructure you buy your way out; on a free box you have to schedule your way out.

2. Attribution gaps. When someone asks why the morning was slow, nobody can answer with data. Without a per-owner record you get vibes, blame, and a rule that everyone ignores because it cannot be enforced.

3. Retry amplification. A denied call becomes five retries, five become twenty-five, and the box spends its remaining capacity apologizing to itself. This is the failure mode that turns a bad hour into a bad day.

This is exactly the situation a small team creates for itself when it points a shared free server at a shared free model quota. MonkeyCode is an open-source project that offers free model access and a free server option, with an operator-described token grant currently quoted at ten million tokens; treat that number as a starting claim to verify on the project page, since quotas move. Nothing below depends on the exact figure, which is the point.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

If you stripped every product mention out of this article, the playbook would still work, because the artifact is a scheduling rule, not an integration.

Rule 1: Everything is a lane, and lanes have hours

Stop classifying requests by team and start classifying them by who is waiting. Interactive means a human is blocked on the answer right now. Batch means the result will be read tomorrow and nobody will notice an hour of delay.

Work type Lane Allowed window What you give up
Debugging an agent failure interactive all hours nothing; this is the reserve
Prompt or tool-schema iteration interactive all hours nothing
Embedding backfill batch outside 09:00–18:00 latency
Nightly eval sweep batch outside 09:00–18:00 latency
Anything with an external deadline interactive all hours you must say so in the ticket

The batch window is not a punishment; it is the cheapest capacity control you can build. If your team works across time zones, make the window a per-run environment variable rather than a hard-coded constant, so the duty owner can shift it without a code change.

Rule 2: One run in flight per owner

Two parallel runs from the same person is almost always an accident, and it is the accident that starves everyone else. Enforce it with an advisory lock per owner, not with a spreadsheet rule that depends on memory.

Rule 3: Three strikes, then a human

The stop rule is the anti-amplification control. After three consecutive non-ok runs, the broker refuses to launch anything else for that owner until a human clears the block. A blocked person is cheaper than a retry storm, and the block is the moment when you learn that the quota, not the code, is the constraint.

The broker: about eighty lines, stdlib only

lanes.py enforces all three rules and writes an append-only JSONL ledger. It is deliberately boring: fcntl for locking, subprocess for execution, no daemon and no database.

#!/usr/bin/env python3
"""lanes.py - a contention broker for a shared free-tier agent box.

R1  batch runs are refused inside the interactive window
R2  one run in flight per owner (advisory fcntl lock)
R3  N consecutive non-ok runs block further runs until a human clears it
Stdlib only. The ledger is append-only JSONL.
"""
import argparse, fcntl, json, os, subprocess, sys, time, uuid
from datetime import datetime, timezone
from pathlib import Path

STATE = Path(os.environ.get("LANE_STATE", "/srv/lanes"))
LOCKS = STATE / "locks"
LEDGER = STATE / "ledger.jsonl"


def stamp():
    return datetime.now(timezone.utc).isoformat(timespec="seconds")


def append(record):
    LEDGER.parent.mkdir(parents=True, exist_ok=True)
    fd = os.open(LEDGER, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
    try:
        os.write(fd, (json.dumps(record, sort_keys=True) + "\n").encode())
    finally:
        os.close(fd)


def acquire(owner, wait_s):
    LOCKS.mkdir(parents=True, exist_ok=True)
    fd = os.open(LOCKS / f"{owner}.lock", os.O_RDWR | os.O_CREAT, 0o644)
    deadline = time.monotonic() + wait_s
    while True:
        try:
            fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
            os.ftruncate(fd, 0)
            os.write(fd, f"{os.getpid()} {stamp()}\n".encode())
            return fd
        except BlockingIOError:
            if time.monotonic() >= deadline:
                os.close(fd)
                return None
            time.sleep(1.0)


def interactive_window():
    start, end = (int(x) for x in os.environ.get("LANE_WINDOW", "9-18").split("-"))
    return start <= datetime.now().hour < end


def streak(owner, limit):
    if not LEDGER.exists():
        return 0
    count = 0
    for line in reversed(LEDGER.read_text().splitlines()):
        rec = json.loads(line)
        if rec.get("owner") != owner:
            continue
        if rec.get("outcome") == "ok":
            break
        count += 1
        if count >= limit:
            break
    return count


def cmd_run(args):
    if args.lane == "batch" and interactive_window():
        print("batch lane is closed inside the interactive window", file=sys.stderr)
        return 3
    if streak(args.owner, args.stop_after) >= args.stop_after:
        print(f"{args.owner}: {args.stop_after} non-ok runs in a row - stop and page the duty owner", file=sys.stderr)
        return 4
    fd = acquire(args.owner, args.wait)
    if fd is None:
        print(f"{args.owner} already has a run in flight", file=sys.stderr)
        return 5
    run_id, started = uuid.uuid4().hex[:12], time.monotonic()
    try:
        rc = subprocess.call(args.cmd)
    finally:
        fcntl.flock(fd, fcntl.LOCK_UN)
        os.close(fd)
    append({"run_id": run_id, "owner": args.owner, "lane": args.lane,
            "ticket": args.ticket, "rc": rc, "outcome": "ok" if rc == 0 else "error",
            "wall_s": round(time.monotonic() - started, 1), "started": stamp()})
    return rc


def main(argv=None):
    p = argparse.ArgumentParser()
    p.add_argument("--owner", required=True)
    p.add_argument("--lane", choices=["interactive", "batch"], required=True)
    p.add_argument("--ticket", default="none")
    p.add_argument("--wait", type=int, default=0)
    p.add_argument("--stop-after", type=int, default=3)
    p.add_argument("cmd", nargs=argparse.REMAINDER)
    args = p.parse_args(argv)
    args.cmd = args.cmd[1:] if args.cmd[:1] == ["--"] else args.cmd
    if not args.cmd:
        p.error("pass the command after --, e.g. -- python job.py")
    return cmd_run(args)


if __name__ == "__main__":
    sys.exit(main())
Enter fullscreen mode Exit fullscreen mode

Exit codes are part of the interface, so your wrappers can distinguish a refusal from a failure: 3 means wrong window, 4 means stop rule, 5 means the lock is busy. Return the child process's own exit code otherwise, so existing CI wrappers keep working unchanged.

Wire it into the box

Three commands get you from a fresh shell to an enforced policy. Run them once, then put the environment variables in the box's shell profile so every teammate inherits the same window.

sudo mkdir -p /srv/lanes && sudo chown "$USER" /srv/lanes
install -m 0755 lanes.py /srv/lanes/lanes.py
export LANE_STATE=/srv/lanes LANE_WINDOW=9-18

# interactive work, any hour
/srv/lanes/lanes.py --owner priya --lane interactive --ticket AGT-201 \
  -- python -m pytest tests/agent -x

# batch work, refused before 18:00
/srv/lanes/lanes.py --owner sam --lane batch --ticket AGT-214 --wait 0 \
  -- python embed_backfill.py
Enter fullscreen mode Exit fullscreen mode

Attribution becomes a one-liner, which is the only reason anyone will actually check it. The auditor reads the ledger, never edits the broker, and reports the top consumers weekly.

jq -r '"\(.owner)\t\(.lane)\t\(.outcome)"' /srv/lanes/ledger.jsonl \
  | sort | uniq -c | sort -rn | head -20
Enter fullscreen mode Exit fullscreen mode

A nightly audit that takes ten seconds beats a dashboard nobody opens. If a number in that output surprises you, you have found a real question about capacity rather than a debate about etiquette.

The one-page run for your wiki

Paste this as-is. It fits on one screen, and every line names a person or a command.

# Shared Agent Box - Contention Run (v1)

## Roles
- Broker maintainer: owns lanes.py and LANE_WINDOW. Changes land by PR, never by ssh.
- Duty owner: rotates Monday 10:00. Clears stop-rule blocks. Only person who can grant a batch exception.
- Auditor: reads the ledger daily, reports the top three consumers weekly, changes no code.

## Handoff at rotation (10 minutes)
1. Duty owner runs the jq summary and reads the last 20 ledger lines aloud.
2. Any open stop-rule block is reassigned to a named person, not to "the team".
3. The incoming owner states the current interactive window and confirms it in writing.

## Escalation ladder
- Lock busy: wait, then ask the holder in the ticket. Do not run a second copy.
- Three non-ok runs: stop, page the duty owner, paste the last three ledger lines.
- Quota or capacity exhausted: duty owner decides what gets cut. Nobody self-grants an exception.

## Stop rules
- No batch work inside the interactive window without a written exception in the ticket.
- No edits to lanes.py while a run is in flight.
- If the broker itself is broken, the box is read-only until the maintainer says otherwise.
Enter fullscreen mode Exit fullscreen mode

That last stop rule matters more than it looks. A broken referee is worse than no referee, because everyone quietly invents a different policy while it is down.

Where this playbook stops working

This broker distributes scarcity honestly; it does not create capacity. If the quota is genuinely too small for the team, the ledger will simply prove it sooner, and you will need to cut scope rather than tune rules. It also does not survive the box disappearing mid-run, because an advisory fcntl lock in /srv dies with the filesystem; keep long jobs resumable and treat the lock as coordination, not as durability. Finally, the ledger records wall time and outcomes, not token counts, so do not quote it as a spend report unless you add that measurement yourself.

Skip this approach if you need guaranteed latency, if your team is large enough that per-owner locks become a queue of their own, or if your work is genuinely all interactive. A two-person team with a generous quota should just talk to each other and save the script for later. The playbook earns its keep somewhere around three to six people sharing one free box, where memory stops being a coordination mechanism.

Start by running the broker in dry mode for a day, with --lane batch permitted, and read the ledger before you enforce anything. You will usually find one person's job accounting for most of the contention, and that conversation is easier with a file in hand. If you want a no-cost place to try the shape of this before touching your own infrastructure, MonkeyCode's free server option is a reasonable sandbox for exactly this experiment, and none of the rules above change when you move to a paid box later.

Top comments (0)