openloops.tools#
The package’s single list of operations: plain functions, JSON-ready dicts.
Every surface openloops has or might grow — the ol command today, an MCP server or
an HTTP endpoint later — dispatches from this module and nothing else. The functions
here know nothing about argument parsers, transports or agent hosts; they take flat
serialisable arguments and return flat serialisable results, and printing is somebody
else’s job.
Keeping one list is what stops two surfaces from drifting apart. A parity test between two surfaces is a sign that there are two implementations, and there is one here.
Module Attributes
The keys every |
Functions
|
Read what changed in the sessions and write the digests. |
|
The digests in the store, newest last-turn first. |
|
One digest in full, found by session id or by a unique prefix of one. |
|
Where everything is, how much of it there is, and how stale the cache is. |
|
What you still owe your agents, with each obligation re-checked against the world. |
|
What your repositories are waiting on — and what is no longer waiting. |
|
All three answers as one self-contained HTML page, for looking at rather than reading. |
- openloops.tools.ROW_FIELDS = ('session', 'source', 'state', 'title', 'ai_title', 'project', 'branches', 'started', 'ended', 'last_turn', 'turns', 'model', 'confidence', 'verified')#
The keys every
ls()row carries, whatever the digest happens to record.
- openloops.tools.blocked(*, resolve=True, owners=None, repos=None, query='is:blocked', limit=50, timeout=30.0, issues_source=None, blockers_source=None)[source]#
What your repositories are waiting on — and what is no longer waiting.
The sibling of
owed(): an obligation points at a person, a blocker edge points at another repository, and both are commitments nothing is watching. Lists the open issues carrying ablocked_bydependency acrossowners(or exactly thereposyou name), resolves every edge, and reports three states:unblocked(every blocker closed — the work is free and nobody has been told),blocked(naming the foreign repository it waits on) andunknown(nothing could be resolved). Nothing is ever closed, relabelled or written; seeopenloops.blockersfor what discovery costs and what it is measured to miss.The result is an envelope rather than a list, because
listed=False— discovery itself failed — must not be readable as “nothing is waiting”.>>> report = blocked(issues_source=[], blockers_source={}) >>> report['listed'], report['counts']['total'] (True, 0)
- openloops.tools.dashboard(*, verify=True, resolve=True, owners=None, repos=None, limit=50, candidate_limit=50, max_sessions=40, title='Open Loops Board', standalone=True, path=None, made_at=None, owed_report=None, blocked_report=None, sessions=None, digests_store=None)[source]#
All three answers as one self-contained HTML page, for looking at rather than reading.
Runs
owed(),blocked()andls(), then rendersopenloops.dashboard.render_dashboard()over what they returned. The page is a snapshot: it stamps the moment it was made and states, in its largest type, that it has checked nothing since — because a published page cannot. Nothing is written to GitHub, here or anywhere in openloops.pathalso writes the page there, scrubbed, and reports where it went.standalone=Falsereturns the same page without the document scaffold, for a host that supplies its own<head>.Each of the three reads is a seam: pass
owed_report=,blocked_report=orsessions=and that one is not performed. With all three passed this reaches no network and needs nogh, which is how the tests run.countscomes back alongside the page and carriesnull— not0— for any figure that could not be established, so a caller reading the JSON is held to the same three-state rule as a reader looking at the page.>>> result = dashboard(owed_report={'listed': True, 'counts': {'open': 0}, 'rows': []}, ... blocked_report={'listed': False, 'error': 'no gh'}, ... sessions=[], made_at='2026-01-01T00:00:00Z') >>> result['counts']['free_to_proceed'] is None, result['counts']['unknown'] is None (True, True)
- openloops.tools.ls(*, state='open', source=None, project=None, confidence=None, limit=20, digests_store=None)[source]#
The digests in the store, newest last-turn first.
stateisopen,archiveorall.source,projectandconfidencenarrow by the digest’s own header fields. Each row is that front matter plus its key.confidence='high'is the one worth knowing about: most sessions land inopen, and a good few of those are open only because nothing said otherwise. Filtering tohighleaves the ones where the session itself said something.>>> rows = ls(digests_store={'m/open/s1.md': ... '---\nsession: s1\nstate: open\nlast_turn: T9\n---\n'}) >>> rows[0]['session'], rows[0]['key'] ('s1', 'm/open/s1.md')
- openloops.tools.owed(*, verify=True, owners=None, trusted_owners=None, label='manual-task', limit=50, timeout=30.0, predicate_timeout=20.0, issues_source=None, run_predicate=None)[source]#
What you still owe your agents, with each obligation re-checked against the world.
Lists the open
manual-taskissues acrossownersand evaluates the**Verify:**predicate each one carries, reporting three states:open,discharged(the predicate returned 0 — done, but the issue is still open) andunknown(nothing could be checked). Nothing is ever closed, relabelled or written; seeopenloops.obligationsfor the trust boundary that evaluating a predicate crosses, andverify=Falsefor the way to read without executing.The result is an envelope rather than a list, because
listed=False— the query itself failed — must not be readable as “nothing owed”.>>> report = owed(issues_source=[], run_predicate=lambda command: 0) >>> report['listed'], report['counts']['total'] (True, 0)
- openloops.tools.show(session, *, source=None, digests_store=None)[source]#
One digest in full, found by session id or by a unique prefix of one.
An exact id always wins over a prefix, so a session whose id happens to be a prefix of another stays reachable.
sourcenarrows a store that several machines write into, where the same session can legitimately appear more than once.>>> store = {'m/open/abcdef.md': '---\nsession: abcdef\n---\nbody'} >>> show('abc', digests_store=store)['key'] 'm/open/abcdef.md'
- openloops.tools.status(*, source=None, since_days=None, digests_store=None, transcript_source=None)[source]#
Where everything is, how much of it there is, and how stale the cache is.
Reports the cache’s age because a read served from a cache that nothing has refreshed is the failure openloops is built to avoid — a periodic job that died leaves a confident, months-old answer behind, and the only defence is saying how old the answer is.
- openloops.tools.sync(*, source=None, since_days=None, force=False, transcript_source=None, digests_store=None, state_dir=None)[source]#
Read what changed in the sessions and write the digests. Returns a summary.
since_daysbounds the scan to recently-modified transcripts;forcere-derives everything, which must produce an identical store.state_dirscopes the change-detection cache. It matters because the seams do not isolate on their own: swappingtranscript_sourcefor a fixture and leaving the cache alone writes the fixture’s revisions into the caller’s real one.