# The SessionStart hook (memprove hook)

`memprove hook session-start` is the handler `hook install` writes into `.claude/settings.local.json`. At the start
of every Claude Code session in the Workspace it reads the session's JSON on standard input, chooses the Task (this
client session's context, else this directory's context, else the only Task in progress in this Workspace), and prints
one opening statement and the Pack in a fence, within a budget of 9000 code units for the Pack and 10000 for the whole
output; it sends read requests only and always exits 0. The opening says which Task and why, the current UTC time, the
agent the environment declares, and the open-item sentence of the takeover rule; on a `fork` source it adds that the
listed Sessions were not started by this session. A `completed` or `cancelled` Task gets no Pack: When this client
session's context names one, the hook prints only a statement that names the Task and its state; When this directory's
context names one, the hook goes on to the only Task in progress in this Workspace. With no Task to choose it prints
one statement that says so and names the configuration directory it used.

**The hook's time limit.** The hook gives itself 8 seconds to read the Pack, within the handler's own `timeout` of 10
seconds that `hook install` writes, so that Claude Code never cuts it off first. When the daemon has not answered by
then, it stops waiting, prints only

```text
memprove: the Context Pack was not read within 8 seconds; no Context Pack was injected into this session; running memprove resume --pack in this session fetches it.
```

and exits 0, about 8 seconds after it started; it writes no last-Pack record and prints no token. The client then
starts with no Pack and can read one with `memprove resume --pack` (or the MCP `pack` tool) once the daemon answers.
To reproduce it, point the hook at a socket that accepts and never answers, in a Workspace whose context names a Task
(`MEMPROVE_DATA_DIR=/tmp/mute memprove hook session-start`).

### One context per client session

With `MEMPROVE_CLIENT_SESSION` set (the hook sets it for the Bash commands of the Claude Code session), every
command uses that client session's own context file and last-Pack record instead of the directory's. Two sessions in
the same directory then `begin` their own Sessions and never overwrite each other's context. A value that is not 1 to
128 characters of `A-Z`, `a-z`, `0-9`, `_` and `-` is ignored, with a warning. `memprove mcp serve` also takes
`CLAUDE_CODE_SESSION_ID`, which Claude Code sets for it. The agent (`MEMPROVE_AGENT_NAME`, `MEMPROVE_AGENT_ROLE`)
is read by each process from its own environment, not from the client session.

**Sub-agents, one client session each.** An agent that hands pieces of one Task to sub-agents working in the same
directory gives each of them its own client session name, set for every command the sub-agent runs. Such a client
session is new: it has no context, so a plain `begin` is a usage error. The orchestrator takes the two IDs from this
directory's context and puts them in the sub-agent's instructions:

```bash
memprove context show                  # the directory's context: Project, Task (no MEMPROVE_CLIENT_SESSION)
export MEMPROVE_CLIENT_SESSION=<name>  # in the sub-agent's commands
export MEMPROVE_AGENT_NAME=<name> MEMPROVE_AGENT_ROLE=sub_agent MEMPROVE_PARENT_SESSION=<the orchestrator's Session>
memprove begin --project <PROJECT_ID> --task <TASK_ID>
memprove run --test-format cargo -- cargo test
memprove checkpoint create --fact "<what holds, conclusion first>" --evidence r1 --next "<next>"
memprove end --reason "<piece> done"
```

Each sub-agent runs its own commands, records its facts and open items on its own runs, and ends its own Session; it
leaves `decide`, `task complete`, `--withdraw` and `--reaffirm-all` to the orchestrator. It may `--supersedes` the
open items of its own stage and the statements of its own earlier Sessions (the same agent name); it does not
supersede or withdraw a statement of another sub-agent or of the orchestrator. The orchestrator corrects afterwards:
`--supersedes` for a statement that is wrong, `--withdraw` for one that no longer holds, `--reaffirm` for a fact that
holds again. `resume --text` ends each statement with `from Session k` (`from Session k「<name>」` when that Session
declared an agent), which says which sub-agent wrote it.

**What the daemon refuses a sub_agent.** A Session that declared `--agent-role sub_agent` (or
`MEMPROVE_AGENT_ROLE=sub_agent`, or the MCP `begin`'s `agent_role`) is refused three writes with `403
role_forbidden`, exit 1, and nothing is written; standard error ends with the daemon's reason, one fixed sentence that
says who does it instead:

| write | refused when | standard error ends with |
|---|---|---|
| `decide` | always | `a Session declared as sub_agent cannot record a Decision; the orchestrating session or a person does it` |
| `task complete` (and `task transition --to completed`) | the request names a `sub_agent` Session, whatever runs it cites: `task complete` sends the context's Session (with none in the context it is refused, exit 2, before any request), and `task transition` sends it only with `--to completed` of the context's Task while the context has a Session; with no Session in the context (after `end`) or another Task named, it sends none, and the daemon does not judge that completion by role | `a Session declared as sub_agent cannot move the Task to completed; the orchestrating session or a person does it, and they can cite any Session's runs` |
| `checkpoint create` with `--supersedes`, `--reaffirm`, `--reaffirm-all`, `--withdraw`, or `--withdraw-session` | a target was written by a Session of another agent name, or by one that declared no agent, and is not an open item it supersedes (a withdrawal of another agent's open item is refused too) | `a Session declared as sub_agent can supersede open items and the statements written by Sessions of the same agent name, and can withdraw only statements written by Sessions of the same agent name; superseding another agent's facts, next actions, and Decisions, and withdrawing any statement another agent wrote, is for the orchestrating session or a person` |

So a sub-agent may supersede another agent's open item; may not supersede another agent's fact, next action, or
Decision; may not withdraw anything another agent wrote, an open item included; and may supersede or withdraw anything
a Session of its own agent name wrote. `--reaffirm <N>` is a `--supersedes` of fact `N` with the same text, so it
follows the same rule. `--next` alone replaces the next action in force without an edge and is never refused. A
`sub_agent`'s `--next` therefore replaces the next action in force even when another agent wrote it: that replacement
is a rule, not an edge the writer records, so the role check does not see it; the fixed text of the `403` speaks of an
explicit `--supersedes` of a next action. One refused target refuses the whole Checkpoint. Ending another Session with
`session end <SESSION_ID> --abandoned` is allowed to every role. An orchestrator, a person (`--agent-role human`) and
a Session that declared no agent are refused nothing. The role is what the Session said about itself: the check guards
against a mistake, not against an agent that declares another role. A completion sent without a Session (an older
client, `task transition --to completed` after `end` or of another Task, or a direct call of the API without
`session_id`) is not checked by role: the CLI sends only the Session its context holds.

**Ending a sub-agent's Session.** An orchestrator that ends a Session a sub-agent left open, `memprove session end
<SESSION_ID> --abandoned --reason "<why>"` from its own context, is recorded as that Session's ender: its own Session
and the agent it declares. The ended Session's line then ends with `; ended by Session <j>` (`; ended by unlisted
Session <id>` when that Session belongs to another Task, `; ended by agent「<name>」(<role>)` when the ender has no
Session in its context). Nothing is typed for the ender, and no option names another one.
