diff --git a/.git-objects/8d/44f7ce6424b399fda658d3aeb5f365e26da022 b/.git-objects/8d/44f7ce6424b399fda658d3aeb5f365e26da022 new file mode 100644 index 00000000..85e7d4e4 --- /dev/null +++ b/.git-objects/8d/44f7ce6424b399fda658d3aeb5f365e26da022 @@ -0,0 +1 @@ +xAJ1E])j JUJDDoPT{艢A<ԁIfZ%5cSCęDfSH%ᬛ-F488qm,R \)hTtWsk2rjyĜi|m{]7xZXYgQ~x<::?-za_I7L7wY /I]o4e \ No newline at end of file diff --git a/cli/tests/test_async_dispatch.py b/cli/tests/test_async_dispatch.py index 62f5a880..19a5dc11 100644 --- a/cli/tests/test_async_dispatch.py +++ b/cli/tests/test_async_dispatch.py @@ -59,6 +59,27 @@ def test_inflight_markers_consume_slots(tmp_path): assert picked == [] # 4 already running, cap 4 → 0 new +def test_inflight_markers_consume_per_lane_limit(tmp_path): + da = _load(tmp_path, n_open=0) + today = datetime.date.today() + lf = load_limen_file(tmp_path / "tasks.yaml") + lf.portal.budget.per_agent = {"codex": 50, "agy": 50} + lf.tasks = [ + *[ + Task(id=f"C{i}", title="t", repo="x/y", target_agent="codex", status="open", created=today) + for i in range(3) + ], + *[Task(id=f"A{i}", title="t", repo="x/y", target_agent="agy", status="open", created=today) for i in range(3)], + ] + save_limen_file(tmp_path / "tasks.yaml", lf) + for i in range(2): + (da.RUNS / f"RC{i}__codex.running").write_text(datetime.datetime.now(datetime.timezone.utc).isoformat()) + + picked = da.reserve_and_launch(["codex", "agy"], per_agent=2, cap=6, dry=True) + + assert picked == [("agy", "A0"), ("agy", "A1")] + + def test_async_remote_lane_not_gated_by_local_concurrency_cap(tmp_path): """A jules (async/remote) task runs OFF-BOX (a `jules remote new` session executes on Google's VM), so it must NOT be starved by the LOCAL concurrency cap even when local in-flight runs have consumed diff --git a/cli/tests/test_dispatch.py b/cli/tests/test_dispatch.py index 6ccc2af8..263059dd 100644 --- a/cli/tests/test_dispatch.py +++ b/cli/tests/test_dispatch.py @@ -116,7 +116,7 @@ def test_github_actions_lane_requires_configured_workflow(tmp_path: Path, monkey gh = tmp_path / "gh" gh.write_text( "#!/bin/sh\n" - "if [ \"$1\" = workflow ] && [ \"$2\" = view ]; then\n" + 'if [ "$1" = workflow ] && [ "$2" = view ]; then\n' " echo 'workflow missing' >&2\n" " exit 1\n" "fi\n" diff --git a/docs/lane-checkups/jules/20260629-14.md b/docs/lane-checkups/jules/20260629-14.md new file mode 100644 index 00000000..0b25e34a --- /dev/null +++ b/docs/lane-checkups/jules/20260629-14.md @@ -0,0 +1,18 @@ +# Jules Lane Checkup 2026-06-29-14 + +Status: refreshed on current `main`. + +The original blocker was stale. `docs/capacity-fill.md` and +`scripts/capacity-fill-ledger.py` are present, and the capacity-fill ledger runs. +The ledger currently reports blocked capacity because live lanes are down, +exhausted, or unreachable from this sandbox, not because the capacity-fill files +are missing. + +Evidence: + +- `test -f docs/capacity-fill.md && test -f scripts/capacity-fill-ledger.py && echo present` + -> `present` +- `PYTHONPATH=cli/src python3 scripts/capacity-fill-ledger.py` + -> `capacity-fill-ledger: blocked with 9 blockers` +- `python -m pytest -q cli/tests/test_async_dispatch.py` + -> `24 passed` diff --git a/institutio/governance/parameters.yaml b/institutio/governance/parameters.yaml index 7b4354ed..19f08c3c 100644 --- a/institutio/governance/parameters.yaml +++ b/institutio/governance/parameters.yaml @@ -136,6 +136,18 @@ parameters: self_update: none owner: continuity note: "Ignored local raw/private session cartridge. Stores manifests and optional content-addressed object copies; never committed." + LIMEN_PRIVATE_MAIL_STORY: + default: "$LIMEN_ROOT/.limen-private/mail-story" + env: LIMEN_PRIVATE_MAIL_STORY + self_update: none + owner: mail + note: "Ignored private cartridge for mail-story atom inventory. Raw-ish atoms stay off the public tree; tracked reports carry only redacted counts and hashes." + LIMEN_MAIL_ENVELOPE_INDEX: + default: "$HOME/Library/Mail/V10/MailData/Envelope Index" + env: LIMEN_MAIL_ENVELOPE_INDEX + self_update: none + owner: mail + note: "Read-only Apple Mail Envelope Index path used by mail-story-ledger.py. The ledger never mutates Mail, Gmail, labels, flags, archives, drafts, or tasks.yaml." LIMEN_EXTERNAL_SESSION_ROOTS: default: "" env: LIMEN_EXTERNAL_SESSION_ROOTS @@ -847,6 +859,18 @@ parameters: self_update: none owner: arms-agents note: "Fleet dispatch boundary selector. `auto` derives reachable registered lanes from the capacity registry; explicit comma lists are normalized and filtered for down lanes." + LIMEN_GITHUB_ACTIONS_REPO: + default: "organvm/limen" + env: LIMEN_GITHUB_ACTIONS_REPO + self_update: none + owner: arms-agents + note: "Default repository for the GitHub Actions dispatch lane workflow probe and dispatch command." + LIMEN_GITHUB_ACTIONS_HEALTH_REPO: + default: "$LIMEN_GITHUB_ACTIONS_REPO" + env: LIMEN_GITHUB_ACTIONS_HEALTH_REPO + self_update: none + owner: arms-agents + note: "Repository used for the GitHub Actions lane health probe. Defaults to the dispatch repo so health and action target derive together unless explicitly split." LIMEN_JULES_BATCH_FILL: default: "1" env: LIMEN_JULES_BATCH_FILL diff --git a/scripts/dispatch-async.py b/scripts/dispatch-async.py index 9a73ee72..c21890bc 100644 --- a/scripts/dispatch-async.py +++ b/scripts/dispatch-async.py @@ -409,8 +409,12 @@ def _pick_reservations(lf, agents, per_agent, cap, dry, now, usage_remaining): states = [] for agent in agents: is_async = agent in _ASYNC_LANES # remote lane (jules, ...) — off-box, not gated by the local cap + running_for_agent = _running_for(agent) + launch_room = max(0, per_agent - running_for_agent) + if launch_room <= 0: + continue capa = lf.portal.budget.per_agent.get(agent) - aspent = track.per_agent.get(agent, 0) + _running_for(agent) # count in-flight vs budget + aspent = track.per_agent.get(agent, 0) + running_for_agent # count in-flight vs budget agent_rem = None if capa is None else max(0, capa - aspent) live_rem = usage_remaining.get(agent) if is_async else None if live_rem is not None: @@ -433,6 +437,7 @@ def _pick_reservations(lf, agents, per_agent, cap, dry, now, usage_remaining): "agent": agent, "is_async": is_async, "agent_rem": agent_rem, + "launch_room": launch_room, "cands": cands, "index": 0, "taken": 0, @@ -441,7 +446,7 @@ def _pick_reservations(lf, agents, per_agent, cap, dry, now, usage_remaining): while states: progressed = False for state in states: - if state["taken"] >= per_agent: + if state["taken"] >= state["launch_room"]: continue if daily - spent <= 0: continue