Every operation entorhinal serves through the subc daemon, grouped by topic,
with the rule that decides who may call it. The manifest
(manifest() in crates/entorhinal-module/src/main.rs) declares each one as
a query or a mutation; ck-entorhinal --manifest prints it.
entorhinal provides project-identity/v1, agent-identity/v1,
permissions/v1 and tool-provider/v1 (with the call-modes capability of the
tool-provider role). It requires none. It calls engram only to write or read
shared state and serves every read without it, so declaring engram required
would make the daemon refuse reads while engram is down.
ck health entorhinal reports cached health gauges and request latency without
waiting for the store slot. The latency object has fixed resolve,
resolve_project_id, enumerate, agent.resolve and other buckets; each
contains count, totalUs, maxUs, over500ms and over3s. storeUnavailable
counts requests refused before storage was ready, and readyAfterMs records
the first successful store-open time from process start. Requests slower than
500 ms also emit one fleet warning; a resolve warning includes its
canonical_root.
The daemon stamps each route with a principal when it opens. entorhinal
applies these checks, in order, before decoding a request
(ProjectsHandler::admit_with and authorize_write in main.rs):
- Operation-specific rules for the permission operations.
- Flow scope. Any mutation on a route opened under a flow's scope refuses
flow_scope_not_admitted, whoever opened it. - Write rule. By group, as the tables below say. A route with no recorded principal never passes a write rule.
Every query is open to every caller, including flow-scoped and unverified
routes. A mutation that needs the operator's confirmation shows a Touch ID
prompt through the daemon, naming the exact change. One prompt is open at a
time (a second request refuses operator_confirmation_busy); it is withdrawn
when the caller's route closes; the request gives up after 270 seconds. A
decline or timeout refuses operator_declined; a daemon or Mac that cannot
show the prompt refuses operator_presence_unavailable.
Principals in the tables:
| Name | Meaning |
|---|---|
| Direct | A local client such as ck. Every local process, including an agent's shell, connects as Direct. |
| executive | reserved:prefrontal-core, the module that registers repositories and relays the operator's agent changes. |
| orchestrator, host | The daemon's sole provider of agent-orchestrator/v1 or agent-host/v1, resolved per route. |
| any | Every caller. |
Tests: writes_are_refused_unless_the_operator_or_the_executive_opened_the_route,
self_admitted_methods_pass_the_write_gate_for_every_principal,
queries_are_answered_for_every_principal,
a_flow_scoped_route_cannot_write_even_with_the_executives_principal,
the_handler_refuses_a_write_on_a_bound_flow_route,
every_declared_mutation_is_write_checked,
route_closure_wins_ready_prompt_and_lock_and_admission_slot_gap.
Project mutations are admitted from Direct or the executive; anyone else gets
write_not_permitted. A Direct register, assign_workspace,
upgrade_implicit or seed_import that would carry live heads' and hirees'
name claims into another workspace first asks for Touch ID. Domain changes
have the additional confirmation rule below. projects-and-roots.md covers the
concepts.
| Operation | Kind | What it does |
|---|---|---|
resolve |
query | Resolve a filesystem path to its registered or implicit project identity. |
resolve_project_id |
query | Look up a project by its durable id and return its registration record. |
resolve_remote |
query | Find the project owning a GitHub repository through its live owned remotes. |
resolve_root_key |
query | List local roots mapped to a project's machine-neutral root key. |
preview_attach_root |
query | Match a checkout to an existing project and root key without attaching it. |
enumerate |
query | List registered projects with workspace assignments, tags and liveness activity. |
journal_tail |
query | Read the newest registry journal entries. |
trust |
query | Show the project a path belongs to and each root's identity and approval. |
verify |
query | Check registry invariants and replay the journal on a copy, reporting drift without writing. |
register |
mutate | Register a project root under a workspace, minting its durable id. |
assign_workspace |
mutate | Move a registered project to a different workspace. |
set_workspace_root |
mutate | Set or clear a workspace's root directory. |
set_owned_remotes |
mutate | Replace a root's owned remote names, or reset to origin with null. |
upgrade_implicit |
mutate | Promote an implicit project to a registered one, keeping its id. |
remove |
mutate | Remove a project registration; its id stops resolving. |
add_root |
mutate | Add a root to a registered project; it starts unapproved. |
attach_root |
mutate | Attach a local checkout to an existing project's root key; it starts unapproved. |
rebind_root |
mutate | Rebind a replaced or unbound checkout, keeping its project id; approval is still required. |
remove_root |
mutate | Remove one root, retiring its binding and worker containers. |
attach_derived_parent |
mutate | Attach a worker container to a root's current approved binding. |
approve_root, unapprove_root |
mutate | Approve or withdraw approval of one root's current binding. |
approve_project, unapprove_project |
mutate | The same for every root of the project a path belongs to; approval refuses unless every root is identified. |
seed_import |
mutate | Bulk-import registrations from a seed file (recovery). |
rebuild |
mutate | Rebuild the derived tables by replaying the journal (recovery). |
projects.session_liveness |
mutate | The session liveness feed. Host only; never journaled. See session-liveness-contract.md. |
Tests: attach_surface_writes_are_attributed_only_to_direct_and_core,
ownership_write_admission_and_principal_attribution,
rebind_wire_refuses_flow_and_unadmitted_principal_without_binding,
carrying_placement_prompts_exactly_and_cached_reply_is_unchanged.
Memory context v1 defines project domains, versioned accepted workspace documents, coherent reads, digest encodings and fleet upgrade order.
| Operation | Kind | Who | What it does |
|---|---|---|---|
project.set_domain |
mutate | Direct with Touch ID, executive | Set or clear a project's opaque memory domain. |
workspace.set_document |
mutate | Direct with Touch ID only | Accept the next charter/document revision with optimistic concurrency. |
workspace.document |
query | any | Read an accepted revision, latest by default. |
context.project |
query | any | Resolve a folder and read its memory policy, document metadata, membership digest and stamp in one snapshot. |
context.session |
query | any | Read agent resolution, memory/persona pins, catalog generations, whereabouts and stamp in one snapshot. |
Direct register also asks for Touch ID when a supplied domain differs from
the stored value (including a new project's first domain). Omission or the same
domain needs no domain confirmation. When registration also carries name claims, one combined prompt covers both changes.
Agent identity begins in the executive and is imported once
(agent.import). Until then every identity write and session registration
refuses authority_not_cut_over, before any prompt. After that:
agent.create,agent.rename,agent.update_tag,agent.set_labelsandagent.disposeare admitted from the executive, or from Direct after Touch ID. A Direct write that would fail anyway is refused before the prompt.agent.set_avatar,agent.set_github_identity,agent.mergeandagent.importare admitted from the executive only. Direct getsdirect_identity_write_not_admitted; otherswrite_not_permitted.agent.repair_workspacesis Direct only, after Touch ID (direct_caller_requiredotherwise).agent.apply_avatar_manifestis also Direct only, with one Touch ID for the batch. An all-skipped seed-only run succeeds without prompting or writing. Request fields areentries(the owner's exact per-agent manifest objects),replace(default false),request_key, and optionalactor. The read-onlyagent.preview_avatar_manifesttakes onlyentriesand optionalreplace. All entries validate before approval, and the writer re-plans and checks the approved plan before applying anything. Each change uses its own transaction, standardagent.set_avatarshared entry, and<batch key>:<agent_id>request key, so replicas need no new identity entry type. Interrupted batches report written and remaining agents; retries replay completed entries and confirm the remaining writes once. Completed batches and all-skipped runs need no prompt.- Writes carry a request key. A retry with the same key returns the stored reply without prompting again.
agents.md is the operator guide.
| Operation | Kind | What it does |
|---|---|---|
agent.resolve |
query | One agent by id, including retired and merged agents. |
agent.resolve_name |
query | An agent by name within a workspace or the global namespace. |
agent.list |
query | Agents filtered by role, project or workspace, paged by agent id. |
agent.peer_roster |
query | A workspace's live heads: its workspace head and its projects' heads. |
agent.avatar_read |
query | The avatars of up to 64 agents. |
agent.github_identity |
query | The GitHub identity bound to an agent; it names credentials and holds none. |
agent.fleet_identity |
query | Live agents with their project and workspace placement. |
agent.snapshot |
query | Every agent and name claim at one generation, to seed a copy. |
agent.changes |
query | Identity changes after a cursor, optionally waiting up to 25 seconds for the next one. |
agent.preview_repair_workspaces |
query | Preview what agent.repair_workspaces would change, including any refusal. |
agent.preview_avatar_manifest |
query | Validate and preview an avatar manifest, whose genomes come from the avatar owner's export tool, listing each agent by name. Without replace, agents that already have an avatar are listed as skipped. |
agent.create |
mutate | Create an agent. |
agent.rename |
mutate | Rename an agent. |
agent.update_tag |
mutate | Change an agent's tag. |
agent.set_labels |
mutate | Replace an agent's labels. |
agent.set_avatar |
mutate | Set an agent's avatar. |
agent.set_github_identity |
mutate | Bind or clear an agent's GitHub identity. |
agent.dispose |
mutate | Retire an agent; its id stays reserved. |
agent.merge |
mutate | Merge one agent into another and retire the source. |
agent.import |
mutate | Import the executive's agent registry once. |
agent.repair_workspaces |
mutate | Clear stored workspaces on heads and hirees and move their live name claims to their project's workspace. |
agent.apply_avatar_manifest |
mutate | Seed or replace owner-minted avatars with one confirmation and resumable per-agent writes. |
Tests: agent_principal_matrix_precedes_decode_keys_and_cached_replies_before_and_after_cutover,
every_agent_mutation_is_flow_refused_for_every_principal_before_and_after_cutover,
all_reads_are_open_to_every_principal_before_and_after_cutover,
confirmed_direct_create_commits_with_the_exact_summary_and_route,
prechecks_replay_and_domain_refusals_do_not_wait_for_writer_or_prompt,
repair_preview_is_open_and_cutover_refusal_precedes_confirmation.
The tools (projects_create_workspace, projects_register,
agents_create_head, agents_hire, agents_retire_hire,
agents_list_hires, catalog_prepare, catalog_plan, catalog_install,
catalog_generation) are served through tool-provider/v1, with the caller
taken from the daemon's scope stamp. Their rules are in
consent-contract.md.
Catalog tools are available to all live agent roles, and only for the calling agent:
catalog_prepare must target the caller, and catalog_plan, catalog_install and
catalog_generation act only on the caller's own plans and generations. Preparation
is asynchronous, installation reuses the consent coordinator, and generation reads
omit superseded generations, which are local bookkeeping. See catalog.md
for schemas, consumer kinds, refusals, idempotence and capability-token declarations.
| Operation | Kind | Who | What it does |
|---|---|---|---|
permissions.list {agent_id?} |
query | Direct, reserved:cingulate |
List live grants on this Mac. |
permissions.revoke {id} |
mutate | Direct, reserved:cingulate |
Revoke a grant without prompting. |
agent.audit_list {since_ms?, limit?, cursor?} |
query | Direct, reserved:callosum |
Page the local audit of agent tool writes. |
agent.set_hire_cap {project_id, cap} |
mutate | Direct, with Touch ID | Set this Mac's hiring limit for a project. |
catalog.agent_generation {agent_id} |
query | reserved:prefrontal-core only |
Read the agent's active or degraded package generation and any newer ready ones, with exact pin digests, from local state. A generation is one installed set of the agent's pinned packages, where a pin fixes a package's exact version and digests. ready generations wait for core to activate them, active is the set in use, and degraded is an active set that hit an error such as a revoked pin. Optional pinned_status lists non-available pins; a degradation error identifies the revoked skill by name, version and reason. |
catalog.ack_activation {agent_id, generation_id} |
mutate | reserved:prefrontal-core only |
Idempotently activate a ready generation after core renders it. activation_blocked names a revoked/tombstoned pin; withdrawn pins remain usable. No network or consumer discovery. |
Refusals: permissions_operator_required, audit_operator_required,
direct_caller_required, caller_forbidden. These permission and catalog reads
are not open to every caller.
Claims say which of the operator's Macs holds an agent or a role. They are records in engram that entorhinal caches locally; every read here answers from the cache, so it works offline and never waits on engram.
| Operation | Kind | Who | What it does |
|---|---|---|---|
agent.register_session |
mutate | orchestrator or host | Bind an agent's session reference to its host session. |
agent.for_session |
query | any | The agent and reference bound to a host session. |
agent.session_for_ref |
query | any | The agent, host session and project root bound to a reference. |
agent.session_liveness |
query | any | active, gone or unknown for up to 256 references, in order. |
agent.where |
query | any | Which Mac holds an agent, from the cached claim, plus any blocked move's participant and ref explanation. |
agent.where_many |
query | any | Cached claim results for 1–64 agent ids in request order; unknown agents are omitted. |
agent.refresh_claims |
mutate | Direct | Refresh live agents' cached claims from engram. |
role.read |
query | any | Which Mac holds a role, from the cached claim. |
role.take |
mutate | Direct, with Touch ID | Make this Mac the role's holder. |
Role names are 1–64 bytes of lowercase letters, digits and underscores.
role.take carries a request id and the epoch it expects; a retry resends
the approved attempt without prompting again. Other callers of
agent.refresh_claims and role.take get caller_forbidden.
Tests: refresh_admission_empty_disabled_and_sync_seam_are_closed,
role_take_is_confirmed_direct_only_and_ledger_precedes_prompts,
role_lost_reply_retries_exact_attempt_without_another_prompt.
Rules are in agent-move-contract.md; the guide is moves.md.
| Operation | Kind | Who | What it does |
|---|---|---|---|
agent.move {agent_id, to, request_key} |
mutate | Direct with Touch ID; orchestrator; reserved:engram while lid-close moves are on |
Start moving an idle agent to a named Mac. |
agent.move_status {cut_id} |
query | any | One move's local checkpoint, including any optional participant explanation for a diverged block. |
agent.moves |
query | any | Open moves, then the 32 most recently finished, including optional participant explanations for diverged blocks. |
agent.move_abort {cut_id, request_key} |
mutate | Direct with Touch ID; orchestrator | Cancel a move before its boundary. Only Direct can retry a stuck abort. |
agent.set_lid_close_moves {enabled, request_key} |
mutate | Direct with Touch ID | Allow or deny engram's lid-close moves on this Mac. |
Rules are in identity-log-contract.md; the guide is sharing.md.
| Operation | Kind | Who | What it does |
|---|---|---|---|
identity_log.status |
query | any | Local log state, progress and pending writes, without contacting engram. |
identity_log.enable {without_agents?} |
mutate | Direct | Enable the shared log, or join it. |
member.set_name {display_name} |
mutate | Direct | Name this Mac in the shared log. |
Other callers get write_not_permitted (identity_log.enable) or
caller_forbidden (member.set_name).
A refusal is an error envelope with a code, a message and sometimes a
detail. Codes common to every operation:
| Code | Meaning |
|---|---|
invalid_request |
The request does not decode, or a field is out of bounds. |
invalid_params |
A project operation's parameters do not decode. |
unknown_method |
No such operation. |
storage_unavailable |
The store is not open yet. |
store_unavailable |
The store could not be opened at startup. |
storage_error |
A database error. |
Operation-specific codes are listed in the contracts.