Implemented runtime-alignment baseline for v0.2, 2026-06-25.
This document aligns the existing OpenTag protocol objects with the runtime seams that now use them. It is not a proposal to invent a second thread runtime.
Current code already defines the main protocol objects:
ContextPacketWorkThreadRunEvent.visibilityRunEvent.importanceOpenTagRun.contextPacket
The landed v0.2 delta is smaller than a new runtime: add an explicit admission decision seam, persist context packets as stable execution input, pass packets to executors, keep run event metadata consistent across layers, and introduce a minimal durable follow-up path.
OpenTag should keep the user-facing loop simple:
source thread mention
-> normalized event
-> scoped run
-> approved runner
-> quiet callback and audit timeline
The v0.2 goal is to make the existing loop more auditable without changing the basic mention-to-run experience.
V0.2 now focuses on runtime alignment for these seams:
RunAdmissionDecision: new public protocol object and dispatcher seam.ContextPacket: existing protocol object, now treated as stable run input.RunEventmetadata: existing visibility and importance enums, reused consistently instead of redefined per package.- durable follow-up requests for same-thread work that arrives while a run is already active.
- a default-pass agent access profile check hook at admission time.
These remain future direction, not current implementation commitments:
- durable
WorkThreadtable or standalone work-thread lifecycle; - configurable
ProjectionPolicy; - callback behavior driven directly by
RunEvent.visibility; - live active-run input updates;
- generalized guardrails for automatic run lineage;
- operator review UI;
- audit timeline UI;
- full redaction or token-budget enforcement.
ContextPacket already exists in core. Its current shape includes:
type ContextPacket = {
summary: string;
sourcePointers: ContextPointer[];
facts?: Array<{ text: string; sourceUri?: string }>;
risks?: string[];
exclusions?: string[];
mustPreserve?: string[];
redactions?: Array<{ reason: string; sourceUri?: string }>;
assembly?: {
stages: ContextPacketAssemblyStage[];
budgetTokens?: number;
emittedAt?: string;
};
};V0.2 should not remove redactions, assembly, or budgetTokens. They are
valid optional metadata. The important boundary is that their presence does not
mean OpenTag already enforces full redaction policy or token-budget policy.
RunEvent already carries protocol metadata:
type RunEventVisibility = "human" | "audit" | "debug";
type RunEventImportance = "low" | "normal" | "high" | "blocking";V0.2 should keep these enums. Do not rename them to a nicer vocabulary unless
there is a concrete migration reason. operator visibility can be added later
when an operator surface exists.
WorkThread already exists as a protocol object attached to runs. V0.2 should
not create a separate durable work-thread store unless follow-up queues,
secondary anchors, approvals, or cross-run continuity require it.
The only new public protocol object in this document is RunAdmissionDecision.
It records what the dispatcher decided to do with an incoming event before or
while creating a run.
type RunAdmissionAction =
| "start"
| "drop_duplicate"
| "queue_follow_up"
| "attach_to_active_run"
| "needs_human_decision";
type RunAdmissionReasonCode =
| "new_event"
| "duplicate_source_event"
| "active_run_same_thread"
| "active_write_run_same_thread"
| "scope_change_requires_decision"
| "policy_rejected";
type RunAdmissionDecision = {
action: RunAdmissionAction;
reason: string;
reasonCode: RunAdmissionReasonCode;
decidedAt: string;
activeRunId?: string;
eventId?: string;
};V0.2 should implement only the decisions it can make honestly:
| Action | V0.2 behavior |
|---|---|
start |
Create a normal run. |
drop_duplicate |
Reuse the existing run for the same source event and record an audit event. |
queue_follow_up is now implemented as a durable follow-up record that can be
viewed and later promoted into a new run.
attach_to_active_run and needs_human_decision are future-compatible actions.
needs_human_decision is already emitted as a stable API response when the
dispatcher cannot safely create a run. attach_to_active_run remains reserved
until a runner supports live thread updates.
The dispatcher should route run creation through one seam:
type AdmitRunInput = {
runId: string;
event: OpenTagEvent;
receivedAt: string;
};
type AdmitRunResult =
| { decision: RunAdmissionDecision; createRun: true }
| { decision: RunAdmissionDecision; createRun: false; existingRunId?: string };The seam now owns:
- duplicate source-event detection;
- policy rejection that happens before run creation;
- agent access profile checks when provider credentials are involved;
- active-run checks when those become available;
- admission audit metadata.
This keeps deduplication, future follow-up handling, and policy rejection from being scattered across route handlers and repository helpers.
Admission decisions should be recorded in the run timeline.
Suggested event names should follow the current code style:
| Event type | Purpose |
|---|---|
admission.decided |
The dispatcher made an admission decision. |
run.created |
A run record was created. |
run.create_idempotent_replay |
A duplicate source event reused an existing run. |
context_packet.generated |
A context packet snapshot was generated. |
V0.2 should not rename existing callback events to projection events. Current
callback events such as callback.*.queued and callback.*.delivered should
remain callback events until a real projection policy layer exists.
ContextPacket must behave like stable execution input, not a value that
changes whenever read-time derivation logic changes.
The current risk is:
run row stores source event
read path derives protocol fields from source event
assembly logic changes later
old run reads back with a different contextPacket
V0.2 should choose one stable snapshot strategy:
| Option | Tradeoff |
|---|---|
Store context_packet_json on the run row |
Simple read path and explicit durable input. |
Read context_packet.generated event payload when reconstructing a run |
Avoids schema column churn but makes reconstruction depend on event lookup. |
The simpler default is to persist a run-level context packet snapshot. If that
is too much migration work for v0.2, reconstructing from the
context_packet.generated event is acceptable, but recomputing from the source
event is not.
The current ContextPacket shape is valid but could become more explainable.
The next schema change should be additive:
type ContextPacket = {
summary: string;
sourcePointers: ContextPointer[];
intent?: {
rawText: string;
normalizedIntent: string;
requestedBy: ActorIdentity;
};
sources?: Array<{
pointer: ContextPointer;
role: "primary" | "supporting" | "background";
included: boolean;
reason: string;
}>;
facts?: Array<{
text: string;
sourceUri?: string;
source?: ContextPointer;
confidence?: "observed" | "inferred" | "uncertain";
}>;
risks?: string[];
exclusions?: string[];
mustPreserve?: string[];
redactions?: Array<{ reason: string; sourceUri?: string }>;
assembly?: {
stages: ContextPacketAssemblyStage[];
budgetTokens?: number;
emittedAt?: string;
};
};Keep sourcePointers for compatibility. Add intent and sources to explain
why input was selected. Do not remove existing optional metadata just because
the first v0.2 runtime does not enforce every part of it.
ContextPacket should reach executors. Otherwise it is only an audit artifact,
not the execution input boundary.
Executor input should become:
type ExecutorRunInput = {
runId: string;
workspacePath: string;
command: OpenTagCommand;
context: ContextPointer[];
contextPacket?: ContextPacket;
permissions?: PermissionGrant[];
baseBranch?: string;
worktreeRoot?: string;
keepWorktree?: "always" | "on_failure" | "never";
};Prompt builders should prefer the packet when present:
OpenTag context packet:
- summary
- intent
- selected sources
- facts
- exclusions
Source links:
- raw context pointers
Runners that do not understand contextPacket must continue to work with raw
context pointers.
Run event default metadata is protocol behavior. It should not be a private helper in one storage package if other packages need the same interpretation.
V0.2 should move default metadata into core:
function defaultRunEventMetadata(type: string): {
visibility: RunEventVisibility;
importance: RunEventImportance;
};The store can call the shared helper, and future dispatcher/client code can use the same defaults.
Future admission policy should distinguish the human actor who requested a run from the agent access profile used for execution. A run should be admitted only when the source container, target agent, runner binding, permission grants, and credential mode agree.
This keeps OpenTag from borrowing a human user's ambient permissions by default. Agent actions should be auditable under a stable agent-controlled access profile, scoped to the source container and work binding.
The admission seam now exposes a hook for these checks. The default implementation is pass-through. Richer profile resolution and provider-specific credential mode handling remain future work.
queue_follow_up now persists a durable follow-up request with:
- the source event;
- the queued admission decision;
- the active run it is waiting behind;
- a stable read API;
- a promotion path into a later run.
What remains future work is richer acknowledgement copy, auto-promotion policy, and thread-aware prioritization across multiple queued follow-ups.
attach_to_active_run should wait until at least one runner supports live input
or controlled restart semantics.
Run event metadata can eventually drive callback behavior, but v0.2 should continue using the current acknowledgement, final-result, and provider callback logic.
A separate work-thread store is useful only when OpenTag needs continuity
across multiple runs beyond what OpenTagRun.thread, run lineage, and source
events already provide.
RunAdmissionDecisionschema, exports, and tests landed in@opentag/core.ContextPacket.intentandContextPacket.sourceslanded as additive fields.defaultRunEventMetadata(type)moved into@opentag/core.- Dispatcher run creation now routes through
admitRun(...). admission.decidedis recorded for created runs and duplicate source-event replays.ContextPacketnow persists as a stable snapshot instead of being derived only at read time.- Executors now receive optional
contextPacket, and Codex / Claude Code prompts prefer packet summary, facts, and exclusions. - Same-thread active runs now queue durable follow-up requests that can later be promoted into runs.
needs_human_decisionnow has a stable API response shape.
- Core schema accepts
RunAdmissionDecision. - Core schema accepts
ContextPacketwith both oldsourcePointersand newsources. defaultRunEventMetadatareturns current visibility and importance defaults.- Dispatcher duplicate source event records an admission decision.
- Store timeline includes
admission.decidedbefore or alongsiderun.created. - Returned runs preserve the generated context packet snapshot.
- Daemon fake executor receives
contextPacket. - Codex and Claude Code prompt builders include packet summary, facts, and exclusions when a packet exists.
- Same-thread active runs return durable follow-up requests that can be viewed and promoted into runs.
- Old runs without a context packet still execute from raw context pointers.
The v0.2 design is successful if OpenTag can say:
OpenTag records why a mention became a run, preserves the curated context used
for execution, and gives every run event shared audit metadata without changing
the simple mention-to-run experience.