Repository navigation
Releases: pedromvgomes/agentic-driver
Release list
v0.10.0
A run can deny tools outright, closing a gap AllowedTools alone can't reach,
and a prompt embedding a full diff no longer fails to exec on Linux.
Repository:
- A new
Disallowerinterface, separate fromPermitter: a provider can
grant an allowlist without being able to remove anything from it, or the
reverse, and folding removal intoPermissionArgswould force every
existing implementer to change its signature for a capability some of
them don't have. Request.DisallowedToolsnames tools a scripted run must never use,
regardless of whatAllowedToolsorPermissionModeotherwise grant. A
tool that needs no permission is invisible to an allowlist; this is the
field that closes it off directly. Set on a provider that isn't a
Disallower, it is refused withErrDisallowUnsupportedbefore any
process starts, the same way an unsupported turn limit or schema already
is.Driver.Provider()returns the wrappedagentic.Provider, so a caller
can type-assert any capability —Disalloweror one not yet invented —
without holding onto its own concrete reference against the grain of the
README's own usage example, which discards it afteragentic.New, or
submitting aRequestjust to read back the error.InvocationgainsStdin []byte, piped to the child's standard input
when non-nil;nilleaves it on the null device exactly as before, so
this is a no-op for any provider that doesn't set it. Linux'sexecve(2)
caps a single argv element at 128 KiB, and a prompt embedding a full diff
routinely exceeds that on its own — the run used to fail at spawn with
fork/exec ...: argument list too longbefore a single byte of the
request was read. See ADR 0008.Invocation.Argsno longer carries the prompt for either dialect — an
exported-behavior change. A caller that inspectedStreamCommand's
returned argv for the prompt text sees a different shape now. The known
downstream consumer isagentic-toolkit, currently pinning this
repository at v0.9.0.- Governed by
gt2.1.1 (up from 2.0.0):.github/workflows/ci-orchestration.yml
gains a lydite coverage-baseline job, and the managed lydite-clearance
workflow is renamed togt-lydite-clearance.yml.
claudecode:
- Implements
Disallower, emitting--disallowedToolsas one
comma-joined argv element, the same shape--allowedToolsalready uses. - A bare
*is accepted on the deny-list, unlike the allowlist: denying
every tool is a coherent thing to ask for, where granting every tool
under the guise of a restriction is refused. --disallowedToolstakes precedence over both the allowlist and the
permission mode — a denied tool stays denied even under
bypassPermissions.- The prompt travels on
Invocation.Stdininstead of as the value of-p;
-pitself stays in argv soclaudestill runs non-interactively.
codex:
- Does not implement
Disallower. Its nearest analogue,
features.multi_agent, is a single switch over five tools at once, not
a per-tool control, so aDisallowerbuilt on it could only honor a
couple of hardcoded names and silently refuse the rest. A non-empty
DisallowedToolsis refused outright withErrDisallowUnsupported,
exactly likeAllowedToolstoday. - The prompt travels on
Invocation.Stdininstead of as the trailing
positional argument tocodex exec. It is dropped from argv entirely
rather than replaced with-, sincecodex execappends piped stdin as
an extra<stdin>block rather than treating it as the prompt when a
positional is also given.
agentictest:
Fakecaptures what was piped to its standard input, read back with the
newFake.Stdin(t), kept in its own file separate from the existing
argv/env record so a prompt containing newlines orarg:/ENV-prefixed
lines can't corrupt either.- A new
IgnoreStdinfield makes the fake never read its stdin, for
testing a child that exits or stalls while the driver still has a
payload left to write.
Full changelog: v0.9.1...v0.10.0
v0.9.1
PinnedVersion catches up to the Claude Code build v0.9.0's Opus 5.5 alias
already depends on.
claudecode:
PinnedVersionmoves from2.1.258to2.1.280. v0.9.0 resolvedopus
toclaude-opus-5-5without also pinning a CLI build known to run it;
this release closes that gap.- No flag, envelope, or permission-mode change accompanies the bump. The
dialect is unchanged; only the vendored build aNewprovider installs
and verifies moves.
Full changelog: v0.9.0...v0.9.1
v0.9.0
opus resolves to Claude Opus 5.5.
claudecode:
- The
opusfamily alias resolves toclaude-opus-5-5. A driver built with
WithModel("opus"), or a Request naming "opus", now runs Opus 5.5, and
Driver.Model()reportsclaude-opus-5-5. - Opus 5 stays available: name
claude-opus-5and it reaches the CLI
unchanged. A caller that has to stay on Opus 5 pins that name rather than
the alias. - Nothing else in the dialect changes. It sets no thinking, effort or
tool-choice options, so Opus 5.5's API differences from Opus 5 (thinking
cannot be disabled, effort defaults to medium) are the CLI's to apply and
need no change from a caller.
Repository:
- Governed by gt 2.0.0. The security gate is lydite, configured under
.lydite/, and a pull request also needs thelydite/referralstatus to
merge. - AGENTS.md is rendered by agentic-toolkit from
agentic/agentic-driver-repo.md, alongside CLAUDE.md, for both Claude Code
and Codex.
Full changelog: v0.8.0...v0.9.0
v0.8.0
feat(codex): read a Block from turn.failed's error message
feat(codex): claim BlockReporter for exhausted and rejected reasons
test(codex): add derived exhausted-block fixtures and testdata provenance
test(codex): assert BlockReporter behavior and its rejection boundary
test(codex): guard the installed binary against a stale usage-limit phrase
docs: record ADR 0007 and bring 0006, CONTEXT.md, README.md into agreement
feat(codex): classify a spent usage limit as a block, not an ordinary failure (#13)
Full changelog: v0.7.0...v0.8.0
v0.6.0
feat(codex): every exec skips the git repository check
feat: a provider declares whether its runs can be concurrent
feat(codex): resolve family aliases to concrete models
Full changelog: v0.5.0...v0.6.0
v0.5.0
Release the stream decoder's tolerance for lines it does not read, and let a
codex run say which profile it authenticates as.
The decoder reads a line's type on its own before decoding anything else, so a
bookkeeping event whose message is a bare string is skipped rather than
failing the run. A modelled line whose shape is unreadable is surfaced as an
event instead, and only the terminal line stays fatal. This shipped to main
before v0.4.0 was cut against the older decoder, which turned a working stream
into ErrProviderUnavailable for any consumer pinned there.
codex.WithConfigDir exports CODEX_HOME, matching the option claudecode already
had. CODEX_HOME redirects the whole Codex profile rather than settings alone,
so it carries the credential too: a profile holding a session outranks the
token Isolated injects, and HOME is left to Driver.WithHome because codex needs
no second variable to find its cache.
Full changelog: v0.4.0...v0.5.0
v0.4.0
Vendor the Codex CLI at a pinned version, and split the capability that says
which build runs from the one that says who signed it.
codex installs its own copy from npm at an exact version, refusing any tarball
but the one whose SHA-512 this repository commits, and executes it by absolute
path. That is what keeps the decoder and the fixtures it was written against
describing the same CLI: an agent that updates itself moves its event schema
silently, and the break then lands on a user mid-run instead of on a red test at
the bump.
Pinner is that guarantee and nothing more. Installer is Pinner plus provenance —
a named publisher signed the artifact, verified against a trust anchor embedded
here — which claudecode does and codex cannot. OpenAI publishes sigstore bundles
for its linux-musl release assets alone; npm attests every platform, darwin
included, but verifying a Sigstore bundle in Go costs an order of magnitude more
dependencies than this library carries, so that attestation is checked by hand
when the pin moves. docs/adr/0004 records the alternatives and the procedure.
A message's content is a string or a list of blocks, and claudecode modelled
only the list. A bare string is a shape the CLI emits, so a stream that was
working failed the whole run, and it surfaced as the provider being unavailable
rather than as anything a caller could act on.
Breaking:
- codex.New takes a providers root and returns an error. Running whichever
codex is on PATH is codex.NewOnPath(). - agentic.Installer gains SigningIdentity(). A provider that vendors a binary
without verifying a publisher's signature implements agentic.Pinner instead,
and the driver asserts on Pinner wherever it means "can install". - Driver.SigningIdentity() answers ErrProvenanceUnsupported, wrapping
ErrInstallUnsupported for a provider that vendors nothing at all.
Full changelog: v0.3.0...v0.4.0
v0.3.0
Drive Codex for non-interactive single-turn runs, and bind a run's final answer to a JSON Schema.
Codex, and a stream-first interface
The codex provider is written against captured output from the real CLI, and what it found reshaped the interface. codex exec --json is JSONL from its first line to its last and has no envelope to parse — -o/--output-last-message writes bare text to a file rather than a document to stdout — so a Result exists only as a fold over a whole run. Streaming is therefore mandatory on Provider, and Run is Stream folded to its terminal Result.
A terminal result outranks both the exit code and the timeout. Codex reports a rejected credential and an unsupported model by finishing its stream properly and then exiting non-zero; judging the exit code first turns those verdicts into spurious outages.
A turn limit is a capability, not a field every provider honours. Codex has no turn bound at any spelling — -c max_turns=N is accepted and silently ignored without --strict-config — and no per-tool allowlist of any kind, so it refuses MaxTurns and AllowedTools rather than accepting and discarding them.
Schema-constrained structured output
Request.Schema binds a run's final answer to a JSON Schema and Result.Structured carries it, so a caller can fan out N reviewer runs over a diff and unmarshal what comes back.
Both CLIs genuinely constrain rather than suggest — given a schema requiring count between 1000 and 2000 and a prompt insisting on 4, each returns a conforming document. They constrain by different mechanisms that fail differently: codex constrains the decoder, so an unsatisfiable schema is a grammar it cannot finish and the run burns to its output ceiling and reports turn.failed; Claude Code validates each StructuredOutput call, feeds the rejection back, and when the model gives up answers in prose on exit 0 with is_error: false and subtype: "success".
A run that was given a schema and produced no payload is reported as an unmet constraint — IsError set, Structured nil, Text carrying whatever account exists. It is the one verdict the library reaches on its own, and it outranks a sandbox refusal on the same run: refusing is a successful verdict about authority, but the caller still did not get the shape it asked for.
The schema reaches each CLI differently — inline for Claude Code, a file path for codex. The codex file is content-addressed so one request always renders one argv, lives in a per-account directory that is checked before use, and is read back and compared rather than trusted for having the right name.
Breaking changes
For anyone implementing Provider:
CommandandParseare replaced byStreamCommandandNewDecoder(Request); theStreamerinterface andParseEventare gone.MaxTurnsnow requires aTurnLimiter, andRequest.SchemaaSchemaConstrainer. Both are refused before a process starts rather than silently dropped.Resultcarries ajson.RawMessageand is no longer comparable with==; usereflect.DeepEqual.
Notes
Every fixture under testdata is real output from the two CLIs, sanitised. The decisions above are recorded in docs/adr/, and CONTEXT.md fixes the vocabulary — verdict, outage, refusal, unmet constraint — that means one thing regardless of which CLI is behind it.
The API is not yet stable.