The application under an agent, and how it says what it can do.
Saying what a host can do
A capability class names a group and its required methods. A host declares the group by inheriting the class. ABCMeta rejects an incomplete declaration.
provides returns declared groups minus the instance’s without set. without records unavailable optional backends, such as disabled web access or a missing fossick installation.
/opt/hostedtoolcache/Python/3.12.14/x64/lib/python3.12/site-packages/usearch/__init__.py:131: UserWarning: Will download `usearch_sqlite` binary from GitHub.
warnings.warn("Will download `usearch_sqlite` binary from GitHub.", UserWarning)
test_eq(sorted(c.group for c in GROUP_CLASSES), ['api', 'ask', 'code', 'git', 'memory', 'notebook', 'session', 'shell', 'watch', 'web'])assertall(issubclass(c, Capability) for c in GROUP_CLASSES)test_eq(Capability.group, '') # the base names no grouptest_eq(Host.group, 'file') # and the path boundary is its owntest_eq(len({c.group for c in GROUP_CLASSES}), len(GROUP_CLASSES)) # no two share a name
provides is the answer to every “can you?” the toolset asks. Read it against a host that declares two groups and has lost one of them.
test_eq(sorted(d.provides), ['code', 'file']) # every host has the file grouptest_eq(d.can('code'), True)test_eq(d.can('web'), False)test_eq(d.can('memory'), False)
A host with an incomplete capability declaration cannot be constructed. The error names the missing method.
class Broken(Demo, NotebookHost):def nb_cells(self, path): return [] # and no `nb_add_cell`test_fail(Broken, contains='nb_add_cell')
A host over real folders
LocalHost implements every capability group. Optional dependencies and constructor arguments add unavailable groups to without. A kernel can be attached without replacing the host.
The refusals and the skip lists are the host’s, not a tool’s. A tool that reported “outside the open folders” in its own words would be a second place to change when the rule changes.
SANDBOX, SECRET, NO_ROOTS
('path is outside the open folders',
'path holds credentials and is never read',
'no folders are open, so no path is inside them')
assertall(isinstance(s, str) and s for s in (SANDBOX, SECRET, NO_ROOTS))assertlen(DENY) >=15and'*/.env'in DENY and'*.pem'in DENYassert {'.git', '.venv', '__pycache__', 'node_modules'} <= SKIP_DIRSassert {'.pyc', '.so', '.png', '.gguf'} <= SKIP_SUFFIXESassert MAX_VARS >0and LD_CHARS >0
denied is what read_outside still refuses: credential-shaped paths, matched by fnmatch on the resolved path. A read tool is not a way to exfiltrate a key.
[p for p in ('/home/k/.ssh/id_rsa', '/proj/.env', '/proj/key.pem', '/proj/app.py') if denied(p)]
for p in ('/home/k/.ssh/id_rsa', '/home/k/.aws/credentials', '/proj/.env', '/proj/.env.local','/proj/id_ed25519', '/proj/key.pem', '/proj/cert.p12', '/home/k/.netrc'):assert denied(p), pfor p in ('/proj/app.py', '/proj/README.md', '/proj/environment.yml'):assertnot denied(p), ptest_eq(denied('/proj/.env', patterns=()), False) # an empty deny list denies nothing
ld_json pulls the structured data out of a page, which is how a product or an article survives a conversion to markdown that would otherwise throw its fields away.
page ='<html><script type="application/ld+json">{"@type": "Product", "name": "a kettle"}</script></html>'ld_json(page)
def LocalHost( roots:tuple=('.',), # the folders the agent is confined to ns:NoneType=None, # live namespace; fresh dict when None approvals:NoneType=None, # write approval handler note:NoneType=None, # status callback web:bool=True, # use fossick when installed index:bool=True, # start a Kosha sync for every open root graph:bool=False, # build Kosha's call graph during sync rerank:bool=True, # rerank Kosha hits with flashrank rerank_model:NoneType=None, # flashrank model; None uses its default memory:NoneType=None, # memory, ask and watch backend apis:NoneType=None, # API specification backend kernel:NoneType=None, # live kernel; None runs in process read_outside:bool=False, # let read-only tools name any path on this machine deny:tuple=('*/.ssh/*', '*/.aws/*', '*/.gnupg/*', '*/.config/gcloud/*', '*/.netrc', '*/.git-credentials', '*/.codex/auth.json', '*/.claude/.credentials.json', '*/.env', '*/.env.*', '*/id_rsa*', '*/id_ed25519*', '*.pem', '*.key', '*.p12'), # what `read_outside` still refuses to open):
Reference host for local folders.
The examples use one temporary source tree. index=False keeps boundary checks independent of Kosha; the retrieval section creates an indexed host.
return resolved folders for read/write. does not create folders
add_root widens the write boundary. It accepts existing directories and never creates them.
extra = Path(tempfile.mkdtemp()).resolve()/'extra'extra.mkdir(parents=True)(extra/'notes.md').write_text('read me\n')local.add_root(str(extra)), local.added_roots
test_eq(local.added_roots, [str(extra)])test_eq(local.roots[-1], str(extra)) # the new root is open for reads and writestest_eq(local.add_root(str(extra)), str(extra)) # already open: no second entrytest_eq(len(local.added_roots), 1)test_fail(lambda: local.add_root(str(extra/'nope')), contains='no such folder')test_fail(lambda: local.add_root(str(extra/'notes.md')), contains='not a folder')
test_fail(lambda: local.check('../../etc/passwd'), contains='outside the open folders')test_fail(lambda: local.check('pkg/nope.py', must_exist=True), contains='no such file')test_eq(local.check('pkg/sizes.py').name, 'sizes.py')test_eq(local.check('pkg/nope.py').name, 'nope.py') # a path to write need not exist yet
('2 folder(s); nothing outside them is readable',
'1 folder(s); reads may name any path on this machine, writes may not')
assert'nothing outside them is readable'in local.roots_noteassert'reads may name any path'in LocalHost([root], index=False, read_outside=True).roots_note
Every readable file under the open folders, with the generated directories and binary suffixes SKIP_DIRS and SKIP_SUFFIXES name left out.
got = {p.name for p in local.walk()}assert {'sizes.py', 'use.py'} <= got, got(root/'__pycache__').mkdir(exist_ok=True); (root/'__pycache__'/'x.pyc').write_text('junk')assert'x.pyc'notin {p.name for p in local.walk()}
Tell the user something out of band. Never blocks. A host may drop it.
note never blocks. It appends every message to transcript before calling the optional status callback.
said = []noisy = LocalHost([root], index=False, note=said.append)noisy.note('starting a kernel')said, noisy.transcript
(['starting a kernel'], ['starting a kernel'])
test_eq(said, ['starting a kernel'])test_eq(noisy.transcript, ['starting a kernel']) # kept, and `terminal_text` reads it backboom = LocalHost([root], index=False, note=lambda t: 1/0)boom.note('swallowed') # a broken note must not end a turntest_eq(boom.transcript, ['swallowed'])
Seeing the code
Three engines answer a search, and which one answered is part of the result. Kosha’s hybrid index when it has synced, ripgrep when it has not, and reading the files when neither is installed.
Exact matching through ripgrep. None when rgapi is unavailable. The tool then reads files itself.
Exact matching through ripgrep. None means this host has no exact matcher, and the tool then reads the files itself rather than reporting no matches.
hits = local.grep('threshold')assert hits andall(h.line >=1for h in hits), hitsassertany('sizes.py'instr(h.path) for h in hits)test_eq(local.grep('no_such_symbol_anywhere'), [])assert local.grep('def .*old', regex=True), 'a regex must reach ripgrep as a regex'
The code index and the literal scan, fused by rank rather than tried in order.
Kosha’s index and ripgrep fused by rank rather than tried in order, falling back to reading the files when neither is installed. An empty query answers [] rather than everything.
assert local.search('threshold'), 'ripgrep found nothing'test_eq(local.search(''), [])test_eq(local.search(' '), [])
'Kosha semantic + keyword index over 1 folder(s) and environment fused with ripgrep'
assert'fallback'in local.search_note, local.search_note# the sync runs concurrently; read the note oncenote = indexing.search_noteassert (note.startswith('Kosha semantic + keyword index over')or note.startswith('Kosha unavailable')or note =='Kosha sync in progress; literal fallback via ripgrep'), noteif indexing.index_ready: test_eq(indexing.indexed, [str(root)])assert indexing.search('threshold'), 'ripgrep answers whether or not the index has landed'
local.search_note
"Kosha unavailable (AttributeError: 'LocalHost' object has no attribute 'sync_index'); literal fallback"
The defs and classes in one file, as Hits whose score is the indent depth.
From parsing rather than grepping, so a method is reported at its own depth under its qualified name. score is the indent depth.
test_eq([h.symbol for h in local.symbols('pkg/sizes.py')], ['threshold'])test_eq(local.symbols('pkg/sizes.py')[0].score, 0)(root/'pkg'/'cls.py').write_text('class A:\n def m(self): pass\n')test_eq([(h.symbol, h.score) for h in local.symbols('pkg/cls.py')], [('A', 0), ('A.m', 1)])test_eq(local.symbols('pkg/nope.py'), [])
Find references to the symbol defined at path:line.
peers finds references to the definition at a line and excludes the defining line.
peers = local.peers('pkg/sizes.py', 1)assertany('use.py'instr(h.path) for h in peers), peersassertnotany(str(h.path) ==str(root/'pkg'/'sizes.py') and h.line ==1for h in peers)assert local.peers('pkg/sizes.py', 999), 'the last def at or above the line is still a def'test_eq(local.peers('pkg/sizes.py', 0), [])test_eq(local.peers('pkg/nope.py', 1), [])
ids = [c[0] for c in local.nb_cells('nb/new.ipynb')]test_eq(ids, [first, cid, second]) # index=0 went first, -1 appendedtest_eq([c[1] for c in local.nb_cells('nb/new.ipynb')], ['code', 'code', 'markdown'])assert'a heading'in local.text_at('nb/new.ipynb')
Run code in the live namespace. Failures come back as text: a tool cannot usefully raise.
The live namespace persists between calls, which is what makes “bind results to new names” a rule worth briefing an agent on. A failure comes back as text: a tool that raises ends the turn.
local.run_python('import math\nradii = [1, 2, 3]'), local.run_python('areas = [math.pi*r*r for r in radii]\nlen(areas)')
('(no output)', '3')
test_eq(local.run_python('len(areas)'), '3')test_eq(local.run_python('print("a side effect")'), 'a side effect')assert'NameError'in local.run_python('no_such_name + 1')test_eq(local.run_python(''), '(no output)')
Run code without rebinding anything the user made. A kernel enforces this; a copy approximates it.
In-process inspection uses a shallow namespace copy. New bindings disappear after the call; mutations to shared objects remain.
local.inspect_python('doubled = [r*2 for r in radii]\nlen(doubled)'), local.run_python('"doubled" in globals()')
('3', 'False')
test_eq(local.inspect_python('doubled = [r*2 for r in radii]\nlen(doubled)'), '3')test_eq(local.run_python('"doubled" in globals()'), 'False')local.inspect_python('radii.append(99)')test_eq(local.run_python('len(radii)'), '4')assert'only honours'in local.inspect_python('1+1', scope='overlay')
The live namespace runner states whether inspection can run while a cell is busy. The harness uses concurrent to decide whether to inspect now or queue the call.
What is in the live namespace: name, type, and a short value, one per line.
Name, type and a short value, one line each. Callables and dunders are left out: an agent asking what is in the namespace wants the data, not the imports.
rows =dict(l.split(None, 1) for l in local.list_vars().splitlines())assert'radii'in rows and'list'in rows['radii'], rowsassert'math'notin rows andnotany(k.startswith('_') for k in rows)
Run a shell command in a new process group. Interleave stdout and stderr. Kill the process group on timeout.
In its own process group under the first open folder, with stdout and stderr interleaved. A failure is an exit code rather than an exception, and the group is killed on timeout.
test_eq(local.run_cmd('echo hi'), (0, 'hi\n'))test_eq(local.run_cmd('exit 3')[0], 3)test_eq(local.run_cmd('pwd')[1].strip(), str(root))test_eq(local.run_cmd('echo out; echo err 1>&2')[1], 'out\nerr\n') # interleaved, in ordertest_eq(local.run_cmd(''), (0, ''))code, out = local.run_cmd('sleep 5', timeout=1)test_eq(code, 124);assert'killed after 1s'in out
'shell, in /private/var/folders/kg/9vdw4mdd1fs58svgh4k1qhr09x7dqh/T/tmp38ppko54/proj'
assertstr(root) in local.shell_note
The web
Every page arrives as markdown. A URL that is a paper, a repository file or a video gets the reader that knows its shape. Everything else is fetched. A page that comes back too thin to be real is fetched again the expensive way.
Search the web through fossick. An empty query answers []: that is how tools_for probes.
Through fossick, and an empty query answers [] without reaching the network.
local.can('web'), local.web_search('')
(True, [])
test_eq(local.web_search(''), []) # the probe must not hit the networkoffline = LocalHost([root], index=False, web=False)test_eq(offline.can('web'), False)test_fail(lambda: offline.web_search('anything'), NotImplementedError)
test_eq(local.research_note, 'fossick')test_eq(LocalHost([root], index=False, web=False).research_note, 'web access is switched off')
The live check below exercises LocalHost against fossick directly. It is excluded from the default suite because it searches and fetches the public web.
live_host = LocalHost([Path(tempfile.mkdtemp())], index=False)live_hits = live_host.web_search('Python programming language official site', n=5)live_doc = live_host.read_url('https://www.python.org/', remember=False)live_digest = live_host.research('What is the Python programming language?')assert live_hits andall(h.title and h.url.startswith('http') for h in live_hits)assert live_doc.url =='https://www.python.org/'andlen(live_doc.text) >400assertlen(live_digest) >200and'python'in live_digest.lower()
Run every watch that is due and report what fired. One failing watch must not stop the rest.
Memory, watches and an API specification
The memory backend is a vishalakshiVault, opened by whatever built the host, and the forwards are written in the vault’s own vocabulary: remember calls note, memory_tree calls toc, memory_topics calls topic_tree. The other seven names are the same on both sides.
from vishalakshi import Vaultvault = Vault(Path(tempfile.mkdtemp())/'vault.db', offline=True) # hashed embeddings, so no model to fetchremembering = LocalHost([root], index=False, memory=vault)test_eq(sorted(remembering.without), ['api']) # one vault answers memory, ask and watchassert remembering.can('memory') and remembering.can('ask') and remembering.can('watch')sorted(remembering.provides), sorted(remembering.without)
# a host with no vault refuses by naming the missing backend, rather than failing inside a forwardtest_eq(local.can('memory'), False)for nm in ('memory_tree', 'memory_search', 'ask', 'remember', 'memory_topics', 'memory_read', 'memory_forget'):try: getattr(local, nm)('x')except HostError as e: assert'no vault'instr(e), (nm, e)else: assertFalse, f'{nm} did not refuse'for call in (lambda: local.watch('x'), lambda: local.watches(), lambda: local.unwatch('w1'), lambda: local.poll()): test_fail(call, contains='no vault')
Each forward is one line. The round trip below is the real one: a note filed into the vault, found by search, and read back by the node_id that search returned.
doc = remembering.remember('the kettle boils at 100C', title='kettles', tags=['home'])remembering.memory_search('kettle'), [t['title'] for t in remembering.memory_tree()], doc
from inspect import signaturehits = remembering.memory_search('kettle')test_eq(hits[0]['doc_id'], doc['doc_id'])assert'kettle'in hits[0]['snippet']test_eq([t['doc_id'] for t in remembering.memory_tree(doc['doc_id'])], [doc['doc_id']])assert'the kettle boils at 100C'in remembering.memory_read(hits[0]['node_id'])['text']test_eq(len(remembering.memory_topics()), 0) # topics come from the graph, and connect() builds itassert {'ref', 'instruction'} <=set(signature(vault.ask).parameters) # what `ask` forwards, verbatimremembering.memory_forget(doc['doc_id'])assert doc['doc_id'] notin [t['doc_id'] for t in remembering.memory_tree()]
A watch is a job the host re-runs on an interval, and poll is the tick.
w = remembering.watch('check the kettle', action='remind', every='1d')due = remembering.watches(due_only=True) # an interval watch is due the moment it is registeredfired = remembering.poll()[x['id'] for x in due], fired
test_eq([x['id'] for x in remembering.watches()], [w['id']])test_eq(w['every'], 86400) # '1d' reaches the vault as secondstest_eq([x['id'] for x in due], [w['id']])test_eq(fired['ran'], 1)test_eq([r['status'] for r in fired['results']], ['ok'])test_eq(remembering.watch_actions, ('remind',)) # what this host declares it will acceptremembering.unwatch(w['id'])test_eq(len(remembering.watches()), 0)
test_eq(speccy.can('api'), True)test_eq(speccy.api_load(GITHUB)['operations'], 2)test_eq(speccy.api_count(), 2)test_eq([o['name'] for o in speccy.api_ops(match='zen')], ['get_zen']) # fastspec snake_cases operationIdtest_eq([o['name'] for o in speccy.api_ops(group='zen')], ['get_zen']) # and takes the group from the pathtest_eq(speccy.api_ops(group='nothing'), [])test_eq(len(speccy.api_ops(limit=1)), 1)test_eq(speccy.api_ops(limit=1, offset=1)[0]['name'], 'get_repo')test_eq(speccy.api_call('get_zen')['status'], 200)
# and a host with no api backend refuses by naming what is missingfor call in (lambda: local.api_load('x'), lambda: local.api_ops(), lambda: local.api_count(),lambda: local.api_call('x')): test_fail(call, contains='no API specifications')
@patch runs after ABCMeta computes abstract methods. implemented recomputes them after the patches are applied.
Recompute what cls is still missing, after @patch filled some of it in.
class Hollow(Host, ApiHost):"Declares the api group and writes none of it."@propertydef roots(self): return []def check(self, path, must_exist=False, reading=False): return Path(path)def walk(self): return []def read(self, path): returnNonedef write(self, path, text): returnstr(path)def text_at(self, path): return''Hollow.__abstractmethods__ =frozenset() # as the LocalHost cell does, to allow the patchestest_fail(implemented(Hollow), contains='api_load')
@patchdef api_load(self:Hollow, src, name=''): return {}@patchdef api_ops(self:Hollow, group='', name='', match='', limit=None, offset=0): return []@patchdef api_count(self:Hollow, group='', name='', match=''): return0@patchdef api_call(self:Hollow, operation, name='', **params): return {}implemented(Hollow)test_eq(Hollow().provides, {'api', 'file'}) # written, so it builds, and it declares what it wrotetest_eq(Hollow.__abstractmethods__, frozenset())
A kernel, attached to a host that already exists
run_python and related methods delegate to self.kernel when present. Assigning a kernel preserves the host’s capability groups and attached backends.
class Kernel:"The shape `LocalHost` expects of a kernel: four calls and two facts." scopes, kind = ('isolated', 'overlay'), 'ipykernel'def__init__(self): self.ns = {}def run(self, code): exec(code, self.ns);return'ran on the kernel'def inspect(self, code, scope='isolated'): returnf'inspected under {scope}'def list_vars(self): return', '.join(k for k inself.ns ifnot k.startswith('__'))k = LocalHost([root], index=False)before =sorted(k.provides)k.kernel = Kernel()before, sorted(k.provides), k.scopes, k.kernel_kind