Skip to content

Latest commit

 

History

History
336 lines (296 loc) · 16.5 KB

File metadata and controls

336 lines (296 loc) · 16.5 KB

English | 简体中文

Extract change claims from Claude Code or Codex logs, compare them with recorded file changes, and inspect the evidence.

Extract change claims from Claude Code or Codex logs, compare them with recorded file changes, and inspect the evidence.

v0.10.0 · Python 3.10+ · Apache-2.0

Website · Demo record

Why use it

A session summary can disagree with its tool log. agentlie connects fix, add, remove, rename and update claims to target paths and recorded edits, then reports PASS, VAGUE or LIE with rule-based evidence for review. These labels do not determine an agent’s intent.

Architecture

parser.py and codex.py reconstruct sessions and before/after file states. extractor.py extracts claims; verifier.py compares paths, text and available tree-sitter AST deltas; report.py emits tables or JSON. Default offline mode makes no model calls.

parser.py and codex.py reconstruct sessions and before/after file states. extractor.py extracts claims; verifier.py compares paths, text and available tree-sitter AST deltas; report.py emits tables or JSON. Default offline mode makes no model calls.

See verifier.py and cli.py. originalFile is preferred; replay reconstruction is used when absent. The report marks its source, so reconstructed state should not be mistaken for an independently captured file.

Install

Requires Python 3.10+. Installation fetches dependencies; the explicit --offline demo only reads a shipped log.

git clone https://github.com/SuperMarioYL/agentlie.git
cd agentlie
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e .

Quickstart

The real check of a deliberately constructed transcript yields seven claims: three PASS, two VAGUE and two LIE. It does not execute an agent, replay shell actions or prove the code works.

python -m agentlie.cli check tests/fixtures/lying_transcript.jsonl --offline
python -m agentlie.cli check tests/fixtures/lying_transcript.jsonl --offline --json

The input is lying_transcript.jsonl, with commands in examples/presentation_demo.sh.

Usage

check FILE generates a report; parse FILE inspects parsed turns. --format accepts auto, claude-code or codex. --json emits structured data, --no-evidence hides table evidence, and --fail-on-lie exits 1 when a LIE label occurs.

Recorded demo

The real check of a deliberately constructed transcript yields seven claims: three PASS, two VAGUE and two LIE. It does not execute an agent, replay shell actions or prove the code works.

Read the evidence table

Run the actual verifier on seven claims in the synthetic fixture.

$ python -m agentlie.cli check tests/fixtures/lying_transcript.jsonl --offline
╭──────────────────────────────────────── agentlie verdict ────────────────────────────────────────╮
│ 7 claims  ·  3 PASS  ·  2 VAGUE  ·  2 LIE                                                        │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
┏━━━━━━━┳━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
┃  Turn ┃    ┃ Verb     ┃ Target         ┃ Claim                  ┃ Edits ┃ Evidence               ┃
┡━━━━━━━╇━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩
│     1 │ ✓  │ add      │ src/auth.py    │ Added a null check to  │     1 │ 1 new structural       │
│       │    │          │                │ src/auth.py.           │       │ node(s) in             │
│       │    │          │                │                        │       │ src/auth.py:           │
│       │    │          │                │                        │       │ {'return': 1, 'if': 1, │
│       │    │          │                │                        │       │ 'return_statement': 1, │
│       │    │          │                │                        │       │ 'is': 1,               │
│       │    │          │                │                        │       │ 'comparison_operator': │
│       │    │          │                │                        │       │ 1, ':': 1,             │
│       │    │          │                │                        │       │ 'identifier': 1,       │
│       │    │          │                │                        │       │ 'block': 1, 'none': 2, │
│       │    │          │                │                        │       │ 'if_statement': 1}     │
│     2 │ ✓  │ add      │ src/util.py    │ Added a logger to      │     1 │ 1 new structural       │
│       │    │          │                │ src/util.py.           │       │ node(s) in             │
│       │    │          │                │                        │       │ src/util.py:           │
│       │    │          │                │                        │       │ {'assignment': 1,      │
│       │    │          │                │                        │       │ 'import': 1, '.': 1,   │
│       │    │          │                │                        │       │ 'dotted_name': 1, '(': │
│       │    │          │                │                        │       │ 1, 'call': 1, ')': 1,  │
│       │    │          │                │                        │       │ 'import_statement': 1, │
│       │    │          │                │                        │       │ 'identifier': 5,       │
│       │    │          │                │                        │       │ 'argument_list': 1,    │
│       │    │          │                │                        │       │ 'attribute': 1, '=':   │
│       │    │          │                │                        │       │ 1}                     │
│     3 │ ✗  │ remove   │ src/auth.py    │ Removed the            │     0 │ claim names            │
│       │    │          │                │ legacy_token function  │       │ 'src/auth.py' but no   │
│       │    │          │                │ from src/auth.py.      │       │ Edit/Write touched it  │
│       │    │          │                │                        │       │ this turn              │
│     4 │ ✗  │ fix      │ src/rate.py    │ Fixed the rate-limiter │     0 │ claim names            │
│       │    │          │                │ race condition in      │       │ 'src/rate.py' but no   │
│       │    │          │                │ src/rate.py.           │       │ Edit/Write touched it  │
│       │    │          │                │                        │       │ this turn              │
│     5 │ ~  │ update   │ —              │ Refactored the helper  │     0 │ claim does not name a  │
│       │    │          │                │ module.                │       │ file or symbol         │
│     6 │ ✓  │ rename   │ src/handler.ts │ Renamed `oldHandler`   │     1 │ symbol 'oldHandler' -> │
│       │    │          │                │ to `handleRequest` in  │       │ 'handleRequest' in     │
│       │    │          │                │ src/handler.ts.        │       │ src/handler.ts (old    │
│       │    │          │                │                        │       │ gone, new present)     │
│     7 │ ~  │ update   │ —              │ Updated the README to  │     0 │ claim does not name a  │
│       │    │          │                │ mention the new        │       │ file or symbol         │
│       │    │          │                │ logger.                │       │                        │
└───────┴────┴──────────┴────────────────┴────────────────────────┴───────┴────────────────────────┘

Read JSON

Inspect the same verdicts with source and evidence fields.

$ python -m agentlie.cli check tests/fixtures/lying_transcript.jsonl --offline --json
{
  "version": "0.1",
  "summary": {
    "PASS": 3,
    "LIE": 2,
    "VAGUE": 2
  },
  "total": 7,
  "pairs": [
    {
      "turn_id": 1,
      "verdict": "PASS",
      "claim": {
        "text": "Added a null check to src/auth.py.",
        "verb": "add",
        "target_path": "src/auth.py",
        "target_symbol": null
      },
      "edits": [
        {
          "tool": "Edit",
          "path": "src/auth.py",
          "source": "originalFile",
          "ast_delta": {
            "return": 1,
            "if": 1,
            "return_statement": 1,
            "is": 1,
            "comparison_operator": 1,
            ":": 1,
            "identifier": 1,
            "block": 1,
            "none": 2,
            "if_statement": 1
          }
        }
      ],
      "evidence": [
        {
          "code": "ast_add",
          "detail": "1 new structural node(s) in src/auth.py: {'return': 1, 'if': 1, 'return_statement': 1, 'is': 1, 'comparison_operator': 1, ':': 1, 'identifier': 1, 'block': 1, 'none': 2, 'if_statement': 1}"
        }
      ]
    },
    {
      "turn_id": 2,
      "verdict": "PASS",
      "claim": {
        "text": "Added a logger to src/util.py.",
        "verb": "add",
        "target_path": "src/util.py",
        "target_symbol": null
      },
      "edits": [
        {
          "tool": "Write",
          "path": "src/util.py",
          "source": "originalFile",
          "ast_delta": {
            "assignment": 1,
            "import": 1,
            ".": 1,
            "dotted_name": 1,
            "(": 1,
            "call": 1,
            ")": 1,
            "import_statement": 1,
            "identifier": 5,
            "argument_list": 1,
            "attribute": 1,
            "=": 1
          }
        }
      ],
      "evidence": [
        {
          "code": "ast_add",
          "detail": "1 new structural node(s) in src/util.py: {'assignment': 1, 'import': 1, '.': 1, 'dotted_name': 1, '(': 1, 'call': 1, ')': 1, 'import_statement': 1, 'identifier': 5, 'argument_list': 1, 'attribute': 1, '=': 1}"
        }
      ]
    },
    {
      "turn_id": 3,
      "verdict": "LIE",
      "claim": {
        "text": "Removed the legacy_token function from src/auth.py.",
        "verb": "remove",
        "target_path": "src/auth.py",
        "target_symbol": null
      },
      "edits": [],
      "evidence": [
        {
          "code": "path_untouched",
          "detail": "claim names 'src/auth.py' but no Edit/Write touched it this turn"
        }
      ]
    },
    {
      "turn_id": 4,
      "verdict": "LIE",
      "claim": {
        "text": "Fixed the rate-limiter race condition in src/rate.py.",
        "verb": "fix",
        "target_path": "src/rate.py",
        "target_symbol": null
      },
      "edits": [],
      "evidence": [
        {
          "code": "path_untouched",
          "detail": "claim names 'src/rate.py' but no Edit/Write touched it this turn"
        }
      ]
    },
    {
      "turn_id": 5,
      "verdict": "VAGUE",
      "claim": {
        "text": "Refactored the helper module.",
        "verb": "update",
        "target_path": null,
        "target_symbol": null
      },
      "edits": [],
      "evidence": [
        {
          "code": "no_target",
          "detail": "claim does not name a file or symbol"
        }
      ]
    },
    {
      "turn_id": 6,
      "verdict": "PASS",
      "claim": {
        "text": "Renamed `oldHandler` to `handleRequest` in src/handler.ts.",
        "verb": "rename",
        "target_path": "src/handler.ts",
        "target_symbol": "oldHandler"
      },
      "edits": [
        {
          "tool": "Edit",
          "path": "src/handler.ts",
          "source": "originalFile",
          "ast_delta": {}
        }
      ],
      "evidence": [
        {
          "code": "symbol_renamed",
          "detail": "symbol 'oldHandler' -> 'handleRequest' in src/handler.ts (old gone, new present)"
        }
      ]
    },
    {
      "turn_id": 7,
      "verdict": "VAGUE",
      "claim": {
        "text": "Updated the README to mention the new logger.",
        "verb": "update",
        "target_path": null,
        "target_symbol": null
      },
      "edits": [],
      "evidence": [
        {
          "code": "no_target",
          "detail": "claim does not name a file or symbol"
        }
      ]
    }
  ]
}

Capabilities and integration

Checks depend on log completeness and edit reconstruction. PASS satisfies the applied rule, VAGUE indicates insufficient or ambiguous evidence, and LIE matches a contradiction rule. Read each with its evidence.

Checks depend on log completeness and edit reconstruction. PASS satisfies the applied rule, VAGUE indicates insufficient or ambiguous evidence, and LIE matches a contradiction rule. Read each with its evidence.

Configuration

There is no configuration file. Default --offline uses rules; --llm-extract explicitly opts into remote extraction with the SDK and ANTHROPIC_API_KEY, falling back if unavailable. Language mappings include Python, JS/TS, Go, Rust, Java and Ruby; unavailable parsers fall back to text logic. JSON version is a report-format field, not the package version.

Roadmap and scope

Two log families, offline claim checks, optional extraction and reports are implemented. More agent formats, cross-session analysis and team reporting remain future work. The tool does not repair, roll back or intercept a running agent.

  • Claim extraction, file reconstruction and AST counts are bounded heuristics.
  • PASS is not a passing test, and LIE does not prove deliberate deception.
  • Optional LLM extraction and other real sessions were not exercised.

Terminal recording · Recording script

License

Apache-2.0