# `ClaudeWrapper.Jobs`
[🔗](https://github.com/genagent/claude_wrapper_ex/blob/main/lib/claude_wrapper/jobs.ex#L94)

Read-side access to Claude Code's on-disk **background-job** state.

Claude Code ships a supervisor daemon (`claude daemon run`) that
orchestrates background agent tasks launched via the `claude agents`
TUI. Per-task state lives under `~/.claude/jobs/<short_id>/`:

  * `state.json` -- current snapshot: state, intent (original
    prompt), session id, link to the session JSONL, timestamps,
    auto-generated name, etc.
  * `timeline.jsonl` -- append-only event log: at each state
    transition the daemon writes a line carrying timestamp, new
    state, a one-line detail, and (often) the full text body.

The session content itself is a normal
`~/.claude/projects/<slug>/<session_id>.jsonl` -- the same format
`ClaudeWrapper.History` parses. Each summary's `session_path` points
at it for direct cross-linking.

This module is read-only on purpose. The dispatch protocol (how the
TUI launches new tasks) is undocumented and version-sensitive;
mirroring it would defeat the drift defenses the wrapper relies on.

## Liberal parsing

Like `ClaudeWrapper.History`, parsing is forgiving. `list/1` skips
entries that are not directories (e.g. the daemon's `pins.json`),
that carry no `state.json` (a spare worker dir), or whose
`state.json` fails to parse -- malformed jobs are dropped rather
than failing the whole listing. The timeline parser skips blank and
malformed lines.

## Example

    {:ok, root} = ClaudeWrapper.Jobs.home()
    {:ok, summaries} = ClaudeWrapper.Jobs.list(root)

    for s <- summaries do
      IO.puts("#{s.short_id}  [#{s.state}]  #{s.intent}")
    end

    {:ok, job} = ClaudeWrapper.Jobs.get(root, "90c961c7")

    for event <- job.timeline do
      IO.puts("#{event.at}  #{event.state}")
    end

# `t`

```elixir
@type t() :: %ClaudeWrapper.Jobs{root: String.t()}
```

# `at`

```elixir
@spec at(String.t()) :: t()
```

Use a specific path as the jobs root. Useful for tests (point at a
temp dir) and non-default installs.

# `get`

```elixir
@spec get(t(), String.t()) ::
  {:ok, ClaudeWrapper.Jobs.Job.t()} | {:error, ClaudeWrapper.Error.t()}
```

Read one job by short id (its `~/.claude/jobs/<short_id>/` directory
name) into a full `ClaudeWrapper.Jobs.Job`, including the parsed
`timeline.jsonl` and the raw `state.json` map.

Returns `{:error, %ClaudeWrapper.Error{kind: :not_found}}` when no such
directory exists or its `state.json` is missing.

# `home`

```elixir
@spec home() :: {:ok, t()} | {:error, ClaudeWrapper.Error.t()}
```

Resolve the default jobs root, `~/.claude/jobs`.

Returns `{:error, %ClaudeWrapper.Error{kind: :no_home}}` when the user
home cannot be determined.

# `list`

```elixir
@spec list(t()) ::
  {:ok, [ClaudeWrapper.Jobs.Summary.t()]} | {:error, ClaudeWrapper.Error.t()}
```

List every job directory at the root, sorted by `short_id`.

Returns `{:ok, []}` when the root directory does not exist (no
background agents have been launched on this machine yet). Skips
entries that are not directories, that carry no `state.json`, or
whose `state.json` fails to parse.

# `root`

```elixir
@spec root(t()) :: String.t()
```

The configured root directory.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
