API reference¶
claudeloop.domain.errors
¶
Domain-level error hierarchy. Pure — carries no I/O state.
AuthenticationFailedError
¶
Bases: AutoclaudeError
Raised when the agent gateway reports a terminal authentication failure.
Never retryable — the run loop must abort rather than wait.
Source code in src/claudeloop/domain/errors.py
22 23 24 25 26 | |
AutoclaudeError
¶
Bases: Exception
Base class for every error raised by claudeloop's own logic.
Source code in src/claudeloop/domain/errors.py
6 7 | |
BudgetExceededError
¶
Bases: AutoclaudeError
Raised when a run exceeds its configured turn, dollar, or wall-clock budget.
Source code in src/claudeloop/domain/errors.py
18 19 | |
InvalidPlanError
¶
Bases: AutoclaudeError
Raised when a work plan file cannot be parsed into work items.
Source code in src/claudeloop/domain/errors.py
10 11 | |
InvalidSessionSelectorError
¶
Bases: AutoclaudeError
Raised when a session selector is malformed or ambiguous.
Source code in src/claudeloop/domain/errors.py
14 15 | |
claudeloop.domain.plan
¶
Work plan value objects — parsing a handoff markdown file into discrete items.
PlanItem
dataclass
¶
One unit of work parsed from a plan file's checkbox list.
Source code in src/claudeloop/domain/plan.py
13 14 15 16 17 18 19 20 21 22 | |
WorkPlan
dataclass
¶
The full body of a handoff plan, plus any checkbox items found in it.
Source code in src/claudeloop/domain/plan.py
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | |
parse(raw_text)
staticmethod
¶
Parse a markdown plan. Checkbox lines (- [ ] ... / - [x] ...) become
tracked items; a plan with no checkboxes is still valid (bare instructions),
just with an empty items tuple.
Source code in src/claudeloop/domain/plan.py
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | |
with_items_marked_done(done_texts)
¶
Return a new WorkPlan with any item whose text is in done_texts marked done.
Used to reconcile a structured-output verdict's remaining_work against the
plan's own checklist between turns.
Source code in src/claudeloop/domain/plan.py
65 66 67 68 69 70 71 72 73 | |
claudeloop.domain.session
¶
Session reference and selection value objects.
ExplicitSessionSelector
dataclass
¶
Resume a specific, caller-known session id.
Source code in src/claudeloop/domain/session.py
39 40 41 42 43 44 45 46 47 | |
MostRecentSessionSelector
dataclass
¶
Auto-select the most recently modified session for a working directory.
Source code in src/claudeloop/domain/session.py
50 51 52 53 54 55 56 57 58 | |
PlanFileSelector
dataclass
¶
Start a brand-new session seeded from the contents of a plan file.
Source code in src/claudeloop/domain/session.py
28 29 30 31 32 33 34 35 36 | |
SessionRef
dataclass
¶
A resolved reference to a Claude Code session.
Source code in src/claudeloop/domain/session.py
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
claudeloop.domain.capacity
¶
Capacity state — whether the account can currently spend a real turn, and why not if it can't. This is the typed replacement for regex-scraping stream-json for limit language.
AuthenticationFailed
dataclass
¶
Terminal — credentials are invalid or revoked. Never retryable.
Source code in src/claudeloop/domain/capacity.py
40 41 42 43 44 | |
Available
dataclass
¶
Capacity exists; a real turn may be spent. utilization is informational —
it reflects an allowed_warning signal and must never itself block a turn.
Source code in src/claudeloop/domain/capacity.py
11 12 13 14 15 16 | |
CreditsExhausted
dataclass
¶
No token/time budget will fix this — the account is out of usage credits and requires a human to purchase more. There is no reset time by construction: waiting for a clock to advance can never resolve this state, only a probe that notices a top-up can.
Source code in src/claudeloop/domain/capacity.py
30 31 32 33 34 35 36 37 | |
WindowExhausted
dataclass
¶
A rate-limit window (five_hour / seven_day / seven_day_opus / seven_day_sonnet /
overage) has been rejected. resets_at is the trusted reset instant when known;
when None, the caller must fall back to a configured wait interval rather than
assuming any particular reset time.
Source code in src/claudeloop/domain/capacity.py
19 20 21 22 23 24 25 26 27 | |
is_waitable(state)
¶
Whether the run loop should ever schedule a wait/probe cycle for this state. AuthenticationFailed is the only capacity state that must abort outright.
Source code in src/claudeloop/domain/capacity.py
50 51 52 53 | |
claudeloop.domain.classify
¶
Pure classification of raw turn signals into a CapacityState.
This is the direct replacement for extract_limit_signals() in the legacy script
(legacy/claude_autoresume.py:290-333), except it operates on typed fields the
Agent SDK already parsed, instead of regexing a raw JSON stream. rate_limit_status
== "allowed_warning" is deliberately NOT checked as a rejection signal — it falls
through the rejected computation below to Available, so it can never be
mistaken for a hard limit. Once rejected, credit signals are checked before
falling back to WindowExhausted, so a credits rejection can never be mistaken for
a waitable window even if a stray resets_at rides along with it.
TurnSignals
dataclass
¶
Everything the classifier needs from one turn, gathered from the Agent SDK's RateLimitEvent, ResultMessage, and AssistantMessage — deliberately not a single source, because RateLimitEvent is reportedly dropped on some adapter paths.
Source code in src/claudeloop/domain/classify.py
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | |
claudeloop.domain.completion
¶
Completion verdicts — was the whole task finished, or just this turn?
Primary source is the structured-output verdict the model returns per turn (ClaudeAgentOptions.output_format). A legacy substring marker is retained as a fallback for when structured output isn't available on a given model/config.
StructuredVerdict
dataclass
¶
Mirrors the JSON schema handed to the model via output_format: {"complete": bool, "remaining_work": [str], "blocked_on": str|null, "summary": str}
Source code in src/claudeloop/domain/completion.py
33 34 35 36 37 38 39 40 41 42 | |
evaluate(*, structured, output_text, done_marker=DEFAULT_DONE_MARKER)
¶
Decide what a single turn's outcome means for the overall task.
Precedence: a structured verdict is authoritative when present. Only when it is absent do we fall back to substring-matching the legacy marker in raw text.
Source code in src/claudeloop/domain/completion.py
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | |
claudeloop.domain.waiting
¶
Adaptive wait policy — decides the next probe instant, never a blind sleep.
This replaces the time.sleep(wait_seconds) calls in the legacy script
(legacy/claude_autoresume.py:505,667) with a policy that can notice a mid-wait
credit top-up or an overage lift instead of blocking until a fixed deadline. See
docs/architecture/decisions/0004-adaptive-waiting-with-probes-not-sleep.md.
next_probe_instant(state, *, now, started_waiting_at, probe_count, config=DEFAULT_WAIT_POLICY_CONFIG)
¶
Compute the next instant a probe should run. Never returns an instant in the
past relative to now, and — when config.max_wait is set — never proposes an
instant beyond started_waiting_at + config.max_wait (callers must treat that as
"give up", not "wait longer").
Source code in src/claudeloop/domain/waiting.py
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | |
wait_exceeded(*, started_waiting_at, now, config)
¶
Whether the configured max_wait budget has been consumed and the run loop should give up rather than schedule another probe.
Source code in src/claudeloop/domain/waiting.py
81 82 83 84 85 86 | |
claudeloop.domain.budget
¶
Budget guardrails for an unattended, potentially multi-hour/multi-day run.
BudgetLedger
dataclass
¶
Tracks consumption against a Budget. Immutable — every spend returns a new ledger, so the run loop's state transitions stay pure and testable.
Source code in src/claudeloop/domain/budget.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | |
claudeloop.domain.loop
¶
The autonomous run loop's pure state machine.
application.runner.AutonomousRunner executes the Decisions this module produces
against real ports (agent gateway, clock, sleeper, ...). Nothing in this module
performs I/O; every transition is a function of (RunState, an event, now).
RunProbe
dataclass
¶
Spend a cheap, throwaway turn purely to re-check capacity.
Source code in src/claudeloop/domain/loop.py
54 55 56 | |
SendTurn
dataclass
¶
Spend a real turn against the live session.
Source code in src/claudeloop/domain/loop.py
49 50 51 | |
decide_after_probe(state, capacity, *, now, config=DEFAULT_WAIT_POLICY_CONFIG)
¶
Called once a throwaway probe turn has completed while waiting.
Source code in src/claudeloop/domain/loop.py
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | |
decide_after_turn(state, *, capacity, verdict, now, config=DEFAULT_WAIT_POLICY_CONFIG)
¶
Called once a real turn has completed. A capacity rejection always outranks a completion claim — a limit message truncating mid-response could coincidentally contain marker-like text, but hitting a real limit is never "done".
Source code in src/claudeloop/domain/loop.py
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | |
decide_preflight(state, capacity, *, now, config=DEFAULT_WAIT_POLICY_CONFIG)
¶
The very first thing a run does: check whether we're already mid-cooldown before spending a real attempt (mirrors preflight_wait() in the legacy script).
Source code in src/claudeloop/domain/loop.py
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | |