I recently integrated Pi Core into an agent system I use in a work environment.

This was an interesting choice for me because, in my personal development environment, I use DeepSeek Harness (DSH) quite heavily.

DSH and Pi solve related problems, but they approach them from very different directions.

DSH is a full agent harness built around a highly composable architecture — its own description is essentially everything is a plugin. Pi Core, on the other hand, is much closer to a small embeddable agent runtime: state, an agent loop, tool execution, message transformation, events, and hooks around that loop.

For personal development, I enjoy the flexibility of DSH.

For work, I wanted something different.

I needed:

  • stronger and more explicit execution boundaries;
  • predictable orchestration;
  • fewer framework-level abstractions between application code and agent execution;
  • an approval flow that cannot be bypassed by prompting;
  • straightforward integration with existing systems;
  • and a runtime small enough that most policy remains application-owned.

That led me to Pi Core.

The important distinction is not really:

heavy framework vs lightweight framework.

It is closer to:

framework-owned orchestration vs application-owned orchestration.

And for this particular system, I wanted the latter.

What Pi Core actually gives you

At its center, Pi Core provides a stateful agent runtime with tool execution and event streaming.

Conceptually, the loop is very small:

Pi separates the application-level message representation from the messages eventually sent to the model.

Before each model call, agent messages can go through transformContext(), followed by convertToLlm(). Pi also exposes lifecycle hooks around tool execution and low-level APIs for directly operating the agent loop.

That may not sound particularly revolutionary.

And that is actually what I like about it.

Pi doesn’t try to decide how my organization should define:

  • agents;
  • roles;
  • workflows;
  • approval;
  • permissions;
  • task routing;
  • persistence;
  • human interaction;
  • or process integration.

It gives me the inner runtime.

I build the control plane around it.

The agent loop is not the architecture

A common agent architecture can be summarized as:

1
2
3
4
5
6
7
8
9
Prompt
↓
LLM
↓
Tool Call
↓
Tool
↓
LLM

Once tool calling works, it is tempting to keep adding more tools and more instructions to the same agent.

Eventually you end up with something like:

1
2
3
4
5
6
7
8
9
10
General Agent
├── filesystem
├── database
├── shell
├── Slack
├── internal APIs
├── search
├── deployment
├── documents
└── everything else

Technically, this works.

Architecturally, I don’t like it.

The problem is no longer whether the model can perform the task.

The interesting questions become:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Who should receive this task?

What context should that agent see?

What capabilities should it receive?

When should those capabilities become available?

What requires human approval?

What happens after approval?

How does the result return to the parent process?

Who owns the execution policy?

Those are harness problems, not LLM problems.

So I deliberately keep the Pi agent loop relatively boring.

The interesting part lives outside it.

My orchestration model

The current architecture looks roughly like this:

There are three decisions here that matter much more to me than the underlying model.

1. The main agent dispatches work instead of doing everything

The main agent is primarily an orchestrator.

Its job is to understand the user’s intent, maintain the high-level interaction, and dispatch a task to an appropriate predefined subagent.

1
2
3
4
5
6
7
8
9
10
11
12
User Intent
|
v
Main Agent
|
+------> Development Agent
|
+------> Analysis Agent
|
+------> Operations Agent
|
+------> other predefined roles

The important word here is predefined.

I am intentionally not creating a completely new unrestricted agent every time the system sees a new task.

Each subagent has a defined responsibility and a known operating boundary.

That gives me a much simpler mental model.

Instead of asking:

What might this dynamically generated agent decide to become?

I can ask:

Which existing execution role owns this task?

This also makes the system easier to observe and debug.

The same category of task repeatedly enters the same role boundary.

2. Planning and execution use different capability sets

This is probably the most important control I added.

When a subagent first receives a task, it cannot access the real execution tools.

Not:

“You have the tools, but the system prompt tells you not to use them.”

The tools are actually not available.

The initial agent context is intentionally constrained.

Conceptually:

1
2
3
4
5
6
7
8
9
10
11
12
13
Planning Context
{
task
relevant knowledge
planning tools
conversation context
}

NO:
production actions
destructive tools
privileged integration
real execution capability

The subagent can reason about the task and produce a plan.

But it cannot perform the task yet.

The state transition only happens after approval:

1
2
3
4
5
6
7
8
9
10
11
PLAN
|
v
WAITING_FOR_APPROVAL
|
| user approves
v
CAPABILITY_INJECTION
|
v
EXECUTE

After approval, the runtime constructs a new execution context:

1
2
3
4
5
6
7
8
Execution Context
{
approved plan
task context
required resources
real tools
execution-specific instructions
}

Only then can execution begin.

This means that approval is not primarily a prompt-engineering mechanism.

It is a capability boundary.

That difference matters.

Prompt restrictions are not permission systems

Suppose an agent has a deploy_production tool.

You could give it the tool from the beginning and write:

1
Never call deploy_production before the user approves the plan.

That is useful behavioral guidance.

But structurally, the capability already exists.

My preferred model is:

1
2
3
4
5
6
7
Before approval:

availableTools = [
inspect,
search,
plan
]

Then:

1
2
3
4
5
6
7
8
9
10
After approval:

availableTools = [
inspect,
search,
plan,
modify,
execute,
deploy
]

The model doesn’t decide whether it is authorized.

The runtime does.

In other words:

Capabilities should be enforced by the harness, not negotiated through natural language.

Pi fits this model nicely because tools are part of the agent context and the runtime also exposes hooks around execution. Its beforeToolCall lifecycle hook, for example, can block a call before the underlying tool executes.

I still prefer capability injection as the primary boundary.

A pre-tool hook is useful as another policy layer.

The first rule is:

1
Do not expose a capability that should not exist yet.

The second can be:

1
Even if exposed, validate whether this particular invocation is allowed.

Those are separate controls.

Context injection is also part of permission design

Tool access is only one side of the boundary.

Context itself is a capability.

A planning agent may need:

1
2
3
4
task description
architecture documentation
interfaces
historical decisions

But it may not need:

1
2
3
4
credentials
production identifiers
internal execution metadata
sensitive system context

So I treat context injection and tool injection as related concepts.

1
2
3
4
5
6
7
          Capability Envelope
|
+-----------+-----------+
| |
v v
Context Tools
what it knows what it can do

The agent’s effective authority is determined by both.

This becomes particularly useful with specialized subagents because each execution role can receive a deliberately constructed capability envelope.

Why not use DSH for this?

This is where the technology choice gets more interesting.

I already use DSH extensively in my personal environment.

I like its architecture.

DSH is explicitly designed around plugins and composability, with agent-facing components built so that loops, UIs, hooks, orchestrators, and other services can be composed around common abstractions.

That makes it extremely interesting for a personal agent environment.

My personal workspace is intentionally broad:

1
2
3
4
5
6
7
8
9
10
personal assistant
|
+-- development agents
+-- information gathering
+-- LinkedIn operations
+-- messaging integrations
+-- scheduled jobs
+-- persistent sessions
+-- custom connectors
+-- experimental plugins

In that environment, composability is a feature.

I continuously add capabilities.

I experiment with different workflows.

I want new plugins to participate in the same harness.

The system itself is almost a platform.

For my work environment, the requirement is different.

I do not want to create another general-purpose agent platform.

I want a specific agent system with specific workflows.

That changes the optimization target.

More constraints, less framework

This sounds contradictory at first.

Why would I choose a smaller framework when I need stronger constraints?

Because I want those constraints to live directly in application code.

My desired stack is closer to:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Application
|
+-- orchestration policy
|
+-- approval state machine
|
+-- capability policy
|
+-- context policy
|
+-- process integrations
|
+-- observability / usage policy
|
v
Pi Agent Runtime
|
v
LLM

Rather than:

1
2
3
4
5
6
7
8
9
10
11
12
13
Application
|
v
General Agent Platform
|
+-- orchestration abstraction
+-- plugin abstraction
+-- policy abstraction
+-- agent abstraction
+-- runtime abstraction
|
v
LLM

Neither architecture is universally better.

They optimize for different things.

For experimentation and a growing personal agent platform, I like the first-class extensibility of DSH.

For a constrained work workflow, I prefer a smaller execution kernel with the orchestration policy written explicitly around it.

There is simply less distance between:

1
business requirement

and:

1
code enforcing the requirement

That is valuable.

I still use plugins — just at a different layer

Choosing a smaller core doesn’t mean putting everything into one application class.

I still maintain a plugin layer around the runtime.

But these plugins primarily provide infrastructure capabilities instead of deciding the fundamental orchestration model.

Some examples from the current system:

Slack process integration

Slack is not simply another chat interface for the agent.

It participates in the workflow.

For example:

1
2
3
4
5
6
7
8
9
Agent creates plan
|
v
Slack process
|
human review / approval
|
v
Harness resumes execution

This allows agent execution to participate in an existing human process rather than requiring people to move into an agent-specific UI.

The integration belongs to the process layer.

The agent loop doesn’t need to know what Slack is.

Token usage management

Token consumption becomes much more interesting once a single user request can fan out into several agent executions.

1
2
3
4
5
6
7
8
9
User Request
|
+-- Main Agent
|
+-- Subagent A
|
+-- Subagent B
|
+-- Retry / Follow-up

Looking only at the token usage of an individual LLM call is no longer sufficient.

I want token usage to be observable at the orchestration level:

1
2
3
4
request
└── task
└── agent
└── model calls

That creates a foundation for tracking consumption, understanding expensive workflows, and eventually applying budgets or policies independently of individual agents.

Again, this is not something I want each agent to manage itself.

It belongs to the harness.

Multimodal support

I also added multimodal support at the surrounding infrastructure layer.

Instead of teaching every agent how every attachment format should enter the system, the harness normalizes multimodal input before it reaches the execution role.

Conceptually:

1
2
3
4
5
6
7
8
9
10
11
12
13
Slack / UI / API
|
v
text / image / file
|
v
Multimodal Adapter
|
v
Normalized Agent Context
|
v
Agent

This keeps individual agents focused on their domain responsibility.

Transport concerns stay outside the agent loop.

The architecture is becoming two layers

After working with several different agent systems, I increasingly think about them as two separate layers.

Execution plane

This is where Pi lives.

1
2
3
4
5
6
7
LLM interaction
tool calls
tool results
message state
streaming
agent events
context transformation

Control plane

This is what I am building around it.

1
2
3
4
5
6
7
8
9
task routing
agent selection
approval
capability assignment
context injection
process integration
usage policy
observability
workflow state

Putting the two together:

This separation has been useful.

Pi doesn’t need to understand my entire workflow.

My workflow doesn’t need to reimplement an agent loop.

A small runtime can still produce a strict harness

One lesson from this implementation is that a “harness” does not have to mean a large framework.

A harness is really the set of constraints surrounding model execution.

For me, those constraints are increasingly explicit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
The main agent may dispatch.
The subagent may plan.

The subagent may not execute before approval.

Approval changes system state.

System state determines available context.

System state determines available tools.

Tools determine possible actions.

The runtime, not the model, enforces that transition.

The LLM is still given flexibility where flexibility is valuable.

But the application remains deterministic where determinism matters.

That is the balance I currently prefer for work-related agent systems.

What I learned from the technology choice

Using DSH personally and Pi Core at work has also made me less interested in asking:

Which agent framework is best?

The better question is:

Where do I want the architecture to live?

If I am building a continuously extensible personal agent platform, I want a powerful harness with strong composition primitives.

If I am embedding agent behavior inside an existing product or workflow, I may want much less framework between my application and the execution loop.

Pi Core gives me a useful boundary:

1
2
3
4
5
Pi owns:
agent execution

Application owns:
agent governance

That is a surprisingly powerful separation.

And it also reflects where I think a lot of agent engineering is heading.

The basic loop:

1
LLM → tools → LLM

is becoming the easy part.

The harder engineering problems are increasingly:

1
2
3
4
5
6
7
8
9
routing
permissions
context
approval
state
cost
observability
integration
recovery

In other words, not just:

How intelligent is the agent?

But:

What system is controlling the intelligence?

For this project, Pi gives me exactly enough runtime to build that system without forcing me to inherit the rest of the architecture.

And so far, that has been the right trade-off.