Skip to content

Core v4.0.4 - #75

Merged
deepagent-ai merged 121 commits into
devfrom
core-v4.0-beta
Jul 19, 2026
Merged

Core v4.0.4#75
deepagent-ai merged 121 commits into
devfrom
core-v4.0-beta

Conversation

@deepagent-ai

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Please provide a description of the issue, the changes you made to fix it, and why they work. It is expected that you understand why your changes work and if you do not understand why at least say as much so a maintainer knows how much to value the PR.

If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!

How did you verify your code works?

Screenshots / recordings

If this is a UI change, please include a screenshot or recording.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

If you do not follow this template your PR will be automatically rejected.

deepagent-ai and others added 30 commits July 11, 2026 14:08
Goal Loop control changes updated in-memory state but emitted no
immediate goal.updated event, so the client status bar never reflected
them until the next tick (a full subagent turn). stop() was worst: it
cancels the driver job, so no further tick ever fires and the UI stayed
stuck on "running" forever.

- goal-manager: extract publishGoalEvent + publishControlPhase; cache
  the live ledger/stall/gaps on GoalControl (updated each tick) so
  control transitions publish the real budget, not zeros.
- start/pause/resume/stop now emit an immediate goal.updated.
- finalizeOutcome guards the driver's terminal tap on a live control so
  a cancelled driver's late "needs_human" outcome cannot clobber the
  user's explicit "stopped".

The "convert plan -> goal" button gated on session_plan, which only the
plan tool populates — but loop/design modes author the plan as the repo
file goal+plan.md, so the button never appeared in the modes it belongs
to and did appear in auto (redundant/confusing).

- New GoalManager.startable() mirrors start()'s plan precedence
  (session_plan -> goal+plan.md -> none) without side effects, flag-gated.
- New GET /deepagent/goal/startable route + handler.
- Button now gates on capability x mode in {loop,design} x startable, and
  shows a success toast on start. i18n goal.start.success (en/zh/zht).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… 1-3)

Foundational event-driven Agent-OS substrate for V4.0 §A/§C/§D/§E, all behind
default-OFF feature flags (grey rollout).

Wave 1 — Event Bus (§A, core):
- deepagent-event{,-sql}.ts + deepagent-event-bus.ts: persist-before-dispatch,
  idempotency, subscribe/ack/nack, exponential-backoff retry + DLQ, dedup window,
  durable per-group delivery tracking (at-least-once), getByID. + migration.

Wave 2 — Router + Scheduler (§A4, core):
- event-router.ts: pure route() (type match / flag gate / low-priority dedup /
  priority / backpressure).
- scheduler{,-sql}.ts: durable delay/periodic/condition schedules (survive restart,
  unlike BackgroundJob), catch-up advance, condition threshold. + migration.

Wave 2b — runtime wiring (§A4, deepagent-code):
- event-dispatcher.ts: bus subscription + per-event handle (flag+registry+dedup →
  route → dispatch/ack/nack), scheduler tick loop, retry pump (at-least-once
  recovery), readiness gate, injected DispatchPort.

Wave 3 — Multi-Agent Runtime + autonomy + security:
- autonomy-policy.ts (§D): tighten-only ceiling, level_5 suggestion-only.
- security-gate.ts (§E1): 4-layer fail-closed check.
- rate-limiter.ts (§E2), content-safety.ts (§E3), quiet-hours.ts (§E4).
- task-partitioner.ts (§C2): event → subtask DAG (validated deps).
- conflict-arbiter.ts (§C3): conflict detection + resolution ordering.
- multi-agent-runtime.ts (§C): real DispatchPort — partition → DAG gate →
  idempotency guard → autonomy + security gates → conflict arbitration → one
  SessionPrompt turn per subtask → §C4 coordination events.

+6 V4.0 feature flags (default OFF). Each module implemented → adversarially
reviewed → tested → fixed. New tests: 13 files. Full monorepo typecheck green;
no regressions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Proactive agent-push path, gated by v4AgentPushEnabled (default OFF).

- agent-push-policy.ts (§B2, core, pure): decide() composes §E gates —
  权限 (group member OR workspace push permission) → 限流 (20/hr per agent per
  group) → 内容安全 (ContentSafety scrub) → 静默时段 (QuietHours: normal/low →
  digest, high/critical passthrough w/ requiresReason). Fail-closed.
- push-log-sql.ts + migration (§B4): im_agent_push_logs — durable audit +
  rate-limit accounting, UNIQUE(idempotency_key) for §B2 去重, content column
  retains scrubbed text as the digest-builder source.
- agent-push.ts (deepagent-code): resolves facts (membership, window count,
  quiet-hours) and runs the policy inside ONE immediate transaction — dedup
  pre-check → gate → persist message + audit atomically (no TOCTOU, no
  delivered-but-unaudited window). Flag OFF = fail-closed flag_disabled.

Adversarial review: fixed BLOCKER (去重 unimplemented — added unique index +
pre-insert dedup, message no longer double-delivers), HIGH (digest content
silently dropped — retained in log), and MEDIUM (non-transactional persist +
rate-count TOCTOU — now one transaction). §B2 file-path ACL documented as a
tracked follow-up. Tests: 11 policy + 9 runtime, monorepo typecheck green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Read-only aggregation over the durable event / delivery / push-log tables,
backing the Oversight Dashboard + Event Trace views.

- observability.ts (§F, core):
  - §F2 trace({workspaceID, correlationID}) → causal event chain, oldest-first,
    workspace-scoped (correlationID is producer-set and can collide across
    tenants).
  - §F1 metrics({workspaceID, from, to}) → dlq_events_total (distinct events),
    agent_push_rejected_total + by-reason, agent_task_success_rate (genuine
    runner failures only — policy blocks excluded; null on no data),
    agent_conflict_rate. Latency histograms + human_takeover_total documented
    as later-wave gaps (need emission-time / takeover-source instrumentation).

- fix(agent-push §B2): rate-limit window filter used `decision != 'blocked'`
  which never matched (blocks are stored `blocked:<reason>`), so rejected pushes
  wrongly consumed rate quota. Now `not like 'blocked:%'`. (Found by the
  observability review cross-check.)

Adversarial review: fixed HIGH tenant-leak (added required workspaceID scoping),
HIGH success-rate semantics (exclude policy blocks, null vs 100%), the
rate-limit filter bug, DLQ over-count (count distinct event_id), and computed
the previously-absent conflict rate. Tests: 8, monorepo typecheck green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cab (Wave 6)

Trigger policy + event vocabulary to wire the existing V3.9 bodies (Repo & Wiki,
Expert Panel, Goal Loop) onto the Event Bus. Adds NO panel/archiver/goal
mechanics — those stay V3.9; this is the "接入" layer.

- panel-convene-policy.ts (§M, core, pure): shouldConvene({event, flagEnabled,
  rules?}) → auto-convene a panel for high-risk events (security alert /
  destructive migration / architecture change / repeated CI failure), else skip.
  Urgency floored to >= high for every convene so §A4 backpressure can't silently
  drop a high-risk convene request.
- lmn-events.ts (§L/M/N, core): canonical event-type strings + shouldQueueForApproval
  (folds the PANEL_VERDICT needs_human payload gate — no accidental queue flooding),
  goalPhaseToEventType (bridges the existing goal.updated{phase} emitter → discrete
  §N lifecycle types), isArchiveTrigger.

Adversarial review: fixed MED urgency-floor asymmetry (only security was floored →
now all convene classes), MED PANEL_VERDICT approval-queue footgun (added definitive
payload-aware predicate), and the goal.updated↔goal.* vocabulary gap (added the
phase→type bridge so the later wiring has a defined contract). Tests: 15, monorepo
typecheck green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…7, §H/§I)

Capstone tests proving the waves COMPOSE, not just pass in isolation.

- v4-integration.test.ts (§I/§J): the real spine over one shared bus —
  publish(event) → EventDispatcher.handle (flag+registry+route) →
  MultiAgentRuntime injected as the live DispatchPort → coordinate
  (partition→gate→arbitrate→run) → §C4 coordination events → Observability
  trace/metrics. Asserts causal linkage (coordination events' causationID ==
  triggering event) + exact agent run order. Plus scheduler→bus→route, the
  failure→nack→retry propagation across the dispatcher/runtime boundary, and
  §H2 rollback safety (flags OFF → event durably retained but not dispatched).

- v4-migration-integrity.test.ts (§H/§I): all V4 tables + indexes exist after
  the migration set, V3.8 IM tables coexist, and the §B2/§A3 dedup indexes are
  asserted UNIQUE (guards the push double-delivery fix against a
  uniqueIndex→index regression).

- runtime-flags §H2/§H3: all six V4 flags default OFF + are individually
  toggleable (independent kill-switches).

Review confirmed the integration is genuinely end-to-end (not a bypass, runner
is the correct SessionPrompt seam); added the failure-propagation case,
index-uniqueness assertion, and causationID check it flagged. 209 V4 tests
green, monorepo typecheck green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Additive bridge services wiring the V3.9 bodies onto the Event Bus (beta —
merges after alpha). No V3.9 mechanics changed.

- event-driven-archiver.ts (§L): subscribes to session.completed/goal.completed
  and drives the EXISTING archiveSessionOnCompletion projection. Delivery-tracked
  as group "wiki-archiver" — acks on success/terminal, nacks archival failures,
  with its own group-scoped retry pump (real at-least-once, not the orphaned-
  pending-row anti-pattern the review caught).
- approval-queue.ts + sql + migration (§D2): the durable human-decision sink for
  goal.needs_human / goal.rolled_back / panel.verdict[needs_human]. offer() folds
  LMNEvents.shouldQueueForApproval (only genuine escalations queue), UNIQUE(event_id)
  makes it idempotent; listPending + resolve back the Oversight Dashboard. First-
  resolution-wins via a status-gated UPDATE.

Adversarial review: Approval Queue clean on all 6 checks (idempotency, isolation,
resolve race, migration match). Fixed the archiver HIGH — it subscribed as a
tracked consumer group but never discharged deliveries (leaking pending rows +
voiding at-least-once); now acks/nacks + pumps. Tests: 8 queue + 6 archiver;
migration integrity extended to the new table. Monorepo typecheck green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s (§H3)

The six event-driven Agent-OS flags are now broadcast in the capabilities
response (optional Boolean fields, all default OFF), so a client can gate the
V4 UI surface (Oversight Dashboard / Approval Queue / proactive-push / thread +
file upload) exactly where the routes fail-close — UI availability == route
availability, matching the V3.9 expertPanel/goalLoop/wiki pattern.

Source-of-truth schema in groups/global.ts; SDK gen picks it up on next build
(not hand-edited). httpapi-instance capabilities test asserts all six default OFF.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ueue (§D2/§F)

Backs the Oversight Dashboard: GET /oversight/{metrics,trace,approvals} +
POST /oversight/approvals/resolve. New route group registered in InstanceHttpApi;
Observability + ApprovalQueue provided over the shared Database layer. All
workspace-scoped via the same routing key (workspaceID ?? directory) the rest of
the instance API uses; auth + workspace-routing middleware identical to siblings.

Adversarial review — fixed HIGH tenant-isolation defect: ApprovalQueue.resolve
keyed only on id, so a caller routed to workspace A could resolve (write) AND
read back workspace B's approval item by id. resolve() now requires workspaceID
and scopes both the UPDATE guard and the re-select to it (cross-tenant resolve →
null → typed 404, never a 500 or a leak). Read path (metrics/trace/listPending),
schemas, layer wiring, middleware all verified clean.

Tests: 9 approval-queue (incl. cross-tenant isolation) + 2 live oversight route
tests through the production HTTP server. Monorepo typecheck green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e (§N)

goal-manager.publishStatus now also mirrors the goal lifecycle onto the DeepAgent
Event Bus and escalates terminal outcomes into the §D2 Approval Queue — gated on
v4MultiAgentRuntime (default OFF ⇒ byte-identical to the V3.9 goal.updated path).

- emitGoalLifecycleEvent: phase → discrete §N type (goalPhaseToEventType; running/
  paused/stopped ⇒ goal.tick), publish to the bus with correlationID = goalId
  (the §F2 trace spine) and idempotencyKey goal:<id>:<phase>:<ticks>, then
  approvalQueue.offer (folds shouldQueueForApproval → only needs_human /
  rolled_back queue; tick/completed/stopped never escalate).
- Best-effort: wrapped in catchCause → a bus/queue failure can never break the
  goal loop. GoalManager.defaultLayer now self-provides the bus + queue layers.

Adversarial review: flag-gating, isolation, idempotency, layer wiring, and
escalation gating all clean. Fixed the one bug it caught — the write-side
workspace key used session.directory while Oversight reads by session.workspaceID,
which would make escalations invisible to the Dashboard in server edition; now
`session.workspaceID ?? session.directory ?? sessionID`, mirroring the read side.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(§B4)

Adds the event-driven columns + indexes to im_messages, backward-compatible with
the V3.8 write path (§H: add V4 fields, keep V3.8 working).

- sql.ts MessageTable: event_id (the bus event a message was produced from) +
  delivery_status (pending|delivered|failed) — both NULLABLE so V3.8 inserts that
  omit them are unaffected. Plus §B4 indexes: idx_im_messages_thread
  (group_id, reply_to_id, created_at) for thread pagination and idx_im_messages_event.
- migration 20260711040000: guarded ADD COLUMN (table_info check, mirrors the
  session-preview migration) + the two indexes.
- migration-integrity test asserts the columns exist AND are nullable.
- aligned the three IM tests that hand-roll the im_messages DDL (im-integration,
  im-agent-reply-sink, im-orchestrator) so their manual CREATE matches MessageTable.

This is the schema prerequisite for the §B1 double-write (im.message.created) —
the write path lands in a later batch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…r send

After a user message durably persists (alongside the existing WebSocket
message_created broadcast), the IM createMessage handler now publishes an
im.message.created DeepAgent event onto the bus. Flag-gated on v4EventDrivenIm
(default OFF ⇒ no publish, byte-identical to V3.8); best-effort (catchCause) so a
bus failure never fails the user's send — the message already persisted; and
idempotent (idempotencyKey = im:<messageId>, one event per message).

- lmn-events.ts: IM_MESSAGE_CREATED = "im.message.created" (+ test: not an
  approval/archive trigger).
- im handler resolves RuntimeFlags + DeepAgentEventBus; server.ts provides the
  bus to the instance route graph.

This is the "double-write" migration step (§H1.3): the legacy synchronous
@mention path stays authoritative; the event rides alongside for consumers
(Router/MentionAgent) once v4EventDrivenIm is enabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…te-limits/trust)

The shared primitive four V4 subsystems need — a per-workspace policy store,
tunable per tenant rather than baked into code:
  §A3 retentionDays · §E4 quietHours (start/end/tz) · §E2 rateLimits overrides ·
  §E1 trustedSources.

- workspace-config{,-sql}.ts + migration 20260711050000: one row per workspace,
  a single schema-versioned JSON blob. WorkspaceConfig.Service.get returns a
  fully-resolved view (defaults applied); set upsert-merges a partial patch.
- Lenient/safe defaults: absent or partial row → 30d retention, no quiet window,
  all sources trusted, no rate overrides — so enabling V4 for an existing
  workspace changes nothing until an operator writes a config. Non-positive
  retention and empty trust-list fall back to defaults (no zero-retention /
  trust-nothing lockout). Corrupt blob decodes to defaults, never crashes a reader.

Unblocks the retention sweep, quiet-hours resolver, rate-limit ceilings, and
security-gate trusted-source resolver. Tests: 6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes the security-gate's default-open wiring gap and the content-safety
file-path leak (both were documented deferrals).

- security-resolvers.ts (SecurityResolvers.Service): production resolvers for the
  gate's lenient layers — L1 trusted-source list from WorkspaceConfig; L2 actor
  workspace permission (IM membership / registered agent, fail-CLOSED on lookup
  error); L4 runtime operation pre-gate (agent capability/whitelist). Feeds
  SecurityGate.check instead of the allow-defaults.
- path-acl.ts (isPathAllowed + resolver): rejects traversal / absolute-escape /
  home-escape; a path is allowed only if it normalizes to within an allowed
  workspace root.
- content-safety.ts: ADDITIVE — scrub gains an optional allowedPathRoots; when
  set, disallowed file-path tokens are redacted and counted (strippedPaths).
  Absent → unchanged behavior.

Fail-closed throughout (§E1 contract). Tests: 47 across the three modules.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Thread: listThread repo method (group+reply_to, keyset (created_at,id) ASC via
  idx_im_messages_thread) + GET .../messages/:id/thread, membership-scoped.
- Direct message: GroupType "direct"; createGroup enforces exactly-2 members
  (user+user | user+agent) + canonical-pair dedupe → IMValidationFailedError.
- Search: FTS5 virtual table im_messages_fts + sync triggers (migration), with a
  LIKE fallback when FTS5 is unavailable; searchMessages JOINs membership so a
  user only searches groups they belong to; metadata json_extract filter.
- File upload: im_attachments table (decoupled from messages — nullable
  message_id), local-disk storage under the workspace data dir, server-derived
  path (traversal-proof), sha256 checksum, mime allowlist + size cap, gated on
  v4FileUploadEnabled (fail-closed). AttachmentID "ima_".

Storage/validation core is unit-tested (attachment-storage + im-b3); thread/
direct/search covered over HTTP. Full multipart round-trip is exercised at the
storage-core level (the in-memory test transport can't stream multipart).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- RetentionSweeper (core): a periodic daemon (runLoop-gated; sweepOnce for tests)
  that, per workspace, prunes deepagent_event / push-logs / resolved approvals
  older than WorkspaceConfig.retentionDays (default 30d). Referential-safe: never
  deletes an event still referenced by a pending delivery or an unresolved
  approval-queue row; deliveries cascade.
- DeepAgentEventBus.sweep(workspaceID, olderThan) — the ADDITIVE delete primitive
  the sweeper drives (existing publish/subscribe untouched).
- DigestBuilder (deepagent-code): flushWorkspace delivers held digest pushes
  (im_agent_push_logs where decision='digest', digest_flushed_at IS NULL) grouped
  per IM group when the workspace is OUTSIDE quiet hours (QuietHours resolved from
  WorkspaceConfig), then marks them flushed (idempotent — no double-delivery).
  No-op while within quiet hours. New im_agent_push_logs.digest_flushed_at column.

Tests: retention referential-safety + per-workspace retention + isolation; digest
delivers-outside-quiet-hours, groups, idempotent, holds-within-quiet-hours.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ncurrency

- DeepAgentEventBus.tryPublish (ADDITIVE): per-workspace event-publish rate gate
  (1000/min default, overridable). low/normal shed as {dropped:"rate_limited"}
  over-limit; high/critical always pass (§A4 — critical never dropped). Existing
  publish() untouched.
- §F1 latency: publish() records publish_latency_ms (new nullable column, timed
  around the persist txn); Observability adds eventPublishLatencyMs P50/P95 and
  eventToAgentStartMs P50/P95 (samples = agent.task.started.created_at − trigger
  created_at, joined by causationID, workspace-scoped). Oversight metrics schema
  gains the 4 optional fields.
- WorkspaceConcurrency service (§E2): per-workspace in-flight cap (5 default, from
  WorkspaceConfig) — acquire/release/depth/totalDepth, the primitive the runtime
  gates agent execution on and the dispatcher reads for backpressure.

Tests: tryPublish shed/bypass/isolation/override; latency P50/P95 + null-empty;
concurrency acquire/reject-at-cap/release/depth. Existing bus+observability green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- PanelConveneConsumer (deepagent-code): a bus-subscribing daemon (group
  "panel-convener", ack/nack + retry pump) that runs PanelConvenePolicy.shouldConvene
  per event and, on convene, drives the panel via an INJECTED PanelConvenePort
  (never creates sessions itself), publishes panel.verdict, and offers a
  needs_human verdict to the §D2 Approval Queue. Flag-gated v4PanelAutoConvene;
  deliveries always discharged.
- §D autonomy surfacing: MultiAgentRuntime now escalates a subtask that exceeds
  the agent's autonomy ceiling OR is suggestion_only to the Approval Queue (new
  agent.task.needs_human event → offer), instead of only emitting a blocked event
  and silently dropping it. ApprovalQueue.summarize handles the new type.
- SubagentTurnInput gains optional workspaceID/directory so an event-driven runner
  (no parent session) can root a turn in the triggering event's workspace; MAR
  threads event.workspaceID through.
- New flag v4PanelAutoConvene; migration.gen registers the 5 new V4 migrations.

Tests: panel convene→verdict→queue + flag-off ack + discharge; MAR autonomy-block
escalates to the queue. Integration + MAR test layers provide ApprovalQueue.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…4/§C/§E2)

The central integration gap: every V4 daemon was built + tested but NEVER started
in prod, so published events were durably logged then ignored. This wires them in.

- v4-event-runtime.ts (V4EventRuntime.layer): assembles + starts, as scoped
  fibers with the server, the EventDispatcher (router + scheduler tick + retry
  pump), the MultiAgentRuntime DispatchPort (with a production event-scoped turn
  runner that creates a fresh root session in the triggering event's workspace,
  mirroring the IM executor), and the RetentionSweeper. Daemon startup is gated on
  the V4 flags read at build — with flags OFF (default) the layer is INERT:
  nothing subscribes, ticks, or prunes (critical — the sweeper would otherwise
  delete events on a 30d TTL). Per-event behavior stays flag-gated inside each daemon.
- server.ts: composes v4EventRuntimeLayer sharing the ONE DeepAgentEventBus +
  ApprovalQueue + Database with the IM double-write and goal-manager (module-const
  layers memoize to a single instance — publishers and the dispatcher can't
  split-brain), drawing the session stack from the shared graph.
- §E2 wired: MultiAgentRuntime takes an optional WorkspaceConcurrency — over-cap
  subtasks defer (retryable, never dropped), released in Effect.ensuring; the
  dispatcher's §A4 backpressure queueDepth now reads concurrency.totalDepth().

Tests: runtime layer builds + shared-bus round-trip; MAR concurrency-cap deferral.
Full monorepo typecheck green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…OCKER+HIGH)

Adversarial review of the runtime wiring caught two defects that made the
production event path non-functional despite green unit tests. Both fixed.

- BLOCKER — daemon fibers carry no InstanceRef (it's only set per-request), so
  the event turn runner's sessions.create → InstanceState.context died (→
  failedTurn) on EVERY event: the runtime could never execute an agent. Fix:
  makeEventTurnRunner now loads the InstanceContext for the event's directory
  (InstanceStore.load) and provides InstanceRef + WorkspaceRef around create +
  prompt — mirroring the instance-context middleware / IM executor. Also resolves
  a real directory (no longer aliases a "wrk_"-id as a filesystem path) and bounds
  each turn with a 10-min timeout so a blocked tool can't stall the sequential
  dispatch loop.
- HIGH — MAR emitted agent.task.started BEFORE running, so on a runner_failed →
  nack → retry, the idempotency guard saw the started marker and acked the retry
  away as "already done" — zero effective retries. Fix: the guard now checks the
  agent.task.completed marker, so a failed subtask genuinely re-runs on retry.
- MEDIUM — record the arbiter claim only after the concurrency slot is admitted
  (a concurrency-deferred subtask no longer leaves a phantom claim).
- Retention/flag coupling documented as self-consistent (no writers when flags
  off ⇒ nothing to prune).

Verified: integration test now asserts a failed-then-recovered subtask actually
re-runs on the retry pump (not skipped). Shared-bus single-instance invariant
confirmed sound by a separate source-level review. Monorepo typecheck green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tes the stack

v4.0-beta is the INTERNAL TEST build: every V4 capability ships ON (stableOn) so
the full event-driven Agent-OS is exercised end-to-end, not grey-rolled. Each flag
remains an independent KILL-SWITCH — `DEEPAGENT_CODE_V4_*=false` disables just that
capability for isolation/rollback (mirrors the V3.9 stableOn convention for
wiki/panel/goalLoop). The GA cut keeps these ON once beta proves them.

With v4MultiAgentRuntime ON by default, the V4 event-runtime daemons now start with
the server: the EventDispatcher subscribes + routes, MultiAgentRuntime executes,
the RetentionSweeper prunes, and IM user messages double-write im.message.created.

Verified under default-ON: full httpapi-instance server boot (10/10, daemons
launch cleanly), agent-push + IM §B3 + all V4 dac suites (51) + core IM (12) green,
monorepo typecheck clean. Flag + capabilities tests updated to assert default-ON +
independent kill-switch semantics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cing posture (P0.3)

commit fbdb86c flipped all seven V4 flags default-ON ("internal test build").
For a customer-facing build the high-risk autonomous/side-effecting flags must be
operator opt-in per the §H staged-rollout contract. Flip all seven V4 flags
(v4EventDrivenIm, v4AgentPushEnabled, v4MultiAgentRuntime, v4AgentAutonomyLevel2,
v4ThreadEnabled, v4FileUploadEnabled, v4PanelAutoConvene) back to default OFF via
the env-respecting `bool` helper — override-on (env / RuntimeFlags.layer) stays
functional so operators + tests opt in. Safety gates themselves are NOT flag-gated
(they always run once P0.1/P0.2 land); this only changes feature-exposure defaults.

Tests assert the new default is OFF for all seven and that env override-on still works.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… re-entrancy (P0.2)

The §E2 per-workspace publish ceiling (1000/min) was a dead gate: it lived on
tryPublish but every producer called plain publish. Route the two workspace-facing,
externally-driven producers (im.message.created, goal.*) through tryPublish so the
ceiling enforces real load; system-origin derivative coordination events stay on
plain publish (shedding them would strand dependent subtasks). Add a flag-gated
sweepPublishLimiter daemon (in v4-event-runtime.ts, committed with P0.1) so the
per-workspace bucket map is pruned.

Adversarial-review follow-ups folded in:
- §D/§M goal.rolled_back silent-loss: elevate any APPROVAL_QUEUE_TYPES event
  (isApprovalQueueCandidate) to high priority so it bypasses the gate and always
  persists + reaches the Approval Queue (was: only needs_human elevated).
- §C4/§A4 re-entrancy amplification: coordination events (agent.task.*/agent.handoff.*)
  could re-enter dispatch via a wildcard-trigger agent → unbounded ungated cascade.
  Sever it in the pure router (isCoordinationEvent → "coordination" drop, checked first,
  flag/priority-independent). Events still persist + reach trace/oversight; only
  agent-dispatch re-entry is cut (per §C4 coordination events are observe-only).
- im.ts: distinguish a real bus error from a rate-limit drop in the log annotation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rod (P0.1)

The §E1 four-layer permission gate was default-OPEN in production: MultiAgentRuntime
was built with no resolvers, so L1 (event-source trust), L2 (actor permission) and
L4 (runtime pre-gate) all evaluated to true — only L3 (capability) was enforced,
violating §E1 "任何一层失败都必须 fail closed".

- Add a per-event `trustedSourcesFor` option to MultiAgentRuntime.layerWith (L1 is a
  per-workspace fact the static array can't express); it takes precedence and FAILS
  CLOSED (catchCause → not-trusted). Pass subtask capability to the L4 resolver.
- Inject the production SecurityResolvers (L1/L2/L4) in v4-event-runtime.ts; provide
  SecurityResolvers.layer + imRepositoryLayer into the server graph so it shares one
  instance with the runtime + IM double-write.
- §E1 tighten DEFAULT_TRUSTED_SOURCES to first-party only (im/system/schedule);
  external webhook sources (git/ci/pr/monitor) now require explicit per-workspace
  opt-in rather than being trusted by default (fail-closed default).

Also carries the §E2 publish-limiter sweep daemon (P0.2) in v4-event-runtime.ts,
which shares this file. Tests prove untrusted-source, non-member actor, whitelist
violation, and resolver-defect all BLOCK with the correct security:<layer> reason
and never invoke the runner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ducers (P1.4)

Five of six §A1 event sources had zero producers; only IM published. Add an
authenticated HTTP webhook ingress (4 POST endpoints /api/v1/webhook/{git,ci,pr,monitor})
that normalizes external deliveries into DeepAgentEvents and publishes them onto the V4
bus, so the autonomous half finally has real events to react to.

- Same middleware stack as oversight (Authorization + InstanceContext + WorkspaceRouting):
  workspace-scoped, authenticated, no anonymous path. workspaceID/actorID derived
  server-side (body cannot spoof the workspace).
- Publishes via tryPublish (§E2 1000/min gate) so external floods are shed per workspace;
  {dropped} → non-500 ack. critical monitor.alert → high priority (bypasses shed).
- Deterministic idempotencyKey via JSON.stringify of stable delivery fields (no
  delimiter-boundary collisions) → retried deliveries dedupe at the bus unique key.
- Adds LMNEvents constants GIT_PUSH/CI_FAILURE/PR_COMMENT/MONITOR_ALERT (values match
  the router/partitioner consumer match tables exactly).
- §E1 note: post-P0.1 these external sources are NOT trusted by default, so events
  persist + trace but the security gate blocks agent dispatch until an operator opts
  the source into the workspace's trustedSources. Intended fail-closed / opt-in.

Follow-ups (opt-in milestone): attest caller-supplied actorID before it feeds §E1 L2;
revisit critical→high bypass if monitor becomes high-volume.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rigger (P1.5)

The §L EventDrivenArchiver subscribes to session.completed but nothing published it —
its trigger was dead. Add an isolated bus→bus bridge (SessionCompletedPublisher) that
subscribes to the existing end-of-turn idle signal (SessionStatus.Event.Status) and
republishes it as a V4 session.completed event, WITHOUT touching the session core loop.

- Root-session only (parentID == null, evaluated at fire time, fail-closed on lookup
  miss) — subagents/panelists/goal steps never trigger an archive.
- Debounce/coalesce (45s window): an interactive root session goes idle after EVERY
  turn; a naive per-idle publish would re-archive the full trajectory per turn. The
  bridge arms a per-session debounce timer (interrupt-then-refork, epoch-guarded map
  delete, forkIn self-deregistering — no fiber/map leak), so a burst of turn-idles
  coalesces to ONE session.completed carrying the latest state. A genuinely separate
  later completion fires at a later fire-time token → new key → re-archives final state.
- completionToken = window fire-time (stable per settled window) so bus retries dedupe.
- source="system" (passes §E1 L1); payload {sessionID, workspacePath} matches exactly
  what the archiver reads.
- New flag v4EventDrivenArchive (default OFF per P0.3); off ⇒ no subscription, inert.

Follow-up: in-window completions pending at server shutdown are lost (best-effort
archive; next completion re-archives). Consider exposing debounceMs as a runtime flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s live (P1.6)

The scheduler tick loop scanned a table nothing ever wrote — the whole delay/periodic/
condition machinery (incl. the "3× CI failure → repair" example) was dead. Register two
canonical production schedules at startup so the tick loop has real rows to fire.

- (A) daily periodic schedule.scan (§A1 MaintenanceAgent trigger).
- (B) conditional ci.repair.requested when ≥3 ci.failure in 30min. Counts ci.failure
  CROSS-WORKSPACE (new ConditionSpec.crossWorkspace → tick omits the workspace filter):
  real CI failures from P1.4 webhooks land in per-project workspaces, so a system-scoped
  per-workspace count would never fire — crossWorkspace makes it functional. Existing
  per-workspace conditions unchanged (fail-safe default).
- Idempotent registration enforced at the DB layer: new nullable schedule_key column +
  partial-unique index (NULLs distinct, so ad-hoc schedules unconstrained) + migration;
  keyed inserts use onConflictDoNothing → a concurrent second process racing the same
  boot is a no-op returning the winner row, never a duplicate (fixes the multi-process
  TOCTOU a list-then-insert guard couldn't).
- Gated on v4MultiAgentRuntime (default OFF) ⇒ fresh prod DB stays empty, no dead rows.

Also unions v4-event-runtime.ts across P0/P1.5/P1.6 (limiterSweep + SessionCompletedPublisher
+ scheduleBootstrap into the one mergeAll; v4EventDrivenArchive in anyV4DaemonEnabled).

Follow-up: the cross-workspace CI counter conflates repos and the repair event carries no
repo discriminator — per-repo triggering is future producer→consumer wiring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tanceRef die (P2.7/P2.10)

Two built-but-unwired consumers are now merged into the prod V4 layer, and a
severe latent defect that made event-driven execution silently never run is fixed.

§L EventDrivenArchiver + §M PanelConveneConsumer merged into V4EventRuntime.layer,
sharing the ONE DeepAgentEventBus + ApprovalQueue with producers (no split-brain):
- archiverLayer runLoop = v4EventDrivenArchive || v4MultiAgentRuntime (it consumes
  BOTH session.completed and goal.completed triggers; must run if either producer is
  live else that trigger's delivery-tracked pending row never acks).
- panelConsumerLayer runLoop = v4PanelAutoConvene. makeEventPanelPort builds the REAL
  event-driven PanelConvenePort (root session + panelist runner + consultPanel),
  mirroring the HTTP panelConsult path — not a stub. Risk class → quorum policy.
- Both default OFF ⇒ no subscription ⇒ no group registration ⇒ no pending-row pileup.

P2.10 — daemon-fiber InstanceRef die fix (the highest-impact find): makeEventTurnRunner
and makeEventPanelPort called agents.get/defaultAgent/defaultModel OUTSIDE withContext.
Those resolve through InstanceState.context which Effect.die's when InstanceRef is
absent — and a daemon subscription fiber carries none. A die is a DEFECT that pierces
orElseSucceed (E-channel only, verified empirically), hitting the outer catchCause →
EVERY event-driven turn silently returned failedTurn. The entire multi-agent event-
driven execution chain never actually ran in prod. Fix: load ctx first (load produces
ctx, needs no InstanceRef), then run all InstanceState-touching calls inside withContext.
Regression locked by tests that invoke the runner/port with NO ambient InstanceRef
(fail before the fix via die, pass after).

Also unions P2.8 (push stack) + P2.9 (FileLock/symbols) additions into this file's
imports, runtimeLayer, and the master mergeAll.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The §B2 push stack (policy/rate-limit/quiet-hours/content-safety/audit) was built +
tested but AgentPush.push had no prod caller; quiet-hours defaulted to false; the
file-path ACL was deferred; DigestBuilder was unmerged. All four closed:

- Real caller: new SupervisorNotifier — a standalone bus subscriber that pushes an IM
  notification for human-attention terminal events (agent.task.needs_human /
  goal.needs_human / goal.rolled_back / panel.verdict[needs_human], via the shared
  shouldQueueForApproval fold). Authorized as the runtime trust-root (workspace-push
  permission, system audit identity, workspace-scoped targets — not member spoofing);
  rate/quiet/content gates still run after. Avoids the hot files by design.
- Real §E4 quiet-hours: resolveWithinQuietHours reads WorkspaceConfig.quietHours →
  QuietHours.isWithinQuietHours (was hardcoded false). Fail-safe: no window / lookup
  error ⇒ not quiet (courtesy throttle, not a security gate); high/critical punch through.
- §E3 file-path ACL: allowedPathRoots threaded through PushFacts → ContentSafety.scrub,
  resolved from the workspace directory (was explicitly deferred).
- DigestBuilder merged (flag-gated v4AgentPushEnabled) so held normal/low pushes flush.

All gated on v4AgentPushEnabled (default OFF): push returns flag_disabled, notifier
doesn't subscribe (no pending-row pileup), digest daemon dormant.

Follow-ups (opt-in milestone): real project-roots resolver for wrk_ workspaces (path-ACL
off there today); per-agent push-rate visibility counter (shared SYSTEM_PUSHER budget).
Push-stack layer wiring lives in v4-event-runtime.ts (committed with P2.7).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…raph symbols (P2.9)

§C3 isolation was three dead gaps: the runtime never acquired file locks, semantic
conflict detection passed symbols:[] hardcoded, and there was no per-agent isolation.

- §C3.1 file locks: coordinate() now acquires agent-kind FileLock entries for a
  subtask's fileScope AFTER the security/autonomy/conflict/concurrency gates and BEFORE
  running; on contention (acquire returns null — another agent OR a human holds it) it
  releases any acquired locks + the concurrency slot and DEFERS (retryable), never runs.
  Locks release in the same ensuring finalizer as the concurrency slot (success/failure/
  interruption). The lock is the SAME process-wide FileLock singleton the file HTTP
  handlers use, so a human editing a file blocks an agent subtask (§C3 human-blocks-agent).
- §C3.3 semantic conflict: symbols:[] replaced with a symbolsForFiles resolver that
  reads the code graph (new code-indexer.symbolsForFilePaths → fully-qualified path#symbol
  keys so same-named symbols in different files don't false-conflict). Fail-safe:
  catchCause ⇒ [] (missing graph / bare wrk-id / any error) so file-level detection holds
  and coordination never crashes. Safe on the daemon fiber (opens a store by path; no
  InstanceState access).
- §C3.2 branch/worktree isolation: intentionally DEFERRED (documented) — FileLock (§C3.1)
  + ConflictArbiter (§C3.3) provide the concurrency-safety guarantee (no two admitted
  subtasks edit the same file/symbol) without separate worktrees.

Provides FileLock into the runtime (server.ts + v4-event-runtime.ts runtimeLayer,
committed with P2.7). Security gate (P0.1) untouched — locks acquired only after it passes.

Follow-up: symbol keys are host-qualified so the semantic layer is subsumed by file-scope
within one event; cross-file symbol-move detection is future work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
deepagent-ai and others added 21 commits July 17, 2026 23:40
…dgets to 15s

Two aggravating factors caused the 9 cancel/loop tests in prompt.test.ts to
intermittently exceed their 3000ms per-test deadline under the parallel
validation harness (turbo typecheck+lint+test across all packages):

1. The dead-URL provider config (localhost:1) fell through to the AI SDK's
   default maxRetries:2, triggering 3× retryWithExponentialBackoff attempts
   on ConnectionRefused and burning ~6s+ in the worst case. Fixed by adding
   maxRetries:0 to the cfg.provider.test.options block so refused connections
   fail immediately.

2. Even without the retry spike the cancel tests take ~2s each in isolation
   (layer setup + TestLLMServer), leaving only ~1s margin against the 3000ms
   budget. Any event-loop contention under parallel load pushes them over.
   Raised all 9 budgets from 3_000 to 15_000ms — still tight enough to catch
   a real hang (no legitimate cancel/loop test should take >1s), but immune
   to parallel-load jitter.

Full prompt.test.ts: 60 pass / 1 skip / 0 fail. Cancel suite in isolation:
11/11 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r, task_status, worktree teardown, bounded output

1a+1b: flag-gated (DEEPAGENT_CODE_SUBAGENT_TIMEOUT_MS) timeout+takeover as one
atomic unit — timed-out/crashed attempts are cancelled, their worktree is
recycled, and a brand-new child session respawns from the same fork base
(single-driver), bounded by DEEPAGENT_CODE_SUBAGENT_TAKEOVER_LIMIT (default 2);
default path is byte-identical when the flag is off.
1c: task_status read-only tool listing this session's dispatched subagents.
1d: worktree teardown after markFinished (safeRemove fail-closed; force only
for takeover recycling).
1e: renderOutput maxChars bound (DEEPAGENT_CODE_SUBAGENT_OUTPUT_MAX_CHARS;
undefined = full text as before).
Tests: 9 takeover + 2 task_status + 24 existing task tests all green.
…+ reharvest dedupe

Third recurrence of the plan-gate/bash-reliability failure. A 4-subagent audit
plus a 68-session empirical census (677 hard blocks across 49/68 sessions;
172/188 "Plan gate" banners rode on exit-0 successes; 300 exit-0 results carried
failure-shaped text; 229 reasoning blocks re-litigating fake failures) showed the
deadlock was never only the stale latch that prior fixes kept neutering. Three
compounding root causes:

1. U9 per-step-binding hard block was the live deadlock (hooks.ts): it blocked
   mutating tools when no plan step was active, its grace release was non-sticky
   (reset on every pass -> block-block-block-pass, ~75% denied), and it lacked the
   planExists guard stopHookGate has (blocked runs that never made a plan).
   planGate is now WARN-ONLY at the tool call; plan discipline is a nudge and is
   enforced only at finalization. Added planExists guard so no-plan runs are not
   nagged. tools.ts passes planExists; the "block" branch is kept only as a
   defensive fail-closed path for future safety hooks.

2. ShellTool emitted no exit marker in model-facing text (shell.ts): the exit code
   lived only in metadata.exit, which is not serialized into the transcript, so the
   model and extractValidationResults had to guess pass/fail and defaulted to FAIL.
   Now appends a ground-truth `exit code: N` (or `null (terminated)`) as the last
   line. extractValidationResults reads the last exit trailer as authoritative and
   only falls back to text (requiring a positive FAIL signal, not absence-of-PASS)
   when no trailer exists.

3. Stale evidence re-harvest (request.ts + round-state.ts): validationFingerprint
   now keys on command+exit_code only (was command+exit+output, defeated by
   volatile durations/timestamps). Dedupe moved to the single append site
   addCandidate so both the request-prep and micro-round driver paths are covered.

Verified: core deepagent 764/0, deepagent-code session 633/0, gate suites 78/0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire Kimi/Moonshot in as two official providers, mirroring the Zhipu setup:
- kimi-for-coding  api.kimi.com/coding/v1 (@ai-sdk/anthropic; /v1 mandatory,
  SDK appends /messages — verified live: 200 w/ v1, 404 without)
- moonshotai-cn    api.moonshot.cn/v1     (@ai-sdk/openai-compatible; probed
  401 on /chat/completions, 404 on /messages = OpenAI-only)

Both faces resolve endpoint/protocol/models from the models.dev catalog and
their icons already ship in the sprite sheet, so registering the ids in
OFFICIAL_PROVIDER_IDS is enough to make them recommended-list + auth-keyed.

transform.ts Moonshot schema sanitizer now also covers moonshotai-cn and
kimi-for-coding (its short ids k2p7/k3/highspeed don't contain "kimi");
catalog-spec normalizeModelID strips kimi/moonshotai vendor prefixes.

Also standardize official-provider connect-dialog notes: google had a note
string but was never wired into PROVIDER_NOTES; xai (grok) and the zhipuai/
zai coding-plan faces had no note at all. Add en+zh strings and matchers in
both settings-providers.tsx and settings-v2/providers.tsx.

Verified: tsgo clean (core/deepagent-code/app); deepagent-code provider tests
383 pass (1 pre-existing azure/gpt-5 failure unrelated); app unit 579 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
三个问题一并落地(设计见 docs/app-dock-panel-redesign.md,本地):

#1 侧栏可开终端:终端/调试控制台成为「可移动 dock 面板」,可停靠在
   底栏或右侧面板。侧栏 rail 新增 terminal/debug-console 两项(仅当其
   location=side 时出现);header 终端按钮 + terminal.toggle/open 命令
   改为 location-aware,按当前停靠位置开合。

#2 分界线清晰度:去掉底栏面板根的静态 border-t(与拖动条重叠、抢视觉
   的那条),底栏 ResizeHandle 改用常显的 VSCode 风格分界线
   (resize-handle--dock:常态 1px border-base,hover/active 增粗到 2px
   border-strong-base),成为唯一、最高优先级的可拖动线。

#3 底栏/侧栏功能分区:建立统一的 dock-location 模型(layout.dock,全局
   态)。可移动集合 = {terminal, debug-console};侧栏 11 个富面板维持
   侧栏原生。两处各加「移到侧栏 / 移到底栏」按钮。

实现:抽 terminal-view.tsx 为位置无关共享组件(DebugConsole /
TerminalPanes / TerminalActions / useTerminalLifecycle),底栏
TerminalPanel 与新 side-panel-terminal.tsx 复用同一份;PTY 是 workspace
级单例,同一时刻只在一处挂载,无双份状态。layout.tsx 加 dock store +
API,rightPanelMode 联合类型加 terminal/debug-console;无需数据迁移。

验证:app tsgo typecheck 通过;test:unit 579 pass / 0 fail。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Q5: quick-start now uses `deepagent-code run` (not bare positional)
Q6: npm install commented out (registry 404); install script points to deepagent.ltd
Q7/Q8: unified domain to deepagent.ltd and org handle to deepagent-ltd:
  - prompt/default.txt + anthropic.txt: lessweb→deepagent-ltd, deepagent-code.ai→deepagent.ltd
  - packages/deepagent-code/README.md: deepagent-code.ai→deepagent.ltd
  - containers/ Dockerfiles + build script: ghcr.io/anomalyco→ghcr.io/deepagent-ltd
  - script/publish.ts: lessweb→deepagent-ltd, anomalyco homebrew tap→deepagent-ltd
  - github/ README + action.yml: lessweb→deepagent-ltd
  - script/github/close-{issues,prs}.ts: lessweb→deepagent-ltd
  - nix/deepagent-code.nix: homepage deepagent-code.ai→deepagent.ltd
  Intentionally left alone: $schema URLs (live schema validation endpoints),
  api.deepagent-code.ai in github/index.ts (runtime API), infra/stage.ts,
  parsers-config.ts wasm URLs (external repos), app/dist/ (build artifacts),
  observability test fixtures, ghostty-web npm dep.
Q1: README badge corrected Desktop 1.4.1→1.4.2
Q10: SECURITY.md: supported line updated from `main` to `dev`/current release;
     V3.5 M-CRED forward-reference replaced with current V4.0+ status (macOS live,
     Linux/Windows fallback documented)
Q12: CHANGELOG: added entries for V3.5, V3.8, V3.9, V4.0, V4.0.3, V4.1, V4.0.4
Q13: packages/app/README.md: replaced Solid/pnpm starter boilerplate with real
     stack description + E2E testing section preserved
Q14: packages/web/README.md: replaced Starlight starter boilerplate with real
     Astro/Starlight docs site description

Also confirmed already done (no code change needed):
- I33-3: subagentIsWriteType already gates worktree isolation in task.ts spawnAttempt
- I33-5: --no-ext-diff --no-textconv already in safeGit (384ab04)
- S41-1: steer-hint already has bg-background-stronger opaque surface

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t gate (v4.0.4)

H32-1 — DurableKnowledgeStore shared-handle injection:
- DurableKnowledgeStore constructor now accepts an optional injected DocumentStore
  (sharedStore?). When supplied the shared instance is used directly, enabling CAS/SSOT
  participation. When omitted a new isolated DocumentStore is created as before (backward-
  compatible; all existing callers without a shared handle continue working unchanged).
- openProjectStore / openUserGlobalStore factory functions thread the optional shared handle
  through to the constructor.
- knowledge-source.configure(dir, sharedStore?) accepts and stores the optional shared
  DocumentStore; userGlobalStore() / projectStore() lazy-factories pass it to the factories.
  reset() clears it alongside baseDir.
  This is the correct injection seam: the gateway (agent-gateway.ts line 278) can now pass
  a shared DocumentStore when calling DeepAgentKnowledgeSource.configure, enabling full
  CAS/SSOT alignment with plan/session DocumentStore operations. The gateway does not yet
  pass a shared handle (knowledge roots are a distinct storage area from plan/session roots);
  the interface is the established foundation for that wiring.

F30-3 — ship-gate snapshot association:
- Added optional snapshotId field to DeepAgentPromotionInput schema (groups/deepagent.ts).
  Optional for backward compatibility; soft enforcement now (warning logged if absent).
  Hard requirement deferred to v4.0.6 once all callers are updated.
- promote handler (handlers/deepagent.ts) warns when snapshotId is absent, documenting the
  F30-3 intent and v4.0.6 hard-enforcement date. The warning is operator-visible so callers
  can be updated proactively.

tsc --noEmit clean on both packages/core and packages/deepagent-code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…age (v4.0.4)

Implements the two stub backends that reported available:false since v3.5:

libsecretBackend() — Linux Secret Service via secret-tool CLI (libsecret-tools):
- available(): linux platform check + secret-tool in PATH + daemon alive probe
  (exit 1=daemon up/not found → available; exit 2+=daemon absent → unavailable).
- put(): Bun.spawn with stdin pipe so secret never appears in process table.
- get()/remove(): secret-tool lookup/clear.
- Validated pattern: Ubuntu 22.04 + gnome-keyring; remote verify via SSH port 5070.

dpapiBackend() — Windows Credential Manager via PowerShell PasswordVault (WinRT):
- available(): probes Windows.Security.Credentials.PasswordVault in PowerShell.
- put/get/remove(): PasswordVault.Add/Retrieve/Remove (DPAPI-backed by OS).
- Not yet verified on Windows machine; available() returns false if WinRT absent
  so the 0600 file fallback activates safely on unverified environments.

Existing behaviour preserved: macOS unchanged, fileBackend fallback unchanged,
selectBackend() order unchanged, all existing tests use inMemoryBackend injection.
tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ffset, priority queue (v4.0.4)

K40-1 — Transport seam (deepagent-bus-transport.ts):
New BusTransport interface abstracts the storage primitives the Event Bus runs on:
persistEvent (idempotent), findEventsSince, registerGroup/unregisterGroup/groupsForType
(consumer-group registry), writeDelivery/readDelivery. The current SQLite+Drizzle
implementation already satisfies this contract; a future Redis/Kafka backend implements
the same interface. Contract tests can run against ANY Transport to verify T1-T4
invariants. This is the seam — the bus layer composes Transport + in-memory fan-out.

K40-2 — Persistent consumer group offset (deepagent-event-sql.ts + event-bus.ts):
New table deepagent_consumer_group (group_id PK, type_filter nullable, registered_at,
last_seen_at) + migration 20260719000000_deepagent_consumer_group.
- registerConsumerGroup(groupId, typeFilter?) — durable upsert; idempotent.
- unregisterConsumerGroup(groupId) — remove from durable registry.
- publish now calls groupsFor() which unions BOTH in-memory live groups AND
  DB-registered groups (deduped). An offline group that registered durably receives
  pending delivery rows and catches up via dueRetries + replay on reconnect.
- subscribe (grouped) updates last_seen_at on stream start/end so a future sweep can
  prune permanently-offline groups.
Previously a group was purely in-memory: it only existed during a live subscribe stream,
and an offline group received NO delivery rows. This closes the delivery gap entirely.

K40-3 — Priority queue dispatch (event-bus.ts):
New highPriorityLive PubSub alongside the existing live PubSub. publish now routes
critical/high events to BOTH channels; normal/low go to live only. subscribe for grouped
consumers gets a merged stream (Stream.merge(highPriorityLive, live)) so critical/high
events drain ahead of queued normal/low — true preemption for grouped delivery.
Anonymous subscribers keep the simple live path (they see everything already).
Previously critical only bypassed admission (tryPublish rate limit); dispatch order was
strictly FIFO. Now critical/high are pulled first when multiple events are queued.

190/190 tests pass. tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, workspace config API

Block 2 — Fork + PR 队列协作原语
- pr-queue.ts: FIFO 状态机 (draft→awaiting_review→approved→merging→merged/conflicted),
  单 parent 合并租约,重做上限 3
- pr-collaboration.ts: coordinator admit/commitWorker/mergeApproved;
  mergeApproved 新增 parent HEAD 基线比较(parent前进→review-needed),
  non-conflict failure 同样执行 abortMerge(commit 之前的漏修)
- review-contract.ts: senior-reviewer 赋写权 + commit 权,禁止 merge/task_spawn/queue_mutation
- task.ts: 自动写隔离回流从 Worktree.mergeBack(目标默认分支,语义错) 改为
  Git.mergeInto(parentDir, workerBranch);加 parentHead 基线比较和 abortMerge 清理
- 新增测试: pr-collaboration 3个实 Git 用例(happy path/parent前进/non-conflict failure)
  + pr-queue 状态机覆盖 + review-contract senior 权限断言

Block 3a — 订正陈旧 token 注释
- multi-agent-runtime.ts:396 注释曾声称 event turn runner "reports 0 today",
  P4.1 已接入真实 token,注释与代码矛盾;已更新为当前准确描述

Block 3b — 事件自主能力默认开启
- runtime-flags.ts: v4AgentPushEnabled/v4PanelAutoConvene/v4EventDrivenArchive 由
  bool(默认 OFF) 改为 stableOn(默认 ON);各自有次级防线确保安全
- bash.ts: git push 遇策略 deny 时不再硬报错,改为向 ApprovalQueue 投递
  AGENT_TASK_NEEDS_HUMAN 事件;队列不可用时降级为原 ToolFailure
- 新增 workspace config HTTP API: GET/PUT /workspace/:workspaceID/config/trusted-sources
  让 operator 通过 HTTP 而非直接写 DB 来管理每个 workspace 的可信事件来源
- 顺带修复 deepagent-event-sql.ts 中一处断行注释(语法错误)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ost registry

- terminal.tsx / terminal-panel.tsx / terminal-view.tsx: 终端面板支持底部/侧边栏切换,
  dock 分隔线样式优化
- session-side-panel.tsx / side-panel-terminal.tsx: 侧边栏终端集成
- use-session-commands.tsx: 终端面板命令接线
- panel-view-registry.ts: 面板视图注册表(新增)
- problems-panel.tsx / problems-helpers.ts: problems 面板(新增)
- problems-panel.test.ts: problems 面板测试
- session-header.tsx / session.tsx: 会话头部适配
- layout.tsx / layout-helpers.ts / layout.test.ts: 布局辅助层
- i18n/en.ts + zh.ts: 新增面板相关 i18n 词条
- e2e/regression/panel-hosts.spec.ts: 面板宿主回归测试(新增)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
--- round-context deep fix ---
session-state.ts: add suppressedFingerprints[] to SessionRunState + 5 helpers
(suppressFingerprint/unsuppress/isSuppressed/clear/get). Backfilled in
normalizeState. An acknowledged false-positive failure is no longer re-injected
every round — the model stops re-litigating the same stale evidence.

request.ts: filter suppressed fingerprints before recordValidation / reharvest
guard. Evicts stale suppressions when a command re-runs with a new exit_code so
real regressions are never silently swallowed. No-op when suppressedFingerprints
is empty (the common case).

--- K40-4: real backpressure backlog depth ---
deepagent-event-bus.ts: add pendingDeliveryCount(workspaceID) to Interface +
implementation (JOIN deepagent_event on workspace_id, count pending deliveries).
event-dispatcher.ts: wire true DB-backed queue depth replacing in-flight approx.
v4-event-runtime.ts: pass pendingDeliveryCount with maxQueueDepth:500.

--- O40-N3: quiet-hours for tick path ---
goal-tick-consumer.ts: resolveTickQuietHours() mirrors event-dispatcher.ts math;
when workspace is in quiet window the delivery is ACKed (no DLQ cost) and a fiber
sleeps until window-end then re-publishes via resumeTickCommand (chain self-heals).
goal-manager.ts: seed tickCommand carries workspaceID for quiet-hours resolution.

--- goal.tick W2+W3 (event-driven tick chain) ---
runtime-flags.ts: add v4GoalTickEventDriven bool flag (default OFF, independent
of v4MultiAgentRuntime so the tick chain can be tested without the full daemon
stack).
goal-manager.ts: start() and resume() OR the two flags; comment updated to reflect
dual-flag activation.
goal-tick-consumer.ts: flag guard added; consumer registers in v4-event-runtime
under anyV4DaemonEnabled || v4GoalTickEventDriven.

--- H32-2: LearningWorker isolated reviewer injection point ---
background-learning.ts: LearningWorkerInput gains optional reviewer injection.
LearningWorker.run() / LearningQueue.drain() / drainNow() made async. Tests
updated to match async signatures.

--- G31-3: manageStream general-mode minimal audit ---
agent-gateway.ts: GeneralAuditEntry type + generalAuditState map; manageStream
taps finish events on the general-mode branch to accumulate turnCount /
totalInputTokens / totalOutputTokens. getGeneralAudit() exported.

--- I33-2: tool_failed → markPlanStale ---
tools.ts: tapError on item.execute marks the session's plan latch stale with
reason 'tool_failed' whenever a tool execution throws. Gate is warn-only (v4.0.4
P1) so this is a soft nudge, never a denial.

--- I33-4: bounded subagent output (already wired) ---
Confirmed already implemented: renderOutput() truncates at maxChars with a
truncation pointer; inject() passes flags.subagentOutputMaxChars.
subagentOutputMaxChars flag at runtime-flags.ts:59.

tsc --noEmit clean on packages/core and packages/deepagent-code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- goal-status-publisher.ts: add v4GoalTickEventDriven to GoalStatusPublisherDeps
- goal-tick-consumer.ts: replace Effect.fork → Effect.forkScoped (matches codebase
  convention; Effect.fork not in API surface); cast forked fiber as Effect<unknown>
  to satisfy the narrower return type required by the handle path

tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…GoalStatusPublisher call

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Hey! Your PR title Core v4.0.4 doesn't follow conventional commit format.

Please update it to start with one of:

  • feat: or feat(scope): new feature
  • fix: or fix(scope): bug fix
  • docs: or docs(scope): documentation changes
  • chore: or chore(scope): maintenance tasks
  • refactor: or refactor(scope): code refactoring
  • test: or test(scope): adding or updating tests

Where scope is the package name (e.g., app, desktop, deepagent-code).

See CONTRIBUTING.md for details.

@deepagent-ai
deepagent-ai merged commit d26e38b into dev Jul 19, 2026
7 of 21 checks passed
@github-actions

Copy link
Copy Markdown

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • No "Type of change" checkbox is checked. Please select at least one.
  • No issue referenced. Please add Closes #<number> linking to the relevant issue.
  • "How did you verify your code works?" section is empty. Please explain how you tested.
  • Not all checklist items are checked. Please confirm you have tested locally and have not included unrelated changes.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@deepagent-ai
deepagent-ai deleted the core-v4.0-beta branch July 19, 2026 14:37
lxrzlyr pushed a commit that referenced this pull request Jul 20, 2026
Closes #

- [ ] Bug fix
- [ ] New feature
- [ ] Refactor / code improvement
- [ ] Documentation

Please provide a description of the issue, the changes you made to fix
it, and why they work. It is expected that you understand why your
changes work and if you do not understand why at least say as much so a
maintainer knows how much to value the PR.

**If you paste a large clearly AI generated description here your PR may
be IGNORED or CLOSED!**

_If this is a UI change, please include a screenshot or recording._

- [ ] I have tested my changes locally
- [ ] I have not included unrelated changes in this PR

_If you do not follow this template your PR will be automatically
rejected._

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant