Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
066b11a
Add Project repository associations and non-destructive Owner closure
zy95-12 Nov 2, 2026
61e0abc
Separate personal-agent identities from Project participations
zy95-12 Nov 2, 2026
6eeba41
Authenticate scoped personal-agent connections with existing delegati…
zy95-12 Nov 2, 2026
b4fa8de
Preserve Task-role definitions and latest SDK session across retries
zy95-12 Nov 2, 2026
439a41c
Expose durable handoff contracts and private consultation flows
zy95-12 Nov 2, 2026
dba40f8
Require approved durable Project role changes
zy95-12 Nov 3, 2026
3e90554
Integrate approved role governance with personal agent identities
zy95-12 Nov 3, 2026
3629495
Integrate approved roles with current personal agent authorization
zy95-12 Nov 3, 2026
cbab78a
Merge branch 'feat/2183-agent-auth' into feat/2184-execution-lifecycle
zy95-12 Nov 3, 2026
a11696d
Require native execution quiescence before releasing Task context
zy95-12 Nov 3, 2026
f404153
Execute custom Project roles with pinned capability policies
zy95-12 Nov 3, 2026
32f7941
Activate exact native context before resuming pool handoffs
zy95-12 Nov 3, 2026
3f7fe22
Require consistent native exit proof across pool and supervisor recovery
zy95-12 Nov 3, 2026
29f0e02
Revalidate retained role execution and stop revoked claims through ex…
zy95-12 Nov 3, 2026
36210b9
Compose current execution and role governance with durable handoff co…
zy95-12 Nov 3, 2026
d9318bf
Sync delivery integration ancestry for #2210
zy95-12 Nov 3, 2026
3014b90
Sync delivery integration ancestry for #2210
zy95-12 Nov 3, 2026
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
4 changes: 4 additions & 0 deletions LESSONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -715,3 +715,7 @@ facts without auto-resuming an old worker or confusing a reused PID with ownersh
### 2026-11-03 — Preserve context and recheck current authority separately

Seen 1× (native pre-step and existing scheduler regressions). Admission-time rights do not cover role/resource revocation during a long execution. Reuse one live authority check and the existing pool stop/acknowledgement contract. Keep frozen persona/model for continuation, permit only the retained active retired-role claim, and do not await an Agent's idle inside its own pre-step hook. A resumed physical Session also exists in history: select its current live carrier for authorization. Recheck the actual human sender after awaiting the initiating authority.

### 2026-11-03 — Revalidate handoff targets after exit inspection

Seen 1× (composed role governance/contract regression). Confirming an old source exit yields to concurrent governance. Re-read the committed destination before acquiring a new claim; retirement keeps the Task blocked instead of authorizing a stale role.
219 changes: 24 additions & 195 deletions PROJECT.md

Large diffs are not rendered by default.

37 changes: 19 additions & 18 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,24 +199,9 @@ outcome is recorded in [`decisions/`](decisions/README.md) with its evidence, pa
dissent. A stage moving from "not started" to "active" is itself a decision, not an edit alone.


### M7.2 — 2026-10-31

M7.1 PR #26 passed full verify and actual DSH context/lifecycle checks. M7.2 implementation on feat/m7-resource-pools adds resource capabilities/leases and real local+Docker execution probes; integration acceptance with SDKs remains in M7.5.


### M7.3 checkpoint — 2026-10-31

M7.2 PR #27 passed full verify and the opt-in actual local/Docker resource probe. M7.3 adapters are in progress; the implementation PR remains draft until remaining integration and real authorized SDK acceptance are complete; Chief now uses a host-validated scoped action bridge.


### M7.4 — 2026-10-31

M7.3 PR #28 passed full verify at 034d86c, remains draft pending authenticated acceptance. M7.4 implementation supplies Owner runtime configuration/probes/private PTY and strengthens positive worker-stop confirmation; M7.5 cross-resource delivery and final acceptance remain next.


### M7.5 — 2026-10-31

M7 implementation PRs: #26 role Sessions, #27 resource pools, #28 official SDK Harnesses, #29 Owner setup, #30 handoff/integration. Full verify passed #26–#29 tips; final #30 validation/report is recorded in its PR. Real authenticated SDK/browser mixed-resource acceptance remains pending dedicated authorized profiles; M8 continuous CI/review and M9 persistent dogfood operations remain later work.
Completed M7.2–M7.5 implementation checkpoints are retained verbatim in
[the historical record](docs/history/2026-11-03-completed-m7-checkpoints.md). Current
role/resource/Session contracts and outstanding acceptance remain below and in the active roadmap.

### M7 validation update — 2026-11-01

Expand Down Expand Up @@ -421,6 +406,22 @@ capabilities. Native Task-role context restoration, parent waits, transactional
and external command receipts remain dependent acceptance; no live-provider or deploy
claim from controlled process tests. README/API rewrite remains last (#2189).


### 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.

Native prior-host reconciliation now reuses verified supervisor process-group facts; native v2 registered-work/lineage and shared adapter-exit regressions guard against premature initiative transfer. In-flight current-rights abort and composed-stack acceptance remain active.

Canonical ongoing role/resource authority checks now reuse native step/SDK input and the existing scheduler + pool stop. Frozen role context remains stable; current revoked rights stop execution, with unknown exit held. Compose descendants and verify parent Task resume/API projections next.
1 change: 1 addition & 0 deletions changelog.d/2175.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Expose scoped persistent handoff contracts and separate consultation APIs. Route Chief questions to an authorized personal agent before the human, retain safe/watch approval boundaries, and fence downstream claims until old writers confirm exit. Revalidate current target roles after source-exit inspection while retaining native/SDK exit proofs and pinned role contexts.
4 changes: 2 additions & 2 deletions core/src/host/authz.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ export function install(rt) {
const ep = String(endpoint ?? '')

if (ep === 'platform-login') return rt.platformIdentityEnabled ? ALLOW : deny('bot/forbidden', 'Platform identity is not configured')
if (['project-development', 'project-rooms', 'project-room-create', 'project-room-read', 'project-room-message', 'project-room-attachment', 'project-room-stop', 'project-agent-stop', 'workbench-state', 'project-resources', 'project-resource-environment-check', 'project-execution-input', 'project-runtime-check', 'project-terminal-open', 'project-terminal-poll', 'project-terminal-input', 'project-terminal-resize', 'project-terminal-close', 'project-queue-cancel', 'project-queue-retry', 'project-task-pools', 'project-github-read', 'project-chief-question', 'project-agent-mode', 'project-scheduled-task', 'project-chief-configure', 'project-chief-state', 'project-chief-inspect', 'project-attempt-stop', 'project-credential-status', 'project-github-comment', 'project-agents', 'project-agent-message', 'project-agent-history', 'project-agent-memory-write', 'project-role-memory-write', 'project-agent-definition', 'project-roles', 'project-role-proposals', 'project-role-decision', 'project-agent-proposal-apply', 'project-knowledge-write', 'project-task-run', 'project-task-detail', 'project-task-accept', 'project-task-reject', 'project-attempt-log', 'project-attempt-cleanup', 'project-attempt-publication-retry', 'project-members', 'project-invite', 'project-join', 'project-leave', 'project-member-remove', 'project-quota', 'project-workflow'].includes(ep) && !rt.sharedProjectMode?.()) return deny('bot/forbidden', 'Project workbench is not enabled')
if (['project-development', 'project-rooms', 'project-room-create', 'project-room-read', 'project-room-message', 'project-room-attachment', 'project-room-stop', 'project-agent-stop', 'workbench-state', 'project-resources', 'project-resource-environment-check', 'project-execution-input', 'project-runtime-check', 'project-terminal-open', 'project-terminal-poll', 'project-terminal-input', 'project-terminal-resize', 'project-terminal-close', 'project-queue-cancel', 'project-queue-retry', 'project-task-pools', 'project-github-read', 'project-consultation-reply', 'project-consultation-ask', 'project-consultation-get', 'project-consultation-list', 'project-handoff-get', 'project-handoff-list', 'project-chief-question', 'project-agent-mode', 'project-scheduled-task', 'project-chief-configure', 'project-chief-state', 'project-chief-inspect', 'project-attempt-stop', 'project-credential-status', 'project-github-comment', 'project-agents', 'project-agent-message', 'project-agent-history', 'project-agent-memory-write', 'project-role-memory-write', 'project-agent-definition', 'project-roles', 'project-role-proposals', 'project-role-decision', 'project-agent-proposal-apply', 'project-knowledge-write', 'project-task-run', 'project-task-detail', 'project-task-accept', 'project-task-reject', 'project-attempt-log', 'project-attempt-cleanup', 'project-attempt-publication-retry', 'project-members', 'project-invite', 'project-join', 'project-leave', 'project-member-remove', 'project-quota', 'project-workflow'].includes(ep) && !rt.sharedProjectMode?.()) return deny('bot/forbidden', 'Project workbench is not enabled')
if (rt.platformIdentityEnabled && ['auth-invite', 'auth-redeem', 'auth-remove-member'].includes(ep)) return deny('bot/platform-auth', 'Use control-plane account authentication')
if (!actor) {
if (NO_ACTOR_ALLOWED.has(ep)) return ALLOW
Expand All @@ -253,7 +253,7 @@ export function install(rt) {
if (rt.sharedProjectMode?.()) {
const agentSeats = [...(Array.isArray(body?.members) ? body.members : []), ...(Array.isArray(body?.add) ? body.add : []), ...(Array.isArray(body?.participants) ? body.participants.filter(row => row?.kind === 'bot').map(row => row.id) : [])]
if (['create-room', 'update-room'].includes(ep) && agentSeats.some(id => state.bots?.[id]?.projectAgent)) return deny('bot/forbidden', 'Project agents cannot join group chats')
if (['project-development', 'project-rooms', 'project-room-create', 'project-room-read', 'project-room-message', 'project-room-attachment', 'project-room-stop', 'project-agent-stop', 'workbench-state', 'project-resources', 'project-resource-environment-check', 'project-execution-input', 'project-runtime-check', 'project-terminal-open', 'project-terminal-poll', 'project-terminal-input', 'project-terminal-resize', 'project-terminal-close', 'project-queue-cancel', 'project-queue-retry', 'project-task-pools', 'project-github-read', 'project-chief-question', 'project-agent-mode', 'project-scheduled-task', 'project-chief-configure', 'project-chief-state', 'project-chief-inspect', 'project-attempt-stop', 'project-credential-status', 'project-github-comment', 'project-agents', 'project-agent-message', 'project-agent-history', 'project-agent-memory-write', 'project-role-memory-write', 'project-agent-definition', 'project-roles', 'project-role-proposals', 'project-role-decision', 'project-agent-proposal-apply', 'project-knowledge-write', 'project-task-run', 'project-task-detail', 'project-task-accept', 'project-task-reject', 'project-attempt-log', 'project-attempt-cleanup', 'project-attempt-publication-retry', 'project-members', 'project-invite', 'project-join', 'project-leave', 'project-member-remove', 'project-quota', 'project-workflow'].includes(ep)) return ALLOW
if (['project-development', 'project-rooms', 'project-room-create', 'project-room-read', 'project-room-message', 'project-room-attachment', 'project-room-stop', 'project-agent-stop', 'workbench-state', 'project-resources', 'project-resource-environment-check', 'project-execution-input', 'project-runtime-check', 'project-terminal-open', 'project-terminal-poll', 'project-terminal-input', 'project-terminal-resize', 'project-terminal-close', 'project-queue-cancel', 'project-queue-retry', 'project-task-pools', 'project-github-read', 'project-consultation-reply', 'project-consultation-ask', 'project-consultation-get', 'project-consultation-list', 'project-handoff-get', 'project-handoff-list', 'project-chief-question', 'project-agent-mode', 'project-scheduled-task', 'project-chief-configure', 'project-chief-state', 'project-chief-inspect', 'project-attempt-stop', 'project-credential-status', 'project-github-comment', 'project-agents', 'project-agent-message', 'project-agent-history', 'project-agent-memory-write', 'project-role-memory-write', 'project-agent-definition', 'project-roles', 'project-role-proposals', 'project-role-decision', 'project-agent-proposal-apply', 'project-knowledge-write', 'project-task-run', 'project-task-detail', 'project-task-accept', 'project-task-reject', 'project-attempt-log', 'project-attempt-cleanup', 'project-attempt-publication-retry', 'project-members', 'project-invite', 'project-join', 'project-leave', 'project-member-remove', 'project-quota', 'project-workflow'].includes(ep)) return ALLOW
if (ep.startsWith('flybot/') && !['flybot/bots', 'flybot/changes', 'flybot/decide'].includes(ep)) return deny('bot/project-required', 'Use a Project-scoped endpoint')
if (['workflows', 'workflow-save', 'usage'].includes(ep)) return deny('bot/project-required', 'Use a Project-scoped endpoint')
}
Expand Down
8 changes: 7 additions & 1 deletion core/src/host/channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ export function install(rt) {
// What a host that only reads the team may still answer.
// Updates change the dsh profile, not the team, so a reading host may run them too.
// auth-whoami is a read of the already-resolved actor; invite/redeem/revoke/remove-member write.
const READS = new Set(['project-roles', 'project-role-proposals', 'personal-agent-token-list', 'personal-agent-history', 'personal-agent-read', 'personal-agent-list', 'personal-agent-participations', 'project-repositories', 'project-rooms', 'project-room-read', 'project-task-pools', 'project-chief-state', 'project-github-read', 'auth-mode', 'workbench-state', 'project-resources', 'projects', 'project-read', 'project-board', 'project-members', 'project-task-detail', 'project-attempt-log', 'project-detail', 'state', 'models', 'efforts', 'earlier', 'room-progress', 'room-history', 'exchange', 'usage', 'schedules', 'memory-view', 'browser-wait', 'browser-result', 'diagnostics', 'diagnostics-log', 'diagnostics-clear', 'update-check', 'update-install', 'dsh-update-install', 'repo-stars', 'approval-answer', 'auth-whoami', 'workflows'])
const READS = new Set(['project-roles', 'project-role-proposals', 'personal-agent-token-list', 'personal-agent-history', 'personal-agent-read', 'personal-agent-list', 'personal-agent-participations', 'project-repositories', 'project-rooms', 'project-room-read', 'project-handoff-get', 'project-handoff-list', 'project-consultation-get', 'project-consultation-list', 'project-task-pools', 'project-chief-state', 'project-github-read', 'auth-mode', 'workbench-state', 'project-resources', 'projects', 'project-read', 'project-board', 'project-members', 'project-task-detail', 'project-attempt-log', 'project-detail', 'state', 'models', 'efforts', 'earlier', 'room-progress', 'room-history', 'exchange', 'usage', 'schedules', 'memory-view', 'browser-wait', 'browser-result', 'diagnostics', 'diagnostics-log', 'diagnostics-clear', 'update-check', 'update-install', 'dsh-update-install', 'repo-stars', 'approval-answer', 'auth-whoami', 'workflows'])

// A chat reference chip names a Session whose snapshot rides to every recipient of
// the message, so the author must be able to read what they share (#53, #106).
Expand Down Expand Up @@ -845,6 +845,12 @@ export function install(rt) {
case 'project-close-confirm':
case 'project-development':
case 'project-task-pools':
case 'project-consultation-reply':
case 'project-consultation-ask':
case 'project-consultation-get':
case 'project-consultation-list':
case 'project-handoff-get':
case 'project-handoff-list':
case 'project-chief-question':
case 'project-github-read':
case 'project-agent-mode':
Expand Down
6 changes: 3 additions & 3 deletions core/src/host/personal-agent-auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { WORKBENCH_ENDPOINTS } from './workbench-gateway.js'
const hash = value => createHash('sha256').update(value).digest('hex')
const refusal = (message, code = 'bot/forbidden') => Object.assign(new Error(message), { code })
const endpoints = new Set(['personal-agent-token-create', 'personal-agent-token-list', 'personal-agent-token-revoke', 'personal-agent-delegation-set', 'personal-agent-propose-task'])
const reads = new Set(['projects', 'project-read', 'project-detail', 'project-board', 'project-members', 'project-repositories', 'project-task-detail', 'project-attempt-log', 'project-task-pools', 'project-chief-state'])
const reads = new Set(['project-handoff-get', 'project-handoff-list', 'project-consultation-get', 'project-consultation-list', 'projects', 'project-read', 'project-detail', 'project-board', 'project-members', 'project-repositories', 'project-task-detail', 'project-attempt-log', 'project-task-pools', 'project-chief-state'])
const reserved = new Set(['project-agent-mode', 'project-agent-proposal-apply', 'project-agent-message', 'project-agent-history', 'project-agent-memory-write', 'project-agent-stop', 'project-delete', 'project-archive', 'project-close-prepare', 'project-close-confirm'])
const publicCredential = row => ({ version: row.version, id: row.id, agentId: row.agentId, projectIds: [...row.projectIds], createdAt: row.createdAt, expiresAt: row.expiresAt, status: row.status })
const bounded = (value, name, max) => {
Expand Down Expand Up @@ -66,12 +66,12 @@ export function install(rt) {
try {
current(actor)
if (['auth-whoami', 'auth-revoke', 'projects'].includes(endpoint)) return nativeAuthorize(actor, endpoint, body)
if (endpoint === 'personal-agent-propose-task' || endpoint === 'project-close-request') scope(actor, body.projectId)
if (['personal-agent-propose-task', 'project-close-request', 'project-consultation-ask', 'project-consultation-reply', 'project-chief-question'].includes(endpoint)) scope(actor, body.projectId)
else if (reads.has(endpoint)) {
const projectId = body.projectId ?? body.id ?? ownEntry(state.tasks, body.taskId)?.projectId ?? ownEntry(state.projectAttempts ?? {}, body.attemptId)?.projectId
scope(actor, projectId)
} else if (endpoint === 'project-agents' && body.view === 'summary') scope(actor, body.projectId)
else if (endpoint === 'project-development' && rt.projectDevelopment.isQuery(body)) scope(actor, body.projectId)
else if (endpoint === 'project-development' && (rt.projectDevelopment.isQuery(body) || body.kind === 'command' && ['consultation.ask', 'consultation.reply'].includes(body.name))) scope(actor, body.projectId)
else {
const projectId = body.projectId ?? ownEntry(state.tasks, body.id ?? body.taskId)?.projectId ?? ownEntry(state.projectAttempts ?? {}, body.attemptId)?.projectId ?? ownEntry(state.projectQueue ?? {}, body.queueId)?.projectId ?? body.id
const { grant } = scope(actor, projectId)
Expand Down
3 changes: 3 additions & 0 deletions core/src/host/project-access.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ export function install(rt) {
if (endpoint === 'project-rooms' || endpoint.startsWith('project-room-') || endpoint === 'project-agent-stop') return rt.projectAgents.endpoint(endpoint, body, actor)
if (endpoint === 'project-github-read') return rt.projectGithubRead(body, actor)
if (endpoint === 'project-chief-question') return rt.projectAgents.chiefQuestion(body, actor)
if (['project-consultation-reply', 'project-consultation-ask', 'project-consultation-get', 'project-consultation-list'].includes(endpoint)) return rt.projectAgents.consultation(endpoint, body, actor)
if (endpoint === 'project-handoff-get') return { handoff: rt.projectPools.get(body.taskId, body.handoffId, actor) }
if (endpoint === 'project-handoff-list') return rt.projectPools.list(body.taskId, actor, body)
if (endpoint === 'project-credential-status') return rt.projectCredentials.endpoint(endpoint, body, actor)
if (['project-agents', 'project-agent-message', 'project-agent-history', 'project-agent-memory-write', 'project-role-memory-write', 'project-agent-definition', 'project-roles', 'project-role-proposals', 'project-role-decision', 'project-knowledge-write'].includes(endpoint)) return rt.projectAgents.endpoint(endpoint, body, actor)
if (['project-runtime-check', 'project-terminal-open', 'project-terminal-poll', 'project-terminal-input', 'project-terminal-resize', 'project-terminal-close'].includes(endpoint)) return rt.projectRuntimeSetup.endpoint(endpoint, body, actor)
Expand Down
Loading
Loading