mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-21 10:16:03 +00:00
feat(core): add embedded v2 session runtime and tool foundation (#30632)
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
# Catalog / Config / Plugin Lifecycle Options
|
||||
|
||||
Status: current core has selected replayable Location-scoped Catalog transforms, aligned with option B. Reload/watch behavior and deferred external plugin activation remain design work; the option comparison below is retained as historical context.
|
||||
|
||||
We need to choose where provider/model inputs live and how visible catalog state changes after boot. The designs below compare config, models.dev, auth, plugin activation/disablement, config edits, and policy changes under each option.
|
||||
|
||||
## Scenarios
|
||||
|
||||
+42
-15
@@ -44,6 +44,7 @@ export type OpenAICompletions = typeof OpenAICompletions.Type
|
||||
const AISDK = Schema.Struct({
|
||||
type: Schema.Literal("aisdk"),
|
||||
package: Schema.String,
|
||||
url: Schema.String.pipe(Schema.optional),
|
||||
})
|
||||
|
||||
const AnthropicMessages = Schema.Struct({
|
||||
@@ -67,13 +68,22 @@ export type Endpoint = typeof Endpoint.Type
|
||||
export const Options = Schema.Struct({
|
||||
headers: Schema.Record(Schema.String, Schema.String),
|
||||
body: Schema.Record(Schema.String, Schema.Any),
|
||||
aisdk: Schema.Struct({
|
||||
provider: Schema.Record(Schema.String, Schema.Any),
|
||||
request: Schema.Record(Schema.String, Schema.Any),
|
||||
}),
|
||||
})
|
||||
export type Options = typeof Options.Type
|
||||
|
||||
export class Info extends Schema.Class<Info>("ProviderV2.Info")({
|
||||
id: ID,
|
||||
name: Schema.String,
|
||||
enabled: Schema.Boolean,
|
||||
enabled: Schema.Union([
|
||||
Schema.Literal(false),
|
||||
Schema.Struct({ via: Schema.Literal("env"), name: Schema.String }),
|
||||
Schema.Struct({ via: Schema.Literal("account"), service: Schema.String }),
|
||||
Schema.Struct({ via: Schema.Literal("custom"), data: Schema.Record(Schema.String, Schema.Any) }),
|
||||
]),
|
||||
env: Schema.String.pipe(Schema.Array),
|
||||
endpoint: Endpoint,
|
||||
options: Options,
|
||||
@@ -90,6 +100,7 @@ export class Info extends Schema.Class<Info>("ProviderV2.Info")({
|
||||
options: {
|
||||
headers: {},
|
||||
body: {},
|
||||
aisdk: { provider: {}, request: {} },
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -149,12 +160,13 @@ export type Limit = typeof Limit.Type
|
||||
export const Ref = Schema.Struct({
|
||||
id: ID,
|
||||
providerID: ProviderV2.ID,
|
||||
variant: VariantID,
|
||||
variant: VariantID.pipe(Schema.optional),
|
||||
})
|
||||
export type Ref = typeof Ref.Type
|
||||
|
||||
export class Info extends Schema.Class<Info>("ModelV2.Info")({
|
||||
id: ID,
|
||||
apiID: ID,
|
||||
providerID: ProviderV2.ID,
|
||||
family: Family.pipe(Schema.optional),
|
||||
name: Schema.String,
|
||||
@@ -170,11 +182,13 @@ export class Info extends Schema.Class<Info>("ModelV2.Info")({
|
||||
}),
|
||||
cost: Cost.pipe(Schema.Array),
|
||||
status: Schema.Literals(["alpha", "beta", "deprecated", "active"]),
|
||||
enabled: Schema.Boolean,
|
||||
limit: Limit,
|
||||
}) {
|
||||
static empty(providerID: ProviderV2.ID, modelID: ID) {
|
||||
return new Info({
|
||||
id: modelID,
|
||||
apiID: modelID,
|
||||
providerID,
|
||||
name: modelID,
|
||||
endpoint: {
|
||||
@@ -188,6 +202,7 @@ export class Info extends Schema.Class<Info>("ModelV2.Info")({
|
||||
options: {
|
||||
headers: {},
|
||||
body: {},
|
||||
aisdk: { provider: {}, request: {} },
|
||||
},
|
||||
variants: [],
|
||||
time: {
|
||||
@@ -195,6 +210,7 @@ export class Info extends Schema.Class<Info>("ModelV2.Info")({
|
||||
},
|
||||
cost: [],
|
||||
status: "active",
|
||||
enabled: true,
|
||||
limit: {
|
||||
context: 0,
|
||||
output: 0,
|
||||
@@ -208,22 +224,15 @@ export class Info extends Schema.Class<Info>("ModelV2.Info")({
|
||||
|
||||
```ts
|
||||
export interface Interface {
|
||||
readonly transform: State.Interface<Data, Editor>["transform"]
|
||||
readonly provider: {
|
||||
readonly get: (providerID: ProviderV2.ID) => Effect.Effect<Option.Option<ProviderV2.Info>>
|
||||
readonly update: (providerID: ProviderV2.ID, fn: (provider: Draft<ProviderV2.Info>) => void) => Effect.Effect<void>
|
||||
readonly remove: (providerID: ProviderV2.ID) => Effect.Effect<void>
|
||||
readonly get: (providerID: ProviderV2.ID) => Effect.Effect<ProviderV2.Info, ProviderNotFoundError>
|
||||
readonly all: () => Effect.Effect<ProviderV2.Info[]>
|
||||
readonly available: () => Effect.Effect<ProviderV2.Info[]>
|
||||
}
|
||||
|
||||
readonly model: {
|
||||
readonly get: (providerID: ProviderV2.ID, modelID: ModelV2.ID) => Effect.Effect<Option.Option<ModelV2.Info>>
|
||||
readonly update: (
|
||||
providerID: ProviderV2.ID,
|
||||
modelID: ModelV2.ID,
|
||||
fn: (model: Draft<ModelV2.Info>) => void,
|
||||
) => Effect.Effect<void>
|
||||
readonly remove: (providerID: ProviderV2.ID, modelID: ModelV2.ID) => Effect.Effect<void>
|
||||
readonly get: (providerID: ProviderV2.ID, modelID: ModelV2.ID) => Effect.Effect<ModelV2.Info, ProviderNotFoundError | ModelNotFoundError>
|
||||
readonly all: () => Effect.Effect<ModelV2.Info[]>
|
||||
readonly available: () => Effect.Effect<ModelV2.Info[]>
|
||||
readonly default: () => Effect.Effect<Option.Option<ModelV2.Info>>
|
||||
@@ -232,7 +241,7 @@ export interface Interface {
|
||||
}
|
||||
```
|
||||
|
||||
`ProviderV2.Info.enabled` is stored provider state. Provider plugins set this field after checking env, account, config, or provider-specific availability.
|
||||
`ProviderV2.Info.enabled` is stored provider state. Provider plugins set it to `false` or record whether availability comes from environment, account, or custom configuration.
|
||||
|
||||
`ProviderV2.Endpoint` includes `{ type: "unknown" }`. `CatalogV2.model.get()` and `CatalogV2.model.all()` resolve `unknown` endpoints from the provider before returning models.
|
||||
|
||||
@@ -247,12 +256,30 @@ type ProviderRecord = {
|
||||
let records = HashMap.empty<ProviderV2.ID, ProviderRecord>()
|
||||
```
|
||||
|
||||
`ModelV2.Info` does not have an `enabled` field. Model availability is derived by `CatalogV2.model.available()` from provider state and model status.
|
||||
`ModelV2.Info.enabled` stores model availability. `CatalogV2.model.available()` also requires a usable provider.
|
||||
|
||||
```ts
|
||||
const available = provider.enabled && model.status !== "deprecated"
|
||||
const available = provider.enabled !== false && model.enabled
|
||||
```
|
||||
|
||||
## Current Session Runner Adaptation
|
||||
|
||||
The first local V2 Session runner waits for Location plugin boot, then resolves an explicit Session model without silently falling back. Without an explicit model it uses a supported Location catalog default, then falls back to the first available model with a supported route, and otherwise fails with `SessionRunnerModel.ModelNotSelectedError`. Its native adaptation surface is deliberately narrow:
|
||||
|
||||
```text
|
||||
openai/responses over HTTP
|
||||
openai/completions for OpenAI Chat
|
||||
openai/completions for OpenAI-compatible Chat
|
||||
anthropic/messages
|
||||
aisdk:@ai-sdk/openai
|
||||
aisdk:@ai-sdk/openai-compatible with an explicit URL
|
||||
aisdk:@ai-sdk/anthropic
|
||||
```
|
||||
|
||||
Native endpoint URLs are complete endpoint URLs and are split into base URL plus request path when building an LLM route. AI SDK endpoint URLs remain base URLs. The adapter preserves model headers and body options, environment-backed provider credentials, direct model API keys, and selected Session variant overlays.
|
||||
|
||||
Unsupported routes fail explicitly with `SessionRunnerModel.UnsupportedEndpointError`. In particular, `openai/responses` with WebSocket transport must not silently downgrade to HTTP. Google, Azure, Bedrock, OpenRouter-specific behavior, GitHub Copilot, Vertex, gateway adapters, and signed authentication remain future provider slices.
|
||||
|
||||
## Plugin Interface
|
||||
|
||||
```ts
|
||||
|
||||
@@ -0,0 +1,665 @@
|
||||
# V2 Schema Changelog
|
||||
|
||||
Record V2 database, durable-event, projected-message, HTTP, and generated SDK schema changes here. Each entry states why the contract changed and whether consumers or stored data need compatibility handling. Commit messages for schema-affecting changes should include the same summary.
|
||||
|
||||
This document covers meaningful contract changes introduced on the `feat/opencode-embedded-api` branch since its divergence from `origin/dev`. Mechanical file moves and internal refactors are omitted unless they changed stored data, replay behavior, public HTTP or SDK shapes, or model-facing tool contracts.
|
||||
|
||||
## Earlier Branch History
|
||||
|
||||
### Replayable Session Event Refinement And Cursor Stream
|
||||
|
||||
Affected schema:
|
||||
|
||||
- Existing synchronized `session.next.*` event family in `packages/core/src/session/event.ts`.
|
||||
- Existing projected V2 Session-message union in `packages/core/src/session/message.ts`.
|
||||
- New explicit durable-event union and internal replay cursor returned by `sessions.events({ sessionID, after? })`.
|
||||
|
||||
Change:
|
||||
|
||||
- Keep the existing Session lifecycle event family and projected-message union rather than introducing them in this branch.
|
||||
- Stop synchronizing text deltas, reasoning deltas, and tool-input deltas; keep them explicitly ephemeral.
|
||||
- Add an explicit durable-event union for replay-safe consumers.
|
||||
- Add replay-and-tail aggregate cursors backed by durable Session-event sequence.
|
||||
- Encode synchronized event payloads before writing JSON storage and decode them while replaying so schema transforms remain explicit at the durable boundary.
|
||||
|
||||
Reason:
|
||||
|
||||
- Embedded Session execution needs a reconnect-safe replay stream over the existing durable log and derived chronological read model.
|
||||
- Fragment streams are useful to connected renderers but must not advance durable cursors or inflate synchronized storage.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- The `session.next.*` lifecycle event family predates this branch; this branch refines its experimental V2 durability and replay contracts.
|
||||
- Durable replay cursors are per-aggregate event sequences; ephemeral deltas are intentionally absent after reconnect.
|
||||
|
||||
### Deterministic IDs From External Keys
|
||||
|
||||
Affected schema:
|
||||
|
||||
- Session and Event ID construction helpers.
|
||||
|
||||
Change:
|
||||
|
||||
- Add deterministic `SessionSchema.ID.fromExternal(...)` and `EventV2.ID.fromExternal(...)` constructors for trusted external keys.
|
||||
|
||||
Reason:
|
||||
|
||||
- Embedded adapters need stable local identities when the same external conversation or stimulus is delivered more than once.
|
||||
- Deterministic IDs let durable admission and event publication retain their idempotency boundaries across retries.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- Existing generated Session and Event IDs retain their current prefixes and generation behavior.
|
||||
- Deterministic constructors are additive internal helpers; public ID schemas remain strings with their existing prefixes.
|
||||
|
||||
### Durable Step Settlement Ownership
|
||||
|
||||
Affected schema:
|
||||
|
||||
- `session.next.step.ended` and `session.next.step.failed` synchronized event version `2`.
|
||||
|
||||
Change:
|
||||
|
||||
- Bind step settlement to an explicit `assistantMessageID`.
|
||||
|
||||
Reason:
|
||||
|
||||
- Provider-local call identifiers can repeat across turns.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- Step settlement uses synchronized event version `2` because the durable payload changed.
|
||||
|
||||
### Durable Session Input Inbox
|
||||
|
||||
Affected schema:
|
||||
|
||||
- New `session_input` table from `20260603141458_session_input_inbox.ts`.
|
||||
- Updated pending-input index from `20260603160727_jittery_ezekiel_stane.ts`.
|
||||
- New `SessionInput.Admitted` schema and `Prompted.delivery` field.
|
||||
- Prompt-admission conflict behavior in `SessionV2.prompt(...)`.
|
||||
|
||||
Change:
|
||||
|
||||
- Persist admitted prompts before projection with an autoincrement inbox sequence, unique message ID, Session ID, encoded prompt, `steer` or `queue` delivery mode, optional promoted event sequence, and creation time.
|
||||
- Index pending inputs by Session, promotion state, delivery mode, and admission sequence.
|
||||
|
||||
Reason:
|
||||
|
||||
- Prompt admission and model-visible promotion must be separate durable operations.
|
||||
- Steering must promote at safe provider-turn boundaries while queued prompts remain separate FIFO activities.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- Database migration creates the inbox table and replaces its first pending index with a delivery-aware index.
|
||||
- Exact prompt retries are idempotent; reusing a message ID for different input fails.
|
||||
|
||||
### Durable Session Projection Order
|
||||
|
||||
Affected schema:
|
||||
|
||||
- `session_message.seq` from `20260603040000_session_message_projection_order.ts`.
|
||||
- Session-message and event indexes from `20260603001617_session_message_projection_indexes.ts`, `20260603040000_session_message_projection_order.ts`, and `20260603160727_jittery_ezekiel_stane.ts`.
|
||||
|
||||
Change:
|
||||
|
||||
- Add and backfill `session_message.seq` from matching synchronized events.
|
||||
- Add event aggregate-sequence and aggregate-type-sequence indexes.
|
||||
- Add Session-message sequence, type-sequence, and compatibility timestamp indexes.
|
||||
|
||||
Reason:
|
||||
|
||||
- Projected history, replay, compaction lookup, and pagination must follow durable aggregate order rather than timestamps or caller-generated IDs.
|
||||
- Runner and HTTP read paths need covering indexes for their concrete lookup shapes.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- Migration fails rather than inventing chronology if an existing projected Session message has no matching durable event.
|
||||
- The timestamp compatibility index remains for legacy or transitional query shapes.
|
||||
|
||||
### Structured Tool Registry And Canonical Output
|
||||
|
||||
Affected schema:
|
||||
|
||||
- Core-owned typed tool registry contract.
|
||||
- Canonical tool output content and structured settlement schemas.
|
||||
- Canonical tagged tool file sources in `@opencode-ai/llm`.
|
||||
- Durable tool called, progress, success, and failure events and projected assistant-tool states.
|
||||
|
||||
Change:
|
||||
|
||||
- Validate model input against each registered tool's parameter schema.
|
||||
- Validate handler success against each tool's success schema before optional pure model-output lowering.
|
||||
- Generate optional tool-definition output JSON Schema from typed success schemas.
|
||||
- Persist canonical structured output and content for running, completed, and failed tools.
|
||||
- Represent tool files explicitly as inline data, remote URL, or managed file URI sources rather than one ambiguous URI string.
|
||||
|
||||
Reason:
|
||||
|
||||
- Embedded tool execution needs one typed boundary between provider calls, local side effects, durable settlement, and replay.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- These are additive experimental V2 runtime contracts.
|
||||
- Tool results are durably settled before provider continuation.
|
||||
- Legacy text, JSON, and inline-media results remain convertible; unresolved URL and file sources must be materialized or explicitly rejected before provider lowering.
|
||||
|
||||
### Managed Tool-Output Resources
|
||||
|
||||
Affected schema:
|
||||
|
||||
- New `ToolOutputStore.Resource` and `ToolOutputStore.Page` schemas.
|
||||
- New `tool-output://<opaque-id>` URI contract.
|
||||
- `read` tool resource-page input.
|
||||
|
||||
Change:
|
||||
|
||||
- Spill oversized model-facing tool text into Session-owned opaque managed resources.
|
||||
- Page stored UTF-8 content by byte offset with bounded reads and explicit `truncated` and `next` metadata.
|
||||
|
||||
Reason:
|
||||
|
||||
- Tool results need bounded model context without discarding the full output.
|
||||
- Opaque Session ownership prevents one Session from reading another Session's managed output.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- This is an additive internal and model-facing resource contract.
|
||||
- Managed output is retained for a bounded period and is not a public filesystem path.
|
||||
|
||||
### Location-Scoped Filesystem Read And Search Contracts
|
||||
|
||||
Affected schema:
|
||||
|
||||
- Core filesystem read, directory-list, root-resolution, and named-reference inputs.
|
||||
- `LocationSearch.FilesInput`, `LocationSearch.GrepInput`, and bounded result schemas.
|
||||
- `read`, `glob`, and `grep` tool parameters and success payloads.
|
||||
|
||||
Change:
|
||||
|
||||
- Add bounded file reads, paged directory listings, bounded glob results, and bounded grep matches with line previews.
|
||||
- Allow named project references for read-oriented operations.
|
||||
- Resolve and pin canonical approved search roots before traversal.
|
||||
- Exclude hidden path segments from broad V2 glob and grep discovery.
|
||||
|
||||
Reason:
|
||||
|
||||
- Embedded tools need deterministic bounds and a shared path-containment authority.
|
||||
- Broad search should not disclose hidden files implicitly.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- These are additive V2 tool contracts.
|
||||
- Hidden-file discovery is intentionally narrower than an unconditional ripgrep `--hidden` traversal.
|
||||
|
||||
### Location Workspace Identity
|
||||
|
||||
Affected schema:
|
||||
|
||||
- `Location.Ref.workspaceID`.
|
||||
- V2 Location HTTP middleware routing.
|
||||
|
||||
Change:
|
||||
|
||||
- Brand optional Location workspace identity as `WorkspaceV2.ID` instead of an untyped string.
|
||||
- Preserve nested `location[workspace]` and workspace-header routing inputs while decoding them into the branded identity.
|
||||
|
||||
Reason:
|
||||
|
||||
- Location-scoped services and embedded routing need one typed workspace identity boundary.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- Existing workspace strings remain accepted when they satisfy the workspace ID schema.
|
||||
- Generated OpenAPI reflects the workspace prefix constraint.
|
||||
|
||||
### Structured Mutation Authority And File Leaves
|
||||
|
||||
Affected schema:
|
||||
|
||||
- New `LocationMutation.ResolveInput`, planned target, external-directory authorization, and typed path errors.
|
||||
- New `write` and exact `edit` tool schemas.
|
||||
- New internal file-mutation commit service.
|
||||
|
||||
Change:
|
||||
|
||||
- Resolve relative mutation paths within the active Location.
|
||||
- Accept absolute internal paths and require explicit `external_directory` approval before leaf approval for external absolute paths.
|
||||
- Keep named references read-oriented and reject them for mutation.
|
||||
- Revalidate path authority immediately before write mechanics.
|
||||
|
||||
Reason:
|
||||
|
||||
- Mutation tools need explicit capability escalation and symlink/path-swap checks without pretending path APIs provide a syscall-level sandbox.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- These are additive V2 mutation contracts.
|
||||
- Richer V1 fuzzy edit behavior remains intentionally deferred.
|
||||
|
||||
### V2 Permission Requests And Saved Rules
|
||||
|
||||
Affected schema:
|
||||
|
||||
- `PermissionV2.Request`, `AssertInput`, `ReplyInput`, source metadata, tagged errors, and lifecycle events.
|
||||
- V2 permission list, reply, and saved-rule HTTP routes and generated SDK schemas.
|
||||
|
||||
Change:
|
||||
|
||||
- Add Location-scoped pending permission requests with `once`, `always`, and `reject` replies.
|
||||
- Attach optional originating tool message and call IDs.
|
||||
- Preserve authored ordered rules and saved approvals as separate inputs to evaluation.
|
||||
- Establish action and resource conventions for `read`, `glob`, `grep`, `edit`, `external_directory`, `bash`, `todowrite`, and `webfetch` approvals.
|
||||
|
||||
Reason:
|
||||
|
||||
- Embedded tool calls need a Core-owned authorization boundary that can suspend and resume through HTTP.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- These are additive experimental V2 contracts.
|
||||
- Policy authors should account for canonical resource forms; originating tool source metadata remains optional until every registry call carries its durable assistant owner.
|
||||
|
||||
### Initial Core V2 Built-In Tool Schemas
|
||||
|
||||
Affected schema:
|
||||
|
||||
- `read`, `glob`, `grep`, `write`, exact `edit`, `bash`, and `websearch` model-facing tool contracts.
|
||||
|
||||
Change:
|
||||
|
||||
- Add Core-owned Location-scoped built-ins with explicit parameter and success schemas.
|
||||
- Bound bash output and timeout input, search result counts and previews, read sizes, directory pages, and websearch result/context controls.
|
||||
|
||||
Reason:
|
||||
|
||||
- Embedded runner launch requires a minimal typed tool set without importing legacy application orchestration.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- These are additive V2 built-ins.
|
||||
- Richer launch-follow-up leaves such as `apply_patch`, skill loading, task dispatch, and LSP remain separate slices.
|
||||
|
||||
### Bash Advisory Warnings
|
||||
|
||||
Affected schema:
|
||||
|
||||
- Optional `warnings` in the `bash` tool success payload.
|
||||
|
||||
Change:
|
||||
|
||||
- Return advisory warning strings when best-effort command-argument scanning detects external absolute paths; keep structured external `workdir` approval enforced.
|
||||
|
||||
Reason:
|
||||
|
||||
- A shell subprocess has host-user filesystem, process, and network authority. Token scanning cannot honestly provide containment.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- Consumers rendering bash success should tolerate optional warning strings.
|
||||
|
||||
### V2 Session HTTP And Generated SDK Contracts
|
||||
|
||||
Affected schema:
|
||||
|
||||
- V2 Session list, prompt, context, message-list, compact, and wait HTTP routes.
|
||||
- V2 Location query routing fields.
|
||||
- Generated OpenAPI and JavaScript SDK schemas.
|
||||
|
||||
Change:
|
||||
|
||||
- Expose embedded Session creation and read-side behavior over the experimental HTTP API.
|
||||
- Accept optional prompt admission `id`, `delivery`, and `resume` fields so callers can request idempotency, steering or queue semantics, and durable admission without immediate execution.
|
||||
- Keep message cursors opaque and preserve configured Location routing through both legacy flat and nested `location[...]` query parameters in the V2 SDK client.
|
||||
|
||||
Reason:
|
||||
|
||||
- Remote and embedded consumers need one generated contract while Location middleware remains compatible with current server routing.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- These are experimental V2 routes.
|
||||
- Prompt admission now returns the admitted user-shaped message and may return a conflict error when one message ID is reused for different input.
|
||||
- SDK Location GET rewriting preserves existing flat query behavior and adds nested compatibility parameters.
|
||||
|
||||
## 2026-06-03: Durable Session Message Pagination
|
||||
|
||||
Affected schema:
|
||||
|
||||
- Internal `SessionV2.messages()` cursor input.
|
||||
- Opaque cursor payload returned by `GET /api/session/:sessionID/message`.
|
||||
|
||||
Change:
|
||||
|
||||
- Remove wall-clock `time` from the message cursor payload.
|
||||
- Resolve the opaque cursor's projected message `id` to its stored `session_message.seq`.
|
||||
- Apply page boundaries and ordering with durable per-session `seq` rather than `time_created` plus `id`.
|
||||
|
||||
Reason:
|
||||
|
||||
- Projected V2 message chronology is defined by synchronized Session-event order.
|
||||
- Wall-clock timestamps may collide or move backwards, so they are not safe pagination boundaries.
|
||||
- The list endpoint must agree with replay and context loading, which already order by durable sequence.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- No database migration is required. `session_message.seq` and its session-scoped index already exist.
|
||||
- The HTTP cursor remains opaque and existing cursors remain usable because they already carry the projected message `id`; older extra `time` data is ignored while decoding.
|
||||
- No OpenAPI or generated SDK schema changes are required for this pagination correction.
|
||||
|
||||
## 2026-06-03: Public Provider And Model Catalog DTOs
|
||||
|
||||
Affected schema:
|
||||
|
||||
- Responses from `GET /api/provider`, `GET /api/provider/:providerID`, and `GET /api/model`.
|
||||
- Generated `ProviderV2PublicInfo` and `ModelV2PublicInfo` SDK schemas.
|
||||
|
||||
Change:
|
||||
|
||||
- Replace internal catalog response schemas with explicit public DTOs.
|
||||
- Remove provider request headers and bodies, API settings, custom enablement data, model request overrides, and variant request overrides from public responses.
|
||||
|
||||
Reason:
|
||||
|
||||
- Internal catalog records may contain credentials or provider-specific request material and must not cross the public HTTP serialization boundary.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- Public V2 catalog responses intentionally expose fewer fields.
|
||||
- Internal provider and model schemas remain available to the runtime.
|
||||
|
||||
## 2026-06-03: Durable Reasoning And Hosted Tool Replay Metadata
|
||||
|
||||
Affected schema:
|
||||
|
||||
- Durable `session.next.reasoning.started` and `session.next.reasoning.ended` events.
|
||||
- Durable `session.next.tool.success` and `session.next.tool.failed` events.
|
||||
- Projected assistant reasoning and settled tool message state.
|
||||
|
||||
Change:
|
||||
|
||||
- Add optional reasoning `providerMetadata`.
|
||||
- Add optional durable tool `result` and project it into settled tool message state.
|
||||
- Preserve projected tool-call metadata separately from optional settlement-result metadata.
|
||||
- Replay provider-native reasoning and tool metadata only when the historical assistant model matches the selected continuation model.
|
||||
|
||||
Reason:
|
||||
|
||||
- Provider continuation requires signed or encrypted reasoning metadata on later turns.
|
||||
- Provider-executed hosted tool results must survive projection so replay can keep hosted calls and results inline in assistant content.
|
||||
- Recovery settlement must not erase provider-native call metadata needed to reconstruct a valid continuation request.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- Added durable-event fields are optional so previously recorded experimental events remain decodable.
|
||||
- Projected settled tool state gains model-facing result data when available.
|
||||
- Projected assistant tools gain optional result-side provider metadata; the existing metadata slot remains the backward-compatible call-side slot.
|
||||
- OpenAI Responses lowers reconstructed provider-executed hosted results to stored item references instead of rejecting assistant history.
|
||||
- Bedrock Converse signatures, Gemini `thoughtSignature`, and OpenAI-compatible Chat `reasoning_content` now round-trip through canonical continuation parts.
|
||||
|
||||
## 2026-06-03: Projected Assistant Ownership And Full-Value Parts
|
||||
|
||||
Affected schema:
|
||||
|
||||
- Projected assistant text parts.
|
||||
- Durable text and tool lifecycle boundaries.
|
||||
- Projected assistant tool ownership.
|
||||
|
||||
Change:
|
||||
|
||||
- Preserve stable IDs on projected assistant text parts.
|
||||
- Route durable tool projection updates through explicit owning `assistantMessageID` values rather than provider-local call IDs alone.
|
||||
- Replay full-value text and tool-input end checkpoints while keeping fragment deltas ephemeral.
|
||||
|
||||
Reason:
|
||||
|
||||
- Provider-local tool call IDs may repeat across turns.
|
||||
- Durable projection reconstruction must not depend on ephemeral fragments that disappear after reconnect.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- Earlier experimental projected assistant rows without stable text IDs are not assumed replay-compatible.
|
||||
- Current V2 histories reconstruct from durable full-value checkpoints.
|
||||
|
||||
## 2026-06-03: Location-Scoped V2 Questions
|
||||
|
||||
Affected schema:
|
||||
|
||||
- New `QuestionV2.*` domain schemas.
|
||||
- New `question.v2.asked`, `question.v2.replied`, and `question.v2.rejected` events.
|
||||
- New question list, reply, and reject HTTP routes and generated SDK schemas.
|
||||
|
||||
Change:
|
||||
|
||||
- Add schemas for pending requests, question options, ordered answers, and tool ownership metadata.
|
||||
- Add `GET /api/question/request`.
|
||||
- Add `POST /api/session/:sessionID/question/request/:requestID/reply`.
|
||||
- Add `POST /api/session/:sessionID/question/request/:requestID/reject`.
|
||||
|
||||
Reason:
|
||||
|
||||
- Embedded V2 tool execution needs a Location-owned pending-question service whose suspended replies can be settled through HTTP.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- These are additive experimental V2 contracts.
|
||||
- No database migration is required because pending questions are intentionally in-memory Location state.
|
||||
|
||||
## 2026-06-03: Core-Owned Todo Update Event
|
||||
|
||||
Affected schema:
|
||||
|
||||
- Core-owned `SessionTodo.Info`.
|
||||
- Global `todo.updated` event registration.
|
||||
|
||||
Change:
|
||||
|
||||
- Register the todo update event from Core session-todo ownership and expose the existing todo item shape to the Core V2 tool.
|
||||
|
||||
Reason:
|
||||
|
||||
- Embedded V2 `todowrite` execution needs Core-owned persistence and update publication without importing legacy application orchestration.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- The todo table and public todo update event shape are preserved.
|
||||
- No database migration is required.
|
||||
|
||||
## 2026-06-03: Added Core V2 Tool Schemas
|
||||
|
||||
Affected schema:
|
||||
|
||||
- New `todowrite` tool parameters and success payload.
|
||||
- New `question` tool parameters and success payload.
|
||||
- New `webfetch` tool parameters and success payload.
|
||||
|
||||
Change:
|
||||
|
||||
- Add a todo replacement-list tool using `SessionTodo.Info` items.
|
||||
- Add a question tool using ordered `QuestionV2.Prompt` values and ordered answer arrays.
|
||||
- Add an HTTP(S) fetch tool with explicit `text`, `markdown`, and `html` formats, bounded timeout input, and optional managed output resource metadata.
|
||||
|
||||
Reason:
|
||||
|
||||
- Embedded V2 execution needs Core-owned built-ins rather than imports from legacy application orchestration.
|
||||
- Explicit schemas keep model-facing definitions, runtime validation, and durable tool settlement aligned.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- These are additive Location-scoped V2 built-ins.
|
||||
- No database migration or public HTTP API migration is required.
|
||||
|
||||
## 2026-06-03: Conditional File-Mutation Stale Error
|
||||
|
||||
Affected schema:
|
||||
|
||||
- New internal `FileMutation.StaleContentError` tagged error.
|
||||
|
||||
Change:
|
||||
|
||||
- Add a typed error carrying the mutation target path when an approved exact edit no longer matches the bytes at commit time.
|
||||
|
||||
Reason:
|
||||
|
||||
- V2 exact edits must fail rather than stale-clobber a concurrent cooperating write after permission approval.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- This is an additive internal error contract.
|
||||
- No database, HTTP, or generated SDK schema changes are required.
|
||||
|
||||
## 2026-06-03: Provider Stream Watchdog Policy Deferred
|
||||
|
||||
Affected schema:
|
||||
|
||||
- No database, durable-event, HTTP, or generated SDK schema changes.
|
||||
- Internal Session-runner provider-stream policy.
|
||||
|
||||
Change:
|
||||
|
||||
- Do not impose a universal provider-stream inactivity or absolute timeout.
|
||||
- Remove the internal timeout error and hardcoded watchdog service.
|
||||
- Defer provider timeout, retry, watchdog, durable failure-reporting, and drain-chain-release policy to a configurable design slice.
|
||||
|
||||
Reason:
|
||||
|
||||
- V1 had no universal processor inactivity watchdog.
|
||||
- Providers and autonomous workloads have different runtime characteristics, so one hardcoded default is premature.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- No migration or generated artifact regeneration is required.
|
||||
- Embedded runner callers do not receive a runner-defined provider-stream timeout error.
|
||||
|
||||
## 2026-06-03: Keyed Coalescing Durable Tail Signals
|
||||
|
||||
Affected schema:
|
||||
|
||||
- No database, durable-event, HTTP, or generated SDK schema changes.
|
||||
- Internal durable aggregate-tail wake delivery only.
|
||||
|
||||
Change:
|
||||
|
||||
- Replace the process-global unbounded aggregate-ID PubSub with one sliding-capacity-1 dirty signal per active tail and aggregate.
|
||||
- Subscribe and register the signal before historical SQLite replay, then remove it when the tail closes.
|
||||
- Re-query durable rows after each dirty edge and advance only by persisted aggregate sequence.
|
||||
|
||||
Reason:
|
||||
|
||||
- Wake notifications are advisory edges, not durable event payloads.
|
||||
- Slow consumers should not retain an unbounded number of redundant wake IDs when one SQLite query can recover every committed row after their cursor.
|
||||
- Per-tail signaling preserves independent cursors for multiple consumers of the same aggregate.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- No migration, synchronized event version, OpenAPI, or SDK regeneration is required.
|
||||
- `sessions.events({ sessionID, after? })` remains a replay-and-tail stream of every durable event in aggregate sequence order.
|
||||
|
||||
## 2026-06-03: Sequential V2 Apply Patch Tool
|
||||
|
||||
Affected schema:
|
||||
|
||||
- New Core-owned `apply_patch` model-facing tool parameters and success payload.
|
||||
- New Core-owned pure patch hunk representation for add, update, and delete operations.
|
||||
|
||||
Change:
|
||||
|
||||
- Accept `{ patchText: string }` using the `*** Begin Patch` envelope.
|
||||
- Return ordered applied-operation records carrying `type`, canonical `target`, and permission-facing `resource`.
|
||||
- Resolve and approve every target before reading approved update/delete contents.
|
||||
- Preflight update/delete correctness before committing operations sequentially.
|
||||
- Report already-applied resources explicitly when a later commit fails.
|
||||
|
||||
Reason:
|
||||
|
||||
- Embedded V2 agents need reviewable multi-file edits without importing legacy application orchestration into Core.
|
||||
- Sequential semantics are small and honest: they avoid claiming rollback or transactionality that path-based filesystem commits do not provide.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- This is an additive model-facing V2 tool contract.
|
||||
- Moves and atomic rollback are deliberately unsupported in the first slice and remain visible follow-ups.
|
||||
- No database migration, durable-event version, public HTTP, OpenAPI, or generated SDK change is required.
|
||||
|
||||
## 2026-06-03: Embedded Local-Tool Recovery Alignment
|
||||
|
||||
Affected schema:
|
||||
|
||||
- No database, durable-event, HTTP, or generated SDK schema changes.
|
||||
- Internal runner recovery and permission evaluation behavior only.
|
||||
|
||||
Change:
|
||||
|
||||
- Evaluate permissions through the default `build` agent when a Session omits an explicit agent, matching provider-turn execution.
|
||||
- Before assembling a provider request, durably fail local tools still projected as `running` from a previous process with the existing `session.next.tool.failed` shape and `Tool execution interrupted` message.
|
||||
|
||||
Reason:
|
||||
|
||||
- Agent-less embedded Sessions previously executed as `build` while evaluating an empty permission ruleset, so the first local tool could wait forever for an approval surface the local Discord proof did not expose.
|
||||
- A process lost while a local tool was running previously left a dangling tool call that made later provider continuation invalid. Recovery must settle the durable projection without replaying an abandoned side effect.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- No migration, synchronized event version, OpenAPI, or SDK regeneration is required.
|
||||
- Existing experimental Session databases recover dangling local-tool projections on the next provider attempt.
|
||||
|
||||
## 2026-06-03: V2 Skill Tool
|
||||
|
||||
Affected schema:
|
||||
|
||||
- New Core-owned `skill` model-facing tool parameters and success payload.
|
||||
- Existing upstream `SkillV2` service remains the single Location-scoped skill registry.
|
||||
|
||||
Change:
|
||||
|
||||
- Accept `{ name: string }` for one skill selected from the upstream-discovered Location skill list.
|
||||
- Assert `skill` permission for the selected name.
|
||||
- Return V1-shaped `<skill_content name="...">` model output with the skill base directory and a bounded sampled supporting-file list.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- This is an additive model-facing V2 tool contract.
|
||||
- No database migration, durable-event version, public HTTP, OpenAPI, or generated SDK change is required.
|
||||
|
||||
## 2026-06-03: Pre-PR V2 Safety Review
|
||||
|
||||
Affected schema:
|
||||
|
||||
- V2 OpenAPI request bodies preserve requiredness instead of inheriting legacy optional-body normalization.
|
||||
- Existing durable tool-failure and replay-owner schemas are reused without version changes.
|
||||
|
||||
Change:
|
||||
|
||||
- Fence replay envelopes whose aggregate ID differs from the decoded synchronized payload and persist owner claims when replay first adopts an existing unowned aggregate.
|
||||
- Settle abandoned local and provider-executed tools durably before continuation; hosted failures preserve inline provider-executed replay.
|
||||
- Give `apply_patch` add hunks create-only semantics, make sequential commits uninterruptible after preflight, and reject malformed patch grammar eagerly.
|
||||
- Wait for initial plugin boot before materializing the `skill` built-in, discover conventional config-root skill directories, and resolve current skills again during execution.
|
||||
- Sanitize provider and model public API URLs by stripping credentials, queries, and fragments.
|
||||
- Keep V1-like `webfetch` network semantics: approve the requested HTTP(S) URL, allow ordinary hostnames, and delegate redirects to the HTTP transport.
|
||||
- Keep V2 request bodies required in generated OpenAPI and SDK types.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- No database migration is required.
|
||||
- Pre-launch `session.next.*` databases remain disposable experimental state rather than compatibility targets; reset experimental V2 data when upgrading across incompatible event-schema iterations.
|
||||
- V1 returns fetched images as attachments. The first Core V2 typed settlement remains text-only, so V2 continues to reject fetched images and other non-text files until attachment settlement is designed explicitly.
|
||||
|
||||
## 2026-06-03: Defer V2 Bash Background Execution
|
||||
|
||||
Affected schema:
|
||||
|
||||
- Core V2 model-facing `bash` tool parameters and success payload.
|
||||
|
||||
Change:
|
||||
|
||||
- Remove the optional `background` bash parameter and process-local background settlement shape from the shipped tool.
|
||||
- Retain the internal `BackgroundJob` prototype for a later integration slice.
|
||||
|
||||
Reason:
|
||||
|
||||
- The model has no registered observation or cancellation tool for background bash jobs, and process-local status is not a sufficient remote contract.
|
||||
|
||||
Compatibility:
|
||||
|
||||
- Foreground V2 bash execution is unchanged.
|
||||
- Reintroduce background bash only with durable status observation, completion delivery, and explicit cancellation semantics.
|
||||
@@ -1,5 +1,99 @@
|
||||
# Session API
|
||||
|
||||
## Current V2 Core Slice
|
||||
|
||||
The Effect-native core facade treats prompt recording and execution as separate responsibilities:
|
||||
|
||||
```text
|
||||
sessions.create({ id?, location, ... })
|
||||
-> omitted ID generates one internal Session ID
|
||||
-> supplied ID creates the Session when absent
|
||||
-> reused ID returns the existing Session identity
|
||||
|
||||
sessions.prompt({ id?, sessionID, prompt, delivery?, resume? })
|
||||
-> omitted ID generates one internal message ID
|
||||
-> supplied ID admits one durable Session input when absent
|
||||
-> exact reuse returns the same admitted user-shaped message
|
||||
-> reusing one message ID for another Session, prompt, or delivery mode fails
|
||||
-> exact retry schedules another wake unless resume is false
|
||||
-> resume omitted or true schedules execution after admission
|
||||
-> resume false admits only
|
||||
```
|
||||
|
||||
`session_input` is the durable admission inbox. Admitted inputs remain outside model-visible Session history until the serialized runner promotes them by publishing ordinary `Prompted` events. The existing projector atomically writes the visible user message and marks its inbox row promoted in the same event transaction.
|
||||
|
||||
Execution routing starts from only the Session ID:
|
||||
|
||||
```text
|
||||
SessionExecution.resume(sessionID)
|
||||
-> SessionStore.get(sessionID)
|
||||
-> LocationServiceMap.get(session.location)
|
||||
-> SessionRunner.run({ sessionID, force? })
|
||||
```
|
||||
|
||||
`SessionExecution` and the read-side `SessionStore` are process-global. `SessionRunner`, catalog, model resolver, tool registry, permission state, and filesystem are cached per Location. No layer takes a Session ID. An omitted `Location.workspaceID` means implicit-local placement; explicit workspace identity remains reserved for future placement semantics.
|
||||
|
||||
The local runner issues one explicit `llm.stream(request)` per provider turn, projects each complete local tool call durably before eagerly starting its structured child execution, awaits every started tool fiber after provider-stream closure, reloads projected history once before continuation, and fails after 25 provider turns within one local drain activity only when work remains. Tool settlement events carry the owning assistant-message ID because provider-local call IDs may repeat across turns. Before assembling a provider request, the runner durably fails any local tool still projected as `running` from a previous process with `Tool execution interrupted`; abandoned side effects are never silently replayed.
|
||||
|
||||
Projected hosted tools preserve call-side and settlement-side provider metadata separately so settlement and interruption recovery cannot erase continuation identifiers. Provider-native reasoning and provider metadata replay only while the historical assistant model matches the selected continuation model; after a model switch, visible reasoning text remains ordinary assistant text and provider-native metadata is omitted.
|
||||
|
||||
Provider timeout, retry, and watchdog policy is intentionally deferred. The runner does not impose a universal provider-stream inactivity or absolute timeout. A future slice should design configurable policy around provider behavior, durable failure reporting, and local drain-chain release rather than hardcoding one default for every provider.
|
||||
|
||||
Inbox delivery is explicit:
|
||||
|
||||
- `steer` inputs promote at the next safe provider-turn boundary, including continuation inside the current drain.
|
||||
- `queue` inputs form a FIFO of future activities. When the current activity settles, the runner promotes exactly one queued input to open the next activity. Multiple queued inputs remain separate activities.
|
||||
|
||||
Execution has two entry points:
|
||||
|
||||
- `run` is an explicit resume. It joins an active drain chain or starts one, and performs at least one provider attempt even when no input is eligible.
|
||||
- `wake` reports newly recorded durable inbox work. Repeated wakes coalesce. A wake calls the provider only when it can promote eligible input.
|
||||
|
||||
Post-crash activity recovery is intentionally deferred. A wake does not infer that ambiguous provider work is safe to retry after an input has already been promoted. Explicit `run` may deliberately continue from durable projected history. A future recovery slice should model durable activity identity, provider-dispatch ambiguity, required continuation, queue-opener reservation, retry policy, and visible recovery status together.
|
||||
|
||||
A location-scoped `SessionRunCoordinator` serializes each Session drain chain while allowing different Sessions to drain concurrently. Automatic startup discovery, durable multi-node ownership, stale-owner fencing, interruption controls, and retry policy remain future work.
|
||||
|
||||
Inbox promotion coalesces pending steers in durable admission order and opens one queued activity at a time in FIFO order. Add explicit inbox backlog and steering-batch limits before exposing broad multi-caller admission or untrusted queue growth.
|
||||
|
||||
Eager local-tool execution is intentionally unbounded in the current local slice. This minimizes tool latency but does not increase SQLite settlement throughput: Session-event publication remains serialized per provider turn. Before broadening exposure, revisit per-turn call limits, output truncation, and operational backpressure using observed workloads. The `session.next.*` event schemas remain experimental and unshipped; databases created by earlier experimental builds are disposable rather than compatibility targets.
|
||||
|
||||
The synchronized `session.next.*` event family and projected Session-message model predate this branch. This slice refines their replay contract: projected Session messages retain their source aggregate sequence so canonical context ordering and `sessions.messages(...)` pagination follow durable event order even when caller-supplied IDs or timestamps do not. Consumers can use `sessions.events({ sessionID, after? })` to replay durable `session.next.*` events after an aggregate sequence cursor, then tail durable events without a race. Live-only text, reasoning, and tool-input fragments remain available through EventV2 subscriptions for connected renderers; they are intentionally absent from the replayable Session stream.
|
||||
|
||||
The first `sessions.events(...)` contract is durable-only during both replay and live tailing. This keeps one cursor equal to one persisted aggregate sequence and is sufficient for reconnect-safe consumers such as Discord publication. A later UI-facing API may optionally interleave live-only deltas while connected, but those fragments must remain explicitly ephemeral: they cannot advance the durable cursor, replay after reconnect, or be mistaken for publication boundaries. Until that contract is designed, connected renderers can combine `sessions.events(...)` with direct EventV2 delta subscriptions.
|
||||
|
||||
Durable event tail wakeups are advisory and edge-triggered. Each active tail owns one sliding-capacity-1 dirty signal for its aggregate and re-queries SQLite after a wake. Repeated commits coalesce while the tail is busy because durable rows, not in-memory notifications, preserve every event and sequence. Subscribe and register the dirty signal before historical replay, then remove it when the tail closes, so replay handoff cannot miss a commit and inactive aggregates retain no wake state.
|
||||
|
||||
Event replay owner claims are separate from clustered Session execution ownership. The former already fences synchronized projection reconstruction; the latter still needs distributed active-run acquisition, stale-runtime rejection, interruption, and placement orchestration.
|
||||
|
||||
## Current Tool Registry Slice
|
||||
|
||||
`ToolRegistry` is Location-scoped. Contributions are scoped replayable transforms: closing a contribution scope removes its definition and rebuilds the advertised catalog. Execution decodes input, optionally authorizes the call, invokes the retained handler, validates output, and settles failures as typed tool-result errors.
|
||||
|
||||
When a Session omits `agent`, both execution and permission evaluation use the default `build` agent. A caller must not observe `build` model behavior while permission checks silently evaluate an empty no-agent policy.
|
||||
|
||||
The first built-in contribution is bounded `read`:
|
||||
|
||||
```text
|
||||
resolve one path relative to the Location or a named project reference
|
||||
-> reject absolute paths, path escapes, and symlink escapes
|
||||
-> authorize read against the canonical resource identity
|
||||
-> for a file: return UTF-8 text or base64 binary content; page oversized UTF-8 text by bounded line ranges
|
||||
-> for a directory: return direct children in directory-first alphabetical order
|
||||
-> page directory results with one-based offset and next cursor
|
||||
```
|
||||
|
||||
V2 `bash` uses the normal permission semantics: configured agent rules plus saved project approvals, with `ask` as the default when no rule matches. Bash is not sandboxed: the spawned shell runs with the host user's filesystem, process, and network authority. Structured external `workdir` resolution remains an enforced `external_directory` authority check. Best-effort scans of absolute command arguments produce advisory warnings only; they are not sandbox boundaries and do not request or enforce `external_directory` approval.
|
||||
|
||||
The first V2 `apply_patch` leaf supports add, update, and delete hunks. It parses every hunk, resolves every mutation target, approves external directories, approves one edit batch, and preflights approved update/delete targets before committing operations sequentially. A later commit-time failure leaves earlier operations applied and returns an explicit partial-application report. Moves and atomic rollback remain separate follow-ups rather than implied behavior.
|
||||
|
||||
### Current Runner Follow-Ups
|
||||
|
||||
- Keep eager structured local-tool settlement: durably record each complete call, start its child execution immediately, await all started settlements after provider-turn consumption, persist every result, and reload history once before continuation.
|
||||
- Buffer or coalesce streamed deltas before rewriting growing assistant projections.
|
||||
- Revisit additional covering indexes as larger-history query shapes become concrete.
|
||||
- Expose replayable Session events over HTTP and the generated SDK where remote consumers need them, deciding whether that public cursor should be opaque rather than the embedded API's branded aggregate sequence.
|
||||
- Decide whether UI-facing Session subscriptions should optionally interleave ephemeral deltas while connected without advancing the durable cursor.
|
||||
|
||||
## Remove Dedicated `session.init` Route
|
||||
|
||||
The dedicated `POST /session/:sessionID/init` endpoint exists only as a compatibility wrapper around the normal `/init` command flow.
|
||||
|
||||
+95
-4
@@ -15,8 +15,65 @@ and shell commands.
|
||||
|
||||
## Rework agent loop - Kit?
|
||||
|
||||
I think this needs to be done so we can take advantage of the simpler data
|
||||
model. It can stop doing all the
|
||||
The first Effect-native local runner slice is implemented without bridging
|
||||
through legacy `SessionPrompt.loop(...)`:
|
||||
|
||||
- process-global `SessionExecution.resume(sessionID)` discovers Location from
|
||||
the Session read model
|
||||
- cached Location-scoped `SessionRunner` resolves one supported catalog model
|
||||
and issues one explicit `llm.stream(request)` provider turn at a time
|
||||
- durable V2 projections record text, reasoning, provider failures, tool calls,
|
||||
tool results, and assistant output
|
||||
- a scoped `ToolRegistry` advertises definitions and the first permission-checked
|
||||
`read` built-in
|
||||
- local continuation reloads projected history and stops after 25 provider turns within one local drain activity
|
||||
- concurrent resumes for one Session join one process-local run while different
|
||||
Sessions remain concurrent
|
||||
|
||||
Prompt admission now uses a durable `session_input` inbox rather than immediate
|
||||
transcript projection. `steer` inputs coalesce into the active activity at the
|
||||
next safe provider-turn boundary. `queue` inputs form a FIFO of future activities
|
||||
that open one at a time. A location-scoped `SessionRunCoordinator` coalesces process-local wakeups
|
||||
around settlement races. Explicit `run` resumes perform at least one provider
|
||||
attempt; advisory `wake` notifications call the provider only for eligible inbox
|
||||
work. Steers coalesce into the active activity at
|
||||
safe provider boundaries; queued inputs open later activities one at a time in
|
||||
FIFO order.
|
||||
|
||||
Next reviewed slices:
|
||||
|
||||
- preserve eager structured local-tool settlement: durably record each complete
|
||||
call, start its child execution immediately, await every settlement after the
|
||||
provider turn closes, then reload projected history once
|
||||
- revisit per-turn tool-call limits, output truncation, and operational
|
||||
backpressure before broadening exposure; eager local execution is deliberately
|
||||
unbounded in the current local slice while SQLite publication stays serialized
|
||||
- remove the public in-memory `@opencode-ai/llm` tool loop after replacing its
|
||||
remaining one-turn native-adapter use with a narrow typed dispatcher
|
||||
- batch streamed deltas and add covering context indexes
|
||||
- expose replayable Session event cursors over HTTP and the generated SDK where remote consumers need them
|
||||
- integrate the new BackgroundJob service with V2 tool execution: support background
|
||||
bash jobs and background agent dispatch with durable status observation,
|
||||
completion delivery, and explicit cancellation / continuation semantics
|
||||
- add compaction, interruption, retries, and stale-owner fencing
|
||||
only as their slices become concrete
|
||||
|
||||
### Deferred durable activity recovery
|
||||
|
||||
Do not infer that ambiguous provider work is safe to retry from an advisory wake.
|
||||
The first inbox-driven runner intentionally omits outer provider-attempt markers
|
||||
until they have a concrete consumer and a complete recovery policy.
|
||||
|
||||
Design post-crash activity recovery as one explicit slice. It should model:
|
||||
|
||||
- durable activity identity and settlement
|
||||
- queue-opener reservation and steer assignment
|
||||
- provider-attempt preparation versus provider-dispatch ambiguity
|
||||
- required post-tool continuation across process loss
|
||||
- explicit `retry` and `abandon` decisions for unknown outcomes
|
||||
- bounded automatic retry only where provider and tool idempotency make it safe
|
||||
- retry budget, backoff, visible recovery status, startup discovery, and future
|
||||
clustered ownership fencing
|
||||
|
||||
## Rework compaction - Aiden?
|
||||
|
||||
@@ -53,8 +110,42 @@ want / config. They should register models into model database
|
||||
|
||||
## Event - Kit
|
||||
|
||||
I have this v2/event.ts but it needs to be self contained instead of using the
|
||||
old bus system
|
||||
The self-contained durable `EventV2` core service is implemented. It owns
|
||||
sync-versioned persistence, transactional sequencing, pub/sub, replay, and
|
||||
replay-owner claims without relying on the old bus system.
|
||||
|
||||
Remaining slices:
|
||||
|
||||
- expose the embedded consumer-facing Session cursor API over HTTP and the
|
||||
generated SDK where remote consumers need it
|
||||
- keep replay-owner claims distinct from future clustered Session execution
|
||||
ownership and stale-runtime fencing
|
||||
|
||||
## Deferred hardening cleanup
|
||||
|
||||
Keep these visible, but do not block functionality slices on them unless a concrete
|
||||
failure appears during canary work:
|
||||
|
||||
- serialize database migration claiming across processes; current migration
|
||||
application is protected only by an in-process semaphore, so two processes
|
||||
starting against one SQLite database can still race
|
||||
- simplify process-local durable-tail wake lifecycle with Effect `RcMap` and one
|
||||
shared `PubSub.sliding<void>(1)` per active aggregate; keep SQLite cursor replay
|
||||
and subscribe-before-history semantics unchanged
|
||||
- page large durable aggregate replay reads instead of loading every row after a
|
||||
stale cursor into one array
|
||||
- decide whether connected tails need a periodic polling fallback for
|
||||
cross-process SQLite writers; current advisory wakes are intentionally
|
||||
process-local
|
||||
- stream-cap websearch body collection before parsing
|
||||
- add ripgrep execution timeout and bounded line framing
|
||||
- materialize or consistently reject unresolved URL and file attachment sources
|
||||
- decide stateless OpenAI Responses hosted-tool continuation behavior; reconstructed hosted output can replay as a stored `item_reference` when `store !== false`, while `store: false` intentionally omits the unavailable reference path
|
||||
- decide whether to preserve deprecated `@opencode-ai/llm` orchestration exports
|
||||
- preserve or alias renamed filesystem SDK generated type names if compatibility
|
||||
consumers require them
|
||||
- revisit syscall-level mutation confinement for hostile external processes
|
||||
(`openat`, `O_NOFOLLOW`, and descriptor-relative mutation where supported)
|
||||
|
||||
## Everything is hotreloadable - ???
|
||||
|
||||
|
||||
Reference in New Issue
Block a user