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). 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 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.
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.
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 executor: the durable echo/result rows with their persistence order - the echo ahead of the command so its own work sees it - the compaction and goal 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). 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.
Plugin host foundation #
plugins::ManifestCatalog is the session-free fact layer. It reads the global
plugins setting, resolves each enabled explicit path against the agent
directory, and evaluates the chunk in a fresh VM with no host capabilities.
It extracts the returned providers table, validates every value as bounded
pure data (nil/bool/number/string/table, depth and byte limits), and caches the
result by source bytes plus config. Facts are available to registry construction
before any session exists. A provider entry in this pass is facts only;
functions, threads, and userdata are rejected. Project-scope plugins entries
are dropped during settings merge, so a cloned repository cannot execute Lua.
plugins::PluginHost is a session-local deep module with three lifecycle operations:
reconcile(Vec<PluginSource>), snapshot(), and
invoke(&Contribution, JSON, CancellationToken). The host itself carries no session
identifier. This is a public core interface and embedded-VM dependency. It is not
engine, kernel, prompt, settings, or TUI integration, and it does not by itself
activate any optional first-party plugin. 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; a deferred jobs.enqueue
resolves its hook by name, so that name must be unique across active plugins. 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 per-session async adapter for explicit host
operations such as native inference, HTTP, session snapshots, auxiliary inference,
deferred jobs, and OptChat. A request carries the operation, its input, and one
per-invocation identifier; it carries no session, plugin, or generation identity.
Adapters are already scoped to the session and operation namespace that registered
them, so they honor cancellation and 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 cannot call capabilities after
their originating invocation ends. There are no default filesystem, network,
credential, storage, or inference-provider adapters.
Staleness stays a host concern: each generation has one opaque epoch, invocation delivery is fenced by its generation's retirement, and a reload cannot publish a result produced by a retired generation. That fence is correctness, not an authorization model.
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.
Plugins are trusted local code. The VM is Lua::new(): the full safe standard
library, including io, os, load, dofile, print, and package loading, is present;
only the debug library is absent. This is not a hostile-code sandbox. A 16 MiB VM
allocation limit, 1 MiB source limit, callback inventory limits, and an
initialization instruction deadline bound runaway or accidental work. These are
resource bounds, not a security or 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.