Architecture¶
For source-file responsibilities, see the module map.
System flow¶
flowchart TD
W[Workspace] -->|shared records| D[Directives and source anchors]
W -->|one or more| S[Sessions]
S -->|owns| T[Canonical transcript in data buffer]
S -->|admits| R[Request and selected turn engine]
R -->|validated and authorized calls| X[Tool pipeline and agent runtime]
R -->|responses and results| T
T -->|projects into| V[Emacs view and browser view]
S -->|publishes| P[Durable session state]
A workspace shares directive records across sessions. Each session owns its conversation, execution target, authority and retained agents. The data buffer holds the canonical transcript; views project it and submit user actions through the owning session. A request has its own admission and settlement boundary. Tool and agent failures return through that boundary; a rendering failure does not settle the request. See tool execution, agent lifecycle and session publication and recovery for their failure paths.
A turn engine supplies model communication and model-history behavior. The gptel engine keeps its native state machine; the Claude Code engine runs the installed agent through ACP and exposes mevedel tools through MCP. Both retain the admitted request's authority, pipeline, transcript and settlement boundary. Claude-specific authentication and adapter options stay behind that engine; ACP alone does not guarantee prompt replacement or disabled built-in tools. See ADR 0123 for the ownership decision and external conversation lifecycle for its persistence and capability limits.
Memory has separate workspace and user scopes. Selected memory indexes become request context; memory is not a transcript or a session-owned storage slot. See persistent memory.
Browser collaboration projects host state through an encrypted, content-blind relay connection. The Emacs host owns session behavior and validates guest actions. The collaboration manual owns deployment, link authority, attachments, reconnects, notifications and lifecycle details; ADR 0099 explains the boundary.
Loading boundaries¶
mevedel.el loads foundational data and instruction restoration, and exposes
commands through ordinary autoloads. Installation registers the complete tool
catalog, presets, and integration hooks. Focus and theme callbacks act only on
loaded views. Uninstall skips optional owners that have never loaded.
Chat creation loads its session and view dependencies and wires journal scheduling before the session-start event. Pending Plan and directive proposals restore from session/workspace state, independent of whether their UI owner was previously loaded. Tool execution, saved-lobby startup, and Claude requests have separate implementation boundaries; see tools, collaboration, and sessions.
Key data structures¶
Defined in mevedel-structs.el / mevedel-tool-registry.el:
mevedel-structs.el owns these passive shapes and their shape-local
invariants. mevedel-workspace.el owns workspace registry and state lookup,
mevedel-directive.el owns directive mutation and derived lifecycle, and
mevedel-turn.el owns request admission, cancellation, and settlement.
mevedel-workspace: type, id, root, name, file-cache, and the durable directive records shared by every session in the workspace. Its journal and memory observations are disposable UI caches; memory counts never authorize a proposal decision or application. Additional roots live inmevedel-workspace-additional-roots..mevedel/is derived bymevedel-workspace-state-dir, not stored as a slot.mevedel-directive: stable directive id, current authored request, source-anchor description, lifecycle state, bound execution-session id, and Plan-before-implementation preference and proposal, chronological planning, implementation, and discussion turns, and current parent-owned subdirectives. Source overlays retain the id needed to resolve this record; they do not own another request, status, patch, or attempt copy. Current top-level and nested ids are globally unique within the workspace; durable decode rejects any collision before installing records. Historical consumed-subdirective snapshots are evidence rather than current identities. The lifecycle state is derived from the current authored request and surviving activity whenever the record is loaded or queried; it is not persisted as an independent authority.mevedel-subdirective: stable id, current authored request, and attached source anchor for one nested detail owned by a top-level directive. It never owns a session, lifecycle state, attempt list, discussion, or Rewind action.mevedel-directive-attempt: immutable action framing, submitted request, terminal answer or error, authored-request snapshot, outcome, captured patch, capture time and completeness, covered files, explicit gaps, the successful attempt's consumed subdirective snapshots, conservatively detected tool effects outside snapshot coverage, and the session/turn checkpoint that can restore the file state around the attempt, plus any accepted plan and request-local implementation selection. Its directive-local settlement sequence orders it against discussion turns. A complete empty patch means the request observed its covered files and made no changes; incomplete capture never implies complete coverage.mevedel-directive-discussion-turn: immutable local question, submitted request, authored-request snapshot, terminal answer or error, outcome, optional selected-attempt index, session/turn checkpoint, and directive-local settlement sequence. A settlement sequence identifies one event across all three collections -- planning turns, attempts, and discussion turns -- and the codec rejects a record that repeats one.mevedel-session: per-chat state: stable name-independent ID, editable display name and one-shot title eligibility, workspace, immutable execution target, qualified working directory, tasks, touched-files, permission rules/mode, exact or recursive resource grants, reminders, persisted per-conversation workspace-instruction content hashes, deferred tool state, mailbox messages, the retained agent registry, transient unpublished agent reservations, root activity and tree capacity, mention dedup, queued follow-up user messages, skills, session persistence metadata, agent transcript index, invoked skills, session-scoped hook rules/log/context, permission queue, one pending plan approval, selected preset and resolved mevedel preset settings, the current session-owned Goal, and a transient bounded tool-input repair log. Lifecycle events emitted before session materialization wait in the transienttelemetry-pendingqueue and flush to the diagnostic stream; the queue is never persisted as resumable state. A transient side conversation may pointaudit-sessionat its durable parent for redacted audit events and shared target mutation authority; its runtime queues and unsanitized logs remain side-owned. The transientexecution-stateslot is opaque outsidemevedel-execution.el;mevedel-execution-process.elkeeps child processes, timers, spools, and process groups behind an opaque child value, so they never enter the general session model or persisted sidecar.mevedel-execution-telemetry.elowns an opaque per-execution context containing live ownership provenance, mutable summary cells, and privacy-safe event facts, but no live process record. Portable lease generations persist only the boolean unsettled-mutation safety latch needed when those transient records disappear.mevedel-goal: identity, objective, lifecycle status and reason, token/time/turn accounting, optional budget and accepted-plan reference, and timestamps.mevedel-request: per-turn state: process-unique request identity, owning session and agent origin, its once-reserved session turn identity, request start time, accumulated active-work pause time, file-snapshots, directive UUID, immutable Plan read-only authority, cancellers, skill-scoped permission rules, user-attached skill records, hook rules, and transient one-shot-mutation/ephemeral-artifact boundaries. Effective backend/model/effort policy is captured at admission, before pending skill context is consumed; later context generation uses that frozen policy. Request-only overrides do not change the session's saved selection.mevedel-tool: name, handler, description, summary, prompt and prompt provenance, args, category, read-only/destructive/async/snapshot flags, sync/async permission hooks, specifier extractors (get-path,get-pattern,get-domain,get-name), groups, max-result-size, display argument, render transform, renderer, and its provider-facing gptel tool.mevedel--instruction-states: workspace-keyed instruction alists and ID state- Instruction types: References (source-bound context) and Directives (workspace-owned prompts with source presentations)
Instruction ownership is split at the durable-record/presentation boundary:
mevedel-instruction-registry.elowns workspace buckets, IDs, traversal, lookup, and links.mevedel-directive-source.elowns anchors and every operation that changes both a durable directive record and its source presentation, including create, detach, reattach, archive, and delete.mevedel-overlay-ui.elowns action dispatch, labels, keymaps, styling, and redraw.mevedel-overlays.elremains the geometry, containment, tag, navigation, context, and prompt core.
Callers do not update a directive record and then repair its overlay separately. They enter through the source owner, while registry and UI work remain presentation-neutral or durable-record-neutral respectively.
Directive anchors are either Attached, with a live source range, or Detached, with a zero-width source position, former source order, and the last attached anchor evidence. Archived and Source-missing anchors keep the last attached region. Every stored anchor names a file and an ordered region, in every one of those states, and the codec rejects one that does not. Creating a directive therefore requires a file-visiting buffer: without a file nothing could reattach it, and detaching or archiving it would freeze a fileless anchor into a record that no longer loads. Deleting an entire directive range preserves its durable record and replaces the evaporated range overlay with a compact detached row; partial edits use normal overlay resizing. Co-located detached rows are ordered by their former source positions. References keep their source-bound evaporation behavior.
Top-level directive presentations may persist an exact provider and reasoning-effort override. Nested directives are durable details owned by the topmost directive. Acting on any nested presentation resolves that owner, and prompt construction includes every current nested detail in stable source order. When the top-level range detaches, its nested source presentations may disappear, but prompt construction and submission snapshots continue from the parent-owned records. Without an override, the directive inherits the main session model at dispatch.
Directive requests submit an explicit string prompt built from the current
authored request and freshly resolved references, so request construction never
reads surrounding ordinary-chat history. Direct implementation-type prompts
additionally append the record's persisted skill selection as instruction
mentions, revalidated against the session at every dispatch; accepted-plan
handoffs instead carry the approval card's selection. Each request streams as a first-class
turn in the bound execution session's canonical transcript with ordinary user,
response, tool, and interaction roles. Paired durable boundary records identify
the directive turn without replacing any of those roles. Before gptel parses an
ordinary chat request, a synchronous prompt-copy transform marks every enclosed
directive body gptel 'ignore; explicit directive requests continue to use
only the workspace record and directive-local history.
Starting a Ready discussion submits that request directly as the first directive turn. Follow-ups enter a sticky directive scope in the shared composer and add only durable discussion turns whose authored-request snapshot matches the current directive request. The first accepted request binds the directive to its execution session. Each accepted request reserves one session turn identity before tools run; snapshots, transcript metadata, prompt/Rewind indexing, and directive checkpoint links use that same identity, which terminal settlement commits without recomputing it.
Directive startup reserves its request before yielding file preparation or the save prompt. Request-local permission mode and Plan read-only authority are explicit admission inputs, not shared pending state that another request can consume during readiness checks. A busy session refuses a nested submission. Cancellation retires provisional startup state while the request still owns it; resumed startup and error rollback cannot alter a replacement's transcript, directive status, or workflow context, even if the replacement has already settled. Once dispatched, ordinary terminal settlement owns that state.
When Plan before implementation is enabled, every implementation-starting action first records a read-only planning turn in that same bound session. The directive record owns the proposal and approval selection; the session keeps only a transient reservation identifying whether the workflow is planning, awaiting approval, or implementing. The accepted handoff reuses the ordinary directive request path with request-local mode and model policy, and stores the accepted plan on the resulting immutable attempt. Standalone Plan and directive planning are mutually exclusive session owners.
Terminal settlement keeps the complete turn in the transcript and writes the immutable attempt or discussion turn to the workspace record even if the source overlay detached while the request was in flight; this bounded duplication keeps chronological presentation separate from durable follow-up context. The native FSM terminal transition or ACP terminal response captures the final patch before a shared terminal callback and publication transaction, including on abort. The provider's earlier raw abort callback does not settle the directive. Callbacks inspect engine context through the same accessor; external turns own that context on their admitted request without a synthetic gptel FSM. Overlay updates remain optional presentation work. A successful implementation records immutable snapshots of, then consumes, exactly the subdirectives present at dispatch. Failure and abort consume none; details authored while a request is in flight remain current.
Batch processing queues durable top-level directive records in stable source order rather than retaining source overlays. Each item resolves the same live prompt context used by an individual action only when its turn begins, so a prior implementation may detach or remove later source without corrupting the queue. Ready records use Implement and Discussed records without an attempt use Implement this. Either action pauses the batch at the directive approval card when Plan before implementation is enabled. A Source missing record has sufficient context only when it is top-level, bodyless, and has no nested details; region-backed records must be reattached. Records with any implementation attempt or without sufficient current prompt context are reported and skipped; the first failed or aborted request stops the batch. A zero-delay continuation starts the next item only after terminal request cleanup.
Workspace context chain¶
Workspace detection first uses the session or cached workspace, then a
project.el root. Outside recognized projects it uses the nearest ancestor with
.mevedel/workspace-id, falling back to default-directory. File buffers and
Dired therefore share one directory workspace and the same portable session
authority. Plain directories need no project marker. File-based workspaces
remain available through explicit mevedel-workspace-functions customization;
they are not part of default detection.
Retained current-context delivery loads AGENTS.md then AGENTS.local.md from the
workspace root through the session working directory. A successful Read of a
deeper file queues any newly applicable instruction files as a host-generated
same-turn reminder. Content hashes deduplicate unchanged files independently
for /root and each retained agent only while that owner's model-visible
context remains current. Fresh directive requests own separate hashes. Each
mevedel SessionStart context epoch resets local-history /root;
resume resets local-history owners, and retained-agent compaction resets that
agent. Native-history owners keep their learned scopes across reopening and
re-send only changed instruction contents in later prompts.
M-x mevedel-inspect-effective-prompt and /prompt open the same read-only
report of the live preset, profile, prompt components, exact final prompt,
effective tools, prompt provenance, and provider-schema size estimate.
Data buffer (authoritative gptel/org buffer; holds mevedel--workspace,
mevedel--session, and the canonical transcript projected into provider context)
|
View buffer (mevedel-view-mode; holds mevedel--data-buffer and the
input zone / editable composer)
|
Derived buffers / previews / transcript inspection views point back to
their data or parent view buffers as needed
Tools execute in the data-buffer context with default-directory set to
the session working directory. File modifications are tracked per request
via mevedel-request-file-snapshots, while cross-turn file metadata
lives on the workspace file cache and session touched-files map. A cached
entry counts as current only while its modification time, its inode change
time, and its reported size are all unchanged: a modification time alone is
not proof, since restoring timestamps, a backwards clock, and
coarse-grained filesystem stamps all leave it standing, while a change time
cannot be set from userland and so moves anyway. A rewrite that restores
all three is the remaining blind spot, and the cache does not detect such a rewrite without a fresh content read. A file over
mevedel-file-cache-max-file-bytes is cached as a fingerprint with no
content, and mevedel's own session directories are refused outright
(mevedel-session-file-cache-excluded-p); the workspace artifact store is
cached like ordinary files; see docs/reminders.md for what the resulting
edited-file reminder reports.
mevedel-execution-target.el binds each session to one local or TRAMP target,
owns qualified/native path conversion, and probes target readiness. Required
rg compatibility is verified against a bounded target-side fixture using the
Glob and Grep flag surface, rather than inferred from a version string. The
project-owned identity in mevedel-workspace-identity.el lets equivalent
client-specific TRAMP spellings reopen the same workspace. SSH-family
ssh/scp/sshx/scpx, Docker, and Podman targets are supported; a target
without HOME is blocked during readiness rather than failing later path
permission checks. A target missing rg, bash, or setsid is named in the
readiness diagnostic together with the install command for whichever package
manager the probe finds there, so the fix does not require guessing the
target's distribution. Mevedel never pushes binaries to a target.
A remote project mounted into the local filesystem -- SSHFS, rclone mount,
NFS, SMB -- is deliberately ordinary local operation, not a remote target.
Mevedel sees a local path, so files, search, Bash, and sessions use the
ordinary local execution path and its local dependencies. The trade is
that the mount's host runtime is unused: commands run against the local
toolchain, compilers, and container environment rather than the storage host's.
Choose a TRAMP target when the project's runtime matters, and a mount when only
its files do.
mevedel-execution.el owns managed admission, the per-session registry,
observations, delivery, and the public execution facade. Its opaque child
values belong to mevedel-execution-process.el, which owns process creation,
stable child environments, process-group signaling, timeout cleanup, and
bounded disk spooling. Bubblewrap admission and refusal remain with the
managed facade. mevedel-execution-scheduler.el admits managed Bash through a
fair session-scoped readers/writer lane. mevedel-bash-policy.el owns Bash
classification and reusable rules;
mevedel-tool-exec-permission.el owns Bash/Eval authority and prompt adapters;
and mevedel-tool-exec.el retains their tool lifecycle, rendering, and
registration. Native filesystem tools use the execution facade's confined
one-shot helper without entering the Bash scheduler. The Bash adapter also
captures its analyzed exit-outcome resolver at spawn, so
later observations derive the same canonical facts without moving command
semantics into the process owner.
mevedel-execution-telemetry.el owns privacy-safe execution event projection,
sandbox attempt summaries, Eask workload recognition, and optional GNU time
resource capture. It never receives a live execution record.
mevedel-transport.el answers one question: is this Emacs already inside a
remote operation? Durable target I/O started from a timer, a process
filter, or redisplay must not nest inside an in-flight TRAMP command, so
callers route deferrable work through mevedel-transport-run-when-idle
and it runs when the connection is quiet. Deferred work that owns an
in-memory admission fence supplies cancellation cleanup so transport teardown
cannot leave the session permanently busy. Lifecycle events that may fire
repeatedly (turn completion, activation, buffer kill) arm one coalesced
opportunity per key through mevedel-transport-schedule-idle, which the
journal processor, idle journal sealing, the memory pass, and memory recovery
share.
Each pending transport entry owns its retry timer and cancellation callback together. Deferred retries execute and remove entries only while their exact timer still owns the coalescing key. An inline same-key call retires the pending timer before running current work. This identity check also rejects cancelled timers delivered after TRAMP restores a suspended timer list. Bulk cancellation retires its batch before invoking cleanup callbacks, so reentrantly scheduled replacement work keeps its own pending entry and cancellation callback.
TRAMP let-binds the timer lists to nil around its critical sections, and a
process filter or sentinel can run inside one. A plain timer armed there either
fires inside TRAMP's own wait, nested in the remote command, or is discarded
with the binding. Continuations armed from that context use
mevedel-transport-run-at-time: pipeline yields, the ToolCall trampoline,
WaitAgent timeouts, stream-bridge flushes, and child process settlement and
retirement. Inside a TRAMP handler frame it holds the timer and arms it when
the outermost frame returns. The continuation neither disappears nor runs
nested inside the remote command. A held timer cancelled before arming still
fires once; its callers check that their work is current. Agent conversation
saves refused as busy are requeued through mevedel-transport-run-when-idle
rather than dropped.
mevedel-transport-with-exclusive-connection suspends existing timers the
same way. mevedel--timer-pending-p counts a suspended timer as armed. A
pending retry is therefore not activated a second time on the section's
temporary list. Otherwise that copy could fire there and leave the suspended
original marked triggered, which Emacs skips forever.
Replacing a remote file with mevedel--write-file-atomically (ApplyPatch,
skill and plugin files, persisted state) is one pinned control program: the
temporary file, mode and rename happen beside the destination on the target,
where TRAMP's file operations needed about twenty round trips. A symlinked
leaf, an unproven parent spelling, a missing directory or a busy transport falls
back to the TRAMP operations, which keep their existing semantics.
mevedel-session-durability.el owns portable project lease and storage
primitives. mevedel-session-recovery.el owns specialized recovery markers,
mevedel-session-transfer.el owns cooperative control-transfer records, and
mevedel-session-publication.el serializes immutable authoritative
publication and diagnostics. All of them bottom out in
mevedel-session-control-fs.el, which performs control filesystem
operations through a target-side directory descriptor pinning the parent
while the relative operation runs; mevedel-session-control-transfer.el
coordinates cooperative control transfer above the transfer records --
persistence calls it for polling, admission, and committed-state adoption. mevedel-session-save-as.el owns the portable
Save As transaction and live-session adoption. Project sessions use the same
authority profile on local and TRAMP targets: a renewable .lease/ and an
immutable publication head. File-workspace sessions use the explicit
pid-lock profile and .lock instead. The persisted session profile, not
file-remote-p, selects every acquire, release, sweep, and cleanup path;
mixed control artifacts fail closed. The persistence facade and its codec,
artifact, Rewind, and Fork owners form the workflow layer above that boundary.
Before a mutating managed Bash child can start on a portable project target, the execution module asserts the durable parent's current lease and commits its unsettled-mutation latch. Proven terminal settlement clears the latch only after all armed records sharing that authority have settled. Process records remain transient; restore and takeover recover the latch, not an invented process registry.
Each mutable process record points to one immutable origin record containing the session, owner, private mailbox context, data buffer, tool arguments, and tool-use ID. Delivery state is explicit and separate from process completion: a finished result rejected by its mailbox remains unsettled and owner-reachable until either the model or a mailbox consumer claims it.
The execution module also publishes isolated yield, progress, and terminal
event snapshots. The pipeline supplies the durable gptel tool-use ID and
originating data buffer when Bash starts. Live progress remains a bounded,
disposable view projection; terminal output and structured facts replace the
original row's hidden render-data side channel in the authoritative transcript.
If a parallel tool result has not inserted that row yet, the data buffer keeps
a bounded pending terminal projection and retries at later tool and render
boundaries. Agent data buffers also retry unconditionally at their final
response boundary, so this does not depend on an open transcript view.
Completion therefore survives row-order races, cache turnover, and session
persistence without entering the model-visible result. Passive event
subscribers receive independent copies,
never the private owner context, and cannot acknowledge delivery. Terminal
delivery is claimed exactly once by either a model observer or the single
mailbox sink, using the session or agent invocation captured at spawn. The
agent runtime parks an invocation while its owner has an unsettled execution.
The agent's terminal callback remains gated while any owned execution is
unsettled. Whether the last completion arrives before or after the agent's
terminal response, the runtime settles the turn directly. The agent's final
answer remains its RESULT; captured completions are separate EXECUTION
records committed to the spawn parent's mailbox in the same settlement.
Agent execution completion is invocation-local until settlement, does not
wake WaitAgent early, and launches no model request. Ordinary mailbox
messages are delivered before the next model sample or wake an explicit
WaitAgent.
mevedel-background-list.el is the user-facing projection of that private
registry, listed beside active agents. The execution module returns immutable all-owner snapshots and
accepts session-user control by execution ID; process records and operating-
system identifiers remain private. Model tools continue through the narrower
yielded-and-owner-scoped interface. Registry membership changes update the
view's live execution count and cockpit rows, while progress and yield events
refresh live row details without creating transcript state.
gptel integration¶
Request admission captures the owning request identity. Late fork-skill and review preparation/results are ignored after that identity is cancelled, replaced, or its buffer dies. Synthetic results keep their original identity through post-response hooks. Failed send startup and directive rollback end only their own request; nested replacement requests remain active. Interrupted agent startup uses the same rollback as provider errors.
Structured model-not-found recovery may replace only the ordinary root model that is still selected. Directive and accepted-implementation failures do not change the session baseline; a failure from a superseded model likewise leaves the user's newer selection intact.
Bounded native gptel sample recovery¶
mevedel-gptel-retry.el adds request-local recovery only to mevedel-admitted
native gptel requests across all backends, not just Codex OAuth: root,
directive, shared-item, and retained-agent requests.
mevedel-gptel-retry-max-retries defaults to 10 retries after the initial
attempt, at most 11 physical provider calls per
logical model sample; zero disables this controller. Each successful sample
resets the allowance. Root and concurrent children have independent budgets.
Unadmitted background gptel requests and their separate retry policies are
unchanged. Claude Code/ACP remains a separate, self-retrying engine; it keeps
its built-in retries and mevedel's existing one-time credential-refresh-lock
replay.
gptel retains provider transport and parsing. The controller classifies errors conservatively using HTTP status and structured provider errors from native parsing or complete SSE/JSON-line error envelopes, not tool data or prose. Permanent structured refusals take precedence over transient status or message text. Structured transient server/overload/rate-limit errors, HTTP 408/429/5xx, recognized curl/network failures, and narrowly matched temporary overload or model at capacity messages can retry, even without server retry advice. Cancellation, authentication/permission errors, invalid requests, model-not-found, context overflow, exhausted quota, and local bookkeeping failures do not retry. Output-limit and content-filter stops are not mistaken for interrupted transport. Unknown errors are not assumed transient.
Small Responses-specific normalization fills missing error/completion events:
nested Responses errors, flat error events, response.failed errors, and
interrupted Responses streams, including errors delivered with HTTP 200. It
does not impose Responses event semantics on other providers; stream adapters
also recognize completion markers for the standard SSE/JSON stream envelopes.
Truncated recognized streams recover; unknown extension formats retain gptel's
native end-of-stream policy rather than acquiring a Responses completion rule.
Parser adapters
forward native optional arguments, including Gemini's include-text, without
changing provider parsing. For admitted Gemini streams, headers and array
prefixes wait for the first JSON object before entering the native parser,
which otherwise rewinds using nonexistent match data. This guard also applies
with retries disabled and preserves the cursor for the next chunk.
Admission across all native gptel backends is the
recovery scope, not a claim that every provider's paid service or every error
shape has been tested.
Recovery replays the prepared sample below ordinary WAIT preparation, after context and reminder injection. It preserves completed tools, their results, sample count, selected model/effort/permissions, and request identity; it does not replay the user turn or agent invocation. Attempt identities reject stale callbacks. Failed provisional text/reasoning and incomplete tool output are rolled back without removing earlier successful work or unrelated transcript and composer content. Backoff grows approximately 1, 2, 4, 8, 16, then 30 seconds with jitter and honors valid server retry deadlines. Request abort, agent interruption/deadline, buffer death, and teardown cancel backoff even without a registered transport. Once a response hands the machine to tools, cancellation leaves it to their aborted results, and the machine then aborts instead of sampling again. Likewise, with or without recovery, a tool that finishes after its agent invocation settled aborts the machine at the shared dispatch seam instead of spending another provider request. An independently retained child is not interrupted merely because its root is aborted.
Intermediate failures update live status but do not enter terminal settlement
or publish error blocks. On a nonzero curl stream exit after response headers,
the cleanup adapter skips the already-performed WAIT-to-TYPE transition so the
terminal callback runs before ERRS closes its owner; cleanup then settles or
parks the sample exactly once. This ordering repair also applies to admitted
requests with retries disabled; it does not enable recovery for them.
Recovered output returns root progress to the status
the retry wait replaced, unless another owner has claimed it since. Exhaustion
settles once with one expandable ignored request summary: the existing
provider error payload plus :retry-errors (chronological, bounded sanitized
attempt diagnostics, at most 11) and :retry-exhausted. The live FSM flag
:mevedel-retry-exhausted makes the
affected Goal pause instead of multiplying the bound with settled-turn retries.
An owned nonretryable error carries :mevedel-retry-permanent and likewise
pauses the affected Goal rather than retrying a permanent refusal as a new turn.
Provider-reported usage is counted once per physical dispatch with a distinct
telemetry call identity; missing usage remains unknown, not zero. Retry state
is live request state and is not resumed after restart.
Request configuration¶
Direct via gptel-request and gptel-fsm. Tools registered in
gptel--known-tools. Presets use exact declared names and inherit in parent
order (later parents win, then the child). Ordinary preset keys resolve to
mevedel-foo/mevedel--foo before gptel variables and use gptel's value
composition semantics. Persistent application is buffer- and session-local;
request-only application is dynamically scoped for variables. Its agent roster
and tool catalog are read by tool calls after dispatch, so they last until the
request ends, when teardown reinstalls the session preset's. Directive dispatch
applies the default chat preset only to a session without one. The built-ins are
mevedel-discuss, used by directive Discuss, and mevedel-implement.
Directive planning also uses mevedel-implement, bounded by its request's
Plan read-only stamp. Request changes and Retry use ordinary implementation
authority and focused prompt context, not another preset. Presets can also merge named model tiers and workload maps.
Dispatch resolves session values, tier values, workload values, then explicit
Agent policy or request-owning skill policy. Skill preset entries use
$skill-name workload symbols and are consumed before request realization.
Directive overrides are validated before processing starts and applied at
dispatch (as the final prompt transform for gptel). They win for that request and its
continuations without mutating the session model.
Ordinary-chat prompt assembly also runs the directive-boundary transform in
gptel's temporary request copy. It applies gptel 'ignore to complete directive
turns there, including tool spans, while leaving the canonical response and
(tool . id) properties intact for persistence and rendering.
System prompts are assembled dynamically from ordered profiles in
mevedel-system.el. mevedel-define-prompt-component registers reusable
Markdown, literal text, or dynamic producers.
mevedel-define-prompt-profile selects components, and the profile list is the
render order; inline (NAME :file PATH) and (NAME :text STRING) entries keep
one-off role content local. Blank components are omitted. Workspace-aware
profiles must explicitly contain workspace-config and environment, which
the renderer validates before dispatch.
For retained conversations, named observations are delivered separately through
mevedel-context-delivery.el. The table describes component selection across
both channels: roles/policies remain stable system instructions, while workspace
configuration, environment, memory indexes, the specialist tool listing, skills,
resource availability and Goal state become retained updates. Stateless profiles
keep current snapshots.
See retained instruction context.
The built-in selection is deliberate:
| Consumer | Role/tone/context |
|---|---|
| Main | Base role, task policy, main tone, memory use/save policy, tool orchestration, workspace config, memory data, environment, tool listing, skills, Goal |
| Worker | Worker role, task policy, report tone, memory use/save policy, tool orchestration, workspace config, memory data, environment, tool listing, skills |
| Explorer | Explorer role, task policy, report tone, tool orchestration, workspace config, environment, tool listing, skills |
| Verifier | Verifier role, task policy, report tone, tool orchestration, workspace config, environment, tool listing |
| Reviewer | Reviewer role, task policy, tool orchestration, workspace config, environment, tool listing |
| Default agent | Inherited delegator instructions and context: a named delegator's selection, or the root's without Goal and journal |
| Approval reviewer | Isolated approval policy; intent and authority arrive as quoted evidence |
| Context summary | Fixed continuation/handoff summary contract only |
| Buddy / Buddy guide | Respective role, memory use policy, workspace config, memory data, environment |
The shared task policy owns scoped autonomy, permissions, existing edits, instruction provenance/conflicts, and truthful verification. Role text supplies the task purpose, direct-tool limits, and consumed report contract. Guardians and context summaries exclude coding task policy. Tone owns communication; tool descriptions own suitability and calling contracts.
The shared tool-orchestration component describes useful delegation and batching without search/file thresholds. Dependencies, waits, approvals, and conflicting mutations remain sequential. ToolCall suitability lives in its description; activating it does not rewrite the earlier system prompt with a promotion. Resource availability remains context-specific. Provider cache reuse still depends on provider policy and request configuration.
mevedel-gptel-stream-bridge.el isolates private gptel stream advice. It
repairs detached insertion markers, falls back to raw chunks when a stale
transformer fails, and delays early output until the process has a registered
request state machine. Consecutive plain-text inserts are batched for
mevedel-gptel-stream-bridge-insert-batch-delay seconds (0.4 by default);
non-text boundaries and cleanup flush the batch, and the view renders a
flushed batch in the same wakeup. Emacs redisplays after every read of
process output, which on pgtk repaints the whole frame, so the bridge also
paces reading a local curl stream of a mevedel request: after a small read
it stops curl with SIGSTOP and continues it when the batch is due. The
server's output waits in the kernel socket buffer and arrives as one burst;
a read of 4 KiB or more means curl has a backlog, so reading continues. gptel's
sentinel never sees the stop and continue events. A response that has
finished (a stop reason new to this tool round, or an OpenAI-compatible
data: [DONE]) continues at once, so curl exits and gptel settles the
request. Paused processes continue on uninstall, and a guard process
continues them whenever Emacs ends, including a crash: Emacs starts children
in their own sessions, so the kernel would leave them stopped. A process stops
only once the guard knows it. Pacing needs a real local subprocess on a system with job
control; other transports keep batching alone. mevedel-acp.el paces Claude
Code's adapter the same way during a turn, but only in a steady text stream
(three reads in a row holding only text chunks and the SDK's matching
content_block_delta events): a receipt, tool call, reply or request leaves the
adapter running, and every message to it, the end of the turn and an MCP tool
call continue it first. An MCP call reaches Emacs on its own channel, so it
also waits up to 50 ms for the frames the CLI wrote before it, which the
adapter may still hold. Pauses never exceed 0.5 s, whatever the batch delay. Setting the
delay to nil or zero disables batching and pacing. These mechanisms preserve the data buffer as the
transcript authority; view redraw scheduling remains separate.
mevedel-gptel-bridge.el routes native steering commands through the root
composer submission path and refuses native agent/confirmation steering;
there is no second request-local steering queue in managed sessions.
mevedel-view-stream.el owns live-tail render scheduling,
the in-flight tool calls the live render draws as running rows, and
foreground request-progress state, while
mevedel-execution-transcript.el owns durable execution render data and
compaction archive reconciliation. View Stream delegates transcript projection
to mevedel-view-render.el; mevedel-view-disclosure.el owns source-backed
fold state and actions, mevedel-view-composer.el owns the editable input,
submission hooks, and send/fork dispatch, mevedel-view-segments.el owns
ephemeral archived-segment projection, and mevedel-pending-inputs.el owns
queued follow-ups. Durable segment bytes remain in
mevedel-session-artifacts.el. mevedel-view.el coordinates the view mode,
zones, and session lifecycle. The authoritative text remains in the gptel data
buffer.
mevedel-mention-bindings.el owns atomic mention identity as validated text
properties on ordinary prompt strings. Completion or programmatic insertion
binds when an exact target first becomes known; the composer binds remaining
resolvable mentions before asynchronous preparation, queueing, or history
insertion. Draft, queue, retry, transcript, and history paths transport the
same propertized string, while kind-specific skill and mention modules resolve
the stored locator against current state and permissions at dispatch. Valid
unavailability annotates only the temporary request and continues the turn;
malformed live data blocks submission. Input-history persistence rejects and
quarantines incompatible binding data rather than migrating it. The supported
kinds are a closed explicit dispatch over skill source path, reference UUID,
absolute file pathname, and MCP server/URI; there is no resolver registry or
sidecar identity store. See mentions.md.
mevedel-turn.el owns top-level request admission, identity, cancellation, and
the single completion boundary. The ordinary gptel DONE state and awaited
fork-skill workflows call it after response hooks. Error and abort terminals
also save partial responses and file checkpoints before teardown, but do not
dispatch successful-turn follow-ups. Final-patch generation and deferred
settlement retain a nested admission fence until their continuations finish;
abort stops producers without discarding a reservation still needed for durable
settlement. If teardown lost the request before a terminal transition arrived,
the machine's request identity keys a degraded settlement. Transport cancellation
releases the settlement fence and clears the machine's settlement stamp so a
later terminal transition can retry it.
mevedel-turn-end-at-boundary lets a harness caller end a running root or
agent turn without aborting it. Once the current tool results are recorded,
the machine moves to DONE instead of sampling again, so the turn settles as a
completed one: accounting, journal capture, the checkpoint, queued input, and
Goal continuation run as usual. Errors and pending same-turn steering take
precedence, so steering is delivered and the turn ends at the following
boundary. The request is a transition rule added by
mevedel-preset--build-transitions, which root, agent, directive, and
side-conversation machines all use; an agent turn ended this way settles from
its DONE handler. gptel's own
post-tool :stop differs: it records an error and fails the turn. Callers:
an UpdateGoal completion rejected by its verifier (Goals)
an agent reaching its :max-turns cap (Agents), the Goal pause,
budget-limit, and edit controls, and a tool hook's :continue nil
(Hooks).
Terminal continuations settle once and recheck request and session ownership between lifecycle steps. Old patch and directive-attempt evidence remains tied to the captured request; an obsolete continuation cannot replace current patch presentation, clear a newer directive, restore its permissions, or end its request. Response-end markers bound delayed directive capture to the old answer.
Main and agent data buffers install buffer-local gptel pre/post-tool hooks.
The pre-tool hook preserves raw JSON distinctions, validates the call as-is,
and attempts deterministic repair only after failure. A buffer-local ledger
then associates the raw call with pipeline dispatch and final result without
placing argument values in telemetry. The normal pipeline remains the final
validation, permission, execution, and persistence boundary. See
tools.md and
ADR 0011.
mevedel-tool-repair.el owns structured contract validation plus generic
atomic repair. mevedel-tool-repair-gptel.el isolates the temporary
lossless gptel decoding bridge, while mevedel-tool-repair-diagnostics.el
owns value-free audit records, dispatch-result tracking, and redacted
telemetry. mevedel-tool-registry.el owns the schema declarations and lowers
the internal path type to a provider-facing string.
The workspace-config component checks each directory from workspace root
to the session working directory for AGENTS.md. AGENTS.local.md,
when present, is loaded after the shared file in that same directory.
Matching files are included from broadest to closest scope as
## Workspace Configuration so deeper instructions override earlier
ones.
Retained instruction context¶
A retained conversation separates behavioral instructions from observations that change during work. Main and retained-agent system prompts keep role, authority, style, skill dispatch, memory-use policy and a short memory-manual retrieval requirement. Named workspace configuration, environment, memory indexes, skill catalogs, resource availability, the main journal map and root Goal context are delivered after current input through the existing reminder transaction.
The Claude engine puts the complete selected observations into the native system prompt and delivers changes, queued turn events, configured reminders, rosters and compaction restoration through SDK receipts rather than a realized gptel payload; see native context delivery. Unchanged sections are omitted; a change back to the baseline is still an update.
Environment context identifies the execution target as local or a TRAMP
method and destination, such as ssh:alice@build or podman:dev, before the
target-native working directory. Multi-hop connections name the final
destination. Local mounts such as SSHFS remain local; the label describes
execution relative to Emacs. Labels use existing target metadata or parse the
working directory without probing. Container, VM, and WSL environments beyond
what the TRAMP method identifies are not detected.
Environment, active Goal, the specialist tool listing, skills, memory, journal and resource availability are retained as independent current-state sections. If one selected section changes, the next request appends that section's complete current contents. Its notice explicitly replaces only that section and says other previously supplied state remains applicable. Unchanged sections are not repeated; unchanged turns add no update. Workspace guidance and Goal procedures also update independently, so a counter change repeats neither their instructions nor the other fact sections.
mevedel-context-delivery.el owns this delivery boundary. Only components selected
by an agent's frozen definition reach that agent. Workers do not inherit the
root Goal or the main journal map. The default role, which declares no
components of its own, freezes its delegator's selection; a root child takes the
root's minus Goal and journal. An instruction-only recipient receives no fact
sections. Authored inline components remain in the system prompt even when named like a built-in
observation. Stateless Buddy requests retain their current snapshot; the permission reviewer
receives explicit quoted evidence without ambient instruction context;
they do not own a growing conversation prefix.
Acknowledgement and context loss¶
The trusted injected-reminders transcript record remains the source of truth.
Scanners check an opening marker's provenance before seeking its closing marker,
so quoted audit syntax cannot hide a later trusted record. The complete record
must still have trusted provenance before it is decoded or replayed.
An incremental buffer-local index avoids repeatedly decoding old tool-response
records. Edits before its cursor invalidate the index; character-generation
checks also catch replacements that inhibit edit hooks. A fresh process rebuilds
it. Exact message lookup uses a hash index rather than a nested history scan.
This cache is derived, not separately persisted acknowledgement state.
Before suppressing an update, delivery matches a complete trusted reminder message against actual user messages in the realized provider payload. The last matching observation in payload order wins. A substring in tool results, a system message, or quoted prose cannot acknowledge delivery. Missing or unsupported representations cause conservative redelivery. Thus filtering, compaction and rewind cannot silently remove required context while leaving it acknowledged from excluded source history.
Delivery and its transcript record commit together. Aborted preparation does not acknowledge an observation. A removed index, empty roster or inactive Goal produces an explicit current empty/inactive state. Goal procedure text is a separate component so counter changes do not repeat the completion procedure. Direct-child agent notices use the same retained transaction.
Progressive disclosure¶
The root AGENTS.md keeps project design decisions, before-work retrieval triggers, verification requirements, and documentation discovery. Detailed setup, upstream checkout, code-style and test procedures live in development.md.
The system memory-save policy requires Read of mevedel://memory.md before
explicit or model-initiated memory mutations. The manual owns root selection,
frontmatter, index maintenance and forget semantics. Existing write permissions
remain authoritative; the manual is not a new permission gate.
Skill catalogs retain canonical invocation names and a one-line purpose (first sentence/line, at most 160 characters). ListSkills still searches full authored names/descriptions and returns detailed discovery entries; Skill retrieves the body. The context-relative total catalog budget remains a final safeguard. Path-scoped discovery is optional and does not promote a skill into the shared catalog. Stable skill policy remains available even when that catalog is empty.
The specialist tool listing names each tool the conversation may call through ToolCall but does not have natively, as its calling shape and one-line summary (see Tools). It is rendered from the receiving conversation's own catalog and request restrictions, so an agent sees its own specialists, not its root's.
Cache boundaries¶
The goal is to preserve the eligible matching prefix, not to promise a cache hit. Native schema, role, model, provider settings and installed behavioral policy changes can legitimately change that prefix. Provider routing, stored breakpoints and retention can prevent reuse even for identical requests.
Retained updates preserve earlier request messages. They increase history size; compaction can retire obsolete observations. See reminders.md and ADR 0115.
Retained agent configuration¶
A retained agent keeps the role contract and dynamic component selection resolved at spawn. Role-definition changes apply to newly created agents. Create a new agent when its task needs a changed configuration; follow-ups keep the existing agent's snapshot.
Resource addressing¶
Filesystem-shaped tools consume one closed set of eight resource-address
families: work://, artifact://, skill://, agent://, history://,
memory:// (including memory://journal/), mcp://, and mevedel://. Read supports all eight; Glob and
Grep support work://, artifact://, skill://, memory:// (including
memory://journal/), history://saved, and mevedel://. Grep also accepts
concrete live history at history://root[/PATH]; ApplyPatch supports work:// and explicit memory file descendants alongside ordinary filesystem
paths. Addresses serialize canonical resource locators and do not replace
target-native paths, mentions, or permissions. mevedel:// is an always-
available, read-only view of packaged Markdown documentation and exposes no
Elisp source.
The resolver prepares an opaque attempt and logical authority facts after
repair, final validation, and pre-use hooks, then permission and any review
authorize it before execution consumes that attempt without reparsing. Content,
backing paths, and helper roots remain behind the boundary. Session work, artifact,
agent, and history resources belong to the session execution target; skills and
memory retain client-local origin; MCP uses the current configured connection.
Journal resources belong to the explicit workspace target and expose only
validated published records; private capture state is excluded.
Freshness and persistence remain owned by each family, while completion and
atomic mention bindings preserve locator identity without side effects.
Standalone/sticky Plan mode keeps session-only ApplyPatch available across the
root and retained-agent tree. Any ordinary, shared, memory, or bare endpoint,
including mixed and ordinary-only proposals, is rejected before local
materialization. Other edit tools and Eval remain unavailable. Directive
Planning remains strictly read-only and does not allow ApplyPatch, including
session-only proposals, or Eval. The shared local/plans/ namespace holds
durable plans for the parent and retained agents. work://shared/ maps to
workspace-owned, untracked .mevedel/shared/ for temporary working notes and
handoffs across sessions, with agent-chosen filenames and folders. Memory
consolidation extracts their lasting knowledge and removes them
(shared working-note lifecycle).
See address-to-resource.md and
ADR 0104.
Generated workspace state¶
.mevedel/state/ contains internal journal records (journal/), target write
coordination (memory-write/), clipboard images (media/), Git evidence packages
(review-packages/), workspace telemetry (diagnostics/), and plugin runtime data
(plugin-data/). The common parent is ignored by Git. This is persistent state,
not a disposable cache: journal recovery and target write coordination retain
their own cleanup rules, and plugins own their runtime data.
The workspace cleanup opportunity, throttled to once an hour, also collects
generated clipboard/guest PNGs and review packages older than seven days, up to
100 deletions per run. Completed root turns offer it, and on local Linux
workspaces the idle maintenance timer offers it while a root session is open,
so it runs hourly while Emacs is idle. A complete
target-side ripgrep search of .mevedel/ retains files mentioned in saved
conversations, historical publication snapshots, input history, memory, journal,
and other retained state. The artifact and diagnostics directories themselves
are excluded. Live buffers, input rings, drafts, and gptel file contexts also
retain files. References are conservative basename matches; unrelated mentions
can retain a file. References outside managed workspace state are not tracked.
A live foreign session owner (a PID lock held by another live process,
an unreadable or cross-host lock, or a live portable lease), linked candidate
directories, or an incomplete search postpone collection. PID locks held by this
Emacs, whose buffers are searched directly, and stale locks whose same-host
holder is dead do not. Unknown filenames and files over 32 MiB are retained; a
file changed since selection is skipped. Cleanup runs even without public journal
entries and never calls a model.
Workspace diagnostics rotate when an append would exceed 10 MiB, retaining the current file and one archive. See telemetry.
Public journal entries, curated memory, sessions (including session media and
diagnostics), shared working files, input history, identity, and configuration
remain outside this directory. mevedel-workspace-state-dir continues to name
the .mevedel/ configuration and data root.
Persistent memory¶
Memory indexes are read from configured .mevedel/memory/ and
.agents/memory/ roots, both workspace-local and user-global. The first
200 lines of each present MEMORY.md are included when a profile selects
the memory data component, with an absolute modification-date annotation.
Stable memory-policy governs use; main/worker also select
memory-save-policy before the changing data. This short policy requires
retrieving the memory manual before mutations. Buddy receives no saving
procedure. Unchanged indexes do not rewrite this section at midnight. Durable memory bodies live in linked topic files under the
same root, using user, feedback, project, or reference
frontmatter. MEMORY.md should contain one-line links only.
LLM-writable. See memory.md for the full layout, save
policy, staleness rules, and the /remember consolidation command and cockpit.
Completed saved root turns, digest publication and workspace activation offer automatic consolidation. A disposable workspace timing cache keeps the hot check free of target I/O, and the existing transport boundary defers the cold observation. The coordinator rechecks the 24-hour gate under target ownership after publication recovery. Five unreviewed digests normally qualify; even one qualifies one day before ordinary recall expiry. Completed published evidence is eligible while its source session remains live. Idle workspaces wait for their next activity boundary and retain unreviewed evidence in the meantime. Propose is the default; auto applies memory changes through the same checked decision operation and holds instruction proposals for approval.
Chat buffer formatting¶
The data buffer is normally org-mode so gptel can persist
GPTEL_BOUNDS (gptel's other config properties are stripped; the
sidecar owns request configuration). Its shape does not follow the user's
gptel chat settings: mevedel--transcript-org-mode sets buffer-local
gptel-prompt-prefix-alist and gptel-response-prefix-alist to nil, so
user turns are plain text after gptel-response-separator, and
gptel-include-tool-results to t, because the view, history,
compaction, and resume read tool calls and results from the transcript and
mevedel tools do not set gptel's :include slot. Tool results containing
:PROPERTIES: are escaped with , in the data buffer to prevent
nested-drawer confusion; the rendered view strips those storage
artifacts where appropriate.
gptel folds each tool result and reasoning block it inserts with org-cycle.
Nobody reads the data buffer's folds -- the view owns disclosure -- and with
Org's element cache disabled each fold parsed the surrounding structure and ran
the user's TAB hooks: about 65 ms per tool result in a 2.9 MB transcript,
growing with it. The transcript's buffer-local org-cycle-tab-first-hook
holds only mevedel--transcript-cycle-skip, which answers every
non-interactive call; an interactive TAB in the data buffer still cycles.
Transcript structure¶
mevedel-transcript.el owns the canonical transcript grammar. Its primary
entry point, mevedel-transcript-segments, classifies data-buffer spans as
(TYPE START END) where type is user, response, tool, reasoning,
mailbox, reminder, hook-context, task-background, render-data,
prompt, or ignored.
It combines gptel text-property runs with generated
control ranges, protects literal user examples from structural recognition,
and repairs known org/gptel boundary damage.
Hook audit delimiters are structural only when their complete encoded span
carries live mevedel-hook-audit provenance or the persisted
gptel=mevedel-hook-audit property; delimiter-shaped user, assistant,
reasoning, and tool text remains ordinary content.
The module also owns the small structural helpers needed to skip leading property drawers and compaction summaries, recover whole org tool blocks, parse agent mailbox blocks, and find the first real user prompt line outside tool/reasoning/summary scaffolding.
mevedel-transcript-normalize-properties applies those same canonical ranges
when a live or restored Org transcript needs its structural gptel
properties repaired. mevedel-transcript-restore.el owns restoration of
persisted bounds and invokes that normalizer, so persistence and the view do
not maintain their own transcript grammars. Compaction consumes the same
canonical spans directly.
mevedel-transcript-project-evidence freezes consumer-selected ranges as
neutral labelled evidence for mevedel-context-summary.el. The projection
preserves ordering while excluding hidden UI/audit spans, bounding tool
content, and replacing native media with textual metadata. The stateless
generator owns the isolated request, inherits the session's streaming choice,
accepts both streamed and one-shot delivery, and resolves summarization for
continuation/handoff or journal for digests unless given a frozen policy. It
owns preflight, heading validation, cancellation, and request telemetry;
consumers retain source selection, hooks, retries, persistence, and mutation.
Plan feeds both Summary locations the same handoff evidence and exact relevance
focus. Here applies the result through root compaction; Worktree generates once,
caches it in retry state, and applies path portability before target insertion.
Agent context="summary" projects the frozen realized parent transcript,
excludes the triggering tool call, and applies one handoff result as a distinct
child task-background span before the authoritative task.
View rendering, session prompt indexing/rewind, and compaction all read these shared spans. They keep their own policies: the view groups and renders turns, session artifacts build prompt previews, Fork owns projection state, and compaction chooses response boundaries and preserved-tail policy.