Skip to main content
Cynative is a single static Go binary built with Go 1.27 and cobra. There is no server process, no external state, and no daemon to manage — the entire system lives in one executable. Running cynative starts a cobra CLI that drives an LLM research loop directly from your shell, using the credentials already present in your environment.

Components

CLI

Cobra commands: root (interactive session and -p single-task mode), doctor (validates configuration and connector readiness), and agents (lists and shows built-in and user agents). Entry point is cmd/cynative, which calls cli.Execute() and exits with cli.ExitCodeFor(err).

Agent loop

Drives the model through a research iteration. Bounded by max_iterations, max_total_tokens, and max_consecutive_failures. The orchestration tools write_todos, task, and verify_findings live here (in internal/agent) because they drive the loop’s unexported runState — they are registered without the approval wrapper because they perform no credentialed I/O.

Tools

The two I/O primitives are http_request and code_execution. Both are wrapped in an approval decorator at the top level. api_reference is the only I/O tool without the approval wrapper — it reads public vendor metadata or, for Kubernetes connectors, the target cluster’s own OpenAPI documents, and implements UngatedIO. Scripts inside code_execution call inner tools with no approval prompt; each tool must enforce its own gates.

Action gate

Resolves every tool call to its required IAM actions — derived from the providers’ own API definitions — then authorizes against the configured policy before any credential is attached. The gate fails closed: an unclassifiable request, a missing catalog, an unresolved permission, or an unparseable verifier verdict all result in a denial. Policy defaults are SecurityAudit (AWS), roles/viewer (GCP), and Reader (Azure).

Network layer

Pins every request host to its mapped service and region. On a direct route (no proxy configured, or a NO_PROXY match) it verifies the resolved IP before connecting. When the operator’s proxy policy selects a proxy, the connection goes only to that proxy’s canonical authority. https:// is the only allowed scheme; redirects are disabled, and the Host header is rejected.

JS sandbox

An embedded sobek JavaScript runtime. Model-written scripts have no network, filesystem, or package access — only the exposed tools are reachable, as async functions. Run cancels the run context before closing s.done, and a worker that wins a concurrency slot re-reads that context inside the winning arm before calling any tool. Scripts run under a timeout (default 120 s) and a capped output size.

Connectors

AWS, GCP, Azure, EKS, GKE, AKS, self-managed Kubernetes, GitHub, and GitLab. Connectors use the credentials already present in the shell — there is no separate credential store. For AWS assumed-role identities, credentials are re-vended via STS AssumeRole scoped to a managed read-only policy so IAM enforces the boundary independently of the gate.

Audit log

An append-only JSONL log written to ~/.cynative/audit.log by default. The log is fail-closed: if a call cannot be recorded the run aborts. A write failure latches — every later Log returns the error and writes nothing, and the closer reports the failure at exit. Tool results are redacted before being written; approval-prompt arguments are stored verbatim.

Request flow

1

Model decision

The model decides whether to produce an answer directly or invoke a tool. If it calls a tool, the agent loop dispatches the call.
2

Action gate

The action gate resolves the call to its required IAM actions and checks them against the configured read-only policy. Denials return an error to the model and the loop continues. Unclassifiable requests are denied — the gate never falls back to allow.
3

Network layer

For allowed calls, the network layer pins the request host to its mapped service and region. On a direct route it verifies the resolved IP before opening the connection; on a proxied route it connects only to the proxy.
4

Credential injection

Only after the gate authorizes the call are credentials attached to the outgoing request. The model never holds credentials directly.
5

Provider response

The request reaches the cloud or platform provider. The response is returned through the transport layer, which redacts secrets before the result travels any further.
6

Audit record

Every call is written to the audit log as a JSONL record, including the decision, route, and outcome. A failed write aborts the run.
7

Result back to model

The redacted result is fenced as untrusted content and re-enters the transcript. The loop then repeats until the model produces an answer or a bound trips (max_iterations, max_total_tokens, or max_consecutive_failures).

Package map

Composition spine

The dependency spine runs in one direction:
newDeps in internal/cli/wire_shell.go is the composition root — the single place where all dependencies are wired together. Every external dependency (cloud SDKs, OS, filesystem, network, stdio, terminal, the Bifrost client, the sobek runtime) is a constructor-injected interface or function field defaulted to the real implementation. The process environment is read only in the composition root and in *_shell.go files.

Key invariants

The model never holds credentials. Credentials are attached only after the action gate has authorized the call. The model receives only redacted tool results. The gate fails closed on unclassifiable requests. If a request matches no operation in the connector’s metadata, the gate denies it. There is no allow fallback for ambiguous or unknown calls. Per-run mutable state lives on runState, not on *Agent. Concurrent runs, each with their own task sub-runs, share one Agent instance. Putting mutable state on runState (in internal/agent/loop.go) keeps concurrent runs isolated and race-free. JS sandbox: cancel context before close, worker re-reads context inside winning arm. Run cancels the run context before it closes s.done. A worker that wins a concurrency slot re-reads that context inside the winning arm before it calls the tool. Both halves are load-bearing: a script can end its run with the context still live by throwing out of the IIFE wrapper, and the acquire select can have both arms ready at once, so neither signal alone is sufficient to determine the run is over.