feat: agent-core-v2 permission/workspace refactors and transcript durability#2021
Conversation
🦋 Changeset detectedLatest commit: 5f65cb9 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8821919731
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| ?.hooks.onBeforeExecuteTool.register('btw-deny-all', async (ctx) => { | ||
| ctx.decision = { block: true, reason }; | ||
| }); |
There was a problem hiding this comment.
Preserve BTW deny ordering before permission
For BTW child agents created by lifecycle.fork(), the normal agent services have already been ignited before this hook is appended, so the existing permission hook runs first. In manual/ask modes a tool call from the side-question agent can therefore open a permission approval round-trip before btw-deny-all finally blocks it; the old dynamic deny policy ran before every static policy. Register this hook ahead of permission (as the other extracted harness guards do) so BTW tools are denied immediately.
Useful? React with 👍 / 👎.
| // Second fold: tasks / interactions / todos / meta (goal, plan, swarm) | ||
| // come from the non-`context.*` records in the same journal. | ||
| return foldWireRecordFacts(records, base); |
There was a problem hiding this comment.
Replay folded facts into live backfills
This enriches readColdSnapshot() with interactions, todos, attachments, and meta facts, but the same snapshot is also consumed by backfillAgent() for sessions that are still live; that path converts it with snapshotToOps(), which only emits item/turn ops, tasks, and meta. If the transcript service first binds after a TodoWrite, approval/question, or media prompt, the live REST path serves from the backfilled store and silently drops those persisted global entities even though the cold path would show them. Emit interaction.upsert, todo.upsert, and attachment.upsert during snapshot backfill as well.
Useful? React with 👍 / 👎.
| this.buildTranscriptEnvelope(state, 'transcript.ops', { | ||
| agent_id: agentId, | ||
| ops: filtered, | ||
| seq, |
There was a problem hiding this comment.
Keep seq contiguous after grade filtering
The new seq is the unfiltered per-agent journal sequence, but lower transcript grades can drop whole batches here (append-only batches for block, step/frame/append batches for turn). A turn/block subscriber will then see the next delivered frame jump from N to N+k during normal filtered traffic, which violates the consecutive seq contract and makes gap-detecting clients refresh or catch up repeatedly. Either send an empty seq-bearing frame for filtered-out batches or track a recipient-visible watermark.
Useful? React with 👍 / 👎.
- add agent-scoped `toolApproval` domain owning the approval round-trip: builds approval requests, drives the session/approval broker, publishes permission.approval.* events, records session approval rules, and resolves ask continuations - slim permissionPolicy down to the static risk-adjudication chain; drop the dynamic `registerPolicy` mechanism - move harness constraints out of the policy chain into their owning domains as toolExecutor hooks ordered before 'permission': plan-mode guard (plan), swarm batch exclusivity and AgentSwarm approve (swarm), goal-start review (goal), exit-plan review (plan/exitPlanModeReview), and btw deny (session/btw) - delete the now-unused policies (deny-all, plan-mode-guard-deny, plan-mode-tool-approve, goal-start-review-ask, swarm-mode-agent-swarm- approve, agent-swarm-exclusive-deny) - update Permission.md, AGENTS.md, and check-domain-layers for the new domain layout
…ction - add `services` top-level view (`AppServicesView`) on the NavRail, showing the full-width app-scope Service panel grid; session/agent scopes stay in the Chat view's Inspector - extract shared `ServicePanels` from Inspector and add `methodArgs` helper to build per-method argument editors from channel metadata - support variadic service calls in `panels.ts` (`call(svc, method, ...args)`) - update agent-core-dev skill docs for the toolApproval extraction and the guard/review-off-chain permission design
- rename workspaceRegistry to workspace and workspaceLocalConfig to projectLocalConfig - extract id-spelling resolution into the workspaceAliases domain (IWorkspaceAliases) - extract workspace-centric session queries into workspaceSessions (IWorkspaceSessions) - update kap-server routes, klient contracts, and related tests to the new services
- wire: transcriptSeqSchema with per-agent batch seq watermark on transcript.reset/ops and the REST transcript response, the transcript_since subscription cursor, and the GET transcript/ops catch-up shape; seq stays optional everywhere so legacy peers fall back to loss-signal-driven refreshes - wire: drop interactionFrame, interactions live on the interaction item in the ops stream - kap-server: TranscriptService assigns consecutive per-agent batch seqs and retains them in a bounded in-memory journal; the transcript_since cursor replays journaled batches instead of a baseline reset, and the baseline reset is now items-empty because history always pages in over REST - kimi-inspect: transcript REST/WS clients track the op-batch watermark and run seq-gap/reconnect catch-up with full-refresh fallback; add the Transcript audit panel (AuditTrail timeline, structural diff, state tree) docked right of the chat - docs: sync AGENTS.md and agent-core-dev skill notes with the new transcript contract
…add user-messages endpoint
- record each ExitPlanMode submission as a versioned plan blob via a reference-only plan.revision op, projected live and cold as a plan.revision marker plus the plan badge (reviewPath, version)
- persist task.started/task.terminated (with a bounded output tail) and interaction.request/interaction.resolved ops, and add foldFacts so a cold transcript rebuilds tasks, interactions, todos and goal/plan/swarm meta from the wire journal
- add GET /sessions/{session_id}/transcript/user-messages returning all turn-opening prompts grouped per agent
…ssion gate The toolApproval extraction forces the gate to construct early (planService injects it to anchor plan-guard), so the 'permission' hook registered ahead of 'externalHooks' and a policy ask waited on the approval broker before PreToolUse could block, hanging the turn. Fetch the gate first in registerListeners and register the PreToolUse hook with before: 'permission', falling back to appending when the gate is stubbed without its hook.
…h veto-event pattern - introduce BeforeToolExecuteEvent with veto/allow/pass/waitUntil statements - add BeforeToolExecuteEmitter with two-pass fire (immediate then deferred) - split readiness work into separate onWillExecuteTool participation event - migrate all domain listeners: permissionGate, plan, goal, swarm, btw, externalHooks, toolDedupe, mcp - remove IAgentPermissionGate force-injection for hook ordering - update docs, tests, and domain-layer check to match
- veto() and waitUntil factories now carry a plain ExecutableToolResult:
isError reads as a denial, anything else as a short-circuit;
the block/reason/syntheticResult weak union is gone
- add denyToolExecution(reason) helper for the common denial shape, and
narrow the fire/authorize return to BeforeExecuteDecision ({ veto } or
{ executionMetadata })
- narrow the policy 'result' resolution to { kind: 'result'; result }
- settle vetoed calls through a single normalize/merge path in the executor
The permission gate only subscribes `onBeforeExecuteTool` from its constructor. The veto-event refactor removed the ordering-driven force-injections that used to pull it up, so without an explicit resolution tool execution would run without policy adjudication.
…y appendTagged - add `appendTagged(content, tag, origin)` to `IAgentContextMemoryService`, storing content pure with a `tag` field on `ContextMessage` - apply the XML tag at projection time in `contextProjector` via the new `tag.ts` helpers (`wrapTag` / `applyTagToContent`) - delete the `systemReminder` domain and migrate all call sites (contextInjector, goal, plugin, prompt, swarm, btw, sessionInit, toolSelectAnnouncements) to `appendTagged` - build toolDedupe reminder strings with `wrapTag`
…acade Converge three separate facade namespaces (global.providers, global.models, global.catalog) into a single global.kosong facade that exposes two domain concepts: provider (CRUD) and model (read-only view). Add streaming generate() method for direct LLM calls through the facade. - Define ProviderAuth (api-key | oauth), ProviderInput, AnonymousProviderInput, GenerateInput, GenerateParams, GenerateEvent as klient-owned public types - Extend KlientChannel with stream() for AsyncIterable transport - Add streaming IPC protocol (stream/stream_data/stream_end/ stream_error/stream_cancel frame types) - Add StreamingProcedureContract, ScopedStreamCaller, and per-chunk zod validation in the contract layer - Implement generate via dispatcher special-case routing to IModelCatalog.getRequester().request() - Rename events: providers.changed -> kosong.providers.changed, models.changed -> kosong.models.changed, catalog.changed -> kosong.changed - Remove GlobalProvidersFacade, GlobalModelsFacade, GlobalCatalogFacade, and ModelRecord from public exports - Update all tests, examples, and README BREAKING CHANGE: global.providers, global.models, and global.catalog replaced by global.kosong; event names changed; ModelRecord no longer exported.
…ve session creation transcript: - add optional `tag` field to TextFrame, textFrameSchema, and HistoryMessage - propagate tag through contextTranscript MutableMessage kimi-inspect: - render tagged frames with violet badge and distinct styling in ChatView - skip cwd prompt for workspace-based session creation in Sidebar - auto-bind default model on new sessions via resolveDefaultModel
The transcript package's REST/WS schemas and event types lived in
src/wire/, which collided with the engine's persisted wire.jsonl
record vocabulary. Rename it to src/contract/ so "wire" unambiguously
refers to wire.jsonl records (foldWireRecordFacts, HistoryWireRecord
stay unchanged).
- rename src/wire/{schema,events}.ts to src/contract/
- update index exports and test imports accordingly
- reword comments: "wire shape" -> "contract shape", "on the wire" ->
"in ops" / "in transit" / "on the WS channel" / "transcript API"
- events.ts: "transcript frame" -> "transcript event" for WS envelope
messages, avoiding confusion with TranscriptFrame
- kap-server tests: TranscriptWire/TurnWire/FrameWire/OpsCatchupWire/
UserMessagesWire -> *Contract
- update AGENTS.md references
The transcript package's REST/WS schemas and event types lived in
src/wire/, which collided with the engine's persisted wire.jsonl
record vocabulary. Rename it to src/contract/ so "wire" unambiguously
refers to wire.jsonl records (foldWireRecordFacts, HistoryWireRecord
stay unchanged).
- rename src/wire/{schema,events}.ts to src/contract/
- update index exports and test imports accordingly
- reword comments: "wire shape" -> "contract shape", "on the wire" ->
"in ops" / "in transit" / "on the WS channel" / "transcript API"
- kap-server tests: TranscriptWire/TurnWire/FrameWire/OpsCatchupWire/
UserMessagesWire -> *Contract
- update AGENTS.md references
…extMemory appendTagged" This reverts commit 55afaa3. Restore the systemReminder domain: reminders go back to being baked into message text at write time, and ContextMessage loses the `tag` field (projection-time wrapping is removed with it).
- transcript: add step usage/timing/retry, turn durationMs/error/usage, tool inputText/progress, task resultSummary/error, meta.agent status, a global prompts entity, and the 'hook' marker - kap-server: project the new fields in coreEventMap and suppress transcript-projected session events on connections subscribed to the transcript protocol (live fan-out and cursor replay) - kimi-inspect: mechanical type sync for the new snapshot prompts field
1aa3769 to
5f65cb9
Compare
Related Issue
No linked issue — this PR lands a stack of engine refactors and transcript durability work developed together; the problems each part addresses are explained below.
Problem
registerPolicymechanism blurred domain ownership.workspaceRegistry,workspaceLocalConfig), with id-spelling resolution and workspace-centric session queries lacking a home.What changed
1. Extract
toolApprovalfrom the permission gate (agent-core-v2)toolApprovaldomain owns the approval round-trip: builds approval requests, drives the session/approval broker, publishespermission.approval.*events, records session approval rules, and resolves ask continuations.permissionPolicyis slimmed to the static risk-adjudication chain; the dynamicregisterPolicymechanism is dropped.toolExecutorhooks orderedbefore: 'permission': plan-mode guard and exit-plan review (plan), swarm batch exclusivity and AgentSwarm approve (swarm), goal-start review (goal), btw deny (session/btw). The deleted policies are covered by the new hook tests (planGuard.test.ts,toolApproval.test.ts).PreToolUsehooks anchorbefore: 'permission'again — the extraction forces the gate to construct early (planService injects it), which had flipped the hook order and could hang a turn on a policyaskbefore PreToolUse could block.2. Restructure workspace domains (agent-core-v2)
workspaceRegistry→workspaceandworkspaceLocalConfig→projectLocalConfig.workspaceAliases(IWorkspaceAliases) and workspace-centric session queries intoworkspaceSessions(IWorkspaceSessions).3. Transcript op-batch sequencing and point-to-point catch-up
seqwatermark ontranscript.reset/transcript.opsand the REST transcript response, atranscript_sincesubscription cursor, and aGET .../transcript/opscatch-up shape — every field optional so legacy peers fall back to loss-signal-driven refreshes.interactionFrameis dropped; interactions live on the interaction item in the ops stream.4. Persist plan revisions and task/interaction facts; user-messages endpoint
plan/<id>/v<N>.md) and journals a reference-onlyplan.revisionrecord ({id, version, path, sha256, bytes}), projected live and cold as aplan.revisionmarker plus the plan badge (reviewPath,version).task.started/task.terminated(with a bounded output tail) andinteraction.request/interaction.resolvedops persist to the wire journal;foldFactsrebuilds tasks, interactions, todos and goal/plan/swarm meta on cold transcript loads (interactions left pending fold tocancelled).GET /sessions/{session_id}/transcript/user-messagesreturns all turn-opening prompts grouped per agent (optionalagent_id), with referenced attachment metadata.5. kimi-inspect: App Services view
servicestop-level view on the NavRail: full-width app-scope Service panel grid; session/agent scopes stay in the Chat view's Inspector.ServicePanelsextracted from Inspector;methodArgshelper builds per-method argument editors from channel metadata; variadic service calls supported inpanels.ts.Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.