name: bassline description: Work with Bassline: a programming environment for reflexive distributed systems. Use Bassline when you want to make p2p connections with a simple API, build propagation networks, use a TCL with state serialization or manage agents in distributed settings. license: AGPL-3.0 compatibility: Requires @bassline packages installed in the project
Bassline
Everything in Bassline is a resource with two operations:
await resource.get(headers) // → { headers, body }
await resource.put(headers, body) // → { headers, body }
Semantic Addressing
Everything in Bassline is semantically addressed. Resources don't have a single concrete identity—instead they exist relative to other resources.
Routers are resources that delegate interactions by dispatching based on information in the headers. Paths are just a common example of semantic routing information. This lets us talk about resources as though we were interacting with a file-system.
// Read a value - the path describes what we want, not where it lives
const result = await resource.get({ path: '/cells/counter/value' })
// { headers: { type: '/types/cell-value' }, body: 42 }
// Write a value
const result = await resource.put({ path: '/store/users/alice' }, { name: 'Alice' })
// { headers: {}, body: { name: 'Alice' } }
Paths route segment by segment:
/cells/counter/value→cells→counter→value/store/users/alice→store→users→alice
You are free to choose whatever dispatch logic makes sense in your domain.
Response Format
Every operation returns { headers, body }:
// Success
{ headers: {}, body: { name: 'Alice' } }
{ headers: { type: '/types/cell' }, body: { lattice: 'maxNumber', value: 5 } }
// Errors - check headers.condition
{ headers: { condition: 'not-found' }, body: null }
{ headers: { condition: 'error', message: 'Something went wrong' }, body: null }
Always check for conditions:
const result = await kit.get({ path: '/cells/counter/value' })
if (result.headers.condition === 'not-found') {
// Resource doesn't exist
}
if (result.headers.condition === 'error') {
console.error(result.headers.message)
}
The Kit Rule
A kit is simply a resource passed via headers.
When a resource needs flexible or dynamic access to other resources that it doesn't know about ahead of time, it can do so with a kit. This makes kits a form of dependency injection for resources.
const worker = resource({
put: async (h, task) => {
// Access resources via kit - we don't know where they live
const { body: config } = await h.kit.get({ path: '/config' })
// Write results via kit
await h.kit.put({ path: '/results/latest' }, result)
return { headers: {}, body: { done: true } }
},
})
When a resource delegates to another resource, it can provide an alternative kit to change the effective "world" a resource is interacting in. Same resource, different kit = different capabilities.
Because resources don't have concrete identities, kits can work over network boundaries. When interacting with a kit that is located on an external machine, we can simply proxy interactions through another local resource we create.
Creating Resources
Basic Resource
import { resource } from '@bassline/core'
let count = 0
const counter = resource({
get: async h => ({ headers: {}, body: count }),
put: async (h, b) => {
count = b
return { headers: {}, body: count }
},
})
Routing by Path
import { routes } from '@bassline/core'
const app = routes({
cells: cellsResource,
store: storeResource,
fn: fnResource,
unknown: fallbackResource, // catches unmatched paths
})
Capturing Path Parameters
import { bind } from '@bassline/core'
// Captures segment into h.params.id
const users = bind(
'id',
resource({
get: async h => {
const userId = h.params.id // 'alice' from '/alice/profile'
// ...
},
})
)
Quick Reference
| Resource | Create | Read | Write |
|---|---|---|---|
| Cell | PUT /cells/name { lattice: 'maxNumber' } | GET /cells/name/value | PUT /cells/name/value <value> |
| Store | (auto-created) | GET /store/path/to/key | PUT /store/path/to/key <value> |
| Propagator | PUT /propagators/name { inputs, output, fn } | GET /propagators/name | PUT /propagators/name/run |
| Function | PUT /fn/name <function> | GET /fn/name | - |
Packages
Bassline is split into focused packages. See the reference files for detailed usage:
| Package | Purpose | Reference |
|---|---|---|
@bassline/core | Resources, types, stores, propagators, functions | core.md |
@bassline/blit | SQLite-backed persistent applications | blit.md |
@bassline/tcl | TCL scripting for boot scripts and automation | tcl.md |
@bassline/database | SQLite database connections and queries | database.md |
@bassline/services | Claude API integration and agentic loops | services.md |
@bassline/trust | Trust computation and capability gating | trust.md |
When to Use Each
- Building state that merges → cells with lattices (
core.md) - Storing key/value data → store (
core.md) - Reactive computation → propagators (
core.md) - Persisting an application → blits (
blit.md) - Writing boot/init scripts → TCL (
tcl.md) - Direct SQL access → database (
database.md) - LLM-powered features → services (
services.md) - Peer trust/capabilities → trust (
trust.md)