free-agent
Safe HaskellNone
LanguageGHC2024

Free.Agent.Cli

Description

Live CLI agents as opaque shards.

A Cli recipe describes how to invoke an external CLI agent (hermes, kimi, grok, or any shell command). cliQuery runs the recipe with session resume / stale fallback; cliShard seats it as a Shard.

Synopsis

Invocation recipe

data Cli Source #

Invocation recipe for a CLI agent.

Everything a session needs is plain data; there are no laws here beyond what the CLI itself honours.

Constructors

Cli 

Fields

  • cliCommand :: FilePath

    The executable (e.g. "hermes", "binsh").

  • cliArgv :: Text -> Maybe Text -> [String]

    Full argv (excluding the command) for one query, given the prompt and any stored session id (Nothing = fresh session).

  • cliStdin :: Text -> String

    Stdin for the process, from the prompt ('const ""' for argv-only CLIs).

  • cliSessionFile :: FilePath

    Where the session id is persisted between calls.

  • cliSessionId :: Text -> Maybe Text

    Scrape a session id from CLI output. const Nothing for CLIs without sessions; no session file is then ever written.

  • cliStale :: ExitCode -> Text -> Bool

    Is this (exit code, output) pair a stale-session response?

  • cliScrub :: Text -> Text

    Noise filter applied to output before it becomes a reply body.

  • cliStderr :: StderrPolicy

    What to do with the process's stderr channel.

  • cliStderrTee :: Maybe FilePath

    Optional tee: raw stderr appended to this log file on every call, regardless of the policy (interiority stays searchable, never silently dropped).

  • cliTranscript :: Maybe (IORef Int, FilePath)

    Optional transcript sink: (post_id_ref, transcript_jsonl_path). The IORef carries the current post id, set externally before each query. One JSONL record is appended per invocation; failure to write is silent (tee failure is never fatal).

data StderrPolicy Source #

stderr routing for a CLI agent's output channels.

Precedent: Muster.Connector posts -- stdout -- / -- stderr -- marked sections; StderrMark is the in-body equivalent.

Constructors

StderrDrop

Discard stderr (use with cliStderrTee to keep a log).

StderrMerge

Concatenate stdout and stderr (the historical behaviour).

StderrMark

Append stderr after a -- stderr -- section marker.

Instances

Instances details
Eq StderrPolicy Source # 
Instance details

Defined in Free.Agent.Cli

Show StderrPolicy Source # 
Instance details

Defined in Free.Agent.Cli

hermesCli :: Maybe Text -> Maybe Text -> FilePath -> Cli Source #

Recipe for the hermes CLI: hermes chat -q <prompt> … --resume <sid>.

kimiCli :: Maybe Text -> Maybe Text -> FilePath -> Cli Source #

Recipe for the kimi CLI: kimi -p <prompt> [-m <model>] [--provider <provider>] [-r <sid>], text output. kimi prints a plain-text resume hint line, so scraping and scrubbing are line-oriented — no JSON needed. Note: kimi exits 0 even when the prompt fails (and --auto cannot combine with -p), so stale detection is output-based.

stderr (thinking tool progress notices) is dropped from the reply but teed raw to <sessionFile>.stderr.log — interiority stays searchable, never silently dropped.

grokCli :: Maybe Text -> FilePath -> Cli Source #

Recipe for the grok CLI: grok -p <prompt> --output-format json [--resume <sid>]. Plain output carries no session id, so the JSON format is used and the text/sessionId@ fields are extracted.

parseSessionId :: Text -> Maybe Text Source #

Scrape a session_id: line from CLI output.

cleanCliOut :: Text -> Text Source #

Hermes-flavoured TUI noise filter: drops session chatter, decorative rules, and ANSI lines; keeps plain reply text with no trailing newline.

Query

cliQuery :: Cli -> Text -> IO Text Source #

One query against a CLI agent. First call (or no stored session) runs fresh; subsequent calls resume the stored session id. A stale session falls back to fresh and records the new id. Scraped ids are re-persisted on every successful call, so server-side session rotation is followed.

cliQueryBS :: Cli -> Text -> IO ByteString Source #

Like cliQuery but returns raw stdout as ByteString before any decoding or filtering. Uses CreatePipe to read bytes directly from the process rather than going through the locale-aware String path of readCreateProcessWithExitCode.

Shard adapters

cliShard :: Text -> Cli -> IO (Shard IO [Post Text] [Post Text]) Source #

A live CLI agent as a list Shard. Session file and process stay inside IO — apply-only at this boundary. who is the agent nick (from on emitted posts).

Transcript

data TranscriptRecord Source #

One transcript record, as JSONL appended to the transcript log.

encodeTranscriptLine :: TranscriptRecord -> Text Source #

Encode a transcript record as a single JSON line (no trailing newline).

Generic adapters (re-exported from Query)

queryShard :: Text -> (Text -> IO Text) -> IO (Shard IO [Post Text] [Post Text]) #

Opaque evaluate seat: any Text -> IO Text behind list ends.

Commit assembles a session prompt from the input posts; emit is replyPosts of the query result (empty = quiet).

TODO: this generic seat does not have access to stamped log ids, so emitted replies carry no thread edge. Callers that need provenance should use a variant that supplies parent ids.

queryShardWith :: (Text -> [Post Text] -> [PostId] -> Text -> [Post Text]) -> Text -> (Text -> IO Text) -> IO (Shard IO [Post Text] [Post Text]) #

queryShard parameterised on the reply-to-posts builder.

synthShard :: Text -> (Text -> IO Text) -> IO (Shard IO [Post Text] [Post Text]) #

Opaque synthesis seat: like queryShard, but the emit cites every input's sender as ancestry (synthesisPosts). For seats that fold the whole input into one answer — the honest-provenance twin of queryShard.

TODO: like queryShard, the generic seat has no ids and therefore emits syntheses without thread edges.

echoShard :: Text -> IO (Shard IO [Post Text] [Post Text]) #

Mock seat: reply body is the session prompt (echo).

Demonstrates the living-agent path without a real query.

runShardIO :: Shard IO [Post Text] [Post Text] -> [Post Text] -> IO [Post Text] #

One closed shard turn: commit ins, emit replies.

sessionPrompt :: [Post Text] -> Text #

Session assembly for the opaque seat: bodies, oldest-first, one per line.

This is the discoverable side of the boundary (data). How the query folds it is not.

replyPosts :: Text -> [Post Text] -> [PostId] -> Text -> [Post Text] #

Build reply posts from a cleaned agent response.

Addresses the last input's sender, preserves any other names on the original wire (e.g. the bus channel), and threads onto the last input's PostId when one is supplied. Empty reply → no posts (quiet).

The caller passes one PostId per input post in the same order. If the ids are missing or misaligned, the reply is still addressed correctly but carries no thread edge (see mkPost) — the honest fallback when a shard does not have access to the stamped log.

synthesisPosts :: Text -> [Post Text] -> [PostId] -> Text -> [Post Text] #

Build one synthesis post from a cleaned agent response.

The honest twin of replyPosts for seats that fold every input into their answer: ancestry cites every input's PostId (see synthesis), and the audience is every input's sender and wire name, minus self. Empty reply or no inputs → no posts (quiet).

The caller passes one PostId per input post. If ids are missing the synthesis is still addressed correctly but carries no thread edge.