From graph engineering to an AI-native workflow: Kilin CLI

Kilin is a local, CLI-first workflow runtime that turns session-level conventions into validated, shareable workflow packages with explicit boundaries.

17 min read

TL;DR

Kilin turns session-level agent conventions into validated, shareable workflow packages: two files define each workflow, compilation fixes the revision, and a local runtime owns state, boundaries, and failure semantics.

On this page

Kilin is a local, CLI-first workflow runtime experiment. It packages repeatable agent workflows as shareable assets. Progressive disclosure lets AI discover, create, run, and observe a workflow only when needed. After explicit approval, AI can also recover a run according to its contract.

Kilin is a working prototype, not a mature workflow platform.

@kilin-space/cli v0.1.0 · MIT · docs.kilin.space

A workflow needs defined semantics, not only a graph

"Graph engineering" has become a common term in the agent community. The LangGraph retrospective at https://www.langchain.com/blog/3-years-of-graph-engineering-with-langgraph makes a useful point: the label is new, but teams have used graphs to organize complex processes for a long time.

A graph can show nodes, dependencies, parallel branches, feedback loops, and human gates.

Kilin focuses on a narrower problem. Some repeated processes have rules that exist only in prompts, session logs, scripts, and human habits. These rules do not have one executable form.

The required semantics include more than "run A, then run B." They also define:

  • the task of each node
  • the data type that moves between nodes
  • which nodes are read-only and which nodes can write
  • which branches can run in parallel
  • which writes need isolation
  • where a person approves or rejects a result
  • whether a failure causes a retry, continuation, or full revision rerun
  • which definition revision a run uses
  • how a person can inspect that revision later

Kilin puts this information in a workflow contract with clear boundaries. The contract does not make model output deterministic. It also cannot describe every dynamic process. It turns selected session-level conventions into execution objects that a tool can validate.

A workflow can use progressive disclosure like an Agent Skill

Progressive disclosure is an important property of an Agent Skill. An agent first reads a short name and description. It loads the full instructions and supporting files only when the task needs them.

A workflow can use the same method. AI first finds candidate workflows. It loads the executable definition only after it selects one.

A Kilin workflow package contains two files:

  • WORKFLOW.md: name, description, use cases, and prerequisites
  • WORKFLOW.yaml: nodes, edges, permissions, inputs, outputs, and execution constraints

During discovery, Kilin reads limited manifest metadata and checks that the definition file exists. It reads and compiles the full YAML only when a person or an outer agent validates, runs, or views the workflow.

An agent therefore does not need every graph and prompt in its initial context as the workflow library grows.

Each package is also a shareable asset. A team can commit, diff, and review a project workflow with its repository. A user workflow can remain in user scope and work across projects.

The package does not depend on one chat log or a deployed workflow registry. The team shares workflow semantics and discovery metadata, not model output from one run.

Kilin includes Agent Skills that teach AI to use the same contract:

  • generate-kilin-workflow: converts a request into a candidate package and validates it after the write
  • discover-kilin-workflows: finds reusable patterns in an explicitly approved history scope
  • run-kilin-workflow: validates, starts, and observes a run, then reports approval points

The CLI remains responsible for final validation and execution. The workflow package stores the shareable definition. The runtime owns state, boundaries, and failure semantics.

Define once, run many times

A shareable workflow must separate its stable definition from the data for one run.

Kilin stores the graph, prompts, permissions, and node models in a workflow revision. It stores parameters, the working directory, and run options as invocation data.

One definition can receive different parameters and create many runs. Each run still points to the exact revision that it used.

The workflow keeps limited adaptive behavior:

  • choice routing can select a declared branch
  • a bounded revise-or-pass loop can repeat from one to five times
  • independent read-only nodes can run in parallel
  • different agent nodes can use different runtimes and models

Kilin does not remove dynamic behavior. It limits that behavior to forms that it can validate before execution.

"Define once, run many times" does not promise the same output for every run. Project content, providers, models, and network conditions can change. Kilin records the workflow identity, revision, input boundary, and run lineage.

From one task to one reviewable run

Consider a repository review with four stages:

  1. Review security
  2. Review performance
  3. Review maintainability
  4. Combine the findings for human approval

An agent can complete this task in one session. However, a session log does not always answer the questions that appear during review, handoff, or rerun:

  • What was the state and result of each step?
  • Which evidence supported the final decision?
  • Where did a person approve the result?
  • Could concurrent tasks write to the same workspace?
  • Which process revision did the run use?

When a run needs review, a team can need an orchestration layer that is separate from model reasoning. That layer records steps, boundaries, state, and lineage.

Dynamic orchestration combines "select the next step" and "run the step" in one reasoning loop. This flexibility helps exploratory tasks. Fixed approval points, concurrency constraints, and revision lineage still need a script, runtime, or external platform to record and apply them.

This is a system-boundary decision. It does not depend only on model capability.

Three common approaches

These approaches overlap, but each one makes a different trade-off between flexibility, state, and execution boundaries.

1. Use an agent CLI directly

Commands such as codex exec, claude -p, and opencode run work well for direct task execution and interactive iteration.

Run records, approvals, and structured node state depend on each CLI or on external tools. A team that needs one cross-provider run model must add another convention or orchestration layer.

2. Let an agent participate in orchestration

A plan in a prompt, or a primary agent that calls subagents, can change the task after each intermediate result. This approach needs less up-front modeling.

A process that needs fixed order, independent state, or cross-provider execution can still need a separate contract and observation method.

The important difference is not whether a workflow exists. The difference is who owns the plan, how strict the boundaries are, and where state is stored.

3. Use a general workflow engine

Temporal, Airflow, and other directed acyclic graph orchestrators provide mature persistence, scheduling, and recovery. They can wrap an agent in a function, container, or worker.

Teams that run local coding agents usually add adapters or application rules for access modes, workspace writes, provider preflight, and free-form text output.

Kilin puts these local coding boundaries directly in the workflow contract:

  • read-only or write access
  • workspace isolation
  • untrusted input boundaries
  • human approval gates
  • provider executable, version, capability, and authentication checks

General engines can implement these controls. Each project must usually assemble them.

Kilin separates two facts: the orchestration plan can have strict, validated boundaries, while agent output remains uncertain.

Before Kilin starts the first provider process, it compiles the graph shape, node order, write permissions, and approval points into an immutable revision.

The outer agent controls the workflow but is not one of its nodes.

The driver can be a person at a terminal or another agent runtime. It uses CLI commands and recorded state to control a run. A change to WORKFLOW.yaml during execution does not change the active run.

A Kilin workflow package

A project workflow has this directory structure:

  • .agents/
    • workflows/
      • parallel-change-review/
        • WORKFLOW.md
        • WORKFLOW.yaml

The parallel review example declares five nodes:

  • security-scan: a read-only Codex node that returns JSON
  • performance-scan: a read-only Codex node that returns JSON
  • maintainability-scan: a read-only Codex node that returns JSON
  • summarize: a read-only Codex node that returns a decision packet
  • approve: a human approval node

The first three nodes run independently. Their declared outputs enter summarize through three named edges. The summarize result then enters approve.

The definition applies four important constraints.

Access is a node property

Kilin maps read_only and workspace_write to the sandbox arguments of each provider. A workflow author can declare access intent but cannot supply raw runtime arguments.

Each node declares no more than one output type

Available types include text, JSON, artifact, choice, and decision packet. A node fails if its result does not match the declared type.

An edge input is the only data path

Kilin does not connect standard output implicitly. A downstream node receives an upstream result only through a declared input.

Approval is an explicit node

An approval has a separate lifecycle state. The run stops at this node until another local process or the Viewer records a decision.

Install, validate, and run

Use these commands in a terminal:

  1. Install: npm install --global @kilin-space/cli
  2. Validate without a model call: kilin workflow validate parallel-change-review --cwd "$PWD"
  3. Run with three parallel read-only nodes: kilin run parallel-change-review --cwd "$PWD" --max-parallel 3
  4. List runs: kilin runs list
  5. Inspect a run: kilin runs show YOUR_RUN_ID
  6. Approve a waiting node: kilin runs approve YOUR_RUN_ID approve --actor human
  7. Open the local Viewer: kilin ui parallel-change-review --cwd "$PWD" --json

All commands have documented JSON output. kilin run --json sends JSON Lines events for run, node, approval, and error state changes.

Failure events have stable error codes such as NODE_TIMEOUT, LOOP_LIMIT_REACHED, and APPROVAL_REJECTED. An outer agent can select its next action from the code instead of parsing a natural-language message.

System design

Kilin has three runtime levels:

  1. Outer runtime: a person, Codex, Claude Code, or a script creates, starts, monitors, and decides
  2. Kilin: a foreground process validates, compiles, records, and executes the workflow
  3. Inner run: provider processes perform node tasks and return results to Kilin

The outer driver observes and controls a run through public commands. It does not become a node in the run.

The Viewer reads the same run record. Its only write route records an approval decision. It cannot start or rerun a workflow.

The code has four layers, and dependencies point inward:

  • Presentation: src/cli and src/ui
  • Application: use cases, runtime contracts, events, and Viewer projections
  • Domain: workflow, compilation, revision, and lifecycle rules
  • Infrastructure: YAML, SQLite, file locks, child processes, provider adapters, and loopback HTTP

Presentation depends on application. Application depends on domain. Application reaches infrastructure through narrow ports where replacement is required.

Kilin adds an interface only when it isolates an external dependency or supports more than one implementation. V1 has no dependency injection container, runtime registry, event store, or transport framework.

From files to a completed run

Kilin processes a run in ten ordered stages:

  1. Resolve: normalize the working directory, find the nearest package root, and prefer project scope over user scope
  2. Parse: read safe-mode YAML and reject aliases, anchors, custom tags, multiple documents, and duplicate keys
  3. Validate: apply JSON Schema, semantic graph checks, and run-parameter checks
  4. Compile: normalize the definition, calculate its content hash, and produce a bounded acyclic execution plan
  5. Preflight: probe each runtime for its executable, version, capability, and configured authentication
  6. State: open SQLite and check the schema baseline under a separate schema lock
  7. Lock: take a non-blocking exclusive lock for the normalized working directory
  8. Create: repair stale state, create or reuse the revision, create the run, and create node rows in one transaction
  9. Execute: advance ready nodes by stable order and the configured parallel limit
  10. Settle: commit terminal state, release the lock, and close state

The transaction in stage 8 gives an important guarantee: Kilin does not start an agent before the run record commits.

If source, graph, Git, provider, authentication, state, or lock validation fails, Kilin does not create a run or start a provider process.

After Kilin creates a run, it records a failure or cancellation as terminal state before it releases the lock.

Immutable revisions and reruns

Compilation converts the workflow definition to sorted canonical JSON, then calculates a SHA-256 hash.

YAML comments, indentation, key order, and edge order do not change the hash. Node order, prompts, access, models, and graph structure do change it.

Kilin stores the exact normalized bytes in SQLite.

A rerun reads the stored definition from SQLite, compiles it again, and compares both the normalized bytes and content hash. A mismatch means state corruption. The rerun does not read the current workflow source.

This preserves orchestration lineage. It does not make the provider output deterministic.

Lifecycle state

SQLite is the authority for lifecycle state. JSON Lines events, CLI output, and the Viewer show committed state. They do not create or repair it.

A run starts in running and ends in one of four states:

  • succeeded: all required nodes succeeded
  • failed: an execution or engine failure prevented success
  • cancelled: storage received a cancellation request before another terminal state
  • interrupted: the foreground owner ended without a cancellation request

An agent node can move through pending, running, and a terminal state. An approval node moves from pending to waiting_for_approval before it resolves.

Only an approval node can enter waiting_for_approval. A skipped node is not a failure. It means the compiled route did not select the node, or the node became unreachable.

A retry finishes the current attempt as failed. After a bounded backoff, one compare-and-set operation returns the aggregate node to pending and increments the attempt number.

The next pending-to-running transition creates a new attempt row. A terminal attempt never changes again.

Concurrency and write isolation

The max-parallel option defaults to 1 and has a maximum value of 8. It permits only independent read_only nodes to overlap.

Each workspace_write node and approval node is an ordered exclusive barrier. The scheduler does not run these nodes at the same time as another node.

The compiler enforces the same rule. If two unordered nodes share a workspace and at least one can write, semantic validation rejects the workflow.

Separate Git worktree lanes isolate unordered write nodes. They do not let write nodes overlap during execution. Every workspace_write node remains a global exclusive barrier.

Kilin does not merge worktrees automatically. It keeps them after the run for human inspection.

Only one active run can own one normalized working directory. A second process does not wait in a queue. It fails with WORKSPACE_BUSY.

Persistence

Kilin stores lifecycle metadata in a local SQLite database. It stores process streams and node results as files.

The database records:

  • workflow revisions
  • workflow runs and lineage
  • one row for each node execution
  • one row for each started agent attempt
  • detached worktree lanes

The file tree stores:

  • one advisory lock per normalized working directory
  • standard output, standard error, and result files for each node
  • detached worktrees for named workspace lanes

Kilin does not add an event store, blob abstraction, queue, exporter, or object-relational mapper in V1.

Local data directories use 0700 permissions. Database sidecars, locks, and output files use 0600 permissions.

Provider adapters

Each provider adapter implements three operations: probe, create an invocation, and extract the result.

Codex

  • Minimum version: 0.144.0
  • Access: maps read-only and workspace access to provider sandbox settings and forces the working directory trust level to untrusted
  • Result: reads the output-last-message file in batch mode

Claude Code

  • Minimum version: 2.1.215
  • Access: maps access to permission mode and generated settings that enforce the sandbox
  • Result: reads streaming JSON Lines and requires one successful result event

OpenCode

  • Minimum version: 1.18.4
  • Access: supports workspace_write and fails closed for read_only
  • Result: reads streaming JSON Lines and selects the last valid text event

Kilin sends prompts through standard input, not command arguments. It starts provider processes without a shell and closes standard input after it writes the prompt.

A workflow author cannot declare runtime arguments. The adapter owns the executable path, arguments, environment processing, sandbox flags, output flags, and authentication checks.

Capability detection currently parses provider help text. Providers do not expose one capability-negotiation interface, so formatting changes can require a probe update.

Data between nodes remains untrusted

Kilin treats an upstream result as untrusted for its full path into a downstream prompt.

Before a downstream node starts, Kilin:

  1. Recalculates the expected result path and compares it with the database value
  2. Opens the file without following symbolic links
  3. Rejects unexpected hard links and checks file identity again
  4. Validates the result against its declared output type
  5. Serializes all resolved inputs as canonical JSON
  6. Stores the resolved inputs with the node record
  7. Adds a fixed warning that marks the JSON as workflow data, not instructions

These checks reduce path-replacement and prompt-injection risk. Kilin does not claim to remove prompt injection. It marks a fixed boundary and limits data movement to declared downstream nodes.

The Viewer shows the run record

The Viewer does not take control of a workflow. The CLI creates and schedules runs. SQLite remains the state authority. Closing the browser does not change workflow execution.

The current frontend is one TypeScript file compiled to one browser script. It has no framework or bundler. This structure supports the current feature set, but the interface still shows its prototype status.

"Local only" does not replace browser security boundaries. The Viewer:

  • listens only on 127.0.0.1
  • checks the peer, Host, and write-request Origin
  • exchanges a one-time URL-fragment token for an HttpOnly, SameSite=Strict cookie
  • keeps a Cross-Site Request Forgery token in memory
  • starts its Content Security Policy with default-src none

The only Viewer write operation approves or rejects a waiting approval node. The Viewer cannot start, rerun, or change a workflow.

The approval area shows upstream evidence before the decision controls. Untrusted HTML-like content renders as text.

Graph color shows state, not node type. Keyboard users can move through nodes with arrow keys, Home, End, and Enter. Screen readers receive a list in the same order.

V1 limits and suitable uses

Kilin exchanges expressive power for validation before execution. This choice fits repeated processes with stable steps and review requirements.

It does not fit every agent workflow.

The current choice branch can match only a declared value. A loop supports one bounded revise-or-pass form with no more than five iterations.

Kilin cannot create a dynamic fan-out from a list of unknown length or create a child workflow during a run.

Exploratory research, open-ended debugging, and one-time tasks often work better when the agent selects its next step.

An approval node accepts only approve or reject. It is not a general form.

Kilin has no Canvas, daemon, scheduling service, public API, or cloud worker. A mature workflow platform is a better base for a team queue, long background runs, complex human input, or dynamic task generation.

Local execution still depends on provider security

Kilin translates read_only and workspace_write into access modes that each provider CLI supports. This is one control layer, not a complete sandbox.

A provider can still read user configuration, access the network, and process model data according to its own policy.

The child process inherits environment variables from the process that starts Kilin. API keys and tokens in the shell environment also enter the provider process.

Do not use --param for secrets. Parameters can appear in shell history and local process lists.

A rerun is not deterministic replay. It reuses the stored revision, parameter snapshot, and run options. Models, project files, credentials, network state, and provider versions can still change.

Prototype limitations

The current version supports macOS and Linux. Continuous integration covers Ubuntu. Real-model qualification has limited macOS arm64 evidence, while the automated suite uses fake provider runtimes.

Kilin depends on a foreground process. If a terminal session ends unexpectedly, the next Kilin command for the same working directory repairs the remaining running state to interrupted.

It does not undo writes that already reached the workspace. In some detached-process conditions, a child that ignores termination can continue to run.

Run logs and temporary worktrees have no automatic retention policy. Long loops can use substantial disk space.

V1 also has no forward database migration path. If the schema changes, you must archive or reset the local data directory.

These limits make Kilin suitable for experiments and controlled use. It is not a maintenance-free, long-running service.

Why build this?

As agents do more engineering work, teams need more than the final result. They also need to know the execution boundaries:

  • Was the process approved?
  • Were writes controlled?
  • Did a failure leave enough evidence?
  • Which revision did the run use?

Agent runtimes, scripts, general workflow platforms, and dedicated control layers can answer these questions in different ways.

Kilin treats a workflow as a shareable asset that AI can discover like an Agent Skill. AI first reads short metadata and loads the full definition only when needed.

It can create a package, start a run, and observe state. After approval, it can retry, resume, or rerun. A team can review, version, and run the same workflow many times.

Kilin uses a deliberately limited design:

  • two normal files define the workflow
  • compilation and a content hash fix the revision
  • a foreground process and local SQLite record execution state
  • models perform the tasks inside nodes

Kilin tests whether explicit orchestration with adaptive execution is useful. It does not claim that this design is better than a dynamic agent runtime or a mature workflow platform.

Install: npm install --global @kilin-space/cli

Documentation: https://docs.kilin.space

Source: https://github.com/kilin-space/kilin

License: MIT