Building adapters
An adapter teaches tally to move portable context in and out of a harness’s native session store. There are two levels; pick the smallest one that fits. The contract both levels implement is the PAC specification.
Level 1: a declarative manifest (most harnesses)
Section titled “Level 1: a declarative manifest (most harnesses)”If your harness stores sessions as JSONL files (one JSON object per line, a header line carrying the working directory), you do not write any code. You write an adapter.toml and the generic engine does the rest. The full manifest schema lives in the adapter.toml reference.
Workflow
Section titled “Workflow”tally adapter create myharness # scaffolds ./tally-adapter-myharness/adapter.toml$EDITOR tally-adapter-myharness/adapter.tomltally adapter validate ./tally-adapter-myharness # schema + round-trip conformancetally adapter install ./tally-adapter-myharness # or install from a git URLtally session export --harness myharness # it now workstally session materialize --into myharnessShare it by publishing the directory as a git repo; anyone installs it with tally adapter install <git-url>. A community registry, searchable via tally adapter search, launches with the beta; the format is described in The adapter registry.
Validation
Section titled “Validation”tally adapter validate proves the manifest with the PAC conformance smoke: it materializes a marker context into a temp directory, extracts it back, and asserts the task is recovered verbatim, plus per-line JSON validity and the find path. Fix the reported knobs until every check reads PASS.
Security posture and what community adapters can do
Section titled “Security posture and what community adapters can do”The manifest is pure data and is never executed, but install refuses manifests containing shell-exec patterns unless you pass --allow-shell after reviewing it (the pattern list is in the adapter.toml reference).
Level 2: a native Rust adapter (exotic harnesses)
Section titled “Level 2: a native Rust adapter (exotic harnesses)”If the harness’s store is not flat JSONL (SQLite, tree-linked events, date-partitioned rollouts, custom text), write a Rust module implementing the same three operations and wire it in as a built-in. The three in-tree adapters are the reference implementations, in increasing order of format complexity:
src/portable/pi.rs: parent-linked JSONL events, header + typed messages.src/portable/claude_code.rs: unversioned JSONL with many event types, a tolerant-by-construction parser, plan extraction from tool calls.src/portable/codex.rs: date-partitioned rollout files, payload-wrapped messages.
The contract each implements:
pub const HARNESS: &str = "..."; // provenance idpub fn find_latest_session_file(root: &Path, cwd: &str) -> Result<PathBuf, CliError>;pub fn extract_from_file(path: &Path) -> Result<Extracted, CliError>;pub fn materialize(root: &Path, cwd: &str, ctx: &PortableCtx) -> Result<(String, PathBuf), CliError>;House rules for a Level-2 adapter, learned from the first three:
- Build from direct inspection of the public on-disk format, never from a third-party converter.
- Parse tolerantly: skip unknown event types and unparseable lines; session formats grow new event types without notice.
- Extraction is deterministic, no model in the loop, and honest: fields the format does not carry stay empty rather than being invented.
- Prove acceptance with a live spike: forge a minimal session and confirm the real harness resumes it before shipping the adapter.
- Test with the same round-trip property the Level-1 conformance smoke checks: materialize then extract must recover the task.
Open a PR adding the module under src/portable/ plus its wiring; the spec for the layer is the PAC specification.
See also
Section titled “See also”- adapter.toml reference: every manifest field, the interpolation rule, the shell-pattern scan
- The adapter registry: the community registry format and search
- The PAC specification: the contract adapters implement