Skip to content

RFC: durable principal ID and session mapping #86

Description

@zane668

Summary

hcom list --json <peer> reports a launch-time session_id snapshot which may diverge from the tool's later session id (issue #85). This RFC proposes a separate, opaque, hcom-generated principal id, persists it independently of tool session ids, and exposes a principal↔current-known-session mapping.

P1 is deliberately only the identity primitive. A principal is immutable and never silently reassigned, but P1 alone does not decide that a new process is the same principal as an old one. A fork always receives a new principal. Retaining a principal for a tracked hcom resume <name> is RFC-P2. A vanilla restart outside hcom has no continuity guarantee here.

Problem statement (in hcom's own terms)

This is a direct response to issue #85, summarized as follows:

  • hcom list --json <name> for a peer query returns the session_id captured at registration/launch time (data.session_id in src/commands/list.rs), sourced from sender_identity.session_id only on the self query path.
  • After the underlying agent's session id changes — for example after compaction, resume, or fork — the stored value may remain the original launch-time id.
  • The instance is now actually running under a different, newer session id. The two ids live in two different id spaces that never reconcile for peer lookups.
  • Any downstream integration that correlates "this hcom instance" with "the live agent session" by session_id — e.g. for identity/permission checks — silently fails to reconcile the two once the session has resumed or forked.

The issue asks two questions of intent: (1) is the launch-time id deliberate, or an oversight; (2) if deliberate, would hcom be open to exposing the current/live session id as an extra field, or a mapping between the two.

This RFC answers question 2 with a concrete primitive: rather than making callers infer identity from whichever tool-level id appears freshest, hcom should mint and own an identifier whose value never changes or gets silently reused. The persisted mapping can then say which current hcom instance and session, if any, is associated with that principal.

Why not only refresh session_id on peer queries? Because session_id belongs to the underlying tool, and its rotation semantics differ between tools and operations. A durable identity primitive must be generated and owned by hcom. Refreshing live-session metadata can still be useful, but it is a mapping update, not an identity rule.

Proposed capability

  • Generation: hcom already generates a similar-shaped, self-issued, unguessable id today — HCOM_PROCESS_ID, a UUIDv4-like string minted once per launch (generate_process_id, src/launcher.rs:469, called at src/launcher.rs:1807-1808 before injection into the child's environment). A principal can use the same generation pattern but has a different contract: it is persisted as the stable identity key of one admitted principal, while HCOM_PROCESS_ID names one OS process. The two ids must not be merged.
  • Persistence: hcom already has a persistent mapping table for a similar-shaped problem — process_bindings (process_id TEXT PRIMARY KEY, session_id TEXT, instance_name TEXT, updated_at REAL NOT NULL, table created at src/db/mod.rs:199, indexed at src/db/mod.rs:205). A principal_bindings table, keyed by the durable principal id instead of the ephemeral process id, is structurally the same kind of table hcom already maintains — not a new subsystem.
  • Exposure surface (for upstream discussion, not prescriptive):
    • hcom list --json <name> gains a nullable principal field alongside the existing session_id.
    • A query path (CLI and/or JSON) maps principal → current registered instance + current-known session_id, or explicitly returns unresolved/stopped. "Current-known" must not be presented as a guaranteed live tool session when hcom has no fresh observation.
    • Instances may receive HCOM_PRINCIPAL_ID at launch, mirroring HCOM_PROCESS_ID, so hooks can present the principal again within that admitted lifecycle.
  • Where it plugs into existing identity resolution: hcom already has an identity-resolution priority chain (resolve_identity_with_expectation, src/identity.rs:281-290; real order: system_sendersession_idnameprocess_id). A presented principal can be considered before the drifting session_id, provided the presentation channel is admitted and validated. The principal value alone is not authentication; exact precedence and trust rules are implementation details, not preconditions for the exposure surface.

Cost (stated plainly, not minimized)

  • New persistence and backup obligation. Unlike session_id/process_id bindings, which are cheaply reconstructible from live hook traffic, a principal id is self-generated, random, and not re-derivable from anything else. If principal_bindings is lost or corrupted, there is no formula to reconstruct a given principal — only partial, best-effort recovery from whatever event log entries happen to carry a copy (and only for creation paths that actually emit such an event; paths that don't, such as bare, event-less session starts, are structurally unrecoverable). This is a genuine new durability requirement, not a free addition.
  • New write path at every creation point (launch, bare start, rebind, adoption resume, and fork) that must reliably persist and propagate a principal. A tracked resume is the only resume path eligible to retain an existing principal, and only if RFC-P2 is implemented; otherwise it also receives a new principal.
  • A second identifier space to reason about and document alongside process_id and session_id — more surface for tool authors and downstream consumers to learn.

Lifecycle boundary

Operation P1 result
Initial hcom launch / bare start / adoption Mint and persist a new principal.
Hooks or compaction within the admitted lifecycle Preserve the presented principal; update only observed session metadata.
hcom resume <tracked-name> P1 alone makes no retention promise. RFC-P2 may reattach the current principal for that tracked name.
Fork Always mint a new principal. A fork is a new lineage member, not continuation of the parent principal.
Vanilla restart outside hcom No automatic reattachment guarantee. A later admission mechanism must prove or explicitly downgrade continuity.
Explicit name reclaim/rebind Mint a new principal unless a separate, authenticated transfer operation says otherwise. Never inherit merely because the string name matches.

Example

Suppose hcom launches luna with principal p-a, process proc-1, and tool session s-1. A later trusted hook from the same admitted lifecycle may update the mapping to p-a → luna / s-2; the principal stays p-a. Forking luna creates nova with a different principal p-b. If luna later starts as a new process, P1 alone does not say whether it gets p-a or a fresh principal — tracked-resume retention requires P2, while a vanilla restart remains unresolved without a separate admission rule.

Honest limitations

  • P1 does not make resume, fork, or vanilla restart identity-stable. It defines, persists, and exposes the principal and mapping needed for later continuity rules.
  • current-known session_id is only as fresh as hcom's latest trusted observation. If freshness cannot be established, the mapping must say unresolved/stale rather than claim liveness.
  • Corruption/loss handling must fail closed: if persisted state is unavailable or inconsistent, report the principal as unresolvable rather than guess or redirect to another instance.
  • The principal is an opaque correlation key, not proof of a human operator's identity and not, by itself, an authorization policy.

Acceptance criteria

  1. Every creation path either persists a newly minted principal or explicitly reports that principal support is unavailable; no successful tracked instance silently has two principal values.
  2. hcom list --json <name> exposes the instance's principal without changing the meaning of the existing session_id field.
  3. A principal lookup returns its current registered instance and current-known session id, or an explicit stopped/unresolved result. It never guesses from name or recency.
  4. Repeated hooks within one admitted lifecycle do not rotate the principal, even if observed tool session metadata changes.
  5. Fork, adoption, and explicit name reclaim each mint a principal different from the source/previous lifecycle.
  6. Removing or corrupting the mapping cannot cause another live instance to be returned for that principal; the result is unresolved and observable.
  7. Tests cover principal uniqueness, idempotent same-lifecycle writes, fork non-inheritance, name-reclaim non-inheritance, stale mapping behavior, and JSON compatibility for consumers that ignore the new field.

Relationship to RFC-P2

RFC-P2 depends on P1 and adds one narrowly scoped continuity rule: a tracked hcom resume <name> retains the principal currently attached to that tracked instance. P1 remains independently useful as the durable identifier and mapping primitive, but must not be described as solving cross-process continuity by itself.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions