leonard_agent 0.1.3
leonard_agent: ^0.1.3 copied to clipboard
Web-compatible harness library.
leonard_agent #
Web-compatible harness library for Leonard. This
package MUST NOT import dart:io (enforced by
tool/check_no_dart_io.sh); concrete sinks and frontends live in sibling
packages (CLI, DevTools extension).
Trajectory format (JSONL) #
The harness writes one trajectory per session as newline-delimited JSON
(JSONL). The TrajectoryWriter (in lib/src/trajectory/writer.dart)
serializes typed records via a TrajectorySink and flushes after every
record, so a crashed session preserves progress through the last fully
written record (PRD §14).
Ordering: exactly one header on line 1, zero or more turn and
extension_disabled records interleaved in execution order, exactly one
footer on the last line. The writer flushes after every record.
Size budget: sessions are expected to stay under 50 MB / 30 minutes (PRD §23). Readers should stream rather than slurp.
Each record is a JSON object with a type discriminator and snake_case
keys.
header #
Session metadata: goal, AGENTS.md hash, build identifier, model
identifier, harness version, and the manifest of active extensions. The
package_version on each extension lets readers detect extension schema
mismatches between sessions.
{
"type": "header",
"goal": "login",
"agents_md_hash": "sha256:abc...",
"build_identifier": "debug-1.0.0",
"model_identifier": "qwen3.6-35b-a3b@8bit",
"harness_version": "0.1.0",
"extensions": [
{
"namespace": "router",
"package_version": "1.2.3",
"contract_version": "1.0.0"
}
],
"config": {"turn_budget_ms": 30000}
}
turn #
One record per perception-action turn. observation.extensions and
diff.extensions are namespace-keyed maps so each extension's contribution
can be sliced out by readers without parsing the core payload.
{
"type": "turn",
"index": 0,
"observation": {"core": {}, "extensions": {}},
"stability": {"policy": "action_relative"},
"proposed_action": {"tool": "core.tap"},
"validation": {"result": "ok", "retries": 0},
"executed_action": {"tool": "core.tap"},
"diff": {"core": {}, "extensions": {}},
"summary_update": "tapped login button",
"model_metadata": {"tokens_in": 10, "tokens_out": 5, "duration_ms": 200}
}
extension_disabled #
Emitted when an extension is auto-disabled mid-session (e.g. after
repeated failures). The turn field is the index of the next turn
the extension is absent from — the timeline panel renders this as
the auto-disable point.
{
"type": "extension_disabled",
"namespace": "dio",
"reason": "auto_disabled_after_3_failures",
"turn": 7
}
footer #
Always written exactly once on close(), including on
harness_error paths. outcome is one of done, budget_exhausted,
harness_error. harness_error is present only when
outcome == "harness_error".
{
"type": "footer",
"outcome": "budget_exhausted",
"final_summary": "ran out of turns",
"total_turns": 25,
"total_duration_ms": 30000
}
On crash:
{
"type": "footer",
"outcome": "harness_error",
"final_summary": "crashed",
"total_turns": 4,
"total_duration_ms": 5000,
"harness_error": "connection_lost"
}