# Worked examples

Three runnable TypeScript helpers. Each shows its steps, what you get back and the full source.

## Start and follow a native session

Open a Codex session, send it a task and read what comes back.

### Create

`thread-create`

A named session, idle and ready

### Send

`message` · `receipt`

The task is delivered

### Read

`live-items` · `approvals` · `usage`

Output, open prompts and quota

### You need

- Node.js 24.15+ on Linux or macOS
- Codex installed and signed in
- The controller running in this workspace

```sh
npm install jg-agent-hq@8.0.2 jg-utilities@1.7.0
```

Save the helper as control-session.ts and the caller as run-session.ts, then run node run-session.ts.

### Call the helper

```ts
import { randomUUID } from 'node:crypto';
import { resolve } from 'node:path';
import { controlSession } from './control-session.ts';

const actionIds = {
  create: randomUUID(),
  message: randomUUID(),
};
// Retain these IDs in your host before submitting.
console.log(actionIds);

const session = await controlSession({
  stateRoot: resolve('.runtime'),
  workspaceRoot: process.cwd(),
  provider: 'codex',
  name: 'build-reports',
  text: 'Explain how this repository generates reports.',
  actionIds,
});

console.log(session.receipt.delivery);
```

### What you get back

Printed delivery state: acknowledged

Acknowledged means delivered. Turn completion and tool permissions are reported separately.

- `thread`: The managed session.

- `receipt`: The message action and its delivery state.

- `liveItems`: Output the session has produced so far.

- `approvals`: Prompts waiting for your reply.

- `usage`: The account’s quota windows and availability.

### Complete helper

Source: examples/control-session.ts. This is the same helper exercised by the public-client integration tests.

```ts
import { requestAgentHq } from 'jg-agent-hq';
import type { IActionReceipt, IProviderId } from 'jg-agent-hq/contracts';
import { waitUntil } from 'jg-utilities';

/**
 * Creates one native session, sends explicit input and returns actual observations.
 * Start the local controller and sign in to the selected native CLI first. Retain
 * actionIds before calling; inspect those receipts after a lost client response.
 * Acknowledgement is delivery, while completion and tool permission remain native.
 */
export const controlSession = async (input: {
  stateRoot: string;
  workspaceRoot: string;
  provider: IProviderId;
  name: string;
  text: string;
  actionIds: { create: string; message: string };
}) => {
  const { stateRoot } = input;
  const status = await requestAgentHq(stateRoot, { kind: 'status' });
  if (status.kind !== 'status') throw new Error('Expected runtime status');
  const runtimeEpoch = status.result.runtimeEpoch;

  /** Reads the same admitted action; it never retries its native effect. */
  const acknowledged = (actionId: string): Promise<IActionReceipt> =>
    waitUntil(
      async () => {
        const response = await requestAgentHq(stateRoot, { kind: 'receipt', actionId });
        if (response.kind !== 'receipt') throw new Error('Expected action receipt');
        const receipt = response.result;
        if (!receipt || ['queued', 'submitted'].includes(receipt.delivery)) return;
        if (receipt.delivery !== 'acknowledged')
          throw new Error(`Inspect action ${actionId}: ${receipt.delivery}`);
        return receipt;
      },
      { timeoutSeconds: 30, intervalSeconds: 0.1 },
    );

  await requestAgentHq(stateRoot, {
    kind: 'action',
    action: {
      id: input.actionIds.create,
      runtimeEpoch,
      command: {
        kind: 'thread-create',
        provider: input.provider,
        workspaceRoot: input.workspaceRoot,
        name: input.name,
      },
    },
  });
  const created = await acknowledged(input.actionIds.create);
  if (!created.threadId) throw new Error('Creation has no managed thread');
  const threadId = created.threadId;
  const thread = await waitUntil(
    async () => {
      const response = await requestAgentHq(stateRoot, { kind: 'managed-thread', threadId });
      if (response.kind !== 'managed-thread') throw new Error('Expected managed thread');
      if (response.result?.ownership === 'uncertain' || response.result?.state === 'failed')
        throw new Error(`Inspect thread ${threadId} before sending input`);
      return response.result?.state === 'idle' ? response.result : undefined;
    },
    { timeoutSeconds: 30, intervalSeconds: 0.1 },
  );

  await requestAgentHq(stateRoot, {
    kind: 'action',
    action: {
      id: input.actionIds.message,
      runtimeEpoch,
      command: { kind: 'message', threadId, input: { kind: 'text', text: input.text } },
    },
  });
  const receipt = await acknowledged(input.actionIds.message);
  const [liveItems, approvals, usage] = await Promise.all([
    requestAgentHq(stateRoot, { kind: 'live-items', threadId }),
    requestAgentHq(stateRoot, { kind: 'approvals', threadId, state: 'pending' }),
    requestAgentHq(stateRoot, { kind: 'usage', provider: input.provider }),
  ]);
  if (liveItems.kind !== 'live-items' || approvals.kind !== 'approvals' || usage.kind !== 'usage')
    throw new Error('Expected session observations');
  return {
    thread,
    receipt,
    liveItems: liveItems.result,
    approvals: approvals.result.data,
    usage: usage.result,
  };
};
```

## Have another agent review the work

A reviewer checks the work, HQ keeps its exact report and the author assesses it.

### Request a review

`message-publish` · `request-dispatch`

Sent once you confirm it

### Read the report

`request-read` · `report-read`

Exact findings, with ID and hash

### Ask for an assessment

`message-publish` · `request-dispatch`

The author weighs the findings

### You need

- The controller, with both agents signed in
- A task and stream both agents belong to
- A host control that confirms each request
- Optional: jg-review-session and jg-feedback, with current grants

```sh
npm install jg-agent-hq@8.0.2 jg-utilities@1.7.0
```

Save the helper as review-handoff.ts. Connect confirmRequest to a real operator decision and handle native permissions separately.

### Your host supplies

- `stateRoot, operatorRef`: Your controller and the signed-in operator.

- `taskId, streamId`: An existing task and stream.

- `requestIds, dispatchIds`: Four UUIDs saved before sending, so a retry reads the same requests.

- `review, feedback`: Who does each step, what to read and an optional skill. Feedback gets the report reference.

- `confirmRequest`: Shows each request to the operator and returns their decision.

### What you get back

After you confirm both requests: Report kept. Assessment requested.

The assessment may still be running when the helper returns; it waits up to 30 seconds. Findings never approve implementation.

Illustrative · either agent can use either provider

- Review: Claude Code — Reviews the selected work

- Report: report 7f3a — Exact findings, kept by HQ

- Assessment: Codex — Reads the report and responds

- `review`: The review request, its outcome and report ID, or null if declined.

- `feedback`: The assessment request with the report reference, or null.

### Complete helper

Source: examples/review-handoff.ts. This is the same helper exercised by the public-client integration tests.

```ts
import { requestAgentHq } from 'jg-agent-hq';
import type {
  ICollaborationCommand,
  ICollaborationRequestRecord,
  ICollaborationResponse,
  IRequestSpecification,
} from 'jg-agent-hq/contracts';
import { waitUntil } from 'jg-utilities';

/**
 * Requests a review and returns its exact report reference for author assessment.
 * The host has already created the task, stream, memberships and any selected skill grants.
 * Either agent may use any provider. A null skill selection requires no installed skills.
 * Connect confirmRequest to an actual operator decision; native permissions stay separate.
 */
export const reviewHandoff = async (input: {
  stateRoot: string;
  operatorRef: string;
  taskId: string;
  streamId: string;
  requestIds: { review: string; feedback: string };
  dispatchIds: { review: string; feedback: string };
  review: IRequestSpecification;
  feedback: Omit<IRequestSpecification, 'targetReferences'>;
  confirmRequest: (
    phase: 'review' | 'feedback',
    specification: IRequestSpecification,
  ) => Promise<boolean>;
}): Promise<{
  review: ICollaborationRequestRecord | null;
  feedback: ICollaborationRequestRecord | null;
}> => {
  const call = async (command: ICollaborationCommand): Promise<ICollaborationResponse> => {
    const response = await requestAgentHq(input.stateRoot, {
      kind: 'collaboration',
      actor: { kind: 'host', operatorRef: input.operatorRef },
      command,
    });
    if (response.kind !== 'collaboration') throw new Error('Expected collaboration response');
    return response.result;
  };
  const read = async (requestId: string): Promise<ICollaborationRequestRecord> => {
    const result = await call({ kind: 'request-read', taskId: input.taskId, requestId });
    if (result.kind !== 'request') throw new Error('Expected request');
    return result.request;
  };
  const send = async (phase: 'review' | 'feedback', specification: IRequestSpecification) => {
    if (!(await input.confirmRequest(phase, specification))) return null;
    const id = input.requestIds[phase];
    await call({
      kind: 'message-publish',
      id,
      taskId: input.taskId,
      streamId: input.streamId,
      messageKind: 'request',
      summary: phase === 'review' ? 'Review the selected work' : 'Assess the review findings',
      body: specification.arguments,
      references: specification.targetReferences,
      replyToMessageId: null,
      request: specification,
    });
    await call({
      kind: 'request-dispatch',
      id: input.dispatchIds[phase],
      taskId: input.taskId,
      requestId: id,
    });
    return read(id);
  };
  let review = await send('review', input.review);
  if (review === null) return { review: null, feedback: null };
  // These are bounded host reads, with no inference or replacement request on timeout.
  review = await waitUntil(
    async () => {
      const current = await read(input.requestIds.review);
      return ['resolved', 'canceled', 'interrupted', 'waiting-input'].includes(current.state)
        ? current
        : undefined;
    },
    { timeoutSeconds: 30, intervalSeconds: 0.1 },
  );
  if (review.state !== 'resolved' || review.outcome !== 'completed' || !review.lastReportId)
    return { review, feedback: null };
  const report = await call({
    kind: 'report-read',
    taskId: input.taskId,
    reportId: review.lastReportId,
  });
  if (report.kind !== 'report') throw new Error('Expected report reference');
  const feedback = await send('feedback', {
    ...input.feedback,
    targetReferences: [
      { kind: 'report', reportId: report.result.report.id, sha256: report.result.report.sha256 },
    ],
  });
  return { review, feedback };
};
```

## Change the plan. Keep verified work.

Add a requirement mid-plan without losing finished milestones.

### Pause

`begin-revision`

Execution stops and running work is checked

### Revise

`message` · `approve` · `revision-invoke`

jg-plan and jg-breakdown rewrite the plan

### Approve

`finalize` · `approve` · `authorize`

Verified work kept, new scope authorized

### You need

- A finalized plan with verified milestones
- A free planning session in the same workspace
- jg-plan, jg-breakdown and the project’s jg-plans CLI
- Host controls for each approval

```sh
npm install jg-agent-hq@8.0.2 jg-plans@0.3.0 jg-utilities@1.7.0
```

Save the helper as revise-plan.ts and connect its callbacks to real user decisions. It stops at authorization and starts no implementation.

### Your host supplies

- `approvePlan`: Shows the revised plan and returns an approval only if the user approves.

- `requestBreakdown`: Records the request to run jg-breakdown.

- `approveSequence`: Shows the final milestones before approving them.

- `authorize`: Picks the milestones to run, in supervised or auto mode.

### What you get back

Illustrative · every decision accepted: Verified work kept. New scope authorized.

A milestone keeps its evidence when jg-breakdown keeps its ID. Work the new breakdown reopens needs a new completion. Editing a status in the file never counts as verified.

Illustrative revision · one requirement added

- Before: Greeting — Verified

- After: Greeting result — Verified · same ID and evidence; Report — Pending · new milestone

- `skillsBinding`: Current again after the breakdown refresh.

- `coordination.completionCount`: Verified milestones still counted.

- `coordination.execution`: The new authorization: milestones and mode.

- `coordination.revision.phase`: Finished. No implementation has started.

### Complete helper

Source: examples/revise-plan.ts. This is the same helper exercised by the public-client integration tests.

```ts
import { requestAgentHq } from 'jg-agent-hq';
import type {
  IActionEnvelope,
  IPlanActionScope,
  IPlanCommandInput,
  IPlanCheckout,
  IPlanOperatorAction,
  IPlanStatus,
  IPlanTarget,
} from 'jg-agent-hq/contracts';
import { inspectPlan } from 'jg-plans';
import { waitUntil } from 'jg-utilities';

/**
 * Revises a selected sequence through native skills and fresh operator decisions.
 * Supply an available unbound planning thread in the same workspace. The host handles
 * native permissions/questions separately, retains all action IDs before calling,
 * and presents exact source before issuing each approval action. On interruption,
 * inspect those identities and the retained revision; never restart this whole flow
 * to retry an uncertain native effect.
 */
export const revisePlan = async (input: {
  stateRoot: string;
  target: IPlanTarget;
  planningThreadId: string;
  amendments: string;
  begin: IPlanOperatorAction;
  threadIds: string[];
  checkouts: IPlanCheckout[];
  finalize: IPlanOperatorAction;
  nativeActionIds: { plan: string; resume: string; breakdown: string; close: string };
  approvePlan: (source: string, status: IPlanStatus) => Promise<IPlanOperatorAction | null>;
  requestBreakdown: (status: IPlanStatus) => Promise<IPlanOperatorAction | null>;
  approveSequence: (
    source: string | null,
    status: IPlanStatus,
  ) => Promise<IPlanOperatorAction | null>;
  authorize: (status: IPlanStatus) => Promise<{
    action: IPlanOperatorAction;
    mode: 'supervised' | 'auto';
    milestoneIds: string[];
  } | null>;
  planningTimeoutSeconds?: number;
}): Promise<IPlanStatus> => {
  const { stateRoot, target, planningThreadId: threadId } = input;
  const runtime = await requestAgentHq(stateRoot, { kind: 'status' });
  if (runtime.kind !== 'status') throw new Error('Expected runtime status');
  const runtimeEpoch = runtime.result.runtimeEpoch;
  const waitOptions = { timeoutSeconds: input.planningTimeoutSeconds ?? 300, intervalSeconds: 0.2 };

  /** Reads current authority without treating a native turn as approval. */
  const status = async (): Promise<IPlanStatus> => {
    const response = await requestAgentHq(stateRoot, {
      kind: 'plan',
      command: { kind: 'status', target },
    });
    if (response.kind !== 'plan' || response.result.kind !== 'status')
      throw new Error('Expected plan status');
    return response.result.result;
  };
  /** Submits one explicit operator command with the current semantic revision. */
  const plan = (command: IPlanCommandInput) => requestAgentHq(stateRoot, { kind: 'plan', command });
  /** Waits for actual native delivery/completion; permission replies remain with the host. */
  const native = async (action: IActionEnvelope, completed = false) => {
    await requestAgentHq(stateRoot, { kind: 'action', action });
    return waitUntil(async () => {
      const response = await requestAgentHq(stateRoot, { kind: 'receipt', actionId: action.id });
      if (response.kind !== 'receipt') throw new Error('Expected action receipt');
      const receipt = response.result;
      if (!receipt) return;
      if (
        ['uncertain', 'failed', 'canceled'].includes(receipt.delivery) ||
        (receipt.completion && receipt.completion !== 'completed')
      )
        throw new Error(`Inspect native action ${action.id} before continuing`);
      return receipt.delivery === 'acknowledged' &&
        (!completed || receipt.completion === 'completed')
        ? receipt
        : undefined;
    }, waitOptions);
  };
  /** Uses the exact native inventory identity, never a fabricated skill name as input. */
  const invoke = async (name: 'jg-plan' | 'jg-breakdown', scope: IPlanActionScope, id: string) => {
    const catalog = await requestAgentHq(stateRoot, {
      kind: 'skills',
      threadId,
      search: name,
      limit: 128,
    });
    if (
      catalog.kind !== 'skills' ||
      catalog.result.state === 'unavailable' ||
      catalog.result.nextCursor
    )
      throw new Error('Select a complete available native skill inventory');
    const skills = catalog.result.data.filter(
      (skill) => skill.name === name && skill.isEnabled && skill.kind !== 'unavailable',
    );
    if (skills.length !== 1) throw new Error(`Expected one available ${name} skill`);
    await native(
      {
        id,
        runtimeEpoch,
        plan: scope,
        command: {
          kind: 'message',
          threadId,
          input: {
            kind: 'skill',
            skillId: skills[0]!.id,
            arguments: `Use the exact selected directory ${JSON.stringify(target.directory)}. ${name === 'jg-plan' ? input.amendments : 'Refresh coverage while retaining verified work and milestone identities.'}`,
          },
        },
      },
      true,
    );
  };

  await plan({
    kind: 'begin-revision',
    target,
    action: input.begin,
    expected: (await status()).revision,
    intent: 'plan',
    threadIds: input.threadIds,
    checkouts: input.checkouts,
  });
  await waitUntil(async () => {
    const current = await status();
    if (current.coordination.revision?.phase === 'blocked')
      throw new Error('Inspect the blocked revision');
    return current.coordination.revision?.phase === 'editing' ? current : undefined;
  }, waitOptions);
  await invoke(
    'jg-plan',
    { kind: 'planning', revisionId: input.begin.id, invocationId: input.begin.id },
    input.nativeActionIds.plan,
  );
  let current = await status();
  const observed = await inspectPlan(target.directory);
  if (observed.plan.state !== 'readable' || observed.plan.sha256 !== current.inspection.plan)
    throw new Error('Present the current plan again after the source change');
  const approval = await input.approvePlan(observed.plan.value.source, current);
  if (!approval) return current;
  await plan({
    kind: 'approve',
    target,
    action: approval,
    expected: current.revision,
    scope: 'plan',
    presentationHash: observed.plan.sha256,
  });

  current = await status();
  const breakdown = await input.requestBreakdown(current);
  if (!breakdown) return current;
  await plan({
    kind: 'revision-invoke',
    target,
    action: breakdown,
    expected: current.revision,
    revisionId: input.begin.id,
    skill: 'jg-breakdown',
  });
  await waitUntil(async () => {
    const current = await status();
    if (current.coordination.revision?.phase === 'blocked')
      throw new Error('Inspect the blocked revision');
    return current.coordination.revision?.phase === 'editing' ? current : undefined;
  }, waitOptions);
  const scope = {
    kind: 'planning' as const,
    revisionId: input.begin.id,
    invocationId: breakdown.id,
  };
  await native({
    id: input.nativeActionIds.resume,
    runtimeEpoch,
    plan: scope,
    command: { kind: 'thread-resume', threadId, operatorConfirmation: breakdown.source.reference },
  });
  await invoke('jg-breakdown', scope, input.nativeActionIds.breakdown);
  await native({
    id: input.nativeActionIds.close,
    runtimeEpoch,
    command: { kind: 'thread-close', threadId },
  });

  current = await status();
  // finalization records jg-breakdown's milestone IDs and carries completion evidence by ID
  const finalized = await plan({
    kind: 'finalize',
    target,
    action: input.finalize,
    expected: current.revision,
  });
  if (finalized.kind !== 'plan' || finalized.result.kind !== 'finalized')
    throw new Error('Expected finalized presentation');
  current = finalized.result.result;
  const sequenceApproval = await input.approveSequence(finalized.result.content, current);
  if (!sequenceApproval) return current;
  if (finalized.result.content !== null)
    await plan({
      kind: 'approve',
      target,
      action: sequenceApproval,
      expected: current.revision,
      scope: 'sequence',
      presentationHash: current.inspection.milestones!,
    });
  current = await status();
  const execution = await input.authorize(current);
  if (!execution) return current;
  await plan({ kind: 'authorize', target, expected: current.revision, ...execution });
  return status();
};
```

[API reference](/docs/api/) · [Getting started](/docs/)
