Session, agent, and job
Source:
harness/docs/runtime/session-job.md
Status: current
Session, agent, and job
Section titled “Session, agent, and job”Parents: ../canon/grain.md, control-effects.md, approval.md, ../client/transcript.md, ../packages/plexus-expectations/README.md.
Not this file: license pipeline (approval.md); TUI paint (../client/transcript.md); grain spine (../canon/grain.md); compact fold ABI (control-effects.md). Do not rename Actor to Agent. Do not mint Jobs from the client.
History is head and each turn’s tail. Agent is a snapshotted spec plus that pointer. Job is a WorkContainer, not a WorkExpectation. Background is a flag on the node, not a type and not a second owning list. POLICY FOLLOWS HEAD. Leaves license; sequences project. Completions and judges stay PEW.
1. One sentence
Section titled “1. One sentence”History is head and each turn’s tail; Agent is a snapshotted spec plus that pointer; Job is the orchestration graph (a Turn is a Job; a tool call is a host ToolCallJob; tool BODY is the leaf executor after license; completions stay PEW); background is a flag on the node, not a type and not a second lane.
2. Vocabulary
Section titled “2. Vocabulary”| Speech | Meaning |
|---|---|
| SessionRoot | The session plexus document. Workspace path, operator overlay, chrome. Not a message bag. |
| AgentSpec | Reusable recipe. Identity of a kind of conversational worker. Snapshot onto the Agent at mint. Not cwd, not frozen tools, not cost. |
| Agent | This instance: spec snapshot + head pointer, optional parent, optional nickname. Thin. |
| Head | Live list tip (Agent.head; SessionRoot.head is an alias, not a second synced pointer). Attribution walks tail. findRunningTurn / liveTurns start here. |
| Tail | “I continue X”, not parent. SessionTurn.tail is the previous SessionUnit (genesis SessionSystemPrompt). CompletionJob.tail / TurnCompactChunk.tail is the previous tape cell or the turn; remainder continues the tool. Isolated intern sits under the turn with no tail. |
| WorkContainer | Work-graph class. Brand is on the class — do not duck-type .work. walkWork is a preorder of .work. |
| Job | Orchestration graph node: class Job extends WorkContainer. children, background, walkBoundary, orchestrationResult, computed orchestrated, cancelled, completed. Collectors live here. Not a PEW kind. Package has only this class. |
walkBoundary |
Contour flag on Job. false (default): collectors expand this node’s children. true: collectors yield the node and stop. walkWork still descends. SessionTurn stays false. |
| Collectors | jobs / foregroundJobs / backgroundJobs on Job. Expand until walkBoundary. Split by the yielded node’s own background flag. Skip non-Job children. COLLECTORS ARE NOT .work. |
| Approval cursor | approvalQueue / approvalQueueHead on sequence Jobs. Coverable Approvable leaves (needsHuman). Not collectors. Not .work. Law: approval.md. |
background |
Job flag. This child is on the parent Job’s background plane. Not delivery. Not a second list. ReACT join releases at completed || (orchestrated && background). |
| SequenceJob | Host: SessionTurn, ProgrammaticJob. Cursor over leaves. Not Approvable. |
| ProgrammaticJob | Host sequence. Scheme bag (interpretSchemeRepl). Inner admits are nested ToolCallJobs. Guard returns true; inner leaves still license. |
| PEW | World-settling obligation (../canon/grain.md). Completions, judges, licensed MCP tools/call, licensed web_search / web_extract. Not a Job kind. PEW does not parent PEW except a judge child of an ApprovableJob. |
| Actor | One handler per PEW/Work. This Agent is the conversational worker, not a PEW Actor. |
| Honor | Orchestrator autorun over leftover attach PEW. New completions / MCP / web dispatch from origin (complete / Job BODY). |
| Executor | registerExecutor(Class, guard, execute). Guard writes orchestrationResult. Law: approval.md. |
There is no JobHub. There is no JobNode brand. There is no TurnJob class. There is no InterpretationExpectation. There is no delivery field on Job. There is no WorkDemand. Background is the Job flag, not a second conversation lane.
3. Three hosts
Section titled “3. Three hosts”Cohort harnesses store a message list on a Session object. Once the list is gone, that bag is three jobs:
| Host | Holds |
|---|---|
| AgentSpec | Recipe: name, description, constitution, nature (session | project | global; empty until mint / foreground), keepAlive, voidLegal. Snapshot at mint. Upgrade is a new snapshot + emit, not a silent rewrite of sealed genesis. AskAgentSpec is one-shot (omitted-spec ask, or agent/one-shot/* stamped with a recipe) — class, not a catalog recipe, not a nature. |
| Agent | spec + head + optional parent + optional nickname. Test for any field tempted onto Agent: can it be spec default, session overlay, or a walk from head? If yes, it does not belong on Agent. |
| SessionRoot | Document: sessionId, path, kvs, mode (act/plan posture), standingPolicy, actions, queues, allow/block lists, readAllowlist, agent. |
SessionRoot.head is an alias for agent.head. findRunningTurn / liveTurns start there. Head writes are the turn, compact, and eval RPCs. The document owns the verb; the agent owns the pointer.
Sorting test (resume / fork / spawn), before adding a field:
| Test | Host |
|---|---|
| Resume of this conversation must restore it | SessionRoot if it survives minting a new head; head if it is true of this round (inherit via tail) |
| Reuse it to start a different conversation of the same kind | AgentSpec |
| Every new conversation re-reads it from disk | config / project |
| It only exists while the loop is alive | runtime |
Dual-homes model / cwd / mode were dual because those three jobs shared a spelling on one struct.
| Name | Spec | Session overlay | Head / walk |
|---|---|---|---|
| model | Recipe default | kvs.model |
Completion receipt |
| cwd | OUT | SessionRoot.path / env overlay |
env packet on the turn derivation |
| mode | Persona if that exists | act/plan operator posture |
— |
| permissions / tools | Ceiling | — | RegisteredToolsSet frozen for the round; currentPolicy turn overlay; asks on ApprovableJob |
| constitution | IN | — | snapshot on first tail |
Agent.model, Agent.tools, Agent.cwd are traps. Do not add them.
Isolation default is own genesis, fail-closed. A child Agent gets a snapshotted spec plus its own env overlay. It does not inherit parent conversation prefix or tools unless the spawn says so.
4. Spine
Section titled “4. Spine”History is Agent.head and each turn’s tail. Genesis is SessionSystemPrompt (snapshotted constitution + env overlay). Next turn = new SessionTurn linked as tail + advance head. Attribution is the live head, then the tail walk. Tape cells (CompletionJob, TurnCompactChunk) carry the same pointer: they continue the previous cell or the turn, not their parent. There is no tape as a store. doneWork + queue are the ReACT tape (refs); owning list is Job.children. Paint iterates that tape, never collectors (../client/transcript.md).
Control chunks on the queue (TurnPromptChunk, TurnCompactChunk, ReACTChunk, ProgrammaticJob) are WorkContainers, not PEW. Compact fold: control-effects.md.
TEXT-ONLY CONTINUE. After a successful completion with no work tools, if other loop-end guards do not already continue (open page, callbacks, steers, titled-only, pending notes), config.json textOnlyContinue runs intern A (on when the bag is omitted; enabled: false turns it off): isolated json_schema on spoken responseText only (inline thinking is stripped) (intent, awaiting_user), judge wire (judgeConnection / judgeModel, no actor fallback), one shot, fail-seal. Continue iff intent && !awaiting_user. Ask stamps SessionTurn.questionLine (glance unfinished question, not license ask). Two consecutive intern nudges, then stop; work tools reset the budget, and later text-only rounds are classified again. Actor overlay is connection:model (first colon). Isolated intern is a turn child, not ReACT tape. Thought without an answer is a structural continue. Empty speech plus a thinking channel — a responseThought, a <think> block that is the whole message, a thinking content part, encrypted reasoning, billed reasoning tokens, or a stamped thought kind with no visible CoT — has not finished. It continues with no judge, ignoring textOnlyContinue, earlier work tools, and the intern streak. The nudge is the thought-continue note. Eight consecutive thought-only rounds, then stop.
SessionTurn.closed is reducer-exited. Open background children do not block the turn.
5. POLICY FOLLOWS HEAD
Section titled “5. POLICY FOLLOWS HEAD”currentPolicy is a turn overlay of the tri-state (manual | auto | yolo), same shape as toolsDefinition: attributed on the turn that changed it; null inherits tail; first turn attributes from standingPolicy. Unknown / empty stored strings coerce to manual. Effective policy is a computed inherit (SessionTurn.effectivePolicy). SessionRoot.currentPolicy displays that inherit; assignment writes standing and, when a head exists, the overlay.
A new session copies last-choice defaultPolicy once at mint (sessionMintPolicy: project wins when set, else kernel, else manual). Kernel persist is config.json defaultPolicy, not kv.json. Operator policy/set (Shift-Tab) write-throughs session + project + kernel. A local standing write does not. Attach does not re-seed.
The license pipeline reads SessionRoot.currentPolicy (approval.md). Head repoint restores that round’s policy. evalSpec is unattended admission (auto | yolo); observer overlay must not replace it. Unattended does not write last-choice. The policy program is {kernel}/policy.yaml (SessionRoot.policyProgram), not this field.
6. Job cut
Section titled “6. Job cut”Package model: ../packages/plexus-expectations/src/shared/models/Job.ts. Host kinds live in harness-sdk.
- Job is the orchestration graph node. A Turn is a Job. A workflow bag is a Job. Both spawn, both resolve, both can be moved off the parent’s await set.
- A tool call is a host
ToolCallJob(is-aApprovableJob). Emission identity and votes on one object. After an allow-side guard,SystemToolJob(includingFileMutateToolJob) runs as execute;MCPToolJob/ web-search / web-extract BODY intern’s and calls interned methods. Outcome lives onToolCallJob.outcomeJson. Completions remain PEW onJob.childrenwithout being Jobs. - Background is a flag, not a Job type. “Moved to background” is executor behavior: the row stays in the parent’s
children; the parent ReACT no longer joins it (joinReleased:completed || (orchestrated && background);AgentCallJobmay also wait durable acceptance). The row does not leave the turn. walkBoundaryis the contour. Nested background is a child Job with the flag, not a second session plane. Recursing collectors through awalkBoundaryis session-global collectors under a new name.- Normalized form is a Scheme program that can spawn tool calls, completions, turns, agent runs. Host
ProgrammaticJobis that face. A native FS observe is aSystemToolJob;write_file/edit_fileareFileMutateToolJob. Inner tools parent onto.childrenviapushInvocation.
Parent Turn extends Job child Job (still in children) background = false → foregroundJobs → parent ReACT waits background = true → backgroundJobs → parent ReACT proceeds once orchestrated jobs = both honor / watch walk .work (raw children), not collectorsProgrammaticJob execute is interpretSchemeRepl. SessionTurn seals via closed.
COLLECTORS ARE NOT .work. Job.work is raw children. PEW leaves keep WorkExpectation.work = [this]. Honor (walkWork) picks instanceof WorkExpectation. If paint uses jobs by accident, background floods the tape. If honor walked only foregroundJobs, a background PEW never gets an Actor.
Document .work is the head, inbound predecessor turns still in the inbound chain, other unclosed liveTurns, plus leftover background ToolCallJob / ProgrammaticJob on closed turns. The Turn node is a direct item so registerExecutor(SessionTurn) still claims it. Tape is not work.
Relative, not global:
- Child contour detaches its Job → parent
foregroundJobsstill contains the child Job, not the inner; parentbackgroundJobsdoes not gain the inner. - Parent detaches the child Job → parent
foregroundJobsdrops the child and the child’s descendants; childforegroundJobsis unchanged. - Attached child contour with an open inner Job ⇒ parent join stays open; detach the child ⇒ parent proceeds even if the inner is still open.
Flag is on the node. Plexus child.list is single-homed. background is not a per-edge annotation.
7. PEW vs Job
Section titled “7. PEW vs Job”PEW is runner concern: ambient driver-driven resolution of expectations. It does not dictate the orchestration graph shape.
| Row | What | Seal | Collectors |
|---|---|---|---|
| Sequence Job | SessionTurn, ProgrammaticJob |
closed on the turn; execute finally on the bag |
Yes. Also approvalQueue / approvalQueueHead |
| Licensed leaf | ToolCallJob kinds (ApprovableJob) |
Allow-side execute; deny-side receipt + completed; cancel: cancelled + receipt + completed |
Yes for the Job. Judges are PEW (instanceof Job is false) |
| PEW | CompletionExpectation, leftover intern McpToolExpectation / WebSearchExpectation / WebExtractExpectation, remainder classifier child |
Actor seals leftover PEW; new MCP/web seal on the Job | No |
Do not collapse PEW into Job. Do not make every Job a WorkExpectation. WorkExpectation does not extend Job. A licensed tool leaf does not mint tool PEW as identity — BODY is execute after an allow-side guard; MCP/web BODY calls interned methods. Never run a PEW leaf through an executor.
Subagent / background tool / workflow are tenants of work hosted on the producing turn, visibility by ancestry. Watch names hosted children (../client/transcript.md). Do not make a tool-in-background an Agent with an empty spec.
8. Processing
Section titled “8. Processing”../packages/plexus-expectations/src/executor/orchestrator.ts:
- Honor. Autorun over
walkWork(session.root). Skip done, tree-orphan, unknown connection. No approve hook. Honor sees completions and judges — not tool Jobs. Unstarted MCP must not spin — deny and ask are not execute. - Executors.
registerExecutor(Class, guard, execute)autoruns reachable unresolved Jobs of that class. Sequence guards typically returntrue(SessionTurnwaits inbound predecessors, then true — never false, which would deny BODY). An Approvable guard is LICENSE WHEN.ProgrammaticJobexecute is../packages/harness-server/src/scheme-repl/interpret.ts: bindings mint innerToolCallJobs; the trampoline joins those Jobs (Promises, notawaitin Scheme). ReACT join isqueue.shiftpluswaitCompleted→joinReleased. Nested contour is reachable asJob.childrenand must run;liveTurnsis tail-ancestry for chrome, not the executor gate.
../packages/harness-server/src/daemon/session/SessionOrchestrator.ts registers SessionTurn and the host leaf kinds. Interpret while the turn is in walkWork; abort when it leaves. Programs never await (control-effects.md).
Dormancy = origin unreachable from live head. Not a stored status. A job produced on an abandoned branch is invisible to the live branch.
9. Rejected
Section titled “9. Rejected”| Alternative | Failure |
|---|---|
| Session-as-bag | Dual-homes model/cwd/mode. Rollback of head does not restore round-attributed posture. |
currentPolicy session-standing as SSoT |
A later head-repoint keeps a later policy on an earlier round. |
| Last-choice permission mode as a kv key | sessionMintKvs would copy a string bag; kv-clear could reveal a parent. defaultPolicy is the sibling last-choice (config.json / project CRDT). |
WorkDemand as PEW / a second conversation lane |
Deleted. ReACT background is the Job background flag. |
| Package job subclasses | ONLY Job in plexus-expectations. Host kinds in harness. |
License on PEW / ApprovableExpectation |
approval.md. |
| Fat Agent | Session-as-bag renamed. |
| Tape / message list on SessionRoot | Grain forbids journal as product substrate. |
| Tool-in-background as Agent with empty spec | Homonym. No recipe, no spine. |
Job.work = jobs or foregroundJobs |
Collectors and PEW share a list, or background PEW never honors. |
| Await in program bodies | control-effects.md. |
Paint / compact using collector jobs |
Background floods the tape. |
Parallel synced SessionRoot.head and Agent.head |
Two tips. The accessor alias is the law. |
Collectors recurse through walkBoundary |
Nested background collapses to one plane. |
| Client mint of Jobs / PEW | Grain: daemon writes bodies. |
| Rename Actor → Agent | Grain Actor is the PEW handler. |
See also
Section titled “See also”| Doc | Role |
|---|---|
../canon/grain.md |
Causal spine, PEW nouns |
control-effects.md |
Flat programs; compact fold |
approval.md |
LICENSE WHEN; ToolCallJob is-a ApprovableJob |
../client/transcript.md |
Watch / tape paint |
../packages/plexus-expectations/src/shared/models/Job.ts |
Graph geometry |
../packages/harness-sdk/src/models/session/SessionRoot.ts |
Document |
../packages/harness-sdk/src/models/session/Agent.ts |
Thin agent |
../packages/harness-sdk/src/models/session/turn/session-turn.ts |
Turn is a Job |
../packages/harness-server/src/daemon/session/SessionOrchestrator.ts |
Honor + executors |
../packages/harness-server/src/scheme-repl/interpret.ts |
Scheme bag BODY |