<< All versions

Skill v1.0.1

currentAutomated scan100/100
dnotitia/akb/session-ingest

+4 new

──Details
PublishedNovember 1, 2026 at 08:07 AM
Content Hashsha256:948c717356f1fcac...
Git SHA0c6d8920b943
Bump Typepatch
Compare with v1.0.0
──Files
Files (1 file, 32.2 KB)
SKILL.md32.2 KBactive
SKILL.md · 530 lines · 32.2 KB

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

text
Phase 0 — Resolve parse args, resolve JSONL path (auto or explicit), maybe delegate & exit
Phase 1 — Prepare detect duplicates, fetch open tasks
Phase 2 — Draft read JSONL, identify commits/repo, draft session note
Phase 3 — Sub-Draft launch til / task / idea / decision drafters; triage ideas
Phase 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.

bash
SELF="codex"
if [ -n "$DELEGATE" ] && [ "$DELEGATE" = "$SELF" ]; then
echo "Refusing $SELF -> $SELF delegate (would recurse)." >&2
exit 2
fi

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.

bash
if [ -z "$JSONL_PATH" ]; then
if [ -z "${CODEX_THREAD_ID:-}" ]; then
echo "CODEX_THREAD_ID empty — not inside a Codex thread. Pass <jsonl_path> explicitly." >&2
exit 2
fi
CODEX_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" ]; then
echo "No JSONL found for thread ${CODEX_THREAD_ID} under $CODEX_ROOT" >&2
exit 2
fi
fi

Verify the resolved path exists before proceeding:

bash
if [ ! -f "$JSONL_PATH" ]; then
echo "JSONL path not readable: $JSONL_PATH" >&2
exit 2
fi

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 acceptEdits auto-approves file edits; Bash/MCP rely on the skill's allowed-tools.
  • codex: -s workspace-write -c approval_policy="never" — confines file writes to the workspace AND skips approval pauses. --skip-git-repo-check lets 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.
bash
LOG="${TMPDIR:-/tmp}/session-ingest-delegate-$(date +%s)-$$.log"
ARGS="\"$JSONL_PATH\" --vault \"$VAULT\""
[ -n "$LANG_FLAG" ] && ARGS="$ARGS --lang \"$LANG_FLAG\""
case "$DELEGATE" in
claude)
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)" >&2
exit 2
;;
esac
if [ -n "$DELEGATE" ]; then
exit 0
fi

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 sessionId field (e.g., {"type": "worktree-state", "sessionId": "..."} or {"type": "user", "sessionId": "..."}), the JSONL is CC schema (flat per-line events). Set jsonl_schema = "cc".
  • If any line has a top-level payload object containing id (e.g., {"timestamp": "...", "type": "session_meta", "payload": {"id": "...", "cwd": "..."}}), the JSONL is Codex schema (event-wrapped). Set jsonl_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 sessionId field as session_id. (Lines without sessionId such as file-history-snapshot are skipped — they precede the first content event in some sessions.)
  • If a worktree-state entry is present, store worktreeSession.originalCwd as session_cwd; otherwise leave session_cwd unset.

For jsonl_schema == "codex":

  • Find the entry with type == "session_meta" (typically the first line). Store payload.id as session_id and payload.cwd as session_cwd.

For both schemas:

  • Last entry's top-level timestamp → jsonl_last_timestamp.

Store:

  • session_id — the resolved session/thread id
  • jsonl_last_timestamp — the ISO 8601 timestamp of the final entry
  • session_cwd — the source session's working directory (or unset)

Search Vault for Existing Session

text
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:

text
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" messages
  • type: "permission-mode" entries
  • type: "file-history-snapshot" entries
  • User messages containing only /exit, /quit, or session-end signals
  • isMeta: true entries

For jsonl_schema == "codex":

  • type: "turn_context" entries (per-turn configuration)
  • type: "event_msg" with payload.type in {task_started, token_count} (system markers and telemetry)
  • type: "response_item" with payload.type == "reasoning" (model-internal thinking, analogous to CC thinking blocks)
  • User messages (type: "event_msg" with payload.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_cwd set → project_name is its basename. (For CC worktree sessions this is originalCwd, the unworktreed project root, not the worktree name.)
  • session_cwd unset (CC session with no worktree-state entry) → 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

text
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:

ConceptCC schemaCodex schema
User messagetype: "user" entry, message.content[] with type: "text"type: "event_msg", payload.type: "user_message"
Assistant text responsetype: "assistant" entry, message.content[] with type: "text"type: "event_msg", payload.type: "agent_message" (and/or response_item with payload.type: "message")
Tool calltype: "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 resulttype: "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:

bash
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:

bash
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 entry
  • title — the task's title
  • path — 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.

text
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.md with model gpt-5.4-mini and reasoning effort medium
  • ./support/agents/task-drafter.md with model gpt-5.4-mini and reasoning effort medium
  • ./support/agents/idea-drafter.md with model gpt-5.4 and reasoning effort xhigh
  • ./support/agents/decision-drafter.md with model gpt-5.4-mini and reasoning effort medium

Prepend the session context and session headline to each prompt.

Agent Summary

AgentModelPurposeOutput
til-draftergpt-5.4-miniLearnings + vault validation0–N learning notes with dispositions + til_kind tag
task-draftergpt-5.4-miniFollow-up tasks + vault validation0–N task notes with dispositions
idea-draftergpt-5.4Creative ideas + vault validation0–N idea notes with dispositions
decision-draftergpt-5.4-miniLong-lived decisions (ADR) + vault validation0–N decision notes with dispositions

Each agent receives:

  1. Session Context — lightweight roadmap (from Phase 2-5)
  2. Session Headline — frontmatter + > **Summary:** line + numbered ## Open Questions. The full Narrative is not sent — drafters read the JSONL directly for domain-specific detail.
  3. JSONL path — primary source for each drafter's domain analysis
  4. Vault config — vault_name and collections for 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:

  1. impact == low — drop. Safety net for the drafter's self-filter.
  2. effort == large AND confidence == 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:

markdown
## 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:

  1. 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 against task_content (compiled-truth zone preserved as-is).
  2. 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).

  1. (draft_above, draft_entries) = split_body(draft_body). draft_entries must contain exactly one bullet. If it contains zero, treat as drafter error → fall back to akb_update with draft_body unchanged and log a warning.
  2. (_, existing_entries) = split_body(existing_content).
  3. Compose merged content:

``` {draft_above}


## Timeline

{draft_entries[0]} {existing_entries} ```

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

  1. (existing_above, existing_entries) = split_body(existing_content).
  2. If existing_content had no ## Timeline section, append "\n\n---\n\n## Timeline\n\n" to existing_above before proceeding.
  3. Compose merged content:

``` {existing_above}


## Timeline

{new_entry} {existing_entries} ```

  1. Never rewrite the compiled-truth zone in this mode. If the caller needs to change compiled truth, it must use Mode A instead.
← v1.0.0All versions