pa-core #
The session engine.
Scope #
MCP host side (mcp/): auth gating for built-in integrations (the
disabled-skill overrides and /mcp list status), the mcpServers settings
seam, and the mcp.* host-request handlers the kernel's generic MCP
client reaches through (mcp.config, mcp.refresh, optional
mcp.begin_login). The OAuth flow lives behind it (oauth* submodules:
RFC 9728 discovery, dynamic registration, PKCE, the local callback
server, token refresh; McpOAuthConfig::callback_port selects an exact registered
desktop callback port, while dynamic clients retain the candidate range.
Slack's endpoint selects its documented public desktop client; login.rs: login execution that persists the
endpoint-bound credential into the shared auth store and the
wire_begin_login/McpOAuth refresh integration hosts wire). The MCP
protocol itself runs kernel-side (Python
stdio/HTTP); the engine never spawns MCP servers. Tools (bash, edit, python_repl + internal rename/stdout), file mutation queue, truncation and rendering rules, RLM kernel lifecycle (IPython spawn/execute/revive), skills loading, system prompt assembly, compaction, settings/config, package manager (npm/git/local source install/remove/list/update against settings), session manager (persist/resume). Autonomous mode
(autonomous): runtime state with limit normalization, continuation and
gate-failure texts, shell quality gates with retry windows and git
worktree snapshotting, and the AutonomousDriver policy trait a turn
loop consults after every settled turn (the engine holds no autonomous
logic of its own). The RLM recursion host seam
(session_engine::rlm_host): the trait the kernel's rlm.spawn/
rlm.create_session/rlm.list_subagents/rlm.collect/
rlm.delete_subagent host requests call into, with the roster/collect/
selector-error vocabulary the daemon implements over the supervisor link.
Platform wall (platform): process control (signals/process groups, kernel exit waits), file locking, file permissions, and shell selection - every OS-specific behavior in the engine lives there behind cfg-gated implementations (the durable-write rename primitive is pa-telemetry's rename_onto, re-exported as platform::rename_onto). Scheduled jobs (cron, the AgentCronJobStore port of core/cron-jobs.ts): file-backed job state under session artifacts (scheduled-jobs.json partitions) with cross-process locking, plus the public read-only scan (cron::store::read_scheduled_jobs_artifact) the update flow's roster projection and boot re-arm read. Tools (bash, edit, python_repl + internal rename/stdout), file mutation queue, truncation and rendering rules, RLM kernel lifecycle (IPython spawn/execute/revive), skills loading, system prompt assembly, compaction, settings/config, package manager (npm/git/local source install/remove/list/update against settings, plus resolve(): precedence-ranked session resource resolution over configured packages, settings arrays, auto-discovery, and bundled skills), session manager (persist/resume). origin/main
Provider resilience policies in session_engine: the shared quick-retry
policy (TS provider-retry.ts: permanent-kind classification,
Retry-After-aware capped delays), the interactive auto-retry loop, and the
provider-failover driver (when a provider exhausts its quick retries and
another configured provider serves the same model, the failed turn
re-routes to the next provider in catalog order; the TS
auto_retry_start/reason: "backup" event vocabulary surfaces the
progression). The drivers are pure decision logic: the host (pa-daemon)
owns the attempt, the wait, and the model switch.
Session HTML export (export_html): the standalone viewer file for
/export and session export - the embedded product template
(assets/export-html, vendored marked/highlight.js attributed in its
NOTICE.md) plus the theme-to-CSS resolution over the bundled theme data
(pa-types) and custom themes under <agent-dir>/themes/, the tools
section mapping (tools_section: the session's tool registry to the
template's name/description/parameters list), and the custom-tool
pre-render pipeline (ToolHtmlRenderer/pre_render_custom_tools +
ansi_to_html: the exporter walks the entries, the session layer's
renderer produces line-oriented output, ANSI converts to inline-styled
HTML at the export step), and the two entry points
(export_session_to_html for the daemon worker's export_html
command, export_from_file for the CLI). Terminal theme rendering stays
pa-tui's; tool renderers live with the session's tool surface;
the share-viewer flow (gist creation) is the caller's.
Cloud workspace snapshots (workspace_snapshot): the portable, verifiable
capture of a git worktree's working state - tracked modifications, staged
paths, deletions, and nonignored untracked files - staged into an
owner-private directory as content-addressed blobs plus a deterministic
manifest that hashes them, with credential-shaped, symlink-ancestor,
escaping-symlink, nested-repo, and submodule exclusions recorded in the
manifest, hard entry/size bounds, race-free openat(O_NOFOLLOW) leaf reads
(Unix), and offline verification of a staged snapshot before upload. Capture
refuses on non-Unix targets: owner-only 0700 staging and 0600 files cannot be
enforced there. The baseline is an explicit caller choice (BaselineMode):
HEAD's tree content staged through the same filters as the delta (each entry
verified against HEAD's recorded object id and mode, no git history ships),
or an explicit external reference the consumer must reach itself. The secret
skip is a filename-shaped denylist only; ordinary-named files containing
secrets stage verbatim. Snapshot directories are secret-bearing: every
consumer must treat them as credentials. Capture does synchronous file I/O
inside its async entry point; callers must offload the entire operation with
tokio::task::spawn_blocking and Handle::block_on. Foundation plumbing
only: nothing wires it to a user-facing cloud toggle yet.
Non-goals #
No provider HTTP (pa-ai), no loop policy (pa-agent), no daemon supervision (pa-daemon), no TUI (pa-tui). No update coordination (the pa-cli coordinator owns the FSM; the update-flow seam here is the daemon-free support layer: update::version (semver/channel policy), update::install (the managed install-root layout), update::release (the channel manifest fetch), update::download (sha256-verified archive download + staging)). The package manager installs sources and resolves resource paths only. No workspace snapshot upload, transport, or remote materialization (staging and verification live here; the cloud attach surface owns the transport when it ships).
Public API #
session_engine::PluginJobSink and AgentSession::set_plugin_job_sink let hosts
observe future terminal deferred-job outcomes. Callbacks run outside the job
registry lock; retired generations cannot publish. Observation never consumes
results returned by the independent drain API. Successful job results may include
message using the existing custom-message payload (customType, content,
display, optional object details) for a host to present; the generic job
registry does not interpret that payload.
SessionEngine (message in -> events out), export_html (the HTML exporter plus its renderer seam: tools_section, pre_render_custom_tools + ToolHtmlRenderer/RenderedToolHtml/RenderedToolResult, and ansi_to_html::ansi_lines_to_html/tool_render::trim_rendered_result_lines for renderer implementers), ToolRegistry, kernel manager, settings (SettingsManager), packages (packages::PackageManager + source types), platform (process control, file locking, permissions, shell selection - usable by higher crates, e.g. pa-cli's detached spawn), session_engine::rlm_host::RlmSubagentHost (implemented by pa-daemon for supervisor-backed children and by pa-cli's print mode for in-process children; the default is no children; the trait crate also owns the shared spawn/depth facts — DEFAULT_RLM_MAX_DEPTH, the depth-limit and name errors, the daemonless create_session refusal, rlm_child_session_dir, and the persisted_rlm_max_depth/resolve_rlm_max_depth precedence chain), session_engine::semantic_edges (the TS semantic-edges.ts WRITER: SemanticEdgeRecorder/SemanticEdgeIdentity, semantic_edge_ledger_path + last_committed_request_id, and the X-ACP-Model-Request-ID/Idempotency-Key constants — one durable per-session ledger of model-request events, byte-compatible with the TS lines; the fold and any publishing stay downstream), mcp::McpManager (the session's host-side MCP manager, exposed as SessionEngine.mcp_manager: auth gating for built-in integrations, the mcpServers setting seam, and the mcp.* host-request handlers - mcp.config/mcp.refresh/optional mcp.begin_login - the kernel's generic MCP client resolves through), mcp::McpManager::connection_roster + mcp::McpConnectionEntry (the /mcp view's roster: every resolved integration with its connected state, display kind, transport, and generic-listable flag), mcp::McpManager::service_catalog_views + service_catalog_diagnostics (the /mcp view's resolved service-catalog cards and their diagnostics, answered from local state - the TS buildServiceCatalogViews parity; the picker never round-trips the kernel), mcp::McpManager::api_key_credential_views + mcp::API_KEY_CREDENTIALS/McpCredentialView (the /mcp view's api-key credential rows - the stored keys the surface manages alongside the connections, e.g. the web-search key - configured from the shared auth store), models (the registry and resolver, plus models::order_for_picker: the picker-catalog order the composition root applies to its snapshot), session_engine::provider_adapter (switchable_stream_fn/ProviderTarget: the live provider-target slot a host swaps on set_model; the adapter bridges the loop's abort into pa-ai's StreamOptions::signal cancellation token, so a turn abort cancels the in-flight HTTP fetch at the transport — closed by ModelStream::close and the stream's drop), session_engine::ipython_state (the post-compaction ipython_state kernel-persistence notice: the CompactionKernelProbe view of the kernel provisioner the engine wiring binds via AgentSession::set_kernel_state_probe, and the notice row that rides CompactRun for the surfaces to broadcast). session_engine::session_commands (the /compact//goal//autonomous executor: the durable echo/result rows with their persistence order - the echo ahead of the command so its own work sees it - the compaction/goal/autonomous arms, and the post-execution live-context rebuild that keeps the rows in the agent state; hosts own the wire surface). session_engine::goal_boundary (the goal arms of the natural turn end on SessionEngine: the --goal construction seed with its next-turn context row - TS _pendingNextTurnMessages on AgentSession - the usage accounting, the budget-limit steer mint, the natural continuation mint with its slot rollback, the RLM deferral behind unsettled descendant work (mark/drop/take owed, the wake-up ownership probe, and the latest settled turn the post-settle deliveries decide from), and the terminal-error finish; the transports - the print driver's in-loop hook, the ACP settle loop, the daemon worker's queue - drive the one goal driver through these methods). SessionEngine::expand_skill_submission + AgentSession's prompt-path expansion (the /skill:<name> submission seam, TS _expandSkillCommand: skills::expand_skill_command builds the <skill ...> block over the shared pa_types::skill_blocks parse, the accepted-turn row and the admitted user message agree on the expanded text, and the skill_use_count session counter counts at the admission). workspace_snapshot (create_workspace_snapshot: bounded, secret/nested-repo/submodule/symlink-ancestor-excluding capture of a git worktree into an owner-private staging directory, with the caller-chosen BaselineMode (HEAD-tree content verified against HEAD's object ids, or explicit external) and a ConcurrentMutation error when the worktree changes under the capture; verify_workspace_snapshot: offline manifest/blob/baseline integrity check; the SnapshotManifest/WorkspaceSnapshot/SnapshotLimits/BaselineMode/SnapshotError types). All subsystem internals pub(crate); the engine is the only facade - subsystems must not import each other directly (the package manager consumes the public settings API only).
Depends on #
pa-types, pa-ai, pa-agent (one-way).
The Unix platform process API exposes current_user_id() for the daemon
endpoint namespace to match the TypeScript process.getuid() contract on
macOS as well as Linux. This reads the OS identity without filesystem or
subprocess work.
Automatic trace delivery (agent_traces::ContinuousTraceUpload) owns consent-gated
persist scheduling and startup outbox recovery alongside the existing manual uploader.
Hosts reuse load_settings to capture consent before their existing settings parse,
attach persisted after successful disk writes, and own the returned installation
for the session lifetime. Replacements use rebind with the effective cwd; forks
use forked. The installation never drains on drop. A detached process-wide runtime
owns all transcript reads, scans, settings reloads, networking and retries; a maximum
of 256 registered controllers and four deliveries bounds resident work. Admission
returns None at capacity and logs rejection; hosts attach no hook in that case.
Retired descriptors free capacity only after background recovery acknowledges them.
Session replacement transfers its existing slot and cancels the predecessor; forks
require a distinct slot. Replacement retains a bounded retirement flag until the
background recovery snapshot acknowledges its exact controller identity. The synchronous
persist seam checks the cached consent snapshot and performs an exists/create
pending-marker write; consent metadata reads and reloads stay in the worker after
the host captures its initial settings generation; it does not fsync the marker (matching TS v0.9.8 process-crash
recovery rather than promising power-loss durability). A short nonblocking mutation lease prevents cursor/prune races; contention
records one stable fallback marker per session, which is retained conservatively
until a future sender has delivered it. Its cost must be measured
separately; startup/paint parity is not established by scheduler unit tests.
Active requests monitor consent changes every second on the worker and cancel on
revocation/errors; bytes already sent cannot be recalled. Recovery belongs to all
live hosts sharing the agent directory and cancels when the last host disappears.
Catch-up uses at most three delivery cycles per pending entry, honors Retry-After,
and releases upload capacity during retry waits; exhaustion retains durable intent.
Plugin host foundation #
plugins::PluginHost is a session-local deep module with three lifecycle operations:
reconcile(Vec<PluginSource>), snapshot(), and
invoke(&Contribution, JSON, CancellationToken). This is a new public core interface
and embedded-VM dependency. It is not engine, kernel, prompt, settings, or TUI integration,
and it does not make the continual harness optional by itself. A native test consumer
is provisional until those integration slices replace their current policies.
Each enabled source returns a declaration table with optional dense exports,
commands, and hooks arrays. Each entry has name, optional description, optional
mode (unary by default, or streaming), and a run(input, ctx) Lua function.
Exports map to plugin.<plugin>.<name> when a kernel adapter is added; commands map
to /<name>. Hooks are plugin-scoped named callbacks. The future engine adapter
must define stage inputs, outcomes, and ordering; this host does not invent stage
policy or claim context/compaction/provider contributions are implemented.
Unknown declaration fields and unsupported modes are errors, not ignored stubs.
Names are lowercase ASCII identifiers, at most 64 bytes. Duplicate plugins,
per-kind callbacks, and global command names are rejected. The composition root
supplies the reserved command inventory. No optional plugin command is
permanently reserved by the host.
Reconcile loads changed sources outside the registry lock, validates the whole inventory, then commits once. Any failure preserves the old snapshot and live Lua state. Concurrent reconciles cannot overwrite a newer commit. Unchanged sources retain their generation. Successful replacements and omitted (disabled) plugins retire previous generations, remove every callback registration, and cancel work. The host has no persistence or data-deletion capability: disabling never deletes saved plugin data. Each generation owns one VM; hosts do not share mutable Lua state. Snapshot descriptors contain opaque generation IDs and cannot retarget a replacement or cross to another host/session. No global provider registry changes.
invoke returns a native asynchronous Invocation. Its next() produces bounded,
backpressured JSON updates and one typed terminal Unary/Streamed outcome or error.
Unary callbacks cannot emit updates. Cancellation, consumer drop, host drop, disable,
and reload drop pending capability futures. Delivery checks retirement/cancellation
again, so buffered predecessor results cannot regain authority. Pure Lua callbacks
yield cooperatively at instruction intervals, allowing native cancellation to run.
Cancelled/retired work is never treated as a fallback candidate.
A callback receives only invocation-scoped ctx.call(operation, JSON) and
ctx.emit(JSON). HostCapabilities is a dynamically supplied async adapter for
explicit host operations, including future auxiliary inference. Requests carry the
session, plugin, and generation identity. Adapters must authorize operations, honor
cancellation, and use explicit private runtime paths. They must not block while
constructing or polling their future. Registry locks are not held during callbacks;
mlua VM locks release on each pending coroutine poll. Retained Lua contexts lose
capability authority when their originating invocation ends. There are no default
filesystem, network, credential, storage, or inference-provider adapters.
The reviewed dependency is mlua 0.11.6 (MIT), with lua54, vendored, async,
send, and serialize: an actual embedded Lua VM plus coroutine/JSON support that
existing workspace crates cannot supply. Vendored Lua avoids ambient system-Lua ABI
selection. Lua values and functions stay private to generation/runtime files.
Only table/string/math/UTF-8 standard libraries are loaded; ambient OS, IO, package
loading, and console output are absent. Local plugins are trusted code, not a
hostile-code sandbox. A 16 MiB VM allocation limit, 1 MiB source limit, callback
inventory limits, and initialization instruction budget bound common mistakes;
these are not a complete security/resource isolation promise.
Future contribution forms should add an explicit declaration validator, typed inventory descriptor, and generation-bound invocation adapter together. They must use this same retirement and capability lifecycle, not add a parallel registry or leak Lua values to the engine. Context strategy selection and ordered compaction policy remain separate from this generic callback host.
Session persistence embeddings use session::manager::SessionEntryWriter through
SessionManager::set_entry_writer. Each complete entry envelope goes to the
embedding's durable log before the manager publishes it. Native engine event
persistence runs before agent event delivery; metadata uses the same append path.
The engine has no session ownership, adoption, epoch, acknowledgement, or revision
protocol. session_engine::runtime::SessionRuntime remains the kernel host runtime.