The host-side capability services live here, one class per domain, from shell authorization to Skill editing. HostDispatcher composes them and stays wrapped around every call as the shared RPC envelope: argument validation, permission and approval checks, audit records, untrusted-output screening, activity events, and soft-error routing. Nothing in this package is a network endpoint of its own; each service only implements the behavior of its own domain.
Python's worker-side host facade emits a synchronous host_call. kernel/manager.py hands it to HostDispatcher, which applies policy and invokes one of the services below. The returned value goes back in the matching host_response, so the blocked Cell can resume. Native control tools go through the same dispatcher where their capability overlaps, which keeps control-plane and in-kernel policy consistent.
A service can fail softly by returning the single-key shape {"error": message}. The Python worker turns that into a RuntimeError; it is never a successful scientific result or a task completion. Permission, replay, audit, and injection policy stay in the dispatcher, and most services deliberately do not reimplement them.
| File | Responsibility |
|---|---|
__init__.py |
Re-exports most of the service classes used by composition code. BashAuthorizationService and ScienceConnectorService are not in __all__; callers import those from their own modules. |
bash.py |
Authorizes kernel-local host.bash without ever running it; the module does not import subprocess. The trusted host repeats the safety and egress checks the worker already did, redacts the proposal, then mints a short-lived token bound to the command digest, cwd, worker generation, and challenge. That token can be redeemed exactly once. Whatever the worker reports back is bounded and redacted before it is recorded. |
accelerators.py |
Probes GPUs visible to the local daemon with nvidia-smi, separately reports container-runtime availability, and deliberately makes no claim that a model repository, checkpoint, or backend is ready. |
code_evidence.py |
Completion evidence for turns whose deliverable is source code. The produce-file reconciler asks whether a claimed file exists — right for a figure, wrong for a pipeline, where existence says nothing about whether the module imports or whether any test ran. For the reusable_pipeline and codebase_change task modes the payload must carry source_files, entry_points, architecture_summary and test_evidence, and each is checked against something the run cannot author: files resolve inside the evidence roots, match a declared sha256, and are registered artifacts; a Python entry point must compile() from its own bytes and is never exec'd (verifying an entry point must not become a way to run agent code in the daemon), while R and everything else are existence-checked because the daemon has no honest parser for them; and each test command names the cell that ran it, whose stored ok status and recorded stdout decide pass or fail. There is deliberately no field for output text, so "the tests passed" cannot be a claim. analysis_run skips all of it — the fields stay optional and unvalidated. A missing context is a refusal, not a degraded accept: "cannot verify" is not "verified". |
completion.py |
The only success contract a Cell has. It validates an output, one to four completed-action bullets, and an optional output schema, and keeps one valid submission for the active dispatch context. The bullets' English past-tense guard accepts a regular -ed form, a listed irregular past (took, did, cut, reset), or a re-/over-/un-/mis-/out-/up-/pre- prefix on an irregular past spelled unlike its base verb (overwrote, rebuilt, undid) — never on one spelled like it, so output and upset stay refused, as do future and progressive forms. It does not apply to CJK bullets, whose morphology carries no tense. When an evidence provider is wired in, claims are reconciled per claim: every file or artifact the output names must be backed by the artifact store, an executed cell's recorded writes, or the filesystem, and numbers the summary repeats must agree with the same submission's metrics. An output too large or too deep to verify exhaustively is refused rather than partially checked, while a raising evidence provider degrades to the legacy unreconciled accept — refusing every honest submission would deadlock the only completion signal the loop accepts. |
credentials.py |
Session-local credentials, held in memory and handed out as short-lived, action-bound, single-use leases. Rotating a credential invalidates its outstanding leases. No raw value is persisted here. |
data.py |
The Store-backed data surface. Read-only SQL, schema access, and frame browsing sit on one side; Artifact metadata, versions, paths, save, restore, and image projection on the other, together with provenance and lineage reads and reports. Enumeration and lookup of Artifacts stay inside the caller's own session and project. In team mode, artifact_path never returns a process-wide snapshot path to a Cell: it streams checksum/size-verified bytes into an opaque root-frame-scoped cache that the sandbox exposes read-only. Persistent kernels keep one stable private copy per distinct version they open; per-session and daemon-wide byte/file quotas plus a free-space reserve bound that linear growth, and session/project deletion reclaims each root's cache. The current frame is only the handle that resolves that root_frame_id/project_id scope, so Artifacts written by earlier Cells of the same session stay reachable. A sibling session's file is never read in place: materialise_artifact brings the version into the calling session as its own Artifact with a lineage edge back and a private copy of the frozen, checksummed bytes, so no writable alias can mutate either version identity. A cross-session read would otherwise leave the borrower holding an analysis whose input has no version in its own history. Its refusals are indistinguishable from absence, as every version-keyed read here is, and the lineage walk is bounded by default: omitting max_depth/max_nodes used to walk every edge in the table across every session, and a silently partial graph is a lineage claim that is wrong rather than absent. |
delegation.py |
The front of the session's delegation runtime. Merging a stored agent profile into one delegate call is two merges, not one, because two of those fields are restrictions and the rest are settings: model, provider, steps, permissions and capabilities are defaults the caller may override, while skill_names, connectors and unrestricted are intersected through resource_allowlist.narrow and unrestricted is a floor a call cannot raise. Treating all of them alike made a stored Specialist restriction advisory — the agent chose its own allowlist through the documented host.delegate(...) signature. The specialist persona is prepended recursively across all three request shapes, including the list that means fan-out; handling only the string and the dict produced generic children from a fan-out to a named specialist, and the caller saw a successful delegation. The delegate, children, collect, stop, message, and stats calls themselves pass through to the runtime that owns the children. |
delegation_policy.py |
Parses a child Agent's method and capability policy once, then freezes it. Naming any capability makes the policy restricted. Even then, five methods (submit_output, prov_record, prov_resolve_path, search_capabilities, capabilities) are allowed on top of the listed capabilities and their aliases — under every restricted policy, including one whose capability list is empty. Per-method allow/ask/deny decisions and tool visibility ride along, and the separate unrestricted mode stays explicit in the projection rather than being implied. |
resource_allowlist.py |
Tri-state Skill/Connector allowlists and the delegation narrowing rule. None inherits, [] denies everything, a list is exactly those — and Python spells the first two false, so if not allowed: reads naturally and turns "deny everything" into "allow everything": a permission check that fails open for the one configuration chosen to lock something down. A child may only narrow; inheriting is not widening, so delegation cannot become the way out of an allowlist. |
endpoints.py |
Loopback port allocation, endpoint metadata with its start and stop scripts, and a readiness probe against the live route. Registration stores those lifecycle scripts; it does not run them, and it introduces no egress policy of its own. |
files.py |
The workspace path boundary, plus the budgets that keep a bounded question from costing unbounded work. It resolves the late-bound session workspace — memoized on the two cheap inputs that can change it, because relative() calls it once per candidate and a glob over N files was paying N resolve()+mkdir() pairs — keeps relative paths inside it, and refuses secret paths. That refusal is directory-aware over paths relative to the explicitly trusted workspace root: from a root such as $HOME, a basename-only denylist let read_file return .aws/credentials and .ssh/known_hosts verbatim. A run deliberately rooted inside a credential directory remains usable, because the root's parent segments are not reapplied to every child. The check also examines the resolved relative path and completes a bounded, no-follow workspace alias inventory before using a candidate, so a sibling symlink cannot hide the same canonical secret tree; an unreadable, timed-out, or entry-truncated inventory is refused. On POSIX, actual I/O pins the workspace and every parent through dir_fd/openat-style opens with O_NOFOLLOW; final regular files are validated and consumed through the same descriptor, multiply linked files are refused, and writes publish through the pinned parent. Native Windows lacks these required capabilities, so the operations fail closed rather than falling back to pathname replacement or check-then-open. This closes userspace namespace-substitution races, not attacks by a compromised kernel or arbitrary kernel-semantics changes. Two tiers share the one table: is_secret_path for the unconditional hard pre-gate, and the wider is_credential_path for automatic approval review. A wider-tier match becomes an audited ask: an attached human can review a false positive, while a headless run refuses it under deterministic policy before Guardian can authorize it. It also owns BoundedTextReader (incremental UTF-8 decode under a byte budget, with counters that say how much was not read) and BoundedSelection (the smallest N keys of a stream, plus the truncation counters every workspace collection tool returns), so one definition of "what a bounded channel reports" serves read, glob, grep and list. The remaining methods are compatibility dispatch into the class-based file tools, which is where concrete I/O behavior lives; those tools import the two primitives lazily to keep openai4s.tools off the ~40 ms openai4s.host import on the CLI startup path. |
llm.py |
Calls the configured model synchronously from a running Cell. A batch request fans out concurrently under the fan-out cap. The service also reports the current model, and its model listing is not a catalogue: it returns exactly one entry, the configured model plus its context window. |
mcp.py |
Resolves a persisted MCP connector by id first, then by exact display name, and hands list/tools/call/resource/prompt operations to the MCP manager. It carries the tri-state connector allowlist, and enforces it at that one lookup rather than at each of the six RPC entry points: the launch config is built out of the row connector() returns, so a connector this specialist may not reach has no command to be started with — stronger than refusing per call site and forgetting one. A denied connector is reported absent rather than refused, so a distinct refusal cannot be used to enumerate what exists, and the catalogue is filtered too, because a name the agent can see is a name it will ask for. Discovery is zero-spawn when the connector is disabled: call already refused, but discovery is what launches the process, so enabled has to gate the spawn. The launch env comes from the Store's credential broker, so a secret:// reference is resolved rather than handed to the server literally. Screening what comes back is not its job. Two connectors get extra treatment here. The bundled protein-design server is confined at every spawn path: its path authority is rooted in the caller's workspace rather than the daemon's cwd, model admission stays required unless an operator explicitly turns it off, the cached process is partitioned per workspace, and the transport deadline is ordered to outlive the backend's own timeout so a long fold fails with a terminal record instead of an orphaned child. The managed volcengine-datapro connector is narrowed to its single dataPro_search tool with a validated one-key query, answers discovery locally so no gate-free authenticated session ever probes the user's live key, redacts that key from results, and indexes each successful search. Permission and untrusted-output checks stay in the dispatcher; connection state, deadlines and reader discipline stay in mcp_client.py. |
model_admission.py |
Supplies the connector-neutral live-process state machine that binds a successful parsed canary to one exact operation, backend revision, checkpoint digest, and execution target. It deliberately forgets admission on process restart; connectors still own the real inference and scientific output parser. |
progress.py |
Todos live in memory here; plan steps and reviewer progress live in the Store. Approval is not a precondition for ticking a step: absent an explicit plan_id, the plan it updates is whatever the Store returns for the frame, which is the newest plan that has not been discarded. |
remote_capabilities.py |
Registration is gated on evidence. A narrowly structured probe spec is normalized into one safe remote command and run to check that the remote capability is actually there; only then does the verified service metadata enter the remote-compute registry. |
remote_science.py |
Runs the registered folding and mutation-scoring wrappers over SSH, parses their explicit result markers, and buffers remote provenance for the producing cell. A missing or failed service returns an error. It does not fabricate science. |
connector_manifest.py |
What each science connector depends on from its upstream API, declared. Two levels: required (the array container and the record id, without which the connector returns nothing) and expected (fields the parser reads but degrades without). Not a second copy of the parse logic — an offline test proves each required path is present in the connector's own fixture and load-bearing (delete it and the adapter stops returning the record), so the manifest cannot over-claim. Backs the nightly canary. |
science.py |
Seven public databases behind one envelope: UniProt, PDB, Ensembl, ChEMBL, PubChem, arXiv, and OpenAlex. Requests are built against the allowlist and go out through the shared fetch path, and each response is normalized into the same record shape. Every search also returns a provenance envelope — source, request URL, query and filters, the retrieval time, the normalization version, and one entry per upstream response with its SHA-256 — without which a retrieved record cannot answer the two questions that decide whether it is evidence: when was this true, and are these the same bytes. The digest is over the response as received, taken in the one place all seven adapters pass through; hashing the decoded string instead answered "do these normalize the same", because the fetch path replaces invalid bytes and round-trips JSON. A transport that cannot describe the raw bytes says so through hashed rather than presenting a content hash as a response hash, and a search that made several requests keeps all of them, in order, since a result assembled from two responses is only reproducible if both are named. |
stage10_science.py |
Stage 10 ClinVar, PubMed, and ClinicalTrials.gov adapters. They stay out of the default catalog; the flag adds them to the same search envelope, with pagination, a short cache, honest empty/429/schema errors, and a versioned Artifact provenance payload. |
session.py |
Pins control operations to the dispatcher's current root session, so no call can reach into another conversation. Checkpoints and pending permission requests always come from Store. Branch and recovery status come from the attached Web session-domain service, which is the normal Web runtime; with no domain attached, the status projection falls back to a read-only branch list from Store and reports recovery as unavailable. The filesystem-aware checkpoint, fork, revert, and recovery operations are delegated to that same domain service. |
skills.py |
The Skill lifecycle end to end: search, read, edit, publish, version, roll back, delete. Scope decides which directory on disk owns a Skill; bundled Skills keep precedence over user ones, and writes stay confined to the skill directories. In team mode every Host-originated mutation is admin-only: project membership authorizes the separate human HTTP controls, not a model planting SKILL.md instructions or a kernel.py sidecar for peers. This is also where the tri-state Skill allowlist is held and enforced — on the writes as well as the reads, since a child restricted to ["a"] that could still overwrite, publish or delete skill b hands the parent a recipe it will go on to execute. Because the allowlist lives on this object and the corpus lives on the loader beside it, two views are rendered from here rather than from the loader: the system-prompt block, and the in-kernel sidecar gate. A caller holding HostDispatcher.skill_loader gets the unfiltered corpus, which is exactly how a restricted child once had all 34 Skills named in its prompt. |
HostDispatcher, not an individual service, is the authorization and audit boundary. Calling a service directly is trusted in-process composition and bypasses that envelope.host.bash never runs here. Its shell execution stays inside the scientific worker through sdk/bash.py, and bash.py only mints and redeems the one-shot capability; reported stdout/stderr is bounded and redacted before persistence. Do not read that as a package-wide property. remote_science.py and remote_capabilities.py each default their runner to subprocess.run and shell out from the trusted host process, running ssh -o ConnectTimeout=15 -o BatchMode=yes <host> <command> to reach a registered remote GPU host.credentials.py, but any consumer that receives a redeemed value holds the power that goes with it. Name-based redaction is not a proof that arbitrary output contains no secret.files.py confines paths, while the actual Tool classes own read/write behavior. Artifact snapshots and provenance registration are separate persistence steps that try but do not guarantee; they are not one global filesystem/SQLite transaction.host.compute, remote capability provisioning, folding, and mutation scoring are evolving integration surfaces. A registered route or service class does not prove that provider credentials, remote software, GPU capacity, or end-to-end UI recovery are configured.