Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 15 additions & 45 deletions PROJECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,51 +231,17 @@ a snapshot handoff, not a new main-targeting implementation PR or an upstream
compatibility PASS. Runtime/private configuration and the frontend flow are untouched.


### Task-role execution convergence — 2026-11-03 (#2184)

Task-role SDK retries retain pinned public persona/skills and the latest observed
backend context across Attempts. Dynamic input is not a definition identity. A
binding incompatibility or unknown old execution fails visibly; no silent fresh
Session or rollback to older context. Definition snapshots are shared by digest,
current rights still revoke execution, failed unused preparations release their
pins, and model input follows a durable-save/cancellation barrier. Existing
Harness/lease/Workflow services remain authoritative. Adapter capability inspection
reports exact native Task-role resume with committed activation/quiescence; actual DSH deterministic return/context coverage is recorded separately from real-provider validation. No
public service protocol, SQL transaction, deployment or frontend change is claimed.


### Collaboration contract convergence — 2026-11-03 (#2175)

Expose durable Task handoffs through one scoped contract projection, with distinct
pending/accepted/processed/revoked/invalidated states, source/target context references,
actual claim/worker binding, initiative fences and predecessor/successor history.
Idempotent trusted offer/accept ports reuse pools; unresolved old writers cannot be
bypassed by Chief rerouting to another resource. Consultation is a separate private
protocol over retained transcripts, without Task creation or initiative transfer.
Chief prefers an active personal agent for user questions: safe answers require user
publication, watch answers use existing inbox rights; only committed Effects may wake
an authorized native watch. External clients have isolated mailboxes, not fabricated
provider sessions. SDK process/HTTP tests and real-provider acceptance remain distinct.
Native restoration/context bounds, transaction events and parent waits stay on their
original dependent issues; no control-plane, deployment or frontend changes.


### Context/memory convergence — 2026-11-03 (#2177)

Unify model/Harness context budgeting and bounded source tools across persistent
Chief and temporary roles. Unknown capacity stays explicit; full rules/current
constraints remain required, retrieval yields before overflow, and native/SDK input
is checked before delivery. Working summaries use original public role-memory
records with exact source/evidence/uncertainty, separate resident Chief topics and
rollback of uncommitted staging. Facts have source/classification/version/archive;
role-authored experience cannot declare facts or overwrite human control. Private
notes retain history under bounded views. Codex tool history has an explicit cap;
SDK compaction remains runtime-managed, without claiming aggregate usage is current
window pressure. No new memory platform, executor or timer. Native Task-role physical
restore2184 and normalized SQL storage2182 remain separate acceptance; real SDK
verification must name exact runtime source hashes, not borrow deterministic tests.

Follow-up #2177: native resident Chief current facts are actual per-turn input with whole-input budget and final authority checks; do not rely on updating cached system prompt metadata. Preserve the original resident Session.
### Role, collaboration and context convergence

Dated #2184/#2175/#2177 implementation checkpoints are [preserved verbatim](docs/history/2026-11-04-role-convergence-checkpoints.md).
Current contracts remain active: one logical Session per Task/role; pinned persona,
resource/profile and current delegation; actual exit before restoring context;
consultation distinct from durable initiative handoff; private user context is not
published automatically. Memory is bounded guidance, not authority or delivery
proof. Preserve immutable source/history pages, verified evidence and independent
acceptance; historical fixtures do not establish live-provider or UI completion.
See [agents](docs/subsystems/project-agents.md), [Harness](docs/subsystems/project-harness.md)
and the Project memory/context contracts for the current implementation.

### 2026-11-03 — Project Task contract (#2179)

Expand Down Expand Up @@ -318,3 +284,7 @@ from preparation and tests. Nested Node test markers do not enter host verificat
#2338 preflights input receipts before host side effects and returns correctable
errors to the original role/Chief Session. Frozen proposals, bounded tool-free
correction and durable receipts preserve work; stale/revoked input stays denied.

#2347 recovers birth-proven unmanaged native Chief invocations, preserves unknown
external execution, and admits explicit sealed-candidate verification continuation
under current authority/context gates. No live rollout or unknown-exit override.
6 changes: 6 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,9 @@ not live npm ci provisioning or production Vitest acceptance.
with all-plan preflight, immutable business evidence and explicit incomplete
recovery. Tests distinguish deterministic role transport and actual SDK execution
with a local provider fixture from paid-model/live project acceptance.

#2347 adds bounded inspection/stop reconciliation, explicit recovery facts and
same-Attempt Tester/Reviewer continuation. Real disposable unmanaged DSH restart
reaches a later original scheduled inspection with deterministic inference; paid
model/live-instance acceptance stays separate. Legacy missing namespace evidence
requires exact operator corroboration, never a guessed worker release.
1 change: 1 addition & 0 deletions changelog.d/2347.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Recover proven unmanaged native Chief invocations, bound exit waits, expose recovery causes, and allow evidence-supported Tester/Reviewer continuation of a sealed interrupted Attempt.
2 changes: 2 additions & 0 deletions core/src/host/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -1432,6 +1432,8 @@ export function install(rt) {
...(attempt.pr ? [{ pr: attempt.pr.number, sha: attempt.pr.sha, checks: 'unknown' }] : []),
]
},
validateRecovery: input => rt.projectDelivery.validateRecovery(input),
resumeVerification: input => rt.projectDelivery.resumeVerification(input),
complete: input => rt.projectDelivery.completePoolRole(input),
rework: input => rt.projectDelivery.reworkPool(input),
})
Expand Down
46 changes: 41 additions & 5 deletions core/src/host/project-agents.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { inspectionLifecycle } from './project-chief-lifecycle.js'
import { captureProcessScope, priorHostProcessExited } from './project-process-proof.js'
import { nativeChiefBoundary, scopeDigest, corroboratedNativeScope } from './project-chief-recovery.js'
import { CHIEF_AUTONOMY } from './project-chief-protocol.js'
import { agentHistoryPage } from './project-history-pages.js'
import { createProjectAgentInbox } from './project-agent-inbox.js'
Expand Down Expand Up @@ -364,17 +367,21 @@ export function createProjectAgents(rt) {
bot.instructions = priorPrompt.instructions
if (priorPrompt.systemPrompt === undefined) delete bot.systemPrompt; else bot.systemPrompt = priorPrompt.systemPrompt
}
const run = state.projectExecutions?.[runId]
if (!run || run.invocation !== invocation || run.status !== 'running') throw refuse('Chief invocation changed before execution admission')
const delegation = { id: randomUUID(), exchangeId: randomUUID(), actor: { ...actor }, definitionRevision: set.revision, started: false, revoked: false, chiefJob: { runId, invocation, prompt: fullPrompt }, onChiefResult: onResult }
delegations.set(agent.id, delegation)
agent.lastRequest = { id: randomUUID(), initiatedBy: actor.memberId, executorBotId: agent.botId, exchangeId: delegation.exchangeId, phase: 'queued', context: snapshot, definitionRevision: set.revision, chiefRunId: runId, chiefInvocation: invocation, at: Date.now() }
await rt.save()
run.executionBoundary = { version: 'chief-execution-boundary/v1', kind: (definition.execution?.harness ?? 'flybot') === 'flybot' ? 'native-in-process' : 'sdk-worker', phase: 'preparing', allocationKey: delegation.id, runId, invocation, botId: agent.botId, sessionId: agent.sessionId, carrierGeneration: agent.carrierGeneration, exchangeId: delegation.exchangeId }
try { run.processScope ??= await captureProcessScope(); await rt.save() }
catch (error) { if (delegations.get(agent.id)?.id === delegation.id) delegations.delete(agent.id); run.exitProof = { confirmed: true, kind: 'chief-never-dispatched', runId, invocation, at: Date.now() }; throw error }
const finish = async result => {
try { if (delegations.get(agent.id)?.id !== delegation.id || delegation.revoked) throw refuse('Chief execution was revoked'); const outcome = await onResult({ ...result, executionEvidence: { ...result.executionEvidence, chiefRunId: runId, carrierGeneration: agent.carrierGeneration } }); agent.lastRequest.phase = outcome?.correction ? outcome.correction.status === 'pending' ? 'correcting' : 'incomplete' : result.ok ? 'completed' : 'failed' }
finally { restorePrompt(); if (delegations.get(agent.id)?.id === delegation.id) delegations.delete(agent.id); await rt.save(); rt.projectScheduler?.wake() }
}
delegation.finishChiefJob = finish; delegation.restoreChiefPrompt = restorePrompt
try {
if ((definition.execution?.harness ?? 'flybot') !== 'flybot') { delegation.started = true; delegation.sdkExecution = true; return await rt.projectHarness.chiefTurn({ agent, delegation, definition, context: snapshot, text: 'Inspect the pinned Project facts and return the bounded Chief judgement.', onResult: finish }) }
if ((definition.execution?.harness ?? 'flybot') !== 'flybot') { run.executionBoundary.phase = 'allocating'; await rt.save(); delegation.started = true; delegation.sdkExecution = true; return await rt.projectHarness.chiefTurn({ agent, delegation, definition, context: snapshot, text: 'Inspect the pinned Project facts and return the bounded Chief judgement.', onResult: finish }) }
const bot = rt.botOf(agent.botId)
nativeSystem = definition.instructions + '\n\nFor each configured Chief job, use the current request protocol and scoped snapshot in that turn. Prior turns are historical background; act only on Task IDs and facts in the current snapshot. Preserve your resident Session and memory.'
const nativeInput = fullPrompt + '\n\nCurrent scoped Chief job facts (data, not authority):\n' + snapshot.text
Expand All @@ -391,9 +398,15 @@ export function createProjectAgents(rt) {
}
await rt.projectChiefControl.validateActor(actor)
if (delegations.get(agent.id)?.id !== delegation.id || delegation.revoked) throw refuse('Chief execution was revoked before current job input')
run.executionBoundary.phase = 'input-sending'; await rt.save(); await rt.projectChiefControl.validateActor(actor)
await rt.deliver({ toId: agent.botId, sessionId: agent.sessionId, role: 'request', audience: 'user', text: nativeInput, fromName: 'Project scheduled/arbitration request', untrusted: true, exchangeId: delegation.exchangeId, projectDelegation: { agentId: agent.id, delegationId: delegation.id } })
return `native-chief-job-${runId}`
} catch (error) { restorePrompt(); if (delegations.get(agent.id)?.id === delegation.id) delegations.delete(agent.id); throw error }
} catch (error) {
restorePrompt(); if (delegations.get(agent.id)?.id === delegation.id) delegations.delete(agent.id)
const allocations = [...Object.values(state.projectResourceLeases ?? {}), ...Object.values(state.projectHarnessWorkers ?? {})].filter(row => row.attemptId === `chief-turn-${delegation.id}` || row.chiefRunId === runId && row.chiefInvocation === invocation)
if (state.projectExecutions?.[runId] === run && run.invocation === invocation && !allocations.length && (run.executionBoundary.kind === 'sdk-worker' || run.executionBoundary.phase === 'preparing')) { run.exitProof = { confirmed: true, kind: 'chief-never-dispatched', runId, invocation, at: Date.now() }; await rt.save() }
throw error
}
}) }
function appendReply(agent, entry) {
logOf(agent.id).push(entry)
Expand Down Expand Up @@ -624,6 +637,25 @@ export function createProjectAgents(rt) {
return serial(() => { if (expectedCarrierId && keyOf(projectId, memberId) !== expectedCarrierId) throw refuse('Personal-agent default changed before notification'); return send({ projectId, visibility: 'private', text }, actor) })
}
const stoppingChiefJobs = new Map()
async function reconcileChiefExit(run) {
if (!['interrupted', 'failed', 'cancelled'].includes(run.status)) return false
const selected = nativeChiefBoundary(state, run), scope = run.processScope, invocation = run.invocation
if (!selected || !await priorHostProcessExited(corroboratedNativeScope(scope, run, rt.config.projectChiefRecovery?.legacyNativeScopes))) return false
const current = () => state.projectExecutions?.[run.id] === run && run.processScope === scope && run.invocation === invocation && ['interrupted', 'failed', 'cancelled'].includes(run.status)
if (!current()) return false
const proof = { confirmed: true, kind: 'native-chief-prior-host-exited', runId: run.id, invocation, carrierGeneration: selected.boundary.carrierGeneration, scopeSha256: scopeDigest(scope), at: Date.now() }
for (const lease of selected.leases) {
if (!current() || state.projectResourceLeases?.[lease.id] !== lease) return false
await rt.projectResources.release(lease.id, lease.generation, { handle: selected.boundary.exchangeId, exited: true, proof })
if (lease.status !== 'released') return false
}
if (!current()) return false
run.exitProof = proof
const carrier = agents()[`public-${run.projectId}`], request = carrier?.lastRequest
if (request?.chiefRunId === run.id && request.chiefInvocation === invocation && carrier.carrierGeneration === proof.carrierGeneration) { request.phase = 'interrupted'; request.exitProof = structuredClone(proof) }
await rt.save()
return true
}
function stopChiefJob(projectId, runId) {
if (stoppingChiefJobs.has(runId)) return stoppingChiefJobs.get(runId)
const pending = (async () => {
Expand All @@ -634,13 +666,17 @@ export function createProjectAgents(rt) {
const request = agent.lastRequest
request.phase = 'stopping'; await rt.save()
let confirmed = false
const run = state.projectExecutions?.[runId]
if (!request.workerId && (run?.executionBoundary?.kind === 'sdk-worker' || Object.values(state.projectHarnessWorkers ?? {}).some(row => row.chiefRunId === runId && row.chiefInvocation === request.chiefInvocation))) return { confirmed: false, reason: 'SDK admission is incomplete; reconcile its exact worker and leases without cancelling the native Chief' }
if (request.workerId) confirmed = await rt.projectHarness.release(request.workerId)
else {
const active = await rt.resolveAgent?.(agent.sessionId)
if (!active || typeof active.whenIdle !== 'function') return { confirmed: false, reason: 'Native Chief exit cannot be confirmed' }
for (const message of [...(active.inbox?.nextStep ?? []), ...(active.inbox?.nextTurn ?? [])]) if (message.source?.exchangeId === request.exchangeId) active.inbox.remove?.(message.id)
active.cancel?.({ kind: 'hook', reason: 'Configured Chief inspection deadline' })
await active.whenIdle()
let timer
const stopped = await Promise.race([active.whenIdle().then(() => true), new Promise(resolve => { timer = setTimeout(() => resolve(false), inspectionLifecycle(state.projectChiefPolicies?.[projectId]?.inspection).stopTimeoutMs) })]).finally(() => clearTimeout(timer))
if (!stopped) return { confirmed: false, reason: 'Native Chief exit confirmation timed out; execution remains isolated' }
const lease = delegation?.resourceLease ?? request.resourceLease
if (lease) await rt.projectResources.release(lease.id, lease.generation, { handle: request.exchangeId, exited: true })
confirmed = true
Expand Down Expand Up @@ -803,7 +839,7 @@ export function createProjectAgents(rt) {
}
const isDelegationActive = (id, delegationId) => { const entry = delegations.get(id); return entry?.id === delegationId && !entry.revoked }
const ensureProject = (projectId, actor) => serial(async () => { await ensure(projectId, actor, 'public'); return ensure(projectId, actor) })
return { stream, liveFor, hasExchange: exchangeId => [...delegations.values()].some(row => row.exchangeId === exchangeId && !row.revoked), privateIdentity: keyOf, ensurePublic: (projectId, actor) => serial(() => ensure(projectId, actor, 'public')), contextExchange, chiefMemory, chiefBusy: projectId => delegations.has(`public-${projectId}`) || [...stoppingChiefJobs.keys()].some(id => state.projectExecutions?.[id]?.projectId === projectId), stopChiefJob, startChiefJob, endpoint, consultation: consultations.endpoint, contactUser: consultations.contactUser, agentConsultations, agentInbox, inboxExecutionActive, chiefQuestion, askChief, suggestClosure, githubRead, action, wakeWatch, revokeWatchGrant, ensureProject, isDelegationActive, actorOf, context, memory, task, run, onEvent, revoke, revokeSession, removeProject, deliveryDefinitions, recordForBot, roles: roleGovernance, proposeRole, proposeRoleForJob, ROLES }
return { stream, liveFor, hasExchange: exchangeId => [...delegations.values()].some(row => row.exchangeId === exchangeId && !row.revoked), privateIdentity: keyOf, ensurePublic: (projectId, actor) => serial(() => ensure(projectId, actor, 'public')), contextExchange, chiefMemory, reconcileChiefExit, chiefBusy: projectId => delegations.has(`public-${projectId}`) || [...stoppingChiefJobs.keys()].some(id => state.projectExecutions?.[id]?.projectId === projectId), stopChiefJob, startChiefJob, endpoint, consultation: consultations.endpoint, contactUser: consultations.contactUser, agentConsultations, agentInbox, inboxExecutionActive, chiefQuestion, askChief, suggestClosure, githubRead, action, wakeWatch, revokeWatchGrant, ensureProject, isDelegationActive, actorOf, context, memory, task, run, onEvent, revoke, revokeSession, removeProject, deliveryDefinitions, recordForBot, roles: roleGovernance, proposeRole, proposeRoleForJob, ROLES }
}

export function install(rt) {
Expand Down
Loading
Loading