alissa-tools-github-devloop
The alissa.tools.github.devloop module: a GitHub watcher that turns
alissa:develop-labeled issues into fresh developer sessions — the
developer-side counterpart of
alissa-tools-github-reviewloop.
This distribution ships only that module. Everything above it —
alissa, alissa.tools, alissa.tools.github — is a
PEP 420 namespace package with no
__init__.py, so other distributions in other repositories can contribute
their own packages under the same namespace:
alissa/ ← namespace (no __init__.py)
└── tools/ ← namespace
└── github/ ← namespace
├── devloop/ __init__.py ← THIS distribution
└── reviewloop/ __init__.py ← ships from alissa-github-review-daemon
The rule: a distribution owns a leaf package and declares only that subtree
(find_namespace_packages(include=[PACKAGE, f"{PACKAGE}.*"])). Adding an
__init__.py at any namespace level would claim it for one distribution and
shadow the others.
Install
pip install -e ./alissa-tools-github-devloop
Console scripts
| Command | Entry point |
|---|---|
alissa-devloop |
alissa.tools.github.devloop.__main__:main |
Layout
src/main holds the package tree, src/test mirrors it as test_*. The
distribution version lives in the plain-text version file next to the module
it versions (src/main/alissa/tools/github/devloop/version), read by both
setup.py and version.py.
Account-level conditions
The prompt responder (prompts.py classifies, loop.py acts) treats four
pane conditions as the ACCOUNT's, not the worker's — nothing a keystroke can
answer: login_expired, auth_rejected (a 401), usage_limit and
out_of_credits. Each pages the operator once per kind per 6 h on the lane's
issue/PR and holds every new spawn while it stands (at most 1 h on one pane,
then kill-and-retry re-confirms) — except usage_limit, whose session is
never killed and whose hold is the ledger's (below).
Usage limits (issues #142, #144). usage_limit admits the window wordings
(You've hit your weekly limit, Usage limit reached, …), the "limit
reached … resets …" family (Claude usage limit reached. Your limit will reset at 5pm (America/New_York)., 5-hour limit reached ∙ resets 3pm,
Opus weekly limit reached ∙ resets Oct 6, 1pm) and the
MODEL-named one Claude Code prints now — You've reached your Fable limit. Run /usage-credits to continue or switch models with /model. (also Opus,
Sonnet, …): one capitalised model token, a known family name or any token
corroborated by that trailing hint on the same line. An over-limit account
answers the directive with that notice as the worker's ONLY assistant turn
and leaves the session idle at ❯, which the banner rule (never a ● row)
cannot see, so the first-turn limit check reads the responder's capture of
any young worker (develop-*, fix-*, maintain-*, revert-*, spawned by
this ledger inside the last stale_minutes): the notice, above an idle
prompt, with no other ● turn since the directive. A match
(source: "first_turn", matched: "model_limit", the model) is:
- held — the daemon-wide spawn hold, clocked on the ledger from the
newest limit (it survives a restart and a listing failure) and lifted only
when a spawn made AFTER the limit gets through its first turn: after 1 h
ONE probe spawn is admitted at a time (none while a probe is in flight,
none while another account notice holds; under
max_sessionsthe probe is judged without the kept limited sessions, which would otherwise fill the cap for good); a probe whose first turn is the notice again is refunded, re-arms the clock and is killed — it did no work, and keeping it would add one session per hour of the limit (pastmax_sessionstoo), so the live sessions stay at the ones the limit found at work plus at most the one probe in flight; the first probe that does real work lifts the hold (usage-limit-lifted:on the escalation ledger,escalation.usage_limit_liftedon the wire) and each kept limited session's lane gets a line saying to resume it; - paged — once per 6 h window, through the account-kind page;
- never killed (issue #144; a failed probe, above, is the one exception) —
a retry would hit the same wall, and the idle session is the lane's own work for the operator to resume once the
account is back (or
alissa tmux kill, to let the retry edge respawn); the wedge kills skip a limited session too; - refunded — the spawn row is stamped
limited_at, and everyattempt_capcount leaves limited rows out (numbering stays monotonic, so session names never repeat). An account over its limit never caps a lane.
A usage-limit BANNER on a working session (source: "pane") is held the same
way: its row is stamped (refunded), the hold is the ledger's, and the session
is never killed. The same notice after real worker output (a tool call, the
worker's own prose, a quoted tool row) stays worker output. Each limited
session is one usage.limited loop event: data: { kind: "usage_limit", source: "pane" | "first_turn", matched: "model_limit" | "usage_limit", model, resetsAt? } — resetsAt only when the notice states a reset, as an ISO-8601
UTC instant when that needs no guess (the legacy |<epoch> form, or a clock
time with an explicit zone) and as the stated phrase otherwise; no pane text,
no credential material.
The fix edge stops presuming. A stale fix round with no triage reply from the author used to be re-enqueued on the timer alone ("presumed dead"). It now reads the tmux roster first: ALIVE is in flight (the responder owns a live idle pane; a once-per-episode "Fix loop stalled?" ping floors the wait), INDETERMINATE defers one pass, and only DEAD respawns — "CONFIRMED dead", or, after a refunded limit, "previous session limited (usage limit; attempt refunded)". A kept limited session reads ALIVE, so no edge respawns over it.
The re-enqueue guard (issue #144)
The issue edge's respawn rule reads the linked-PR signal over every PR
state: GitHub.linked_pull_requests walks the timeline once and reports each
same-repo cross-referenced PR as open, merged or closed. When a stale
attempt's session is CONFIRMED dead with no open PR, a merged PR of the
daemon's own that closes the issue is SKIPPED "finished (PR #n merged)" and
a closed-unmerged one is SKIPPED "closed unmerged — orchestrator's page" —
never a respawn (provenance: token author and a closing keyword, fetched; an
unreadable candidate stands the respawn down). And _spawn re-reads the
issue immediately before every enqueue: a closed issue is SKIPPED "issue
closed between search and spawn", nothing is claimed, and the reserved slot
is handed back. The respawn-with-resume on an open draft PR is unchanged.
The discovery round (issue #164)
A discovery issue is a no-code lane in the fleet's plans repository
(design alissa-build-discovery.md §3.2, lane DS-7a). Studio's feed_plan
renders it with the trailers Alissa-Kind: discovery, Alissa-Reads: <owner>/<repo>@<40-hex sha> (the code repository it reads, at a pinned SHA)
and Autonomous-Scope: knowledge-base/<YYYY>/<MM>/<DD>/TASK-<n>/**. The
issue edge spawns the ordinary develop session — same name, same caps, same
liveness rules — with DISCOVERY_DIRECTIVE (in loop.py, byte-pinned in
test_discovery.py) instead of DEV_DIRECTIVE. Every other issue gets
today's directive byte for byte; a resume of a dead discovery session keeps
RESUME_DIRECTIVE.
The directive has the worker:
- create its
TASK-<n>-<DESC>branch in the plans repository, the branch grammar unchanged; - read the code at the pinned SHA, read-only — a
git -C <hub> worktree add --detach <scratch> <sha>when the workspace has a hub of the read repository (no branch, removed at the end), otherwisegh api repos/<repo>/contents/<path>?ref=<sha>andgh search code; never a clone, never a commit or push there. The read is bounded by the SHA, not a file count; the output by the findings schema's limits; - write exactly one directory,
knowledge-base/<YYYY>/<MM>/<DD>/TASK-<n>/, holding onlyfindings.md(findings schema v1,schema/findings.v1.jsonin the plans repository); - run
node lint/plan-lint.ts --kb <dir>locally with its ownALISSA_API_TOKEN(K6/K7) and never push findings it refuses; - open a ready pull request with
Closes #<n>, both task refs and the marker<!-- alissa-discovery v1 -->on a line of its own, attach aFindingsevidence item, move its task topending_validation, request no reviewer (revloop skips such PRs; orcloop merges on the lint of record) and end its session.
The push guard. Before it enqueues a discovery session (fresh or
resumed), the daemon writes ${ALISSA_DISCOVERY_DIR:-/workspace/.discovery}/ ali-<session>.json naming the plans repository; a marker that cannot be
written stands the spawn down for the pass. The container's shell guard
(docker/claude/hooks/guard-shell.py) finds the marker through
$ALISSA_TMUX_SESSION and refuses, with a PreToolUse deny and a reason, any
git push from that session whose destination is not the plans repository —
resolved from the remote name or URL in the directory the push runs in
(-C, cd), and refused outright when it cannot be resolved. Sessions with
no marker are untouched. ALISSA_SHELL_GUARD=off disables this guard with
the rest.
Seat parking: what counts as managed (issue #166)
The idle block on GET /api/state (parking.idle_block, issue #153) is what
orcloop's D2 invariant reads before it drains and parks the seat: idle only
when live == managed == queued == owed == 0 with no timer and a fresh pass.
live and managed come from alissa tmux ls --json:
- a row is live unless it says otherwise (its
liveflag, elsestatus != "gone"); - a row is managed when it is live, or when it is dead but its
session still backs a lane the seat holds — the session named by a stage
of the latest poll snapshot that is owed or in flight (
spawned/in-flight), any session the spawn ledgers record on such a stage's lane (an earlier attempt the lane may still resume), or a session the alissa queue holds an undispatched item for; - a dead row backing none of these is an orphan: it is not managed, and
is reported in the block's
gonefield as{count, sessions}so the console and orcloop's logs still show it.
Unknown is never idle: a failed listing leaves live, managed and gone
null, and before the first completed pass (no snapshot to read lanes from)
every dead row stays managed. The console only reports an orphan — it never
kills or reaps one. The fleet-vitals push applies the same rule to
sessions.managed and idle.managed (Studio's park guard reads both) and
leaves gone out, since Studio's idle schema is strict.
License
Licensed under the
Apache License, Version 2.0 — the
LICENSE and NOTICE files shipped with this distribution carry the full text
and the attribution and trademark terms. Both ship inside the sdist and the
wheel's .dist-info.
"Alissa" and "Fahera" are trademarks of CORE FAHERA ENTERPRISE HOLDINGS S. DE R.L. and are not covered by the license — the grant covers the code, not the names.
Metadata
Release files for alissa-tools-github-devloop 0.8.44
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| alissa_tools_github_devloop-0.8.44.tar.gz | 379.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| alissa_tools_github_devloop-0.8.44-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 769.0 kB
Release files / alissa_tools_github_devloop-0.8.44.tar.gz
| Download URL | alissa_tools_github_devloop-0.8.44.tar.gz |
|---|---|
| Size | 379.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
611a790bb6c4a4a080574c7860c2ff35d162b5e4dc5f4ff7feb7ea6eccb38a8e
|
|
BLAKE2b-256 checksum How to use checksums |
fa4844405898869fd6e7d54fe6d91f561a3371511588d9a7e07ee36c7a16efe9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / alissa_tools_github_devloop-0.8.44-py3-none-any.whl
| Download URL | alissa_tools_github_devloop-0.8.44-py3-none-any.whl |
|---|---|
| Size | 390.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5e9f5e69b5317a238478e8c76927915513c68de29a63261e1614ff3c619b9dcc
|
|
BLAKE2b-256 checksum How to use checksums |
f465001936eda2d7f463c6d4491d19dee97a17c555a380a97adcd59ad797a473
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|