syntaur 0.70.0 → 0.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dashboard/dist/assets/{_basePickBy-DKk6tHtk.js → _basePickBy-QfpY8LS2.js} +1 -1
  3. package/dashboard/dist/assets/{_baseUniq-DM-f7DWz.js → _baseUniq-J2dp0xSF.js} +1 -1
  4. package/dashboard/dist/assets/{arc-ZBlA3YdV.js → arc-7V3bSBdi.js} +1 -1
  5. package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-BUmvtGTF.js → architectureDiagram-2XIMDMQ5-L4xF7_w_.js} +1 -1
  6. package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-B3qxWK6s.js → blockDiagram-WCTKOSBZ-D85LV89B.js} +1 -1
  7. package/dashboard/dist/assets/{c4Diagram-IC4MRINW-BEq_UJO-.js → c4Diagram-IC4MRINW-BuLOHFHk.js} +1 -1
  8. package/dashboard/dist/assets/channel-BPiUfFXP.js +1 -0
  9. package/dashboard/dist/assets/{chunk-4BX2VUAB-C-Y9ryMm.js → chunk-4BX2VUAB-PMmPIfmW.js} +1 -1
  10. package/dashboard/dist/assets/{chunk-55IACEB6-CGdtbsjw.js → chunk-55IACEB6-CuzXx0Ve.js} +1 -1
  11. package/dashboard/dist/assets/{chunk-FMBD7UC4-DllxJhUp.js → chunk-FMBD7UC4-DgbPJiW4.js} +1 -1
  12. package/dashboard/dist/assets/{chunk-JSJVCQXG-jjMM8O5F.js → chunk-JSJVCQXG-BIdP1PgR.js} +1 -1
  13. package/dashboard/dist/assets/{chunk-KX2RTZJC-B_6BPltQ.js → chunk-KX2RTZJC-Jx0-O-nW.js} +1 -1
  14. package/dashboard/dist/assets/{chunk-NQ4KR5QH-D0hJiXHp.js → chunk-NQ4KR5QH-CZy5XF4L.js} +1 -1
  15. package/dashboard/dist/assets/{chunk-QZHKN3VN-BCWo4hLS.js → chunk-QZHKN3VN-Clgnd2ni.js} +1 -1
  16. package/dashboard/dist/assets/{chunk-WL4C6EOR-DH_jEAwg.js → chunk-WL4C6EOR-DItFgUMd.js} +1 -1
  17. package/dashboard/dist/assets/classDiagram-VBA2DB6C-HDWgoi6J.js +1 -0
  18. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-HDWgoi6J.js +1 -0
  19. package/dashboard/dist/assets/clone-BdrN_Gqo.js +1 -0
  20. package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-5ld00TOH.js → cose-bilkent-S5V4N54A-CdvW6L_R.js} +1 -1
  21. package/dashboard/dist/assets/{dagre-KLK3FWXG-Cnu6eQWy.js → dagre-KLK3FWXG-200WRn9D.js} +1 -1
  22. package/dashboard/dist/assets/{diagram-E7M64L7V-_CBBKNP-.js → diagram-E7M64L7V-B_4QJiws.js} +1 -1
  23. package/dashboard/dist/assets/{diagram-IFDJBPK2-DE6WjIb1.js → diagram-IFDJBPK2-DiSnANLV.js} +1 -1
  24. package/dashboard/dist/assets/{diagram-P4PSJMXO-DpW7UzNK.js → diagram-P4PSJMXO-DjtHY2zM.js} +1 -1
  25. package/dashboard/dist/assets/{erDiagram-INFDFZHY-BD2409fE.js → erDiagram-INFDFZHY-032TAwXT.js} +1 -1
  26. package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-1p0khhFI.js → flowDiagram-PKNHOUZH-DGNPxjzE.js} +1 -1
  27. package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-B2zFyA4s.js → ganttDiagram-A5KZAMGK-BjbxtEVv.js} +1 -1
  28. package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-bH-4YH7h.js → gitGraphDiagram-K3NZZRJ6-CgFktzLw.js} +1 -1
  29. package/dashboard/dist/assets/{graph-BT24B6iQ.js → graph-CyuCoTKt.js} +1 -1
  30. package/dashboard/dist/assets/{index-Cxqr3rQB.js → index-DsFBSYQj.js} +102 -102
  31. package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-CMzP4Hcg.js → infoDiagram-LFFYTUFH-Dav72O_U.js} +1 -1
  32. package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-DMEmFC7M.js → ishikawaDiagram-PHBUUO56-RAyYfa7q.js} +1 -1
  33. package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-CAtzYQUm.js → journeyDiagram-4ABVD52K-D_9LHvxq.js} +1 -1
  34. package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-d1JVbtvX.js → kanban-definition-K7BYSVSG-BxawyHsy.js} +1 -1
  35. package/dashboard/dist/assets/{layout-BVuI38I_.js → layout-BuWGns-1.js} +1 -1
  36. package/dashboard/dist/assets/{linear-Bc8PGMbp.js → linear-Dml-S448.js} +1 -1
  37. package/dashboard/dist/assets/{mermaid.core-UtwFYLNj.js → mermaid.core-p144DJAn.js} +4 -4
  38. package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-DHc5RCDj.js → mindmap-definition-YRQLILUH-ffX-VsIz.js} +1 -1
  39. package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-9anIsdIA.js → pieDiagram-SKSYHLDU-CneGfBsg.js} +1 -1
  40. package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-FZ0D9HnU.js → quadrantDiagram-337W2JSQ-B1fvKMwB.js} +1 -1
  41. package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-BkjCvH_u.js → requirementDiagram-Z7DCOOCP-B-rOBvHF.js} +1 -1
  42. package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-DzqwYHDo.js → sankeyDiagram-WA2Y5GQK-Bb_m_ROm.js} +1 -1
  43. package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-BW4g5Ao-.js → sequenceDiagram-2WXFIKYE-B-H6H5KN.js} +1 -1
  44. package/dashboard/dist/assets/{stateDiagram-RAJIS63D-D0tKU3Z0.js → stateDiagram-RAJIS63D-CsDEjjit.js} +1 -1
  45. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DmheKY2h.js +1 -0
  46. package/dashboard/dist/assets/{timeline-definition-YZTLITO2-Bu269QDX.js → timeline-definition-YZTLITO2-d0zf4ELB.js} +1 -1
  47. package/dashboard/dist/assets/{treemap-KZPCXAKY-BGu_rrva.js → treemap-KZPCXAKY-DZZjDZEs.js} +1 -1
  48. package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-Cx_n5FRZ.js → vennDiagram-LZ73GAT5-CoxX9DoM.js} +1 -1
  49. package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-BOJsKV_W.js → xychartDiagram-JWTSCODW-BN1K72KM.js} +1 -1
  50. package/dashboard/dist/index.html +1 -1
  51. package/dist/dashboard/server.js +1385 -540
  52. package/dist/dashboard/server.js.map +1 -1
  53. package/dist/index.js +2770 -1535
  54. package/dist/index.js.map +1 -1
  55. package/dist/launch/index.js +594 -109
  56. package/dist/launch/index.js.map +1 -1
  57. package/package.json +1 -1
  58. package/platforms/claude-code/.claude-plugin/plugin.json +1 -1
  59. package/platforms/claude-code/hooks/enforce-boundaries.sh +49 -16
  60. package/platforms/claude-code/hooks/session-cleanup.sh +4 -2
  61. package/platforms/claude-code/skills/add-memory/SKILL.md +3 -2
  62. package/platforms/claude-code/skills/add-resource/SKILL.md +3 -2
  63. package/platforms/claude-code/skills/bundle-worktree/SKILL.md +3 -3
  64. package/platforms/claude-code/skills/capture-artifacts/SKILL.md +2 -2
  65. package/platforms/claude-code/skills/claim-resource/SKILL.md +3 -3
  66. package/platforms/claude-code/skills/clear-assignment/SKILL.md +9 -27
  67. package/platforms/claude-code/skills/complete-assignment/SKILL.md +3 -3
  68. package/platforms/claude-code/skills/create-assignment/SKILL.md +2 -2
  69. package/platforms/claude-code/skills/extend-resource/SKILL.md +2 -2
  70. package/platforms/claude-code/skills/grab-assignment/SKILL.md +17 -12
  71. package/platforms/claude-code/skills/grab-bundle/SKILL.md +10 -6
  72. package/platforms/claude-code/skills/log-progress/SKILL.md +6 -4
  73. package/platforms/claude-code/skills/plan-assignment/SKILL.md +3 -3
  74. package/platforms/claude-code/skills/plan-bundle/SKILL.md +4 -3
  75. package/platforms/claude-code/skills/replan/SKILL.md +9 -6
  76. package/platforms/claude-code/skills/resume-session/SKILL.md +14 -8
  77. package/platforms/claude-code/skills/save-session-summary/SKILL.md +4 -4
  78. package/platforms/claude-code/skills/set-workspace/SKILL.md +9 -6
  79. package/platforms/claude-code/skills/syntaur-protocol/SKILL.md +9 -7
  80. package/platforms/claude-code/skills/syntaur-protocol/references/file-ownership.md +1 -1
  81. package/platforms/claude-code/skills/syntaur-worktree/SKILL.md +11 -7
  82. package/platforms/codex/.codex-plugin/plugin.json +1 -1
  83. package/platforms/codex/scripts/enforce-boundaries.sh +74 -12
  84. package/platforms/codex/scripts/session-cleanup.sh +20 -13
  85. package/platforms/codex/skills/add-memory/SKILL.md +3 -2
  86. package/platforms/codex/skills/add-resource/SKILL.md +3 -2
  87. package/platforms/codex/skills/bundle-worktree/SKILL.md +3 -3
  88. package/platforms/codex/skills/capture-artifacts/SKILL.md +2 -2
  89. package/platforms/codex/skills/claim-resource/SKILL.md +3 -3
  90. package/platforms/codex/skills/clear-assignment/SKILL.md +9 -27
  91. package/platforms/codex/skills/complete-assignment/SKILL.md +3 -3
  92. package/platforms/codex/skills/create-assignment/SKILL.md +2 -2
  93. package/platforms/codex/skills/extend-resource/SKILL.md +2 -2
  94. package/platforms/codex/skills/grab-assignment/SKILL.md +17 -12
  95. package/platforms/codex/skills/grab-bundle/SKILL.md +10 -6
  96. package/platforms/codex/skills/log-progress/SKILL.md +6 -4
  97. package/platforms/codex/skills/plan-assignment/SKILL.md +3 -3
  98. package/platforms/codex/skills/plan-bundle/SKILL.md +4 -3
  99. package/platforms/codex/skills/replan/SKILL.md +9 -6
  100. package/platforms/codex/skills/resume-session/SKILL.md +14 -8
  101. package/platforms/codex/skills/save-session-summary/SKILL.md +4 -4
  102. package/platforms/codex/skills/set-workspace/SKILL.md +9 -6
  103. package/platforms/codex/skills/syntaur-protocol/SKILL.md +9 -7
  104. package/platforms/codex/skills/syntaur-protocol/references/file-ownership.md +1 -1
  105. package/platforms/codex/skills/syntaur-worktree/SKILL.md +11 -7
  106. package/platforms/hermes/plugins/syntaur/__init__.py +29 -3
  107. package/platforms/hermes/plugins/syntaur/__pycache__/__init__.cpython-312.pyc +0 -0
  108. package/platforms/hermes/plugins/syntaur/__pycache__/boundary.cpython-312.pyc +0 -0
  109. package/platforms/hermes/plugins/syntaur/boundary.py +18 -9
  110. package/platforms/pi/extensions/syntaur/index.ts +84 -29
  111. package/skills/add-memory/SKILL.md +3 -2
  112. package/skills/add-resource/SKILL.md +3 -2
  113. package/skills/bundle-worktree/SKILL.md +3 -3
  114. package/skills/capture-artifacts/SKILL.md +2 -2
  115. package/skills/claim-resource/SKILL.md +3 -3
  116. package/skills/clear-assignment/SKILL.md +9 -27
  117. package/skills/complete-assignment/SKILL.md +3 -3
  118. package/skills/create-assignment/SKILL.md +2 -2
  119. package/skills/extend-resource/SKILL.md +2 -2
  120. package/skills/grab-assignment/SKILL.md +17 -12
  121. package/skills/grab-bundle/SKILL.md +10 -6
  122. package/skills/log-progress/SKILL.md +6 -4
  123. package/skills/plan-assignment/SKILL.md +3 -3
  124. package/skills/plan-bundle/SKILL.md +4 -3
  125. package/skills/replan/SKILL.md +9 -6
  126. package/skills/resume-session/SKILL.md +14 -8
  127. package/skills/save-session-summary/SKILL.md +4 -4
  128. package/skills/set-workspace/SKILL.md +9 -6
  129. package/skills/syntaur-protocol/SKILL.md +9 -7
  130. package/skills/syntaur-protocol/references/file-ownership.md +1 -1
  131. package/skills/syntaur-worktree/SKILL.md +11 -7
  132. package/dashboard/dist/assets/channel-fypxffzQ.js +0 -1
  133. package/dashboard/dist/assets/classDiagram-VBA2DB6C-1KnjQvtL.js +0 -1
  134. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-1KnjQvtL.js +0 -1
  135. package/dashboard/dist/assets/clone-CKMabBhS.js +0 -1
  136. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DxWQhjNO.js +0 -1
@@ -32,7 +32,7 @@ Respect file ownership boundaries. The Claude Code and Codex plugins enforce the
32
32
  - `~/.syntaur/projects/<project>/resources/<slug>.md`
33
33
  - `~/.syntaur/projects/<project>/memories/<slug>.md`
34
34
  3. **Workspace files** inside the assignment's configured `workspace.worktreePath` / `workspace.repository`.
35
- 4. **Context file:** `.syntaur/context.json` in the current working directory.
35
+ 4. **Workspace marker:** `.syntaur/context.json` in the current working directory (repository/branch/worktree markers plus legacy session/lease bookkeeping — not the active-assignment source of truth).
36
36
 
37
37
  ### Files written only via CLI (never edit directly)
38
38
 
@@ -51,16 +51,18 @@ Per-project `agent.md` / `claude.md` do NOT exist in protocol v2.0. Agent-level
51
51
 
52
52
  ## Current Assignment Context
53
53
 
54
- If `.syntaur/context.json` exists in the current working directory, read it before making changes. Fields you will see:
54
+ The **active assignment** is resolved from the session's OPEN engagement — the assignment this session is currently bound to (established by `syntaur track-session`). It is NOT read from `.syntaur/context.json`. To learn the active assignment, run `syntaur session resume` (or `--json`); to bind a different one, `grab-assignment` (which calls `track-session`).
55
55
 
56
- - `projectSlug` containing project slug (`null` for standalone assignments)
57
- - `assignmentSlug` — assignment slug (for standalone, the UUID is the folder name; slug is display-only)
58
- - `projectDir` — absolute path to the project folder (`null` for standalone)
59
- - `assignmentDir` — absolute path to the assignment folder
56
+ `.syntaur/context.json` is a WORKSPACE MARKER file — it identifies the workspace directory and carries legacy session and resource-lease bookkeeping. It is NOT authoritative for the active assignment. Read it for workspace markers and leases; do NOT treat `projectSlug` / `assignmentSlug` / `assignmentDir` as the active-assignment source of truth (any such scalars are non-authoritative legacy hints). Fields you may see:
57
+
58
+ - `repository` — workspace repository (path or remote URL)
59
+ - `branch` — workspace branch, if known
60
+ - `worktreePath` — absolute path to the worktree, if this workspace is a worktree
60
61
  - `workspaceRoot` — absolute path to the code workspace
61
- - `sessionId` — real agent-runtime session id (never a synthesized UUID)
62
+ - `sessionId` — real agent-runtime session id (legacy hint; never a synthesized UUID, and a co-tenant can clobber it — not authoritative for identity)
62
63
  - `transcriptPath` — absolute path to the agent's rollout/transcript file, if known
63
64
  - `leases` — array of active resource-lease records (managed by `/claim-resource` and `/release-resource`). Entry shape: `{ lease_id, inventory_slug, member_id, expires_at, metadata, claimed_at }`. Leases are NOT auto-released on session end or assignment completion in v1 — call `/release-resource` explicitly (or let the TTL expire).
65
+ - `bundleId` and related bundle fields — present when this workspace is bound to a todo bundle (managed by the bundle skills).
64
66
 
65
67
  ## Required Reading Order
66
68
 
@@ -64,4 +64,4 @@ When working on code (not protocol files), you may write to files within the wor
64
64
 
65
65
  - `workspace.worktreePath` or `workspace.repository` defines your code root.
66
66
  - You may create and edit source files within that workspace.
67
- - The `.syntaur/context.json` context file in your working directory is also writable (merge, don't overwrite — the platform SessionStart hook may have populated `sessionId` and `transcriptPath`).
67
+ - The `.syntaur/context.json` workspace-marker file in your working directory is also writable (merge, don't overwrite — the platform SessionStart hook may have populated `sessionId` and `transcriptPath`). It marks the workspace (repository/branch/worktree); it is not the active-assignment source of truth — the active assignment resolves from the session's open engagement.
@@ -23,8 +23,9 @@ operations:
23
23
  transactionally — rolls back the worktree on write failure).
24
24
  3. `syntaur assign` + `syntaur start` — claim the assignment for this agent
25
25
  and transition to `in_progress`.
26
- 4. Write `.syntaur/context.json` inside the new worktree so the session is
27
- bound there.
26
+ 4. Write `.syntaur/context.json` (a workspace marker) inside the new worktree,
27
+ then `syntaur track-session` to bind the session's engagement to the
28
+ assignment.
28
29
 
29
30
  ## When NOT to use this skill
30
31
 
@@ -82,12 +83,15 @@ syntaur start <assignment> --project <project-slug> # only if status was pendi
82
83
  Skip `start` for any non-`pending` status — never rewind a `review`,
83
84
  `completed`, or `failed` assignment.
84
85
 
85
- ## Step 5: Bind the new worktree to your session
86
+ ## Step 5: Write the workspace marker
86
87
 
87
- `cd` into the new worktree path. Write
88
- `<worktreePath>/.syntaur/context.json` mirroring the format produced by
89
- `/grab-assignment` (projectSlug, assignmentSlug, projectDir, assignmentDir,
90
- workspaceRoot, title, branch, sessionId, transcriptPath, grabbedAt).
88
+ `cd` into the new worktree path. Write `<worktreePath>/.syntaur/context.json`
89
+ mirroring the WORKSPACE-MARKER format produced by `/grab-assignment`:
90
+ `repository`, `branch`, `worktreePath`, `workspaceRoot`, `grabbedAt`, plus
91
+ `sessionId` / `transcriptPath` when known. Do NOT write `projectSlug` /
92
+ `assignmentSlug` / `assignmentDir` / `projectDir` / `title` — context.json is a
93
+ workspace marker, not the active-assignment source. The assignment binds in the
94
+ next step via the session's engagement (`track-session`).
91
95
 
92
96
  If the runtime exposes a real session id (e.g. Claude Code's
93
97
  `~/.claude/sessions/`), include it. Otherwise omit `sessionId` entirely; the
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "syntaur",
3
- "version": "0.70.0",
3
+ "version": "0.72.0",
4
4
  "description": "Run Syntaur project and assignment workflows from Codex, including claiming work, planning, completing handoffs, session/server tracking, save-session-summary continuity, and write-boundary enforcement.",
5
5
  "author": {
6
6
  "name": "Brennen"
@@ -45,39 +45,97 @@ if [ -z "$FILE_PATH" ]; then
45
45
  allow_and_exit
46
46
  fi
47
47
 
48
+ # context.json is a WORKSPACE MARKER. Its presence means "Syntaur-enforced
49
+ # workspace"; its absence means "not a Syntaur workspace → allow".
48
50
  CONTEXT_FILE=".syntaur/context.json"
49
51
  if [ ! -f "$CONTEXT_FILE" ]; then
50
52
  allow_and_exit
51
53
  fi
52
54
 
53
- ASSIGNMENT_DIR=$(jq -r '.assignmentDir // empty' "$CONTEXT_FILE" 2>/dev/null)
54
- MISSION_DIR=$(jq -r '.projectDir // empty' "$CONTEXT_FILE" 2>/dev/null)
55
- WORKSPACE_ROOT=$(jq -r '.workspaceRoot // empty' "$CONTEXT_FILE" 2>/dev/null)
55
+ # Resolve the write boundary from the session's OPEN engagement (the assignment
56
+ # scalars were demoted out of context.json). Codex's PreToolUse stdin MAY carry
57
+ # .session_id; if absent, fall back to the ancestor-pid runtime markers (same
58
+ # scheme as session-cleanup.sh). Pass whatever id we have to the CLI explicitly.
59
+ # If the CLI is unavailable or resolves nothing, ASSIGNMENT_DIR/MISSION_DIR stay
60
+ # empty → WORKSPACE-ONLY enforcement below (NOT fail-open).
61
+ ASSIGNMENT_DIR=""
62
+ MISSION_DIR=""
63
+ WORKSPACE_ROOT=""
64
+
65
+ CONTEXT_DIR="$(cd "$(dirname "$CONTEXT_FILE")/.." 2>/dev/null && pwd)"
66
+ [ -z "$CONTEXT_DIR" ] && CONTEXT_DIR="$(pwd)"
67
+
68
+ SID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
69
+ if [ -z "$SID" ]; then
70
+ # Fall back to ancestor-pid runtime markers (pid-reuse-guarded by procStart).
71
+ RUNTIME_DIR="${SYNTAUR_RUNTIME_SESSIONS_DIR:-$HOME/.syntaur/runtime/sessions}"
72
+ _pid="$PPID"
73
+ _depth=0
74
+ while [ "$_depth" -lt 12 ]; do
75
+ case "$_pid" in
76
+ '' | *[!0-9]*) break ;;
77
+ esac
78
+ [ "$_pid" -le 1 ] && break
79
+ _marker="$RUNTIME_DIR/$_pid.json"
80
+ if [ -f "$_marker" ]; then
81
+ _sid=$(jq -r '.sessionId // empty' "$_marker" 2>/dev/null)
82
+ _procstart=$(jq -r '.procStart // empty' "$_marker" 2>/dev/null)
83
+ if [ -n "$_sid" ]; then
84
+ if [ -n "$_procstart" ]; then
85
+ _actual=$(ps -o lstart= -p "$_pid" 2>/dev/null | sed 's/^ *//;s/ *$//')
86
+ if [ -n "$_actual" ] && [ "$_actual" = "$_procstart" ]; then
87
+ SID="$_sid"
88
+ break
89
+ fi
90
+ else
91
+ SID="$_sid"
92
+ break
93
+ fi
94
+ fi
95
+ fi
96
+ _pid=$(ps -o ppid= -p "$_pid" 2>/dev/null | tr -d ' ')
97
+ _depth=$((_depth + 1))
98
+ done
99
+ fi
56
100
 
57
- if [ -z "$ASSIGNMENT_DIR" ] || [ -z "$MISSION_DIR" ]; then
58
- allow_and_exit
101
+ if command -v syntaur >/dev/null 2>&1; then
102
+ BOUNDARY_JSON=$(cd "$CONTEXT_DIR" 2>/dev/null && \
103
+ syntaur session boundary --json ${SID:+--session-id "$SID"} 2>/dev/null)
104
+ if [ -n "$BOUNDARY_JSON" ]; then
105
+ ASSIGNMENT_DIR=$(echo "$BOUNDARY_JSON" | jq -r '.assignmentDir // empty' 2>/dev/null)
106
+ MISSION_DIR=$(echo "$BOUNDARY_JSON" | jq -r '.projectDir // empty' 2>/dev/null)
107
+ WORKSPACE_ROOT=$(echo "$BOUNDARY_JSON" | jq -r '.workspaceRoot // empty' 2>/dev/null)
108
+ fi
59
109
  fi
60
110
 
61
- ASSIGNMENT_DIR="${ASSIGNMENT_DIR/#\~/$HOME}"
62
- MISSION_DIR="${MISSION_DIR/#\~/$HOME}"
111
+ # Fall back to the context.json workspace marker if the CLI did not surface one.
112
+ if [ -z "$WORKSPACE_ROOT" ]; then
113
+ WORKSPACE_ROOT=$(jq -r '.workspaceRoot // empty' "$CONTEXT_FILE" 2>/dev/null)
114
+ fi
115
+
116
+ [ -n "$ASSIGNMENT_DIR" ] && ASSIGNMENT_DIR="${ASSIGNMENT_DIR/#\~/$HOME}"
117
+ [ -n "$MISSION_DIR" ] && MISSION_DIR="${MISSION_DIR/#\~/$HOME}"
63
118
  if [ -n "$WORKSPACE_ROOT" ] && [ "$WORKSPACE_ROOT" != "null" ]; then
64
119
  WORKSPACE_ROOT="${WORKSPACE_ROOT/#\~/$HOME}"
65
120
  else
66
121
  WORKSPACE_ROOT=""
67
122
  fi
68
123
 
69
- if [[ "$FILE_PATH" == "$ASSIGNMENT_DIR"/* ]]; then
124
+ # Every prefix test is guarded so an EMPTY $DIR never globs to "/*" and allows
125
+ # the whole filesystem. No assignment → only workspace-root/context match →
126
+ # WORKSPACE-ONLY enforcement (NOT fail-open).
127
+ if [ -n "$ASSIGNMENT_DIR" ] && [[ "$FILE_PATH" == "$ASSIGNMENT_DIR"/* ]]; then
70
128
  allow_and_exit
71
129
  fi
72
130
 
73
- if [[ "$FILE_PATH" == "$MISSION_DIR/resources/"* ]]; then
131
+ if [ -n "$MISSION_DIR" ] && [[ "$FILE_PATH" == "$MISSION_DIR/resources/"* ]]; then
74
132
  BASENAME=$(basename "$FILE_PATH")
75
133
  if [[ "$BASENAME" != _* ]]; then
76
134
  allow_and_exit
77
135
  fi
78
136
  fi
79
137
 
80
- if [[ "$FILE_PATH" == "$MISSION_DIR/memories/"* ]]; then
138
+ if [ -n "$MISSION_DIR" ] && [[ "$FILE_PATH" == "$MISSION_DIR/memories/"* ]]; then
81
139
  BASENAME=$(basename "$FILE_PATH")
82
140
  if [[ "$BASENAME" != _* ]]; then
83
141
  allow_and_exit
@@ -85,7 +143,7 @@ if [[ "$FILE_PATH" == "$MISSION_DIR/memories/"* ]]; then
85
143
  fi
86
144
 
87
145
  CONTEXT_ABS="$(cd "$(dirname "$CONTEXT_FILE")" 2>/dev/null && echo "$(pwd)/$(basename "$CONTEXT_FILE")")"
88
- if [ "$FILE_PATH" = "$CONTEXT_ABS" ]; then
146
+ if [ -n "$CONTEXT_ABS" ] && [ "$FILE_PATH" = "$CONTEXT_ABS" ]; then
89
147
  allow_and_exit
90
148
  fi
91
149
 
@@ -93,7 +151,11 @@ if [ -n "$WORKSPACE_ROOT" ] && [[ "$FILE_PATH" == "$WORKSPACE_ROOT"/* ]]; then
93
151
  allow_and_exit
94
152
  fi
95
153
 
96
- REASON="Syntaur write boundary violation: Cannot write to '$FILE_PATH'. Allowed paths: assignment dir ($ASSIGNMENT_DIR), project resources/memories, workspace ($WORKSPACE_ROOT)."
154
+ if [ -n "$ASSIGNMENT_DIR" ]; then
155
+ REASON="Syntaur write boundary violation: Cannot write to '$FILE_PATH'. Allowed paths: assignment dir ($ASSIGNMENT_DIR), project resources/memories${MISSION_DIR:+ ($MISSION_DIR)}, workspace (${WORKSPACE_ROOT:-none})."
156
+ else
157
+ REASON="Syntaur write boundary violation: Cannot write to '$FILE_PATH'. No active assignment for this session — writes are restricted to the workspace (${WORKSPACE_ROOT:-none})."
158
+ fi
97
159
  REASON_ESCAPED=$(echo "$REASON" | jq -Rs '.' 2>/dev/null)
98
160
  if [ -z "$REASON_ESCAPED" ]; then
99
161
  REASON_ESCAPED="\"Syntaur write boundary violation\""
@@ -31,19 +31,6 @@ if [ ! -f "$CONTEXT_FILE" ]; then
31
31
  exit 0
32
32
  fi
33
33
 
34
- # Keep derived status fresh on session end — CLI-direct (NO dashboard
35
- # dependency, unlike the session-stop PATCH below), migration-gated, and bounded
36
- # (~3s; no `timeout` on stock macOS). Best-effort; never blocks teardown.
37
- if command -v syntaur >/dev/null 2>&1; then
38
- ( cd "$CWD" 2>/dev/null && syntaur recompute --if-migrated >/dev/null 2>&1 ) &
39
- syntaur_rc_pid=$!
40
- ( sleep 3; kill -KILL "$syntaur_rc_pid" 2>/dev/null ) >/dev/null 2>&1 &
41
- syntaur_rc_killer=$!
42
- wait "$syntaur_rc_pid" 2>/dev/null || true
43
- kill -KILL "$syntaur_rc_killer" 2>/dev/null
44
- wait "$syntaur_rc_killer" 2>/dev/null || true
45
- fi
46
-
47
34
  RUNTIME_DIR="${SYNTAUR_RUNTIME_SESSIONS_DIR:-$HOME/.syntaur/runtime/sessions}"
48
35
 
49
36
  # Walk the ancestor-pid chain reading runtime markers. Returns the nearest
@@ -84,6 +71,26 @@ resolve_session_from_markers() {
84
71
  }
85
72
 
86
73
  SESSION_ID=$(resolve_session_from_markers || true)
74
+
75
+ # Keep derived status fresh on session end — keyed on the ending session's EXACT
76
+ # id (the marker-resolved id, else the stdin id for runtimes that provide one).
77
+ # Skip when NEITHER resolves, rather than fall back to ambiguous cwd own-session
78
+ # resolution that could recompute the WRONG assignment in a shared worktree.
79
+ # Runs BEFORE the stop PATCH below, so the engagement is still open. CLI-direct
80
+ # (no dashboard), migration-gated, bounded (~3s; no `timeout` on stock macOS).
81
+ STDIN_SID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
82
+ RECOMPUTE_SID="${SESSION_ID:-$STDIN_SID}"
83
+ if [ -n "$RECOMPUTE_SID" ] && command -v syntaur >/dev/null 2>&1; then
84
+ ( cd "$CWD" 2>/dev/null && \
85
+ syntaur recompute --if-migrated --session-id "$RECOMPUTE_SID" >/dev/null 2>&1 ) &
86
+ syntaur_rc_pid=$!
87
+ ( sleep 3; kill -KILL "$syntaur_rc_pid" 2>/dev/null ) >/dev/null 2>&1 &
88
+ syntaur_rc_killer=$!
89
+ wait "$syntaur_rc_pid" 2>/dev/null || true
90
+ kill -KILL "$syntaur_rc_killer" 2>/dev/null
91
+ wait "$syntaur_rc_killer" 2>/dev/null || true
92
+ fi
93
+
87
94
  MISSION_SLUG=$(jq -r '.projectSlug // empty' "$CONTEXT_FILE" 2>/dev/null)
88
95
 
89
96
  # No EXACT id — do not risk stopping the wrong co-tenant session.
@@ -32,8 +32,9 @@ file-ownership protocol.
32
32
 
33
33
  ## Step 1: Resolve project
34
34
 
35
- If `.syntaur/context.json` is present and has `projectSlug`, default to that.
36
- Otherwise ask the user which project to add the memory to.
35
+ If the session has an open engagement with an active assignment (`syntaur
36
+ session resume --json`), default to its `projectSlug`. Otherwise ask the user
37
+ which project to add the memory to.
37
38
 
38
39
  ## Step 2: Gather inputs
39
40
 
@@ -30,8 +30,9 @@ CLI-managed file per the file-ownership protocol).
30
30
 
31
31
  ## Step 1: Resolve project
32
32
 
33
- If `.syntaur/context.json` is present and has `projectSlug`, default to that.
34
- Otherwise ask the user which project to add the resource to.
33
+ If the session has an open engagement with an active assignment (`syntaur
34
+ session resume --json`), default to its `projectSlug`. Otherwise ask the user
35
+ which project to add the resource to.
35
36
 
36
37
  ## Step 2: Gather inputs
37
38
 
@@ -45,9 +45,9 @@ The computed worktree path is **always**
45
45
 
46
46
  ## Step 2: Pre-flight
47
47
 
48
- - Confirm `.syntaur/context.json` is a bundle context (has `bundleId`,
49
- no assignment fields). If it's an assignment context, stop and tell the
50
- user to `/grab-bundle <id>` first.
48
+ - Confirm `.syntaur/context.json` is a bundle context (has `bundleId`). If the
49
+ session is instead bound to an assignment (an open engagement) with no
50
+ `bundleId`, stop and tell the user to `/grab-bundle <id>` first.
51
51
  - Confirm `<repository>/.git` exists.
52
52
  - Confirm the branch does NOT already exist. If it does, the CLI will
53
53
  surface a `GitWorktreeError` — surface that verbatim.
@@ -17,7 +17,7 @@ Attach proof to your work. Reviewers should be able to scroll a single page (`pr
17
17
 
18
18
  ## Input
19
19
 
20
- No arguments. Reads `.syntaur/context.json` from the current working directory to identify the active assignment.
20
+ No arguments. The active assignment is resolved from the session's open engagement (`.syntaur/context.json` is only a workspace marker, not the assignment source).
21
21
 
22
22
  ## Step 1: Decide whether to capture
23
23
 
@@ -60,7 +60,7 @@ Rules:
60
60
  - All other kinds (`screenshot`, `video`, `asciinema`) require `--file`. The CLI rejects nonexistent / non-file paths.
61
61
  - `--criterion <index>` is optional — pass the **0-based** index into the `## Acceptance Criteria` checklist when you want to anchor the artifact to a specific criterion.
62
62
  - `--transcribe` is video-only and writes a sibling `<id>.transcript.md` (requires `ELEVENLABS_API_KEY` + `ffmpeg`). `proof build` renders the transcript next to the player; clicking a phrase seeks the video.
63
- - If you have a `.syntaur/context.json` in the cwd, the positional target argument is unnecessary; otherwise pass `--project <slug> <assignment-slug>` or a bare assignment UUID.
63
+ - If this session has an open engagement (active assignment), the positional target argument is unnecessary the CLI resolves the target from the engagement. Otherwise pass `--project <slug> <assignment-slug>` or a bare assignment UUID.
64
64
 
65
65
  The CLI copies the file (if any) under `<assignmentDir>/proof/<criterion|untagged>/<id>.<ext>` and inserts a row in `~/.syntaur/syntaur.db`. Output prints the artifact id and absolute path.
66
66
 
@@ -29,15 +29,15 @@ Examples:
29
29
 
30
30
  Extract `<inventory_slug>` (required, first positional), plus optional `--ttl <duration>` and `--for <tag>`.
31
31
 
32
- ### Step 2: Pre-check assignment context
32
+ ### Step 2: Pre-check workspace context
33
33
 
34
- Read `.syntaur/context.json` from the current working directory. If the file is missing, OR has neither `sessionId` nor any of `projectSlug`/`assignmentSlug`/`assignmentDir`, abort with:
34
+ Read `.syntaur/context.json` (a workspace marker) from the current working directory. If the file is missing, OR carries no workspace markers (`repository`/`branch`/`worktreePath`/`workspaceRoot`) and no `sessionId`, abort with:
35
35
 
36
36
  > "No active Syntaur context in this workspace. Grab an assignment first (`/grab-assignment <project> <slug>`) or restart the session so the SessionStart hook can populate `.syntaur/context.json`."
37
37
 
38
38
  This guard exists because writing a leases-only context file would trip `syntaur doctor`'s workspace check.
39
39
 
40
- If `--for` was not provided, derive its value from the context: prefer `assignmentSlug` if present, otherwise `sessionId`.
40
+ If `--for` was not provided, derive its value: prefer the active assignment's slug from the session's open engagement (`syntaur session resume --json`); otherwise fall back to the `sessionId` from the workspace marker.
41
41
 
42
42
  ### Step 3: Run the claim CLI
43
43
 
@@ -14,7 +14,7 @@ metadata:
14
14
 
15
15
  # Clear Assignment
16
16
 
17
- Drop the active assignment binding from the current workspace. The assignment itself is left untouched in `~/.syntaur/projects/.../assignments/` — only the local `.syntaur/context.json` pointer is cleared so the session is no longer scoped to it.
17
+ Drop the active assignment binding from the current session. The assignment itself is left untouched in `~/.syntaur/projects/.../assignments/` — only the session's open engagement (the binding that makes it the active assignment) is closed so the session is no longer scoped to it. `.syntaur/context.json` is a workspace marker and is not the binding — it does not hold the active assignment.
18
18
 
19
19
  This is the inverse of `grab-assignment`. Unlike `complete-assignment`, it does **not** transition lifecycle state, write a handoff, or close out the work. Use it when:
20
20
 
@@ -28,17 +28,15 @@ If the assignment is actually done, use `complete-assignment` instead so a hando
28
28
 
29
29
  Optional flags from the user:
30
30
 
31
- - `--keep-session` — preserve `sessionId` / `transcriptPath` fields in `context.json` (just strip the assignment fields). Default: full delete.
32
- - `--unassign` — also run `syntaur unassign <slug> --project <project>` so the assignment is no longer claimed by this agent. Default: leave the claim in place (only the local context is cleared).
31
+ - `--unassign` — also run `syntaur unassign <slug> --project <project>` so the assignment is no longer claimed by this agent. Default: leave the claim in place (only the session's engagement is closed).
33
32
 
34
33
  ## Step 1: Load Context
35
34
 
36
- Read `.syntaur/context.json` from the current working directory.
35
+ The active assignment is resolved from the session's open engagement. Run `syntaur session resume --json` to read it.
37
36
 
38
- - If the file does not exist, tell the user: "No active assignment context to clear." and stop.
39
- - If the file exists but contains only session fields (`sessionId`, `transcriptPath`) and no `projectSlug` / `assignmentSlug`, tell the user: "No active assignment is bound — only a platform session record exists. Nothing to clear." and stop (unless they explicitly ask to wipe the session record too).
37
+ - If there is no open engagement, tell the user: "No active assignment is bound to this session — nothing to clear." and stop. (`.syntaur/context.json` is only a workspace marker; its presence does not mean an assignment is bound.)
40
38
 
41
- Otherwise extract: `projectSlug`, `assignmentSlug`, `assignmentDir`, `title`.
39
+ From the resolved engagement, note: `projectSlug`, `assignmentSlug`, `assignmentDir`, `title`.
42
40
 
43
41
  ## Step 2: Confirm with the User
44
42
 
@@ -68,29 +66,15 @@ For standalone assignments use the UUID (the folder name) in place of the slug,
68
66
 
69
67
  `syntaur unassign` clears the assignee on the assignment frontmatter (the inverse of `assign`) and bumps `updated`.
70
68
 
71
- ## Step 4: Clear the Context File
69
+ ## Step 4: Close the Engagement
72
70
 
73
- Default behaviordelete the file entirely:
71
+ Closing the session's open engagement is what drops the active-assignment binding that is the operation that "clears" the assignment. The dashboard status PATCH in Step 5 (to `cleared`) closes the open engagement for a live session.
74
72
 
75
- ```bash
76
- rm .syntaur/context.json
77
- ```
78
-
79
- If the user passed `--keep-session`, preserve session fields and strip everything else instead:
80
-
81
- ```bash
82
- jq '{sessionId, transcriptPath} | with_entries(select(.value != null))' \
83
- .syntaur/context.json > .syntaur/context.json.tmp \
84
- && mv .syntaur/context.json.tmp .syntaur/context.json
85
- ```
86
-
87
- If the resulting file would be empty (`{}`), delete it instead of leaving an empty stub.
88
-
89
- Do not delete the `.syntaur/` directory itself — other tooling may use it.
73
+ Do NOT delete or rewrite `.syntaur/context.json` to clear the assignment — it is a workspace marker and no longer carries the active assignment. Leave its repository/branch/worktree, session, and lease fields intact so other tooling keeps recognizing the workspace. Do not delete the `.syntaur/` directory.
90
74
 
91
75
  ## Step 5: Close Session (optional)
92
76
 
93
- If the Syntaur dashboard is running, mark this session as cleared so the dashboard does not keep showing it as active. Resolve `<session-id>` from *your* running process — prefer `$CLAUDE_CODE_SESSION_ID` (or the peer `OPENCODE_SESSION_ID` / `PI_SESSION_ID`), otherwise run `syntaur session resolve-id`. Only if neither yields an id, fall back to the `sessionId` you captured from the original `context.json` in Step 1 (before deletion) — that scalar is a shared, legacy hint a co-tenant can clobber, so don't treat it as authoritative:
77
+ If the Syntaur dashboard is running, mark this session as cleared so the dashboard does not keep showing it as active (this also closes the session's open engagement). Resolve `<session-id>` from *your* running process — prefer `$CLAUDE_CODE_SESSION_ID` (or the peer `OPENCODE_SESSION_ID` / `PI_SESSION_ID`), otherwise run `syntaur session resolve-id`. Only if neither yields an id, fall back to the legacy `sessionId` scalar in `.syntaur/context.json` — that scalar is a shared, legacy hint a co-tenant can clobber, so don't treat it as authoritative:
94
78
 
95
79
  ```bash
96
80
  curl -s -X PATCH "http://localhost:$(cat ~/.syntaur/dashboard-port 2>/dev/null || echo 4800)/api/agent-sessions/<session-id>/status" \
@@ -100,8 +84,6 @@ curl -s -X PATCH "http://localhost:$(cat ~/.syntaur/dashboard-port 2>/dev/null |
100
84
 
101
85
  If this fails (e.g., dashboard not running, endpoint not present in the installed version), it is non-critical — silently continue.
102
86
 
103
- Skip this step entirely when `--keep-session` is set.
104
-
105
87
  ## Step 6: Report to User
106
88
 
107
89
  Summarize:
@@ -19,11 +19,11 @@ Optional: the user may pass `--complete` to transition directly to `completed` i
19
19
 
20
20
  ## Step 1: Load Context
21
21
 
22
- Read `.syntaur/context.json` from the current working directory.
22
+ The active assignment is resolved from the session's open engagement. Run `syntaur session resume --json` to read it.
23
23
 
24
- If the file does not exist, tell the user: "No active assignment found. Run `grab-assignment` first."
24
+ If there is no open engagement (no active assignment), tell the user: "No active assignment for this session — grab one first." `.syntaur/context.json` is only a workspace marker; do not read the assignment from it.
25
25
 
26
- Extract: `projectSlug`, `assignmentSlug`, `assignmentDir`, `projectDir`.
26
+ From the resolved engagement, note: `projectSlug`, `assignmentSlug`, `assignmentDir`, `projectDir`.
27
27
 
28
28
  ## Step 2: Load Playbooks
29
29
 
@@ -30,9 +30,9 @@ Expects arguments from the user:
30
30
 
31
31
  If no title was provided, ask the user what the assignment should be called.
32
32
 
33
- If neither `--project` nor `--one-off` was provided, check `.syntaur/context.json` for an active assignment. If present, default `--project` to that context's `projectSlug` and confirm with the user: "Add this assignment to project `<projectSlug>`?"
33
+ If neither `--project` nor `--one-off` was provided, check for an active assignment via the session's open engagement (`syntaur session resume --json`). If there is one, default `--project` to its `projectSlug` and confirm with the user: "Add this assignment to project `<projectSlug>`?"
34
34
 
35
- If no active context and no project flag, ask the user which project to add it to, or whether it should be a one-off.
35
+ If there is no open engagement and no project flag, ask the user which project to add it to, or whether it should be a one-off.
36
36
 
37
37
  ## Step 1: Run the CLI
38
38
 
@@ -25,9 +25,9 @@ Examples:
25
25
 
26
26
  Extract `<lease_id>` (first positional) and `--ttl <duration>` (required). If either is missing, abort with a usage message and a hint to call `/list-resources` to see active leases.
27
27
 
28
- ### Step 2: Pre-check assignment context
28
+ ### Step 2: Pre-check workspace context
29
29
 
30
- Read `.syntaur/context.json` from the current working directory. If the file is missing, OR has neither `sessionId` nor any of `projectSlug`/`assignmentSlug`/`assignmentDir`, abort with:
30
+ Read `.syntaur/context.json` (a workspace marker) from the current working directory. If the file is missing, OR carries no workspace markers (`repository`/`branch`/`worktreePath`/`workspaceRoot`) and no `sessionId`, abort with:
31
31
 
32
32
  > "No active Syntaur context in this workspace. Grab an assignment first (`/grab-assignment <project> <slug>`) or restart the session so the SessionStart hook can populate `.syntaur/context.json`."
33
33
 
@@ -23,10 +23,16 @@ Expects up to two arguments from the user:
23
23
 
24
24
  ## Pre-flight Check
25
25
 
26
- Check if `.syntaur/context.json` already exists in the current working directory.
26
+ `.syntaur/context.json` is a WORKSPACE MARKER (repository/branch/worktree/workspaceRoot plus legacy session and bundle fields) — it is NOT the active-assignment source of truth. The active assignment binds via the session's open engagement (established by `track-session` in Step 6).
27
27
 
28
- - If it exists AND contains BOTH `projectSlug` and `assignmentSlug`, it represents an active assignment. Warn the user: "You already have an active assignment: `<assignmentSlug>` in project `<projectSlug>`. Grabbing a new one will replace this context. Proceed?" — stop if the user says no.
29
- - If it exists but only has session fields (`sessionId`, `transcriptPath`) and no project/assignment, it was populated by the platform's SessionStart hook (Claude Code, etc.) and does NOT represent an active assignment. Proceed silently and merge assignment fields on top in Step 5.
28
+ Check whether this session already has an open engagement i.e., a different assignment is already active:
29
+
30
+ ```bash
31
+ syntaur session resume --json 2>/dev/null
32
+ ```
33
+
34
+ - If it reports an active assignment, warn the user: "You already have an active assignment: `<assignmentSlug>` in project `<projectSlug>`. Grabbing a new one will rebind this session. Proceed?" — stop if the user says no.
35
+ - If there is no open engagement (no active assignment), proceed. A `.syntaur/context.json` that holds only workspace-marker / session fields is expected — it does not represent an active assignment.
30
36
 
31
37
  ## Step 1: Discover the Project (project-nested path)
32
38
 
@@ -75,25 +81,24 @@ From the assignment frontmatter extract: `title`, `workspace.repository`, `works
75
81
 
76
82
  If `workspace.repository` and `workspace.worktreePath` are both null, set them to the current working directory. Write boundaries use this path, so it must never be null while an agent is writing code.
77
83
 
78
- ## Step 5: Create or Merge Context File
84
+ ## Step 5: Create or Merge the Workspace Marker
79
85
 
80
- Merge assignment context into `.syntaur/context.json`. Never overwriteif the file already exists (e.g., platform SessionStart hook populated `sessionId` / `transcriptPath`), preserve those fields.
86
+ `.syntaur/context.json` is a WORKSPACE MARKER it records the repository/branch/worktree so tooling can recognize this directory as a Syntaur workspace. It is NOT the active-assignment source of truth: the assignment binds via the session's open engagement (Step 6, `track-session`). Do NOT write `projectSlug` / `assignmentSlug` / `assignmentDir` / `projectDir` / `title` — those scalars are non-authoritative.
87
+
88
+ Merge workspace markers into `.syntaur/context.json`. Never overwrite — if the file already exists (e.g., platform SessionStart hook populated `sessionId` / `transcriptPath`, or a worktree skill wrote bundle/lease fields), preserve those fields.
81
89
 
82
90
  ```bash
83
91
  mkdir -p .syntaur
84
92
  ```
85
93
 
86
- Prepare the assignment payload:
94
+ Prepare the workspace-marker payload:
87
95
 
88
96
  ```json
89
97
  {
90
- "projectSlug": "<project-slug or null for standalone>",
91
- "assignmentSlug": "<assignment-slug>",
92
- "projectDir": "/absolute/path/to/project or null",
93
- "assignmentDir": "/absolute/path/to/assignment",
94
- "workspaceRoot": "<workspace path or current working directory>",
95
- "title": "<assignment title>",
98
+ "repository": "<workspace.repository or null>",
96
99
  "branch": "<workspace.branch or null>",
100
+ "worktreePath": "<workspace.worktreePath or null>",
101
+ "workspaceRoot": "<workspace path or current working directory>",
97
102
  "grabbedAt": "<ISO 8601 timestamp>"
98
103
  }
99
104
  ```
@@ -34,16 +34,20 @@ the user to pick.
34
34
 
35
35
  ## Pre-flight check
36
36
 
37
- Read `.syntaur/context.json` in the current working directory.
37
+ First check whether this session already has an active assignment via its open
38
+ engagement (`syntaur session resume --json`):
39
+
40
+ - If it reports an active assignment, warn the user: "You already have an active
41
+ assignment. Grabbing a bundle will rebind this session. Proceed?" — stop if no.
42
+
43
+ Then read `.syntaur/context.json` (a workspace marker) in the current working
44
+ directory for bundle bookkeeping:
38
45
 
39
- - If it already contains assignment fields (`projectSlug`/`assignmentSlug`/
40
- `assignmentDir`), warn the user: "You already have an active assignment.
41
- Grabbing a bundle will replace this context. Proceed?" — stop if no.
42
46
  - If it already contains different bundle fields (`bundleId` set to a
43
47
  different id), warn: "Context is bound to bundle b:<old>. Switch to b:<new>?"
44
48
  — stop if no.
45
- - If it has only `sessionId` (platform SessionStart hook seeded it), proceed
46
- and merge bundle fields on top.
49
+ - A context.json holding only workspace-marker / `sessionId` fields is expected;
50
+ proceed and merge bundle fields on top.
47
51
 
48
52
  ## Step 1: Load the bundle
49
53
 
@@ -39,9 +39,11 @@ records current in real-time, especially after every meaningful action.
39
39
 
40
40
  ## Step 1: Verify there is an active assignment
41
41
 
42
- Read `.syntaur/context.json` from the current working directory. Extract
43
- `assignmentDir`. If missing, abort with: "No active assignment. Run
44
- `grab-assignment` first."
42
+ The active assignment is resolved from the session's open engagement — `syntaur
43
+ progress log` (Step 3) targets it automatically. `.syntaur/context.json` is only
44
+ a workspace marker; do not read the assignment from it. If there is no open
45
+ engagement (no active assignment), the CLI aborts with "No active assignment for
46
+ this session — grab one first." Run `grab-assignment` first.
45
47
 
46
48
  ## Step 2: Compose the entry
47
49
 
@@ -69,7 +71,7 @@ Run:
69
71
  syntaur progress log "<your composed entry body>"
70
72
  ```
71
73
 
72
- The command resolves the active assignment from `.syntaur/context.json`
74
+ The command resolves the active assignment from the session's open engagement
73
75
  (or pass `--assignment <slug> [--project <slug>]` to target one explicitly),
74
76
  then atomically:
75
77
 
@@ -20,14 +20,14 @@ Optional: the user may provide focus areas or notes to guide the plan.
20
20
 
21
21
  ## Step 1: Load Context
22
22
 
23
- Read `.syntaur/context.json` from the current working directory. If the file does not exist, tell the user: "No active assignment found. Run `grab-assignment` first to claim an assignment."
23
+ The active assignment is resolved from the session's open engagement. Run `syntaur session resume --json` to read it. If there is no open engagement (no active assignment), tell the user: "No active assignment for this session — grab one first." and stop. `.syntaur/context.json` is only a workspace marker; do not read the assignment from it.
24
24
 
25
- Extract:
25
+ From the resolved engagement, note:
26
26
  - `projectSlug` — the project slug (`null` for standalone assignments)
27
27
  - `assignmentSlug` — the assignment slug
28
28
  - `assignmentDir` — absolute path to the assignment folder
29
29
  - `projectDir` — absolute path to the project folder (may be null for standalone)
30
- - `workspaceRoot` — absolute path to the workspace (may be null)
30
+ - `workspaceRoot` — absolute path to the workspace (a workspace marker; may be null)
31
31
 
32
32
  ## Step 2: Load Playbooks
33
33
 
@@ -19,9 +19,10 @@ the bundle's own to track, only the member todos.
19
19
 
20
20
  ## Step 1: Load context
21
21
 
22
- Read `.syntaur/context.json` from the current working directory. It must
23
- contain `bundleId` (a bundle context). If it instead has assignment fields,
24
- stop and tell the user: "Active context is an assignment, not a bundle.
22
+ Read `.syntaur/context.json` (a workspace marker) from the current working
23
+ directory. It must contain `bundleId` (a bundle context). If there is no
24
+ `bundleId` but the session has an open engagement (an active assignment), stop
25
+ and tell the user: "This session is bound to an assignment, not a bundle.
25
26
  Use `/plan-assignment` instead, or `/grab-bundle <id>` to switch."
26
27
 
27
28
  Extract `bundleId`, `bundleScope`, `bundleScopeId`, `todoIds`, `planDir`.