Skill v1.0.1
currentAutomated scan100/100+4 new
name: session-ingest description: Ingest a coding session JSONL into AKB as structured notes — session report + parallel-drafted TIL / task / idea / decision sub-notes. Auto-discovers the current session's JSONL when no positional path is given (Claude / Codex). --delegate {target} fires the work to another target's CLI and exits. metadata: version: "1.0.1"
AKB Session Ingest
Resource identity. Retain the canonical uri returned by search, browse, get, or put alongside every document ID/path. Use it unchanged for akb_get, akb_update, relations, and graph edges; collection-scoped URIs cannot be reconstructed by prefixing a path with /doc/. Capture matched URIs as matched_uri, dedup_uri, existing_uri, or existing_session_uri; draft targets carry append_uri / supersedes_uri, tasks carry task_uri, and newly written documents carry doc_uri / session_uri. IDs remain display/tag values only.
Multi-agent workflow that analyzes a session JSONL file and writes structured notes to an AKB vault.
Workflow classification. LLM-wiki Ingest workflow. Cognitive-debt layer — it captures the in-flight reasoning of a coding session (how the team arrived at an understanding, the decisions and dead ends), turning ephemeral working context into persistent compiled-truth notes (TIL / decision / idea / task) before it evaporates. The four drafter subagents share this classification.
Prerequisites
- AKB MCP server is accessible.
--vault {name}provided.
Execution Flow
Phase 0 — Resolve parse args, resolve JSONL path (auto or explicit), maybe delegate & exitPhase 1 — Prepare detect duplicates, fetch open tasksPhase 2 — Draft read JSONL, identify commits/repo, draft session notePhase 3 — Sub-Draft launch til / task / idea / decision drafters; triage ideasPhase 4 — Write dedup ↔ finalize ↔ write session, sub-notes, resolve tasks
Phase 0 — Resolve & Maybe Delegate
Resolve invocation arguments and the JSONL path. Optionally fire-and-forget to a different target's CLI and exit early. All Phase 1+ work assumes $VAULT and $JSONL_PATH are set and the JSONL file exists.
0-1. Parse Arguments
Parse positional [jsonl_path] (optional) and flags:
--vault {name}(required) — hard-fail with--vault {name} required.if absent--lang {language}(optional) — preferred language for synthesized note body--delegate {claude|codex}(optional) — fire-and-forget to a different target's CLI and exit early
Store as $JSONL_PATH, $VAULT, $LANG_FLAG, $DELEGATE.
0-2. Reject Self-Delegate
The current target was baked in at build time. If --delegate matches it, abort to prevent infinite recursion.
SELF="codex"if [ -n "$DELEGATE" ] && [ "$DELEGATE" = "$SELF" ]; thenecho "Refusing $SELF -> $SELF delegate (would recurse)." >&2exit 2fi
0-3. Resolve JSONL Path
If $JSONL_PATH was supplied positionally, use it. Otherwise discover the current session's JSONL automatically — discovery is target-specific.
Codex target — `$CODEX_THREAD_ID` env var + `find` across active and archived sessions.
if [ -z "$JSONL_PATH" ]; thenif [ -z "${CODEX_THREAD_ID:-}" ]; thenecho "CODEX_THREAD_ID empty — not inside a Codex thread. Pass <jsonl_path> explicitly." >&2exit 2fiCODEX_ROOT="${CODEX_HOME:-$HOME/.codex}"JSONL_PATH=$(find "$CODEX_ROOT/sessions" "$CODEX_ROOT/archived_sessions" \-type f -name "*${CODEX_THREAD_ID}.jsonl" -print 2>/dev/null | head -1)if [ -z "$JSONL_PATH" ]; thenecho "No JSONL found for thread ${CODEX_THREAD_ID} under $CODEX_ROOT" >&2exit 2fifi
Verify the resolved path exists before proceeding:
if [ ! -f "$JSONL_PATH" ]; thenecho "JSONL path not readable: $JSONL_PATH" >&2exit 2fi
0-4. Delegate Fire-and-Forget (Optional)
If $DELEGATE is set, hand the ingest work to a different target and exit. The current session does no ingest work — the receiver runs Phase 1+ on the same JSONL. claude / codex spawn the target's CLI in the background.
Each target runs the receiver unattended while limiting blast radius if the JSONL contains injected content:
claude:--permission-mode acceptEditsauto-approves file edits; Bash/MCP rely on the skill'sallowed-tools.codex:-s workspace-write -c approval_policy="never"— confines file writes to the workspace AND skips approval pauses.--skip-git-repo-checklets the receiver start in a non-git pwd (delegate does not require the JSONL's project to be checked out). Avoid--dangerously-bypass-approvals-and-sandbox: that drops the sandbox entirely, letting prompt-injection in the JSONL escalate to arbitrary shell.
LOG="${TMPDIR:-/tmp}/session-ingest-delegate-$(date +%s)-$$.log"ARGS="\"$JSONL_PATH\" --vault \"$VAULT\""[ -n "$LANG_FLAG" ] && ARGS="$ARGS --lang \"$LANG_FLAG\""case "$DELEGATE" inclaude)nohup claude -p "/session-ingest $ARGS" --permission-mode acceptEdits \>"$LOG" 2>&1 &echo "Delegated to claude (pid $!, log $LOG).";;codex)# $session-ingest must NOT shell-expand. Single-quote the literal,# then concatenate the double-quoted $ARGS so the file path stays quoted.# --skip-git-repo-check: the receiver may run in a non-git pwd (delegate# does not require the JSONL's project to be checked out); codex exec# otherwise aborts before starting.PROMPT='$session-ingest '"$ARGS"nohup codex exec --skip-git-repo-check -s workspace-write -c 'approval_policy="never"' "$PROMPT" \>"$LOG" 2>&1 &echo "Delegated to codex (pid $!, log $LOG).";;""): ;;*)echo "Unknown --delegate target: $DELEGATE (expected: claude, codex)" >&2exit 2;;esacif [ -n "$DELEGATE" ]; thenexit 0fi
Phase 1 — Prepare
1-1. Detect Duplicates
Before reading the full JSONL, check whether this session was already ingested.
Detect JSONL Schema
Read the first ~10 lines of the JSONL (Read with limit: 10). The opening line may be a noise event without identifying fields (CC sessions sometimes start with permission-mode or file-history-snapshot, neither of which carries sessionId); scan all 10 to find the first line that decisively identifies the schema:
- If any of those lines has a top-level
sessionIdfield (e.g.,{"type": "worktree-state", "sessionId": "..."}or{"type": "user", "sessionId": "..."}), the JSONL is CC schema (flat per-line events). Setjsonl_schema = "cc". - If any line has a top-level
payloadobject containingid(e.g.,{"timestamp": "...", "type": "session_meta", "payload": {"id": "...", "cwd": "..."}}), the JSONL is Codex schema (event-wrapped). Setjsonl_schema = "codex". - If none of the first 10 lines yields either signal, hard-fail with
"Unrecognized JSONL schema: $JSONL_PATH"— do not guess.
Schema detection is independent of runtime.target: a Codex JSONL ingested from a Claude harness still uses Codex parsing, and vice versa.
Extract Session ID, Timestamps, and Source CWD
Reuse the first-10-lines window from schema detection and read the last few lines of the JSONL. Capture everything the head window carries in one pass — later phases reuse these values rather than re-scanning. Field lookup is schema-aware:
For jsonl_schema == "cc":
- Store the first encountered
sessionIdfield assession_id. (Lines withoutsessionIdsuch asfile-history-snapshotare skipped — they precede the first content event in some sessions.) - If a
worktree-stateentry is present, storeworktreeSession.originalCwdassession_cwd; otherwise leavesession_cwdunset.
For jsonl_schema == "codex":
- Find the entry with
type == "session_meta"(typically the first line). Storepayload.idassession_idandpayload.cwdassession_cwd.
For both schemas:
- Last entry's top-level
timestamp→jsonl_last_timestamp.
Store:
session_id— the resolved session/thread idjsonl_last_timestamp— the ISO 8601 timestamp of the final entrysession_cwd— the source session's working directory (or unset)
Search Vault for Existing Session
mcp__akb__akb_search(query="{session_id}",vault="{vault_name}",collection="sessions",tags=["session:{session_id}"],type="session",limit=1)
No match → sync_mode = "new", proceed to Phase 2.
Match found → read the existing session note:
mcp__akb__akb_get(uri="{matched_uri}")→ existing_session_doc_id, existing_tags, existing_session_content
Extract the last-synced:{timestamp} tag value from the existing note's tags. Store as last_synced_timestamp. Preserve existing_session_content (the full markdown body) for Phase 2-3 and Phase 4-1.
Compare Timestamps
jsonl_last_timestamp <= last_synced_timestamp→ no new content since last sync → stop:
"Session {session_id} already synced up to {last_synced_timestamp}. No new content. Skipping."
jsonl_last_timestamp > last_synced_timestamp→ proceed to the new-content assessment below.
Assess New Content
Read the JSONL entries with timestamps after last_synced_timestamp. Filter out noise. The filter list is schema-aware:
For jsonl_schema == "cc":
type: "system"messagestype: "permission-mode"entriestype: "file-history-snapshot"entries- User messages containing only
/exit,/quit, or session-end signals isMeta: trueentries
For jsonl_schema == "codex":
type: "turn_context"entries (per-turn configuration)type: "event_msg"withpayload.typein{task_started, token_count}(system markers and telemetry)type: "response_item"withpayload.type == "reasoning"(model-internal thinking, analogous to CC thinking blocks)- User messages (
type: "event_msg"withpayload.type == "user_message") containing only/exit,/quit, or session-end signals type: "session_meta"(metadata-only, already consumed for session_id extraction)
After filtering, judge whether the remaining entries represent meaningful work — substantive user messages, assistant responses with real content, tool calls that changed files, or decisions made.
Not meaningful (e.g., resume then immediately exit, only system noise) → stop: "Session {session_id} has entries after {last_synced_timestamp} but no meaningful new work. Skipping."
Meaningful → set sync_mode = "update", store existing_session_doc_id and last_synced_timestamp, proceed to Phase 1-2.
1-2. Retrieve Open Tasks
Before reading the full JSONL, retrieve project-scoped open tasks from the vault so the session drafter can identify which tasks this session resolved.
Parallel execution with Phase 1-1: the project-name parse step below runs immediately (no MCP), and the akb_search for open tasks is independent of Phase 1-1's duplicate-detection search. Issue Phase 1-1's existing-session search and Phase 1-2's open-task search in a single parallel tool-use block. Act on Phase 1-2 results only after Phase 1-1 greenlights continuation (otherwise discard).
Derive Project Name
Project name is the basename of the source session's working directory. Use session_cwd (captured in Phase 1-1) when set — it is authoritative metadata from the JSONL, and cross-target delegate (e.g. Claude → Codex) puts the receiver in a different pwd than where the session originated, so pwd is not a reliable fallback.
session_cwdset →project_nameis its basename. (For CC worktree sessions this isoriginalCwd, the unworktreed project root, not the worktree name.)session_cwdunset (CC session with noworktree-stateentry) → use the current working directory's basename. The CC JSONL parent directory uses a lossy encoding (/and.both map to-), so decoding it is unreliable and not used.
If derivation fails entirely, set project_name to the empty string. The downstream open-tasks query becomes a no-op (no project: tag match) — degraded but safe.
Query Open Tasks
mcp__akb__akb_search(query="open task",vault="{vault_name}",collection="sessions/tasks",type="task",tags=["project:{project_name}"],limit=20)
From the results, exclude any document whose tags contain resolved. Store the remaining results as open_tasks — a list of {doc_id, title, summary} for each match.
If no results or the query fails, set open_tasks to an empty list and continue.
Phase 2 — Draft Session Note
The main agent acts as the session drafter. Read the JSONL file, extract session information, and produce both a session draft (navigator skeleton) and a session context for sub-agents. The session draft is finalized later in Phase 4-0 after sub-drafter results are reconciled.
2-1. Read Session JSONL
Read the JSONL file. It contains the complete conversation history: user messages, assistant responses, tool calls, and results. The field-name layout differs by jsonl_schema (set in Phase 1-1) — apply the analogous mapping when extracting content:
| Concept | CC schema | Codex schema | |
|---|---|---|---|
| User message | type: "user" entry, message.content[] with type: "text" | type: "event_msg", payload.type: "user_message" | |
| Assistant text response | type: "assistant" entry, message.content[] with type: "text" | type: "event_msg", payload.type: "agent_message" (and/or response_item with payload.type: "message") | |
| Tool call | type: "assistant" entry, message.content[] with type: "tool_use" (nested inside the assistant turn — NOT a top-level event type) | type: "response_item", payload.type: "function_call" | |
| Tool result | type: "user" entry, message.content[] with type: "tool_result" (nested inside a user-role turn that wraps the tool reply) | type: "response_item", payload.type: "function_call_output" |
Update mode (`sync_mode = "update"`): Focus JSONL analysis on entries with timestamps after last_synced_timestamp — this is where the new information lives. The session draft you produce must be a combined compiled truth: read existing_session_content (retrieved in Phase 1-1) and integrate its Summary / Narrative / Artifacts / Knowledge Produced / Open Questions with the new work from the resumed portion. The written draft is the new canonical state of the session, not a delta.
New mode (`sync_mode = "new"`): Read the full JSONL as normal.
Focus on extracting:
- Work performed (main topics and tasks), as a chronological narrative
- Files and commits touched
- External URLs (articles, repos, docs) referenced in the session
- Unresolved questions (to be promoted in Phase 4-0 where possible)
Skip tool call/result noise — focus on user messages and assistant text responses. Reusable lessons and long-lived decisions will be extracted by the Phase 3 drafters; the session draft itself does not need to generalize.
2-2. Identify Session Commits and Repository
Scan the JSONL for git commit activity. Extract commit hashes to determine the range of changes made during the session.
If commits were found, run:
git log --oneline {earliest_commit_hash}^..{latest_commit_hash}git diff --stat {earliest_commit_hash}^..{latest_commit_hash}
If no commits were found in the JSONL, skip these commands.
Also collect the repository URL:
git remote get-url origin
Store the result as repo_url. If the command fails (no remote configured), set repo_url to null.
2-3. Draft Session Note
Compose the session draft per references/session-template.md — that file carries the full draft template, section-omission rules, update-mode handling, and authoring guidelines. The session note is a navigator + narrative + timeline; deep knowledge (reusable lessons, long-lived decisions, follow-up tasks, creative ideas) belongs to the Phase 3 sub-notes and must not be restated here.
Determine the session's primary theme (debugging vs. feature build vs. refactor) and let it guide the emphasis in ## Narrative. Number ## Open Questions bullets Q1, Q2, … so Phase 3 sub-drafters can target them via resolves_question: Q{n}.
2-4. Identify Resolved Tasks
Compare the work performed in the session (from Phase 2-1) against the open_tasks list (from Phase 1-2).
A task is "resolved" if the session performed substantial work directly addressing the task's topic. Partial completion counts — any remaining work will be captured as a new task by the task-drafter.
For each resolved task, record:
doc_id— from the open_tasks entrytitle— the task's titlepath— the task's path (for Knowledge Produced link)reason— brief explanation of how it was addressed (e.g., "Implemented the retry logic described in the task")
Store as resolved_tasks list. If no tasks were resolved, set to an empty list.
2-5. Construct Session Context for Sub-Agents
Build a lightweight session context as a roadmap for the Phase 3 drafter agents. This supplements the session draft and JSONL — it helps agents orient quickly.
Session Context:- Project: {project name}- Repository: {repo_url from Phase 2-2, or "N/A"}- Date: {YYYY-MM-DD from JSONL or today}- Session JSONL: {$JSONL_PATH resolved in Phase 0}- Work performed: {brief list of main topics/tasks}- Committed changes: {from Phase 2-2 — commit messages and files changed, or "None"}- Problems encountered (for possible troubleshooting TILs): {brief list}- Long-lived decisions considered (for possible Decision notes): {brief list}- Rejected alternatives (anti-pattern memory for Decisions): {brief list}- Tools/technologies used: {notable APIs, libraries, frameworks}- Vault name: {resolved vault_name from --vault}- Collections: {plugin's default_collections mapping — includes decisions}- Ingest mode: {new|update}- Last ingested timestamp: {ISO timestamp from existing session note, or "N/A" if new}- JSONL analysis start: {last_synced_timestamp if update, or "beginning" if new}- Open tasks: [{doc_id}] {title} (for each task in open_tasks, or "None")- Resolved tasks: [{doc_id}] {title} — {reason} (for each task in resolved_tasks, or "None")- Open Questions (session draft): Q1: {text}, Q2: {text}, ... (each question available for sub-drafters to resolve via `resolves_question: Q{n}`)
Keep this concise — it is a roadmap, not a comprehensive record.
Phase 3 — Parallel Sub-Drafting & Validation
3-1. Launch Drafter Agents
Launch 4 subagents with fork_context: false. Each subagent receives the session context, a session headline (frontmatter + Summary + numbered Open Questions — not the full Narrative), and the JSONL path.
Open these prompt files and use their contents as the worker prompts:
./support/agents/til-drafter.mdwith modelgpt-5.4-miniand reasoning effortmedium./support/agents/task-drafter.mdwith modelgpt-5.4-miniand reasoning effortmedium./support/agents/idea-drafter.mdwith modelgpt-5.4and reasoning effortxhigh./support/agents/decision-drafter.mdwith modelgpt-5.4-miniand reasoning effortmedium
Prepend the session context and session headline to each prompt.
Agent Summary
| Agent | Model | Purpose | Output | |
|---|---|---|---|---|
| til-drafter | gpt-5.4-mini | Learnings + vault validation | 0–N learning notes with dispositions + til_kind tag | |
| task-drafter | gpt-5.4-mini | Follow-up tasks + vault validation | 0–N task notes with dispositions | |
| idea-drafter | gpt-5.4 | Creative ideas + vault validation | 0–N idea notes with dispositions | |
| decision-drafter | gpt-5.4-mini | Long-lived decisions (ADR) + vault validation | 0–N decision notes with dispositions |
Each agent receives:
- Session Context — lightweight roadmap (from Phase 2-5)
- Session Headline — frontmatter +
> **Summary:**line + numbered## Open Questions. The full Narrative is not sent — drafters read the JSONL directly for domain-specific detail. - JSONL path — primary source for each drafter's domain analysis
- Vault config —
vault_nameandcollectionsfor duplicate checking
Each agent drafts notes, then validates them against the vault using akb_search. Drafts are returned with a disposition (create / append; supersede additionally for decisions), optional resolves_question: Q{n} when a draft addresses a specific session Open Question, and related_to doc IDs. Duplicates are reported as skipped and excluded from the output.
idea-drafter specific. Idea drafts additionally carry impact / effort / confidence enum assessments. The drafter silent-drops any idea it rates impact: low before returning (no report, no skipped block). The main agent re-triages the surviving drafts in Phase 3-2.
Agents that find nothing to write return "No {type} notes for this session."
3-2. Triage Idea Drafts
After the idea-drafter agent returns, the main agent filters the surviving idea drafts using their impact / effort / confidence frontmatter fields. The drafter already silent-dropped any idea it rated impact: low before returning; this phase is a second gate that catches cases the drafter missed and removes high-cost, low-conviction ideas.
Scope. Applies only to idea drafts. TIL / task / decision drafts pass through unchanged.
Inputs. Each idea draft from Phase 3-1 with disposition ∈ {create, append}.
Automatic drop rules — apply in order, drop on first match:
impact == low— drop. Safety net for the drafter's self-filter.effort == largeANDconfidence == low— drop. High-cost, low-conviction ideas clutter the vault.
All remaining idea drafts pass through to Phase 4.
No user prompt. No skipped block written. No vault write. Dropped drafts are discarded in-memory; they do not feed Phase 4-0 or Phase 4-2. If a dropped idea set resolves_question: Q{n}, discard that mapping as well — the Q ID stays in the session's ## Open Questions.
Phase 4 — Write to AKB
Save all drafts with create, append, or (decisions only) supersede disposition directly — no user approval needed. Each drafter sets status: active in its ---DRAFT--- block; Phase 4-1 / 4-2 pass it through verbatim.
4-0. Dedup Drafts & Finalize Session Body
In-memory reconciliation between the session draft and the sub-note drafts before any AKB write. Single in-context pass — no extra LLM call, no MCP call, no per-pair loop. Inputs: session draft body, all sub-note drafts with disposition ∈ {create, append, supersede}, resolved_tasks list.
Step A — Promote Open Questions. For each sub-note draft with resolves_question: normalize the value (strip / upper / ^Q\d+$; non-conforming → log warning + no-match), find the matching Q{n} bullet in the session's ## Open Questions, and remove that bullet entirely — the answer surfaces in ## Knowledge Produced (Step C). Multiple sub-notes can target the same Q{n}; the bullet is removed once. If no session question matches, log a warning and leave the sub-note unchanged.
Step B — Cross-Type Content Overlap Detection. For each sub-note, extract its headline sentence — TIL: > **Key Insight:** …; Task: > **Next Action:** …; Idea: > **Inspiration:** … plus Proposal first sentence; Decision: > **Context:** … plus Decision first sentence. Scan the session Narrative and Artifacts prose for sentences that match a headline. A sentence matches when at least two of these hold: (1) core noun phrases overlap, (2) assertion direction is the same (not negated / qualified differently), (3) reasoning or evidence cited is the same. One criterion alone is not enough — preserve the Narrative sentence if in doubt. On a match, remove the redundant sentence or replace it with a brief pointer ("…the lesson captured in Knowledge Produced"); do not rewrite surrounding prose beyond what grammatical flow needs. Overlap resolution always favors sub-notes — never edit a sub-note body here.
Step C — Finalize Knowledge Produced. Build the block from deduplicated sub-notes plus resolved_tasks, in the order TIL → Idea → Task → Decision → Resolved:
## Knowledge Produced-🧠 TIL: [{title}](sessions%2Flearnings%2F{doc_path}.md) — {one-line hook from Key Insight}-💡 Idea: [{title}](sessions%2Fideas%2F{doc_path}.md) — {one-line hook from Inspiration}-✅ Task: [{title}](sessions%2Ftasks%2F{doc_path}.md) — {one-line hook from Next Action}-📐 Decision: [{title}](sessions%2Fdecisions%2F{doc_path}.md) — {one-line hook from Decision first sentence}-♻️ Resolved: [{title}](sessions%2Ftasks%2F{path}.md) — {reason from resolved_tasks}
For newly-drafted sub-notes the link's doc_path is substituted at render time from each Phase 4-2 put response. For resolved tasks the link uses the existing task's path captured in Phase 2-4. See references/note-templates.md → "Cross-Note Links" for why this hybrid (body link + depends_on frontmatter) is used.
Step D — Final omissions. Omit ## Knowledge Produced entirely when no sub-notes were produced and no tasks resolved. Omit ## Open Questions if Step A left zero unresolved bullets. The resulting body is what Phase 4-1 writes — do not mutate further.
4-1. Write Session Note
The session note is written first because sub-notes reference it via depends_on. Sub-notes always use the full URI session_uri returned by AKB (always-URI cross-ref convention; future-proof against vault tier splits).
New mode (`sync_mode = "new"`). Extract metadata from the draft's ---DRAFT--- block and akb_put(vault="{vault_name}", ...) with type="session", collection={draft.collection}, status={draft.status}, tags=["session", "codex", "session", "session:{session_id}", "project:{project_name}", "last-synced:{jsonl_last_timestamp}", ...topic_tags], and the finalized body from Phase 4-0. Capture session_doc_id and session_doc_path.
Update mode (`sync_mode = "update"`). The session note already exists. Phase 2-3 + 4-0 produced a full compiled-truth replacement plus exactly one new timeline entry. Apply the timeline merge procedure — Mode A (full-body merge) (see appendix) against existing_session_content. Build the updated tag list (replace the old last-synced:{old_timestamp} with last-synced:{jsonl_last_timestamp}; preserve all other tags; add any new topic tags). Then akb_update(uri=existing_session_uri, content={merged}, tags={updated}, summary={draft.summary}, message="Update from resumed session: {brief description}"). Reuse session_uri = existing_session_uri for sub-notes' depends_on.
4-2. Write Sub-Notes (Learning, Task, Idea, Decision)
Issue all sub-note `akb_put` calls in a single parallel tool-use block. Sub-notes are independent of each other; only the session note's order matters (via depends_on).
For each sub-note, extract metadata from the draft and call akb_put(vault="{vault_name}", ...) with collection={draft.collection}, type={draft.type}, status={draft.status}, tags=["session", "codex", ...draft_tags], depends_on=[session_uri], related_to=[...draft.related_to] (full URIs as emitted by drafters). Collection per type: TIL → sessions/learnings, Task → sessions/tasks, Idea → sessions/ideas, Decision → sessions/decisions.
Idea notes — append four namespaced tags to draft_tags before put: category:{draft.category}, impact:{draft.impact}, effort:{draft.effort}, confidence:{draft.confidence}. Drop duplicates (the drafter may have already emitted some as tags). These four tags are the durable carriers for the external maintenance layer's consolidation synthesis and staleness checks.
`append` disposition — the drafter has already merged compiled truth + one new timeline entry. Read the existing document with akb_get(uri=draft.append_uri), apply timeline merge Mode A against draft.body, and akb_update(uri=draft.append_uri, content={merged}, tags=[...existing, ...new from draft], summary=draft.summary, message="Appended from session ingest: {brief description}").
`supersede` disposition (decisions only) — write the new decision via akb_put(vault="{vault_name}", ..., tags=[..., "supersedes:{old_doc_id}"]) (tags use bare doc_id; the : already binds key:value). Capture new_doc_id and new_doc_path; capture new_doc_ref from the returned canonical URI. Then read the old decision via akb_get(uri=draft.supersedes_uri), apply timeline merge Mode B with new_entry = "- **{today}** | Superseded by {new_title} ({new_doc_ref}). {reason}. [Source: session:{session_id}, {today}]", and akb_update(uri=draft.supersedes_uri, content={merged}, tags=[...old, "superseded-by:{new_doc_id}"], status="archived", message="Superseded by {new_doc_ref} in session ingest"). Setting status="archived" removes the old decision from the external maintenance layer's active-document pools.
4-3. Resolve Completed Tasks
For each task in resolved_tasks:
- Read the task document with
akb_get(uri={task_uri}), then compose the resolution timeline entry- **{today YYYY-MM-DD}** | Task resolved in session:{session_id}. {reason}. [Source: session:{session_id}, {today YYYY-MM-DD}]and apply timeline merge Mode B againsttask_content(compiled-truth zone preserved as-is). akb_update(uri={task_uri}, content={merged}, tags=[...existing, "resolved"], status="archived", message="Resolved in session {session_doc_id}").
The archived task document is the record of resolution.
Error Handling
If a drafter agent (Phase 3) fails or returns an error:
- Continue with results from the remaining agents
- Log the failure in the session summary
- Do not block the pipeline for a single agent failure
If an akb_put fails partway through Phase 4:
- Continue writing the remaining notes from this invocation
- The partial result is queryable under the
"session:{session_id}"tag
If Phase 4-0 Step A finds a resolves_question that does not match any session Open Question ID:
- Log a warning naming the sub-note and the invalid Q ID
- Leave the sub-note draft as-is, write without session mutation for that question
References
references/session-template.md— session draft template, section-omission rules, update-mode handling, authoring guidelines.references/note-templates.md— sub-note templates (TIL / task / idea / decision).- Agent prompts in
support/agents/— full drafter-agent instructions.
Appendix: Timeline Merge Procedure
Shared procedure for every skill that writes back to an existing note. Two modes — every caller picks one explicitly.
Shared helper. split_body(content) — locate the first standalone --- line that is followed (eventually) by a ## Timeline heading. Return (body_above, timeline_entries). If no ## Timeline heading exists, return (content, []).
Mode A: Full-body merge
Used when the caller has produced a rewritten compiled-truth body (the new canonical state) plus exactly one new timeline entry. Callers: session-ingest Phase 4-2 (session update), session-ingest Phase 4-3 (sub-note append), and ingest-jira (issue upsert).
Inputs: draft_body (full new body — includes --- + ## Timeline + one new bullet), existing_content (current doc).
(draft_above, draft_entries) = split_body(draft_body).draft_entriesmust contain exactly one bullet. If it contains zero, treat as drafter error → fall back toakb_updatewithdraft_bodyunchanged and log a warning.(_, existing_entries) = split_body(existing_content).- Compose merged content:
``` {draft_above}
## Timeline
{draft_entries[0]} {existing_entries} ```
- Newest entry first. Never edit or remove past entries.
Mode B: Entry-only append
Used when the caller produced only a new timeline bullet and wants the existing compiled-truth zone preserved verbatim. Callers: session-ingest Phase 4-4 (task resolve) and the decision supersede path, ingest-doc re-ingest, and ingest-confluence re-ingest.
Inputs: new_entry (a single bullet line, possibly with sub-bullets), existing_content.
(existing_above, existing_entries) = split_body(existing_content).- If
existing_contenthad no## Timelinesection, append"\n\n---\n\n## Timeline\n\n"toexisting_abovebefore proceeding. - Compose merged content:
``` {existing_above}
## Timeline
{new_entry} {existing_entries} ```
- Never rewrite the compiled-truth zone in this mode. If the caller needs to change compiled truth, it must use Mode A instead.