oski-agent View on GitHub
Open source · designed and built solo

Oski: The Self-Evolving AI Ops Agent

Oski turns Slack messages, cron jobs, and CLI tasks into tool-using agent runs, backed by three explicit memory layers: factual, behavioral, procedural. When it hits a capability gap, it can scaffold a new tool. It cannot grant that tool trust. That decision stays with your team.

View on GitHub
3
explicit memory layers
$2/day
default cost ceiling
120s
model request cutoff
6
safety controls, 5 code-enforced
MIT
fully open source

// the thesis

Two extremes. One middle path.

Most internal agent projects pick an extreme: a static integration list, or an unconstrained agent with a code execution tool. Oski is built for the ground between them, where an agent can genuinely grow inside boundaries a small team can actually audit.

EXTREME A

Static integration list

A fixed set of integrations someone wires up once. Safe, but it never grows past what a human anticipated on day one.

EXTREME B

Unconstrained autonomy

An agent with a code execution tool and no boundary. It can do anything, including the one thing nobody approved.

OSKI

The middle, by design

An agent that can propose new capability and grow its own tool surface, inside boundaries your team sets. It can suggest. It cannot self-authorize.

// system design

One process. No hidden moving parts.

Everything below is a single Node.js process: an in-memory queue, a concurrency-1 runner, a typed tool registry. No message broker, no worker fleet, no vector store. That is a scope decision for a small-team-sized agent, not an oversight.

inbound
Slack
Socket Mode
inbound
CLI
agent:task
inbound
Cron
opt-in
Task queue
in-memory FIFO · concurrency 1
Runner
agentic loop · 10 steps max
Anthropic API
cheap model routes, escalates on step 2
Tool registry
typed ToolDefinition map
Builtin tools
reviewed, shipped in a PR
Generated tools
opt-in, hot-reloaded, untrusted
JSONL logs
queue, cost, instructions, approvals
Reply
draft, or live if trusted

// memory model

Three memory layers. Nothing implicit.

Most agent demos have exactly one kind of memory: whatever fits in the prompt. Oski names three, separately, each with its own update path and its own blast radius.

01 · FACTUAL

What's true right now

Approved workspace files, scoped to OSKI_WORKSPACE_ROOTS. Read live, per task. Never cached or embedded, so it can't go stale between reads.

read_file.ts · search_code.ts
02 · BEHAVIORAL

How it should act

instructions.md, loaded fresh into the system prompt every single turn. Anyone on the team corrects it live with oski learn: in Slack. No redeploy, ever.

instructions.md · update_instructions.ts
03 · PROCEDURAL

What it's able to do

The typed tool registry. Builtins ship as a PR. Generated tools are scaffolded on demand, land in src/tools/generated/, and stay untrusted until a human reviews them.

tool-registry.ts · generate_tool.ts

None of these are vector stores or embeddings. Factual memory is a direct file read. Behavioral memory is a plain-text file versioned in git. Procedural memory is a directory of TypeScript files. That's a deliberate simplicity choice for a small-team-scale agent, not a placeholder for something more exotic.

// see it work

One task, one thread, one decision.

A simulation of the implemented Slack Socket Mode thread flow. A human approval posts the saved draft and writes the approval log.

#ops · Slack
SC
you 10:41 AM
oski: summarize the open items in TODO.md
OS
oski 10:41 AM
Got it. Working on it... (task 3f2a91bc)
OS
oski 10:41 AM
3 open items found
  • Ship the billing fix (owner: A)
  • Write the onboarding doc (owner: B, overdue)
  • Schedule the retro
OS
oski 10:42 AM
DRAFT, NOT POSTED

"This week: billing fix shipped, onboarding doc in review, retro scheduled Fri. Owners: A shipped billing, B is on the onboarding doc."

Socket Mode: reply "approve" or "send it" in this thread
draft-first · nothing ships without a human

// the code, not the pitch

This is the entire boundary between the model and your filesystem.

Not a paragraph promising safety. The actual check, straight from src/tools/builtin/read_file.ts. Symlinks are resolved and re-checked so a link inside an approved root can't point outside it.

src/tools/builtin/read_file.ts
// Both the lexically resolved path and the symlink-resolved real path
// must fall inside an allowed root, so symlinks cannot escape the sandbox.

function isInsideRoot(target: string, root: string): boolean {
  const rel = path.relative(root, target);
  return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
}

function isAllowed(target: string): boolean {
  const roots = getWorkspaceRoots();
  return roots.some(root => isInsideRoot(target, root));
}

// ...
const real = fs.realpathSync(resolved);
if (!isAllowed(real)) {
  return { error: `Access denied: ${requested} resolves outside the configured workspace roots.` };
}
No roots configured

= no access. There is deliberately no default workspace root.

Every tool is one file

A typed ToolDefinition with a declared read / draft / live scope. Drop it in, restart, done.

Every call is logged

Model, tokens, estimated USD, and every tool name called, one JSONL line per turn.

// what this actually is

No autonomy theater.

Every claim on this page maps to real, readable code. Here is the honest split, straight from the repo.

It is

  • A reference architecture for a Slack-native internal ops agent, built for founders and small teams who want to inspect and control what they ship.
  • Able to answer questions about a team's own files and notes without copy-pasting into a chat window.
  • Able to draft internal updates and replies for human review.
  • Able to learn behavioral rules from plain-English feedback.
  • Bounded by a hard daily budget by construction.

It is not

  • Fully autonomous. Every side-effectful action starts as a draft. A human must approve the saved Slack draft or explicitly trust the tool before anything posts.
  • A production back office. No durable job queue, no horizontal scaling, no sandboxed code execution yet.
  • Connected to any CRM, billing system, or support desk by default.
  • Capable of sending email on its own. The optional, unloaded email example only creates Gmail drafts. Sending remains a manual step.
  • Multi-tenant. One agent, one team, one channel. That is the point.

// safety model

Grow capability. Not trust.

The agent can propose new capability. It cannot grant itself trust. Five controls are enforced in code. Review of generated tools is deliberately procedural.

01 / 06

Draft-first

Outbound actions return draft text by default. A team can allowlist a live tool, or a human can approve a saved Slack draft inside its originating thread. The model cannot approve its own work.

// why I built this

Most "AI agent" repos are a chat wrapper around three tool calls and a good README. I wanted to answer a narrower question: how much real operating discipline can you put around a model before it's actually defensible to hand a small team's internal ops to it?

Oski is that answer, built to be read line by line, not taken on faith: a typed tool registry, a queue, a hard budget, draft-first side effects, three named memory layers, and a narrow, human-gated path for the agent to grow its own tools.

I'm the founder of ChiefOS, the AI operating layer for founders and executive teams. ChiefOS applies these same patterns to real company context, executive workflows, and private integrations. Oski is the public framework behind it, open-sourced so other builders don't have to solve the agent safety and execution layer from scratch.

I'm not selling autonomy. I'm selling the boundary around it.

Written by
Steven Callaway · ChiefOS

Inspect it. Constrain it. Extend it.

Small teams do not need another chatbot. They need an internal agent that can operate across real context and grow its own tool surface, without giving up human control. Oski is MIT licensed and built to be read, not just run.