eve-hub
v0.16.18
Published
Bot-side code shared by every hub agent: registry tools, the roster hook, the eve channel, the host sandbox and browser, and model helpers.
Readme
eve-hub
The bot-side code every hub agent shares (milestone M3 in docs/architecture; D0, D12). Bots live in a workspace (their own repo, docs/workspace.md); each keeps a one-line stub per file and the code lives here once.
| Module | What it is | Bots use it as |
|---|---|---|
| tools/update_thread.ts, list_threads.ts, read_thread.ts, react.ts, show.ts, browser.ts | Registry and workspace tools (defineTool) | agent/tools/<same name>.ts |
| tools/create_thread.ts, post_message.ts, archive_thread.ts, unarchive_thread.ts, list_agents.ts, create_agent.ts, remove_agent.ts, restart_agent.ts, tag_threads.ts, apply_tags.ts | The orchestrator's tools | main's agent/tools/<same name>.ts |
| hooks/sandbox.ts | Migrates legacy Docker sessions to the host sandbox (defineHook) | agent/hooks/sandbox.ts |
| instructions/main.ts, main-instructions.mjs | main's instructions (defineInstructions), rendered at build time from the kit's templates/main-instructions.md (identity, role) between the shared templates, each in its own tag, so no workspace carries a copy | main's agent/instructions.ts; workspace-only additions in agent/role/*.md (inside <role>) |
| instructions/protocol.ts | The shared sections alone (templates/team.md, execution.md, engineering.md, deliveries.md, reaching-others.md, writing.md, each in its tag), for a bot whose instructions.md still says who it is and how it works | every other bot's agent/instructions/protocol.ts, appended after its own instructions.md; a bot with agent/role.md has agent/instructions.ts (botInstructions) instead, which renders identity, the shared sections and role in order |
| instructions/engineering.ts | The engineering page alone (templates/engineering.md), for a subagent that doesn't carry the protocol | the subagent's instructions/engineering.ts |
| memory/workspace.ts, memory.mjs | an eve memory slot (default export, constant workspace scope) whose provider recalls the workspace's memory/<id>.md entries (plus the memory/<domain>/ folders the bot lists as domains in its roster entry), one message each, at every turn start and after compaction, only the ones whose text changed since the session last saw them, as new messages after the history; validateMemory(root) for a workspace's tests | every bot's (main too) agent/memory/workspace.ts: export { default } from "eve-hub/memory/workspace" |
| memory/bot.ts | the same for the bot's own agents/<bot>/memory/<id>.md entries; the bot is found from its directory (findBotDir), scope is its name | every bot's agent/memory/bot.ts: export { default } from "eve-hub/memory/bot" |
| channels/eve-roster-user.ts | eveChannel({ auth: [rosterUser, localDev()] }): eve's default steer policy, and the registry's x-roster-user human as a user principal | every bot's agent/channels/eve.ts |
| channels/eve.ts | Re-exports eve-roster-user.ts, for stubs written before every bot shared one channel | older agent/channels/eve.ts stubs |
| tools/disabled.ts | disableTool(): removes an eve default tool | the read-only preset's agent/tools/write_file.ts |
| host-sandbox.ts, host-browser.ts | HostSandbox (the host machine is the sandbox) and each session's headless Chromium | agent/sandbox.ts imports HostSandbox |
| roster-model.mjs | bindRoster(workspace): rosterModel(name, …) (the dynamic model for agent.ts), rosterLimits, agents and subagentCap, for the hub.workspace.json the bot imports; rosterModel / rosterLimits read from disk for bots scaffolded before it; createRosterModel({ agents, subagentCap }): the same for a given roster | bindRoster(workspace) in agent.ts |
| session-limits.mjs | sessionLimits(roster, name): a bot's eve limits (no session timeout, per-session usage limits from hub.workspace.json over the default backstop) | limits: rosterLimits("<name>") from eve-hub/roster-model |
| workspace.mjs | Reads and validates hub.workspace.json, assigns ports, finds a bot's workspace (HUB_WORKSPACE, else up from cwd) | the daemon (lib/workspace-root.mjs) and roster-model.mjs |
| workspace-roster.mjs | agents, humans, SUBAGENT_CAP of the bot's workspace, read from disk | older subagents that resolve their capped model |
| bot-model.mjs, subagent-cap.mjs | Model parsing/selection and the subagent cap | via roster-model.mjs; subagents import subagent-cap.mjs |
The hubChannel from D0 isn't here yet. Turn lifecycle and replies are owned by the registry's eve stream consumer; agents do not report them through hooks.
How a bot uses it
eve discovers tools, hooks, and channels by file under agent/tools, agent/hooks, and agent/channels. A tool's name is its file name, so the file has to exist in the bot. The stub just re-exports the kit's default:
// agents/<bot>/agent/tools/update_thread.ts
export { default } from "eve-hub/tools/update_thread";sandbox.ts stays a small module of its own, because each agent and subagent declares its own environment:
import { defineSandbox } from "eve/sandbox";
import { HostSandbox } from "eve-hub/host-sandbox";
export const environment = HostSandbox.environment();
export default defineSandbox(() => environment.open());lib/agent-template.mjs (KIT_FILES, MAIN_KIT_FILES, PRESETS, kitStub, hostSandboxFile) writes these files: scaffoldMain for hub workspace init, scaffoldAgent for create_agent. test/agent-kit.test.mjs checks that they match.
A bot's role is enforced by its files as well as its prose. create_agent's preset picks them: builder (the default) has every tool; read-only (planners, reviewers) adds agent/tools/write_file.ts → tools/disabled.ts, so it reads code, runs tests and delegates agent copies (which share its tool set) but can't edit files by tool. Its bash is the kit's regular bash, with no approval step.
The package import (D12)
- Before spawning the bots, the daemon links the kit (
lib/workspace-link.mjs):<workspace>/.hub/eve-huband<workspace>/node_modules/eve-hub→<hub>/packages/agent-kit. If the workspace has noeve/zodof its own, they are linked to hub's install too. The daemon only replaces links it created, never a real install. - The workspace's
package.jsondepends on"eve-hub": "link:.hub/eve-hub"(workspace initwrites it), so a laterpnpm installkeeps resolving the kit the same way. - The stubs use the specifiers the
exportsmap serves (eve-hub/tools/x,eve-hub/host-sandbox,eve-hub/roster-model, …). eveandzodare peer dependencies. eve's bundler resolves the kit to its realpath, so the kit's owneveimports come from hub's install. When the workspace's eve is linked to hub's, that's one eve (verified with eve 0.69).
Add a changeset (pnpm changeset, pick eve-hub) when a tool's name, schema, or description, the hook's events, or the channel config changes. Commit only the changeset file: the version is computed from the pending changesets, and pnpm changeset version (which a hub-runner PR runs) leaves eve-hub's alone (a test fails when a kit change adds none). Hub records each agent's kit version and accepts N and N−1 (D0/D12).
Published package (deployed workspaces)
A deployed workspace (lib/workspace-template.mjs, staged by hosted-hub/scripts/stage-workspace.mjs) has no hub checkout to link, so it installs the kit from npm, published as eve-hub: "eve-hub": "<version>". node packages/agent-kit/scripts/pack.mjs <outDir> builds the package: every .ts module compiled to .js + .d.ts in place (node won't strip types under node_modules), the exports map pointed at them, and templates/ included.
eve-hub's version is package.json's version (the base) plus one patch per .changeset/*.md naming eve-hub (lib/kit-version.mjs). A PR that changes the kit only adds its changeset (pnpm changeset), so two open kit PRs never edit the same file. When it merges, ci.yml's deploy-hub job runs scripts/publish.mjs, which stages the package into dist/npm with the computed version, publishes it when npm lacks it, and the eve-hub@<version> GitHub release is created; nothing is committed (main's ruleset allows only PR merges). A minor or major release raises the base and deletes the pending changesets in one PR; a kit change must raise the version, and it never goes down (test/kit-version-bumped.test.mjs). CHANGELOG.md stops at 0.14.x; the release notes and changesets carry later changes. npm auth is the repo's NPM_TOKEN secret (an npm automation token). The template pins the version this checkout carries.
The template's main gets a Vercel Sandbox and only the kit stubs that run without the hub's machine (lib/workspace-template.mjs DEPLOYED_KIT_FILES; eve's own bash replaces the kit's). Every hosted bot's sandbox (lib/agent-template.mjs sandboxFile, written at create_agent) installs eve-code's tooling (installCodeTooling: gh and gh-signed-commit under /usr/local/lib/eve-code), sets no network policy and holds no credential. The template's builder also mounts eve-code (eve/extensions/code: apply_patch, grep, the review skills, a read-only worker) with no github option. Every bot that edits files, in a build from a workspace repo, mounts eve/extensions/git as extensions/git.ts with a github option: connector github/eve-hub-github (the hub's GitHub App, which attachHub links to the bots project for every environment at onboarding), principalType: "user" (each git__gh command runs with the human's own GitHub token, which Vercel Connect resolves through that App; every hub turn, wake-ups and bot handoffs included, runs as the workspace's human, so pushes, PRs and commits are theirs; the first command raises a sign-in card), authorize limiting commands to the repo owner's org (VERCEL_GIT_REPO_OWNER), and a broker that puts the token into the Vercel Sandbox's firewall (setNetworkPolicy) for that command alone. The bot runs git, gh and gh-signed-commit through git__gh; plain git and gh in bash have no credential. A read-only bot (eve's write_file disabled) gets none, since the user token can't be narrowed below the human's own write access; a bot's own committed extensions/git.ts is kept as the workspace's. A build without a repo (an uploaded deployment) mounts no git extension, removes one the tree carries (the legacy extensions/git/extension.ts stub too), and the execution context points the bot to the vercel-kit connection instead (packages/agent-kit/prepare.mjs, execution-context.mjs; the rendered prompts are snapshotted in hosted-hub/test/prompts). A deployed workspace's eve is the one pin in hosted-hub/bots/package.json, which staging copies over the template's (an eve snapshot with eve/extensions/git; hosted-hub/test/stage-workspace.test.mjs checks the staged imports resolve against it). The hub and local bots stay on the hub's eve. The published tarball (scripts/pack.mjs) leaves out the kit__worker extension, which mounts only on the hub's machine. A hosted bot's sandbox.ts from an earlier template (the GITHUB_TOKEN policy, no eve-code tooling) is moved by the hosted kit update (sandbox-migrate.mjs, from hosted-hub/src/onboard/repo-bump.ts kitChange), keeping the bot's own prepare steps; a build from a repo refuses a hosted sandbox git__gh can't run in. The hub reaches each bot's eve channel with Authorization: Bearer $BOT_TOKEN (bot-token.mjs); the kit's calls to the hub (registry-fetch.mjs, at ROSTER_REGISTRY_URL) carry Authorization: Bearer $HUB_TOKEN when set. The hosted hub keeps Vercel Authentication on: a deployed bot sends its project's OIDC token in x-vercel-trusted-oidc-idp-token too (the hub project lists each bots project in its Trusted Sources, hosted-hub/src/onboard/trust.ts trustBots, and never the bypass secret, even while its deployment still holds the env), and a bot on a Mac sends x-vercel-protection-bypass: $HUB_PROTECTION_BYPASS (the hub project's automation bypass secret) when it has one.
Vercel Kit connection
An agent opts into the app's Kit connector with agent/connections/vercel-kit.ts:
export { default } from "eve-hub/connections/vercel-kit";Set KIT_CONNECTOR to the returned Vercel Connect connector UID in Hub's
.env.local (or its process environment). Hub's daemon loads that file before
starting agents. This app uses kit.vercel.sh/hub, attached to
vercel-internal-playground/06-agents-only-one-project.
All agents importing this connection share the connector; Connect authorizes
each authenticated roster user separately. Missing or blank configuration
returns no connection. Connector configuration is re-read at each turn.
Hub's local channel derives a user principal from the registry's x-roster-user
header only during local development. Callers without an authenticated user
cannot use this user-scoped connection. Each user completes Eve's sign-in
challenge when first using Kit. Kit action approvals are separate from sign-in:
when Kit returns consent_required, present its approval link and resume its
saved run only after the user decides.
For another Hub app, create and attach its own connector from its linked project:
vercel connect create kit.vercel.sh/api/mcp --name my-hub --yes
vercel connect attach <returned-connector-uid> --yesUse vercel env pull to refresh local project credentials when needed; preserve
local settings before pulling because it replaces the destination file. Restart
an idle agent after adding its connection stub or updating its environment.
Validate by completing sign-in and asking for one read from a connected service.
Compilation and mocked authorization tests do not prove a live service read.
Reference: Kit integration guide.
